From beeaa55fce64fb93d94206769432442132100b1f Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 1 Sep 2025 12:41:42 +0530 Subject: [PATCH 01/25] feat: migrate the tenants BE repo and update various things --- package-lock.json | 5220 +++++++++++------ packages/tenants-nodejs/.eslintrc.js | 10 + packages/tenants-nodejs/.prettierrc.js | 4 + packages/tenants-nodejs/README.md | 26 + packages/tenants-nodejs/package-lock.json | 32 + packages/tenants-nodejs/package.json | 60 + packages/tenants-nodejs/src/constants.ts | 12 + .../tenants-nodejs/src/defaultEmailService.ts | 85 + packages/tenants-nodejs/src/emailServices.ts | 50 + packages/tenants-nodejs/src/index.ts | 24 + packages/tenants-nodejs/src/plugin.ts | 943 +++ .../src/recipeImplementation.ts | 480 ++ packages/tenants-nodejs/src/roles.ts | 81 + packages/tenants-nodejs/src/types.ts | 159 + packages/tenants-nodejs/src/util.ts | 19 + packages/tenants-nodejs/tsconfig.json | 13 + shared/tenants/.prettierrc.js | 4 + shared/tenants/package.json | 19 + shared/tenants/src/constants.ts | 0 shared/tenants/src/index.ts | 2 + shared/tenants/src/roles.ts | 16 + shared/tenants/src/types.ts | 38 + shared/tenants/tsconfig.json | 12 + 23 files changed, 5424 insertions(+), 1885 deletions(-) create mode 100644 packages/tenants-nodejs/.eslintrc.js create mode 100644 packages/tenants-nodejs/.prettierrc.js create mode 100644 packages/tenants-nodejs/README.md create mode 100644 packages/tenants-nodejs/package-lock.json create mode 100644 packages/tenants-nodejs/package.json create mode 100644 packages/tenants-nodejs/src/constants.ts create mode 100644 packages/tenants-nodejs/src/defaultEmailService.ts create mode 100644 packages/tenants-nodejs/src/emailServices.ts create mode 100644 packages/tenants-nodejs/src/index.ts create mode 100644 packages/tenants-nodejs/src/plugin.ts create mode 100644 packages/tenants-nodejs/src/recipeImplementation.ts create mode 100644 packages/tenants-nodejs/src/roles.ts create mode 100644 packages/tenants-nodejs/src/types.ts create mode 100644 packages/tenants-nodejs/src/util.ts create mode 100644 packages/tenants-nodejs/tsconfig.json create mode 100644 shared/tenants/.prettierrc.js create mode 100644 shared/tenants/package.json create mode 100644 shared/tenants/src/constants.ts create mode 100644 shared/tenants/src/index.ts create mode 100644 shared/tenants/src/roles.ts create mode 100644 shared/tenants/src/types.ts create mode 100644 shared/tenants/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 3a88689..6611f5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,2508 +117,3835 @@ "node": "^18 || >=20" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/code-frame/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=14.0.0" } }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=16.0.0" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "yallist": "^3.0.2" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.879.0.tgz", + "integrity": "sha512-OQIAq8+Ii7Q/whIGDd3q9XPNzvRfNCROc3Cv/a9Pk8pWOdDq4cq8PC+uybc9OPbYlbE5opuFDN9+GGoMRTUGoA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/credential-provider-node": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/signature-v4-multi-region": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@aws-sdk/client-sso": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.879.0.tgz", + "integrity": "sha512-+Pc3OYFpRYpKLKRreovPM63FPPud1/SF9vemwIJfz6KwsBCJdvg7vYD1xLSIp5DVZLeetgf4reCyAA5ImBfZuw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "node_modules/@aws-sdk/core": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.879.0.tgz", + "integrity": "sha512-AhNmLCrx980LsK+SfPXGh7YqTyZxsK0Qmy18mWmkfY0TSq7WLaSDB5zdQbgbnQCACCHy8DUYXbi4KsjlIhv3PA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@aws-sdk/xml-builder": "3.873.0", + "@smithy/core": "^3.9.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.879.0.tgz", + "integrity": "sha512-JgG7A8SSbr5IiCYL8kk39Y9chdSB5GPwBorDW8V8mr19G9L+qd6ohED4fAocoNFaDnYJ5wGAHhCfSJjzcsPBVQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.879.0.tgz", + "integrity": "sha512-2hM5ByLpyK+qORUexjtYyDZsgxVCCUiJQZRMGkNXFEGz6zTpbjfTIWoh3zRgWHEBiqyPIyfEy50eIF69WshcuA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/property-provider": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-stream": "^4.2.4", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.879.0.tgz", + "integrity": "sha512-07M8zfb73KmMBqVO5/V3Ea9kqDspMX0fO0kaI1bsjWI6ngnMye8jCE0/sIhmkVAI0aU709VA0g+Bzlopnw9EoQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.879.0", + "@aws-sdk/credential-provider-env": "3.879.0", + "@aws-sdk/credential-provider-http": "3.879.0", + "@aws-sdk/credential-provider-process": "3.879.0", + "@aws-sdk/credential-provider-sso": "3.879.0", + "@aws-sdk/credential-provider-web-identity": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.879.0.tgz", + "integrity": "sha512-FYaAqJbnSTrVL2iZkNDj2hj5087yMv2RN2GA8DJhe7iOJjzhzRojrtlfpWeJg6IhK0sBKDH+YXbdeexCzUJvtA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.879.0", + "@aws-sdk/credential-provider-http": "3.879.0", + "@aws-sdk/credential-provider-ini": "3.879.0", + "@aws-sdk/credential-provider-process": "3.879.0", + "@aws-sdk/credential-provider-sso": "3.879.0", + "@aws-sdk/credential-provider-web-identity": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.879.0.tgz", + "integrity": "sha512-7r360x1VyEt35Sm1JFOzww2WpnfJNBbvvnzoyLt7WRfK0S/AfsuWhu5ltJ80QvJ0R3AiSNbG+q/btG2IHhDYPQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", - "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.879.0.tgz", + "integrity": "sha512-gd27B0NsgtKlaPNARj4IX7F7US5NuU691rGm0EUSkDsM7TctvJULighKoHzPxDQlrDbVI11PW4WtKS/Zg5zPlQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@aws-sdk/client-sso": "3.879.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/token-providers": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.879.0.tgz", + "integrity": "sha512-Jy4uPFfGzHk1Mxy+/Wr43vuw9yXsE2yiF4e4598vc3aJfO0YtA2nSfbKD3PNKRORwXbeKqWPfph9SCKQpWoxEg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.2" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.873.0.tgz", + "integrity": "sha512-KZ/W1uruWtMOs7D5j3KquOxzCnV79KQW9MjJFZM/M0l6KI8J6V3718MXxFHsTjUE4fpdV6SeCNLV1lwGygsjJA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.876.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.876.0.tgz", + "integrity": "sha512-cpWJhOuMSyz9oV25Z/CMHCBTgafDCbv7fHR80nlRrPdPZ8ETNsahwRgltXP1QJJ8r3X/c1kwpOR7tc+RabVzNA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.873.0.tgz", + "integrity": "sha512-OtgY8EXOzRdEWR//WfPkA/fXl0+WwE8hq0y9iw2caNyKPtca85dzrrZWnPqyBK/cpImosrpR1iKMYr41XshsCg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.2.tgz", - "integrity": "sha512-FVFaVs2/dZgD3Y9ZD+AKNKjyGKzwu0C54laAXWUXgLcVXcCX6YZ6GhK2cp7FogSN2OA0Fu+QT8dP3FUdo9ShSQ==", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.879.0.tgz", + "integrity": "sha512-ZTpLr2AbZcCsEzu18YCtB8Tp8tjAWHT0ccfwy3HiL6g9ncuSMW+7BVi1hDYmBidFwpPbnnIMtM0db3pDMR6/WA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "core-js-pure": "^3.43.0" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-arn-parser": "3.873.0", + "@smithy/core": "^3.9.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.879.0.tgz", + "integrity": "sha512-DDSV8228lQxeMAFKnigkd0fHzzn5aauZMYC3CSj6e5/qE7+9OwpkUcjHfb7HZ9KWG6L2/70aKZXHqiJ4xKhOZw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@aws-sdk/core": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@smithy/core": "^3.9.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", - "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.879.0.tgz", + "integrity": "sha512-7+n9NpIz9QtKYnxmw1fHi9C8o0GrX8LbBR4D50c7bH6Iq5+XdSuL5AFOWWQ5cMD0JhqYYJhK/fJsVau3nUtC4g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", - "debug": "^4.3.1" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.879.0", + "@aws-sdk/middleware-host-header": "3.873.0", + "@aws-sdk/middleware-logger": "3.876.0", + "@aws-sdk/middleware-recursion-detection": "3.873.0", + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/region-config-resolver": "3.873.0", + "@aws-sdk/types": "3.862.0", + "@aws-sdk/util-endpoints": "3.879.0", + "@aws-sdk/util-user-agent-browser": "3.873.0", + "@aws-sdk/util-user-agent-node": "3.879.0", + "@smithy/config-resolver": "^4.1.5", + "@smithy/core": "^3.9.0", + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/hash-node": "^4.0.5", + "@smithy/invalid-dependency": "^4.0.5", + "@smithy/middleware-content-length": "^4.0.5", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-retry": "^4.1.20", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/protocol-http": "^5.1.3", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.27", + "@smithy/util-defaults-mode-node": "^4.0.27", + "@smithy/util-endpoints": "^3.0.7", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.873.0.tgz", + "integrity": "sha512-q9sPoef+BBG6PJnc4x60vK/bfVwvRWsPgcoQyIra057S/QGjq5VkjvNk6H8xedf6vnKlXNBwq9BaANBXnldUJg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@aws-sdk/types": "3.862.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@changesets/apply-release-plan": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.12.tgz", - "integrity": "sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.879.0.tgz", + "integrity": "sha512-MDsw0EWOHyKac75X3gD8tLWtmPuRliS/s4IhWRhsdDCU13wewHIs5IlA5B65kT6ISf49yEIalEH3FHUSVqdmIQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/config": "^3.1.1", - "@changesets/get-version-range-type": "^0.4.0", - "@changesets/git": "^3.0.4", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "detect-indent": "^6.0.0", - "fs-extra": "^7.0.1", - "lodash.startcase": "^4.4.0", - "outdent": "^0.5.0", - "prettier": "^2.7.1", - "resolve-from": "^5.0.0", - "semver": "^7.5.3" + "@aws-sdk/middleware-sdk-s3": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/signature-v4": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/apply-release-plan/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.879.0.tgz", + "integrity": "sha512-47J7sCwXdnw9plRZNAGVkNEOlSiLb/kR2slnDIHRK9NB/ECKsoqgz5OZQJ9E2f0yqOs8zSNJjn3T01KxpgW8Qw==", "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.879.0", + "@aws-sdk/nested-clients": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10.13.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.862.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.862.0.tgz", + "integrity": "sha512-Bei+RL0cDxxV+lW2UezLbCYYNeJm6Nzee0TpW0FfyTRBhH9C1XQh4+x+IClriXvgBnRquTMMYsmJfvx8iyLKrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/assemble-release-plan": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", - "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.873.0.tgz", + "integrity": "sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "semver": "^7.5.3" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/changelog-git": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", - "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.879.0.tgz", + "integrity": "sha512-aVAJwGecYoEmbEFju3127TyJDF9qJsKDUUTRMDuS8tGn+QiWQFnfInmbt+el9GU1gEJupNTXV+E3e74y51fb7A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/types": "^6.1.0" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-endpoints": "^3.0.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/cli": { - "version": "2.29.5", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.5.tgz", - "integrity": "sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.873.0.tgz", + "integrity": "sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/apply-release-plan": "^7.0.12", - "@changesets/assemble-release-plan": "^6.0.9", - "@changesets/changelog-git": "^0.2.1", - "@changesets/config": "^3.1.1", - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/get-release-plan": "^4.0.13", - "@changesets/git": "^3.0.4", - "@changesets/logger": "^0.1.1", - "@changesets/pre": "^2.0.2", - "@changesets/read": "^0.6.5", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@changesets/write": "^0.4.0", - "@manypkg/get-packages": "^1.1.3", - "ansi-colors": "^4.1.3", - "ci-info": "^3.7.0", - "enquirer": "^2.4.1", - "external-editor": "^3.1.0", - "fs-extra": "^7.0.1", - "mri": "^1.2.0", - "p-limit": "^2.2.0", - "package-manager-detector": "^0.2.0", - "picocolors": "^1.1.0", - "resolve-from": "^5.0.0", - "semver": "^7.5.3", - "spawndamnit": "^3.0.1", - "term-size": "^2.1.0" + "tslib": "^2.6.2" }, - "bin": { - "changeset": "bin.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/config": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.1.tgz", - "integrity": "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.873.0.tgz", + "integrity": "sha512-AcRdbK6o19yehEcywI43blIBhOCSo6UgyWcuOJX5CFF8k39xm1ILCjQlRRjchLAxWrm0lU0Q7XV90RiMMFMZtA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/logger": "^0.1.1", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1", - "micromatch": "^4.0.8" + "@aws-sdk/types": "3.862.0", + "@smithy/types": "^4.3.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@changesets/errors": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", - "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.879.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.879.0.tgz", + "integrity": "sha512-A5KGc1S+CJRzYnuxJQQmH1BtGsz46AgyHkqReKfGiNQA8ET/9y9LQ5t2ABqnSBHHIh3+MiCcQSkUZ0S3rTodrQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "extendable-error": "^0.1.5" + "@aws-sdk/middleware-user-agent": "3.879.0", + "@aws-sdk/types": "3.862.0", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@changesets/get-dependents-graph": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", - "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.873.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.873.0.tgz", + "integrity": "sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "picocolors": "^1.1.0", - "semver": "^7.5.3" + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@changesets/get-release-plan": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.13.tgz", - "integrity": "sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@changesets/assemble-release-plan": "^6.0.9", - "@changesets/config": "^3.1.1", - "@changesets/pre": "^2.0.2", - "@changesets/read": "^0.6.5", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@changesets/get-version-range-type": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", - "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, - "node_modules/@changesets/git": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", - "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "license": "MIT", - "dependencies": { - "@changesets/errors": "^0.2.0", - "@manypkg/get-packages": "^1.1.3", - "is-subdir": "^1.1.1", - "micromatch": "^4.0.8", - "spawndamnit": "^3.0.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@changesets/logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", - "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.1.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@changesets/parse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.1.tgz", - "integrity": "sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==", + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "js-yaml": "^3.13.1" + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@changesets/pre": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", - "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@changesets/read": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.5.tgz", - "integrity": "sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==", + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "license": "MIT", "dependencies": { - "@changesets/git": "^3.0.4", - "@changesets/logger": "^0.1.1", - "@changesets/parse": "^0.4.1", - "@changesets/types": "^6.1.0", - "fs-extra": "^7.0.1", - "p-filter": "^2.1.0", - "picocolors": "^1.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@changesets/should-skip-package": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", - "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@changesets/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", - "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, - "node_modules/@changesets/write": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", - "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "fs-extra": "^7.0.1", - "human-id": "^4.1.1", - "prettier": "^2.7.1" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@changesets/write/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "node": ">=6.9.0" } }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.4" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "node": ">=6.9.0" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "node": ">=6.9.0" } }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@ctrl/tinycolor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", - "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=14" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/runtime-corejs3": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.2.tgz", + "integrity": "sha512-FVFaVs2/dZgD3Y9ZD+AKNKjyGKzwu0C54laAXWUXgLcVXcCX6YZ6GhK2cp7FogSN2OA0Fu+QT8dP3FUdo9ShSQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, + "dependencies": { + "core-js-pure": "^3.43.0" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=12" + "node_modules/@changesets/apply-release-plan": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.12.tgz", + "integrity": "sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.1", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], + "node_modules/@changesets/apply-release-plan/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, "engines": { - "node": ">=12" + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", + "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/types": "^6.1.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], + "node_modules/@changesets/cli": { + "version": "2.29.5", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.5.tgz", + "integrity": "sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/apply-release-plan": "^7.0.12", + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.1", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/get-release-plan": "^4.0.13", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.5", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "ci-info": "^3.7.0", + "enquirer": "^2.4.1", + "external-editor": "^3.1.0", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "p-limit": "^2.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], + "node_modules/@changesets/config": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.1.tgz", + "integrity": "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/logger": "^0.1.1", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "extendable-error": "^0.1.5" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", + "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], + "node_modules/@changesets/get-release-plan": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.13.tgz", + "integrity": "sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/config": "^3.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.5", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", - "cpu": [ - "arm64" - ], + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "picocolors": "^1.1.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], + "node_modules/@changesets/parse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.1.tgz", + "integrity": "sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^3.13.1" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", - "cpu": [ - "arm64" - ], + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], + "node_modules/@changesets/read": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.5.tgz", + "integrity": "sha512-UPzNGhsSjHD3Veb0xO/MwvasGe8eMyNrR/sT9gR8Q3DhOQZirgKhhXv/8hVsI0QpPjR004Z9iFxoJU6in3uGMg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.1", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" - } + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "node_modules/@changesets/write/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10.13.0" }, "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" + "engines": { + "node": ">=18" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=10.10.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=12.22" + "node": ">=18" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "engines": { - "node": "20 || >=22" + "node": ">=18" } }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "devOptional": true, + "node_modules/@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, "engines": { - "node": "20 || >=22" + "node": ">=14" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, "engines": { "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=6.0.0" + "node": ">=12" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", - "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", - "license": "BSD-3-Clause" - }, - "node_modules/@lit/react": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz", - "integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==", - "license": "BSD-3-Clause", - "peerDependencies": { - "@types/react": "17 || 18 || 19" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@lit/reactive-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", - "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.4.0" + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@manypkg/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", - "dev": true, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/node": "^12.7.1", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@manypkg/find-root/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@manypkg/find-root/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" } }, - "node_modules/@manypkg/get-packages": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", - "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", - "dev": true, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@changesets/types": "^4.0.1", - "@manypkg/find-root": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "read-yaml-file": "^1.1.0" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", - "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@manypkg/get-packages/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" } }, - "node_modules/@microsoft/api-extractor": { - "version": "7.52.11", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.11.tgz", - "integrity": "sha512-IKQ7bHg6f/Io3dQds6r9QPYk4q0OlR9A4nFDtNhUt3UUIhyitbxAqRN1CLjUVtk6IBk3xzyCMOdwwtIXQ7AlGg==", - "devOptional": true, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@microsoft/api-extractor-model": "7.30.7", - "@microsoft/tsdoc": "~0.15.1", - "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.14.0", - "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.4", - "@rushstack/ts-command-line": "5.0.2", - "lodash": "~4.17.15", - "minimatch": "10.0.3", - "resolve": "~1.22.1", - "semver": "~7.5.4", - "source-map": "~0.6.1", - "typescript": "5.8.2" - }, - "bin": { - "api-extractor": "bin/api-extractor" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@microsoft/api-extractor-model": { - "version": "7.30.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", - "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", - "devOptional": true, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "~0.15.1", - "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.14.0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "devOptional": true, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/@microsoft/api-extractor/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, "engines": { - "node": ">=14.17" + "node": ">=12" } }, - "node_modules/@microsoft/api-extractor/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, - "license": "ISC" + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } }, - "node_modules/@microsoft/tsdoc": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", - "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", - "devOptional": true, - "license": "MIT" + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } }, - "node_modules/@microsoft/tsdoc-config": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", - "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", - "devOptional": true, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "0.15.1", - "ajv": "~8.12.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "devOptional": true, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true, - "license": "MIT" + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "devOptional": true, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 8" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@floating-ui/utils": "^0.2.10" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "optional": true, + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=14" + "node": ">=10.10.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=12.22" }, "funding": { - "url": "https://opencollective.com/pkgr" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/@rollup/pluginutils": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", - "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", - "dev": true, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "devOptional": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=14.0.0" + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], + "node_modules/@lit/react": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz", + "integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==", + "license": "BSD-3-Clause", + "peerDependencies": { + "@types/react": "17 || 18 || 19" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], + "node_modules/@microsoft/api-extractor": { + "version": "7.52.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.11.tgz", + "integrity": "sha512-IKQ7bHg6f/Io3dQds6r9QPYk4q0OlR9A4nFDtNhUt3UUIhyitbxAqRN1CLjUVtk6IBk3xzyCMOdwwtIXQ7AlGg==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@microsoft/api-extractor-model": "7.30.7", + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.14.0", + "@rushstack/rig-package": "0.5.3", + "@rushstack/terminal": "0.15.4", + "@rushstack/ts-command-line": "5.0.2", + "lodash": "~4.17.15", + "minimatch": "10.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.8.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], + "node_modules/@microsoft/api-extractor-model": { + "version": "7.30.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", + "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.14.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], + "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/node-core-library": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", + "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@rushstack/node-core-library/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/@rushstack/rig-package": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", + "integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/rig-package/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", + "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.14.0", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", + "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.15.4", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@shared/eslint": { + "resolved": "shared/eslint", + "link": true + }, + "node_modules/@shared/js": { + "resolved": "shared/js", + "link": true + }, + "node_modules/@shared/nodejs": { + "resolved": "shared/nodejs", + "link": true + }, + "node_modules/@shared/react": { + "resolved": "shared/react", + "link": true + }, + "node_modules/@shared/tenants": { + "resolved": "shared/tenants", + "link": true + }, + "node_modules/@shared/tsconfig": { + "resolved": "shared/tsconfig", + "link": true + }, + "node_modules/@shared/ui": { + "resolved": "shared/ui", + "link": true + }, + "node_modules/@shoelace-style/animations": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@shoelace-style/animations/-/animations-1.2.0.tgz", + "integrity": "sha512-avvo1xxkLbv2dgtabdewBbqcJfV0e0zCwFqkPMnHFGbJbBHorRFfMAHh1NG9ymmXn0jW95ibUVH03E1NYXD6Gw==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/claviska" + } + }, + "node_modules/@shoelace-style/localize": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.1.tgz", + "integrity": "sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA==", + "license": "MIT" + }, + "node_modules/@simplewebauthn/browser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.2.tgz", + "integrity": "sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==", + "license": "MIT", + "peer": true + }, + "node_modules/@simplewebauthn/types": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-12.0.0.tgz", + "integrity": "sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.5.tgz", + "integrity": "sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.5.tgz", + "integrity": "sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.9.0.tgz", + "integrity": "sha512-B/GknvCfS3llXd/b++hcrwIuqnEozQDnRL4sBmOac5/z/dr0/yG1PURNPOyU4Lsiy1IyTj8scPxVqRs5dYWf6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.9", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-stream": "^4.2.4", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.7.tgz", + "integrity": "sha512-dDzrMXA8d8riFNiPvytxn0mNwR4B3h8lgrQ5UjAGu6T9z/kRg/Xncf4tEQHE/+t25sY8IH3CowcmWi+1U5B1Gw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.1.tgz", + "integrity": "sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.3", + "@smithy/querystring-builder": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.5.tgz", + "integrity": "sha512-cv1HHkKhpyRb6ahD8Vcfb2Hgz67vNIXEp2vnhzfxLFGRukLCNEA5QdsorbUEzXma1Rco0u3rx5VTqbM06GcZqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.5.tgz", + "integrity": "sha512-IVnb78Qtf7EJpoEVo7qJ8BEXQwgC4n3igeJNNKEj/MLYtapnx8A67Zt/J3RXAj2xSO1910zk0LdFiygSemuLow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.5.tgz", + "integrity": "sha512-l1jlNZoYzoCC7p0zCtBDE5OBXZ95yMKlRlftooE5jPWQn4YBPLgsp+oeHp7iMHaTGoUdFqmHOPa8c9G3gBsRpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.19", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.19.tgz", + "integrity": "sha512-EAlEPncqo03siNZJ9Tm6adKCQ+sw5fNU8ncxWwaH0zTCwMPsgmERTi6CEKaermZdgJb+4Yvh0NFm36HeO4PGgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.9.0", + "@smithy/middleware-serde": "^4.0.9", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "@smithy/url-parser": "^4.0.5", + "@smithy/util-middleware": "^4.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.20.tgz", + "integrity": "sha512-T3maNEm3Masae99eFdx1Q7PIqBBEVOvRd5hralqKZNeIivnoGNx5OFtI3DiZ5gCjUkl0mNondlzSXeVxkinh7Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/protocol-http": "^5.1.3", + "@smithy/service-error-classification": "^4.0.7", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-retry": "^4.0.7", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.9.tgz", + "integrity": "sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.5.tgz", + "integrity": "sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.4.tgz", + "integrity": "sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.5", + "@smithy/shared-ini-file-loader": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/node-http-handler": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.1.tgz", + "integrity": "sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/querystring-builder": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/property-provider": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.5.tgz", + "integrity": "sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/protocol-http": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.3.tgz", + "integrity": "sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/querystring-builder": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.5.tgz", + "integrity": "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@smithy/querystring-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.5.tgz", + "integrity": "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rushstack/node-core-library": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", - "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/service-error-classification": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.7.tgz", + "integrity": "sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ajv": "~8.13.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~11.3.0", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.5.4" + "@smithy/types": "^4.3.2" }, - "peerDependencies": { - "@types/node": "*" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.5.tgz", + "integrity": "sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/signature-v4": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.3.tgz", + "integrity": "sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.5", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" + "node_modules/@smithy/smithy-client": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.5.0.tgz", + "integrity": "sha512-ZSdE3vl0MuVbEwJBxSftm0J5nL/gw76xp5WF13zW9cN18MFuFXD5/LV0QD8P+sCU5bSWGyy6CTgUupE1HhOo1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.9.0", + "@smithy/middleware-endpoint": "^4.1.19", + "@smithy/middleware-stack": "^4.0.5", + "@smithy/protocol-http": "^5.1.3", + "@smithy/types": "^4.3.2", + "@smithy/util-stream": "^4.2.4", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", - "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/types": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.2.tgz", + "integrity": "sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.14" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true, - "license": "MIT" + "node_modules/@smithy/url-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.5.tgz", + "integrity": "sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.5", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rushstack/node-core-library/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "universalify": "^2.0.0" + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "devOptional": true, - "license": "ISC", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "yallist": "^4.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "tslib": "^2.6.2" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "devOptional": true, - "license": "ISC", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.27.tgz", + "integrity": "sha512-i/Fu6AFT5014VJNgWxKomBJP/GB5uuOsM4iHdcmplLm8B1eAqnRItw4lT2qpdO+mf+6TFmf6dGcggGLAVMZJsQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "lru-cache": "^6.0.0" + "@smithy/property-provider": "^4.0.5", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.27.tgz", + "integrity": "sha512-3W0qClMyxl/ELqTA39aNw1N+pN0IjpXT7lPFvZ8zTxqVFP7XCpACB9QufmN4FQtd39xbgS7/Lekn7LmDa63I5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.5", + "@smithy/credential-provider-imds": "^4.0.7", + "@smithy/node-config-provider": "^4.1.4", + "@smithy/property-provider": "^4.0.5", + "@smithy/smithy-client": "^4.5.0", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-endpoints": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.7.tgz", + "integrity": "sha512-klGBP+RpBp6V5JbrY2C/VKnHXn3d5V2YrifZbmMY8os7M6m8wdYFoO6w/fe5VkP+YVwrEktW3IWYaSQVNZJ8oQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.4", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/node-core-library/node_modules/yallist": { + "node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/@rushstack/rig-package": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", - "integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==", - "devOptional": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "resolve": "~1.22.1", - "strip-json-comments": "~3.1.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/rig-package/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-middleware": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.5.tgz", + "integrity": "sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/terminal": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", - "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-retry": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.7.tgz", + "integrity": "sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@rushstack/node-core-library": "5.14.0", - "supports-color": "~8.1.1" - }, - "peerDependencies": { - "@types/node": "*" + "@smithy/service-error-classification": "^4.0.7", + "@smithy/types": "^4.3.2", + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@rushstack/terminal/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-stream": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.4.tgz", + "integrity": "sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "has-flag": "^4.0.0" + "@smithy/fetch-http-handler": "^5.1.1", + "@smithy/node-http-handler": "^4.1.1", + "@smithy/types": "^4.3.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=18.0.0" } }, - "node_modules/@rushstack/ts-command-line": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", - "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", - "devOptional": true, - "license": "MIT", + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@rushstack/terminal": "0.15.4", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - } - }, - "node_modules/@shared/eslint": { - "resolved": "shared/eslint", - "link": true - }, - "node_modules/@shared/js": { - "resolved": "shared/js", - "link": true - }, - "node_modules/@shared/nodejs": { - "resolved": "shared/nodejs", - "link": true - }, - "node_modules/@shared/react": { - "resolved": "shared/react", - "link": true - }, - "node_modules/@shared/tsconfig": { - "resolved": "shared/tsconfig", - "link": true - }, - "node_modules/@shared/ui": { - "resolved": "shared/ui", - "link": true - }, - "node_modules/@shoelace-style/animations": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@shoelace-style/animations/-/animations-1.2.0.tgz", - "integrity": "sha512-avvo1xxkLbv2dgtabdewBbqcJfV0e0zCwFqkPMnHFGbJbBHorRFfMAHh1NG9ymmXn0jW95ibUVH03E1NYXD6Gw==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/claviska" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@shoelace-style/localize": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.1.tgz", - "integrity": "sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA==", - "license": "MIT" - }, - "node_modules/@simplewebauthn/browser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.2.tgz", - "integrity": "sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==", - "license": "MIT", - "peer": true - }, - "node_modules/@simplewebauthn/types": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-12.0.0.tgz", - "integrity": "sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==", - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, "node_modules/@supertokens-plugins/captcha-nodejs": { "resolved": "packages/captcha-nodejs", @@ -2636,6 +3963,10 @@ "resolved": "packages/tenant-discovery-react", "link": true }, + "node_modules/@supertokens-plugins/tenants-nodejs": { + "resolved": "packages/tenants-nodejs", + "link": true + }, "node_modules/@supertokens-plugins/user-banning-nodejs": { "resolved": "packages/user-banning-nodejs", "link": true @@ -3226,6 +4557,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/nodemailer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.1.tgz", + "integrity": "sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -3289,6 +4631,13 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.39.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", @@ -4501,6 +5850,13 @@ "node": ">=0.10.0" } }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -6663,6 +8019,25 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -11507,6 +12882,19 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/style-observer": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/style-observer/-/style-observer-0.0.7.tgz", @@ -12991,6 +14379,20 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -15541,6 +16943,39 @@ } } }, + "packages/tenants-nodejs": { + "name": "@supertokens-plugins/tenants-nodejs", + "version": "0.0.1", + "devDependencies": { + "@shared/eslint": "*", + "@shared/nodejs": "*", + "@shared/tenants": "*", + "@shared/tsconfig": "*", + "@types/nodemailer": "^7.0.1", + "@types/react": "^17.0.20", + "express": "^5.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "nodemailer": "^6.0.0", + "supertokens-node": ">=23.0.0" + } + }, + "packages/tenants-nodejs/node_modules/@types/react": { + "version": "17.0.88", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", + "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, "packages/user-banning-nodejs": { "name": "@supertokens-plugins/user-banning-nodejs", "version": "0.0.2-beta.2", @@ -16349,6 +17784,21 @@ "supertokens-js-override": "^0.0.4" } }, + "shared/tenants": { + "name": "@shared/tenants", + "version": "0.0.1", + "dependencies": { + "debug": "^4.4.1" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/tsconfig": "*", + "@types/debug": "^4.1.12" + }, + "peerDependencies": { + "supertokens-node": "^23.0.1" + } + }, "shared/tsconfig": { "name": "@shared/tsconfig", "version": "0.0.0", diff --git a/packages/tenants-nodejs/.eslintrc.js b/packages/tenants-nodejs/.eslintrc.js new file mode 100644 index 0000000..26db86f --- /dev/null +++ b/packages/tenants-nodejs/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: [require.resolve('@shared/eslint/node.js')], + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + ignorePatterns: ['**/*.test.ts', '**/*.spec.ts'], +}; diff --git a/packages/tenants-nodejs/.prettierrc.js b/packages/tenants-nodejs/.prettierrc.js new file mode 100644 index 0000000..8986fc5 --- /dev/null +++ b/packages/tenants-nodejs/.prettierrc.js @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + ...require("@shared/eslint/prettier"), +}; diff --git a/packages/tenants-nodejs/README.md b/packages/tenants-nodejs/README.md new file mode 100644 index 0000000..95315ac --- /dev/null +++ b/packages/tenants-nodejs/README.md @@ -0,0 +1,26 @@ +# @supertokens-plugin-profile/tenants-backend + +Backend plugin for tenant management in SuperTokens Plugin Profile system. + +## Features + +- Tenant registration and management +- User-tenant membership handling +- Multi-tenant user data isolation +- Tenant switching functionality + +## API Endpoints + +- `GET /plugin/supertokens-plugin-profile-tenants/tenants` - List all available tenants +- `GET /plugin/supertokens-plugin-profile-tenants/user-tenants` - Get user's tenant memberships +- `POST /plugin/supertokens-plugin-profile-tenants/join-tenant` - Add user to a tenant +- `POST /plugin/supertokens-plugin-profile-tenants/leave-tenant` - Remove user from a tenant +- `POST /plugin/supertokens-plugin-profile-tenants/set-current-tenant` - Set user's current active tenant + +## Usage + +```typescript +import { init } from '@supertokens-plugin-profile/tenants-backend'; + +const tenantPlugin = init(); +``` \ No newline at end of file diff --git a/packages/tenants-nodejs/package-lock.json b/packages/tenants-nodejs/package-lock.json new file mode 100644 index 0000000..b410c7c --- /dev/null +++ b/packages/tenants-nodejs/package-lock.json @@ -0,0 +1,32 @@ +{ + "name": "@supertokens-plugin-profile/tenants-backend", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dev": true, + "requires": { + "undici-types": "~7.10.0" + } + }, + "@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true + } + } +} diff --git a/packages/tenants-nodejs/package.json b/packages/tenants-nodejs/package.json new file mode 100644 index 0000000..9c4fdac --- /dev/null +++ b/packages/tenants-nodejs/package.json @@ -0,0 +1,60 @@ +{ + "name": "@supertokens-plugins/tenants-nodejs", + "version": "0.0.1", + "description": "Tenants Base Plugin for SuperTokens", + "homepage": "https://github.com/supertokens/supertokens-plugins/blob/main/packages/tenants-nodejs/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/supertokens/supertokens-plugins.git", + "directory": "packages/tenants-nodejs" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts", + "pretty": "npx pretty-quick .", + "pretty-check": "npx pretty-quick --check .", + "test": "TEST_MODE=testing vitest run --pool=forks --passWithNoTests" + }, + "keywords": [ + "tenant", + "tenant-base", + "plugin", + "supertokens" + ], + "peerDependencies": { + "nodemailer": "^6.0.0", + "supertokens-node": ">=23.0.0" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/nodejs": "*", + "@shared/tenants": "*", + "@shared/tsconfig": "*", + "@types/nodemailer": "^7.0.1", + "@types/react": "^17.0.20", + "express": "^5.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "browser": { + "fs": false + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + }, + "./index": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + }, + "./index.js": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + } + } +} diff --git a/packages/tenants-nodejs/src/constants.ts b/packages/tenants-nodejs/src/constants.ts new file mode 100644 index 0000000..405960a --- /dev/null +++ b/packages/tenants-nodejs/src/constants.ts @@ -0,0 +1,12 @@ +export const PLUGIN_ID = "supertokens-plugin-tenant-discovery"; +export const PLUGIN_VERSION = "0.0.1"; + +export const PLUGIN_SDK_VERSION = ["23.0.0", "23.0.1", ">=23.0.1"]; + +export const HANDLE_BASE_PATH = `/plugin/${PLUGIN_ID}`; + +export const PLUGIN_ERROR_NAME = `${PLUGIN_ID}-error`; + +export const METADATA_KEY = `${PLUGIN_ID}`; +export const TENANT_CREATE_METADATA_KEY = `${METADATA_KEY}-tenant-create-requests`; +export const TENANT_CREATE_METADATA_REQUESTS_KEY = "requests"; diff --git a/packages/tenants-nodejs/src/defaultEmailService.ts b/packages/tenants-nodejs/src/defaultEmailService.ts new file mode 100644 index 0000000..b408af3 --- /dev/null +++ b/packages/tenants-nodejs/src/defaultEmailService.ts @@ -0,0 +1,85 @@ +import { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types"; +import { UserContext } from "supertokens-node/types"; +import { PluginEmailDeliveryInput } from "./types"; +import { logDebugMessage } from "supertokens-node/lib/build/logger"; + +// Default email service that provides template generation but throws on send +export class DefaultPluginEmailService implements EmailDeliveryInterface { + // Public method to generate email content - can be used in overrides + generateEmailContent(input: PluginEmailDeliveryInput) { + switch (input.type) { + case "TENANT_REQUEST_APPROVAL": + return { + subject: `New request to join ${input.tenantId}!`, + html: ` +
+
+

You're Invited!

+
+
+

${input.senderEmail} has requested to join ${input.tenantId}

+ + Open Requests + + ${ + input.customData?.customMessage + ? ` +
+

"${input.customData.customMessage}"

+
+ ` + : "" +} +
+
+ `, + text: ` + ${input.senderEmail} has requested to join ${input.tenantId} + + Open Requests: ${input.appUrl}/user/tenants + + ${input.customData?.customMessage ? `Message: "${input.customData.customMessage}"` : ""} + `, + }; + + case "TENANT_CREATE_APPROVAL": + return { + subject: "New request to create tenant", + html: ` +
+
+

New Notification

+
+
+

${input.creatorEmail} has requested to create a new tenant ${input.tenantId}

+ + Open Requests + +
+
+ `, + text: ` + ${input.creatorEmail} has requested to create a new tenant ${input.tenantId} + + Open Requests: ${input.appUrl}/user/tenants + `, + }; + + default: + throw new Error("Should never come here"); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + sendEmail = async (input: PluginEmailDeliveryInput & { userContext: UserContext }) => { + logDebugMessage("No email service configured and no override provided for sendEmail"); + throw new Error( + "Email functionality not configured. " + + "Please provide either:\n" + + "1. A service (like PluginSMTPService) in emailDelivery.service, OR\n" + + "2. Override the sendEmail function in emailDelivery.override", + ); + }; +} diff --git a/packages/tenants-nodejs/src/emailServices.ts b/packages/tenants-nodejs/src/emailServices.ts new file mode 100644 index 0000000..e8f67c8 --- /dev/null +++ b/packages/tenants-nodejs/src/emailServices.ts @@ -0,0 +1,50 @@ +import { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types"; +import { createTransport, Transporter } from "nodemailer"; +import { UserContext } from "supertokens-node/types"; +import { PluginEmailDeliveryInput } from "./types"; +import { DefaultPluginEmailService } from "./defaultEmailService"; + +export class PluginSMTPService implements EmailDeliveryInterface { + private transporter: Transporter; + private fromConfig: { name: string; email: string }; + + constructor(config: { + smtpSettings: { + host: string; + port: number; + secure?: boolean; + authUsername?: string; + password: string; + from: { name: string; email: string }; + }; + }) { + this.transporter = createTransport({ + host: config.smtpSettings.host, + port: config.smtpSettings.port, + auth: { + user: config.smtpSettings.authUsername || config.smtpSettings.from.email, + pass: config.smtpSettings.password, + }, + secure: config.smtpSettings.secure ?? false, + }); + this.fromConfig = config.smtpSettings.from; + } + + sendEmail = async (input: PluginEmailDeliveryInput & { userContext: UserContext }) => { + const emailContent = this.generateEmailContent(input); + + await this.transporter.sendMail({ + from: `${this.fromConfig.name} <${this.fromConfig.email}>`, + to: input.email, + subject: emailContent.subject, + html: emailContent.html, + text: emailContent.text, + }); + }; + + private generateEmailContent(input: PluginEmailDeliveryInput) { + // Import shared template generator + const templateService = new DefaultPluginEmailService(); + return templateService.generateEmailContent(input); + } +} diff --git a/packages/tenants-nodejs/src/index.ts b/packages/tenants-nodejs/src/index.ts new file mode 100644 index 0000000..7b2dcf1 --- /dev/null +++ b/packages/tenants-nodejs/src/index.ts @@ -0,0 +1,24 @@ +import { init } from "./plugin"; +import { PluginSMTPService } from "./emailServices"; + +export { init }; +export { PLUGIN_ID } from "./constants"; + +export type { + SuperTokensPluginTenantPluginConfig, + AssociateAllLoginMethodsOfUserWithTenant, + PluginEmailDeliveryInput, + SendPluginEmail, + GetUserIdsInTenantWithRole, + GetAppUrl, +} from "./types"; + +// Export email services for user configuration +export { PluginSMTPService } from "./emailServices"; + +export default { + init, + SMTPService: PluginSMTPService, +}; + +export { assignAdminToUserInTenant, assignRoleToUserInTenant } from "./roles"; diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts new file mode 100644 index 0000000..a5a2aa5 --- /dev/null +++ b/packages/tenants-nodejs/src/plugin.ts @@ -0,0 +1,943 @@ +import { NormalisedAppinfo, SuperTokensPlugin, UserContext } from "supertokens-node/types"; +import MultiTenancy from "supertokens-node/recipe/multitenancy"; +import Session from "supertokens-node/recipe/session"; +import { logDebugMessage } from "supertokens-node/lib/build/logger"; +import supertokens from "supertokens-node"; +import UserRoles from "supertokens-node/recipe/userroles"; + +import { createPluginInitFunction } from "@shared/js"; +import { pluginUserMetadata, withRequestHandler } from "@shared/nodejs"; + +import { + OverrideableTenantFunctionImplementation, + SuperTokensPluginTenantPluginConfig, + PluginEmailDeliveryInput, + SendPluginEmail, + SuperTokensPluginTenantPluginNormalisedConfig +} from "./types"; +import { HANDLE_BASE_PATH, METADATA_KEY, PLUGIN_ID, PLUGIN_SDK_VERSION } from "./constants"; +import { BooleanClaim } from "supertokens-node/lib/build/recipe/session/claims"; +import { ROLES, TenantCreationRequestMetadata, TenantMetadata } from "@shared/tenants"; +import { assignAdminToUserInTenant, assignRoleToUserInTenant, createRoles, getUserIdsInTenantWithRole } from "./roles"; +import { extractInvitationCodeAndTenantId, validateWithoutClaim } from "./util"; +import { getOverrideableTenantFunctionImplementation, rejectRequestToJoinTenant } from "./recipeImplementation"; +import { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types"; +import { DefaultPluginEmailService } from "./defaultEmailService"; + +export const init = createPluginInitFunction< + SuperTokensPlugin, + SuperTokensPluginTenantPluginConfig, + OverrideableTenantFunctionImplementation, + SuperTokensPluginTenantPluginNormalisedConfig +>( + (pluginConfig, implementation) => { + const metadata = pluginUserMetadata(METADATA_KEY); + const tenantCreationRequestMetadata = pluginUserMetadata(METADATA_KEY); + + let appInfo: NormalisedAppinfo; + + // Whether the user should have a non public tenant associated + // with them or not. + // + // This defaults to `false` and is only enabled if the `requireNonPublicTenantAssociation` + // is set to `true`. + const MultipleTenantsPresentClaim = new BooleanClaim({ + key: `${PLUGIN_ID}-multiple-tenants-present`, + fetchValue: async (userId) => { + const userDetails = await supertokens.getUser(userId); + if (!userDetails) { + return false; + } + + // Do not assume that everyone is part of public tenant + return userDetails.tenantIds.length === 1; + }, + }); + + // Initialize email service - use provided service or default + const baseEmailService: EmailDeliveryInterface = + pluginConfig.emailDelivery?.service || new DefaultPluginEmailService(); + + // Apply override if provided (this is where users can provide their sendEmail implementation) + const emailService: EmailDeliveryInterface = pluginConfig.emailDelivery?.override + ? pluginConfig.emailDelivery.override(baseEmailService) + : baseEmailService; + + // Helper function to send emails + const sendPluginEmail: SendPluginEmail = async (input: PluginEmailDeliveryInput, userContext: UserContext) => { + try { + await emailService.sendEmail({ + ...input, + userContext, + }); + } catch (error: any) { + logDebugMessage(`Failed to send email: ${error.message}`); + throw new Error(`Failed to send ${input.type.toLowerCase()} email: ${error.message}`); + } + }; + + return { + id: PLUGIN_ID, + compatibleSDKVersions: PLUGIN_SDK_VERSION, + init: async (appConfig) => { + await createRoles(); + logDebugMessage("TenantPlugin initialized with email service"); + + appInfo = appConfig.appInfo; + }, + routeHandlers() { + return { + status: "OK", + routeHandlers: [ + { + path: `${HANDLE_BASE_PATH}/list`, + method: "get", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + if (!pluginConfig.requireNonPublicTenantAssociation) { + return globalValidators; + } + + return validateWithoutClaim(globalValidators, MultipleTenantsPresentClaim.key); + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + return implementation.getTenants(session); + }), + }, + { + path: `${HANDLE_BASE_PATH}/create`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + if (!pluginConfig.requireNonPublicTenantAssociation) { + return globalValidators; + } + + return validateWithoutClaim(globalValidators, MultipleTenantsPresentClaim.key); + }, + }, + handler: withRequestHandler(async (req, res, session, userContext) => { + if (!session) { + throw new Error("Session not found"); + } + + if (!implementation.isAllowedToCreateTenant(session)) { + return { + status: "ERROR", + message: "You are not allowed to create a tenant", + }; + } + + const payload: { name?: string; firstFactors?: string[] } | undefined = await req.getJSONBody(); + if (!payload?.name?.trim()) { + return { + status: "ERROR", + message: "Name is required", + }; + } + const firstFactors = payload.firstFactors ?? null; + + // We need to check if tenant creation requires approval, in which case + // we will not create the tenant until an app admin approves it. + // Essentially, we will just store the tenant details in the metadata + // until it is approved or rejected. + const appUrl = implementation.getAppUrl(appInfo, req, userContext); + if (await implementation.doesTenantCreationRequireApproval(session)) { + // Add tenant details to metadata + const addTenantCreationRequestResponse = await implementation.addTenantCreationRequest( + session, + { + name: payload.name, + firstFactors, + }, + tenantCreationRequestMetadata, + appUrl, + userContext, + sendPluginEmail, + ); + + return { + ...addTenantCreationRequestResponse, + pendingApproval: true, + firstFactors, + }; + } + + const createResponse = await implementation.createTenantAndAssignAdmin( + { + name: payload.name, + firstFactors, + }, + session.getUserId(), + ); + + if (createResponse.status !== "OK") { + return createResponse; + } + + return { + status: "OK", + createdNew: createResponse.createdNew, + firstFactors, + isPendingApproval: false, + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/tenant-requests/list`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + // Should only be accessible to app admin roles + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.APP_ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + return implementation.getTenantCreationRequests(tenantCreationRequestMetadata); + }), + }, + { + path: `${HANDLE_BASE_PATH}/tenant-requests/accept`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + // Should only be accessible to app admin roles + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.APP_ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const payload: { requestId: string } | undefined = await req.getJSONBody(); + if (!payload?.requestId) { + return { + status: "ERROR", + message: "Request ID is required", + }; + } + + return implementation.acceptTenantCreationRequest( + payload.requestId, + session, + tenantCreationRequestMetadata, + ); + }), + }, + { + path: `${HANDLE_BASE_PATH}/tenant-requests/reject`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + // Should only be accessible to app admin roles + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.APP_ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const payload: { requestId: string } | undefined = await req.getJSONBody(); + if (!payload?.requestId) { + return { + status: "ERROR", + message: "Request ID is required", + }; + } + + return implementation.rejectTenantCreationRequest( + payload.requestId, + session, + tenantCreationRequestMetadata, + ); + }), + }, + { + path: `${HANDLE_BASE_PATH}/join`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + if (!pluginConfig.requireNonPublicTenantAssociation) { + return globalValidators; + } + + return validateWithoutClaim(globalValidators, MultipleTenantsPresentClaim.key); + }, + }, + handler: withRequestHandler(async (req, res, session, userContext) => { + if (!session) { + throw new Error("Session not found"); + } + + const userDetails = await supertokens.getUser(session.getUserId()); + if (!userDetails) { + return { + status: "USER_NOT_FOUND", + message: "User not found", + }; + } + + if (!implementation.isAllowedToJoinTenant(userDetails, session)) { + return { + status: "ERROR", + message: "You are not allowed to join a tenant", + }; + } + + const payload: { tenantId: string } | undefined = await req.getJSONBody(); + if (!payload?.tenantId) { + return { + status: "ERROR", + message: "Tenant ID is required", + }; + } + + const tenantDetails = await MultiTenancy.getTenant(payload.tenantId); + if (!tenantDetails) { + return { + status: "ERROR", + message: "Tenant not found", + }; + } + + // Associate the user with the tenant + // NOTE: Unlikely that login method will be undefined so we can ignore that. + await MultiTenancy.associateUserToTenant(payload.tenantId, userDetails.loginMethods[0]!.recipeUserId); + + // Do session.revoke and create a new session instead of the above + await session.revokeSession(); + await Session.createNewSession( + req, + res, + payload.tenantId, + session.getRecipeUserId(), + session.getAccessTokenPayload(), + undefined, + userContext, + ); + + return { + status: "OK", + message: "User associated with tenant", + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/leave`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + if (!pluginConfig.requireNonPublicTenantAssociation) { + return globalValidators; + } + + return validateWithoutClaim(globalValidators, MultipleTenantsPresentClaim.key); + }, + }, + handler: withRequestHandler(async (req, res, session, userContext) => { + if (!session) { + throw new Error("Session not found"); + } + + const userDetails = await supertokens.getUser(session.getUserId()); + if (!userDetails) { + return { + status: "USER_NOT_FOUND", + message: "User not found", + }; + } + + const tenantId = session.getTenantId(); + + const tenantDetails = await MultiTenancy.getTenant(tenantId); + if (!tenantDetails) { + return { + status: "ERROR", + message: "Tenant not found", + }; + } + + // Associate the user with the tenant + // NOTE: Unlikely that login method will be undefined so we can ignore that. + await MultiTenancy.disassociateUserFromTenant(tenantId, userDetails.loginMethods[0]!.recipeUserId); + + // Do session.revoke and create a new session instead of the above + await session.revokeSession(); + await Session.createNewSession( + req, + res, + tenantId, + session.getRecipeUserId(), + session.getAccessTokenPayload(), + undefined, + userContext, + ); + + return { + status: "OK", + message: "User disassociated from tenant", + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/users`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [ + ...globalValidators, + UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN, ROLES.MEMBER]), + ]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const tenantIdToUse = session.getTenantId(); + const getUsersResponse = await implementation.getTenantUsers(tenantIdToUse); + + return getUsersResponse; + }), + }, + // Invite related routes + { + path: `${HANDLE_BASE_PATH}/invite/add`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + // Parse the tenantId from the body + const payload: { email: string } | undefined = await req.getJSONBody(); + const tenantId = session.getTenantId(); + const email = payload?.email?.trim(); + + if (!email) { + return { + status: "ERROR", + message: "Email is required", + }; + } + + return implementation.addInvitation(email, tenantId, metadata); + }), + }, + { + path: `${HANDLE_BASE_PATH}/invite/remove`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + // Parse the tenantId from the body + const payload: { email: string } | undefined = await req.getJSONBody(); + const tenantId = session.getTenantId(); + const email = payload?.email?.trim(); + if (!email) { + return { + status: "ERROR", + message: "Email is required", + }; + } + + return implementation.removeInvitation(email, tenantId, metadata); + }), + }, + { + path: `${HANDLE_BASE_PATH}/invite/list`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [ + ...globalValidators, + UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN, ROLES.MEMBER]), + ]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const tenantIdToUse = session.getTenantId(); + return implementation.getInvitations(tenantIdToUse, metadata); + }), + }, + { + path: `${HANDLE_BASE_PATH}/invite/accept`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const payload: { tenantId: string; code: string } | undefined = await req.getJSONBody(); + const tenantId = payload?.tenantId; + const code = payload?.code; + if (!tenantId || !code) { + return { + status: "ERROR", + message: "Tenant ID and code are required", + }; + } + + return implementation.acceptInvitation(code, tenantId, session, metadata); + }), + }, + { + // TODO: Remove this before merging + path: `${HANDLE_BASE_PATH}/become-admin`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const payload: { tenantId: string } | undefined = await req.getJSONBody(); + const tenantId = payload?.tenantId; + if (!tenantId) { + return { + status: "ERROR", + message: "Tenant ID is required", + }; + } + + await assignAdminToUserInTenant(tenantId, session.getUserId()); + logDebugMessage(`Admin role assigned to user: ${session.getUserId()}`); + + const roles = await UserRoles.getUsersThatHaveRole(tenantId, ROLES.ADMIN); + logDebugMessage(`roles: ${JSON.stringify(roles)}`); + + // Do session.revoke and create a new session instead of the above + await session.fetchAndSetClaim(UserRoles.UserRoleClaim); + + return { + status: "OK", + message: "Admin role assigned to user", + roles: roles, + }; + }), + }, + // Request related routes + { + path: `${HANDLE_BASE_PATH}/request/list`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const tenantIdToUse = session.getTenantId(); + + // Find all the users in the tenants that do not have a role and return + // that list. + // + // We will find all the users in the tenant and subtract the users that + // have the admin or member role. + const tenantUsers = await implementation.getTenantUsers(tenantIdToUse); + if (tenantUsers.status !== "OK") { + return tenantUsers; + } + + // Find all the users that have the admin and member role + const adminUsers = await getUserIdsInTenantWithRole(tenantIdToUse, ROLES.ADMIN); + const memberUsers = await getUserIdsInTenantWithRole(tenantIdToUse, ROLES.MEMBER); + + // Find all the users that do not have a role + const usersWithoutRole = tenantUsers.users.filter( + (user) => !adminUsers.includes(user.id) && !memberUsers.includes(user.id), + ); + + return { + status: "OK", + users: usersWithoutRole, + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/request/accept`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + // Parse the tenantId from the body + const tenantIdToUse = session.getTenantId(); + const payload: { userId: string } | undefined = await req.getJSONBody(); + + if (!payload?.userId) { + return { + status: "ERROR", + message: "User ID is required", + }; + } + + // We need to check that the user doesn't have an existing role, in which + // case we cannot "accept" the request. + const role = await UserRoles.getRolesForUser(tenantIdToUse, payload.userId); + if (role.roles.length > 0) { + return { + status: "ERROR", + message: "User is already a member of the tenant", + }; + } + + await assignRoleToUserInTenant(tenantIdToUse, payload.userId, ROLES.MEMBER); + + return { + status: "OK", + message: "Request accepted", + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/request/reject`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + const tenantIdToUse = session.getTenantId(); + const payload: { userId: string } | undefined = await req.getJSONBody(); + + if (!payload?.userId) { + return { + status: "ERROR", + message: "User ID is required", + }; + } + + return rejectRequestToJoinTenant(tenantIdToUse, payload.userId); + }), + }, + { + path: `${HANDLE_BASE_PATH}/request/add`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + const userId = session.getUserId(); + + const payload: { tenantId: string } | undefined = await req.getJSONBody(); + if (!payload?.tenantId) { + return { + status: "ERROR", + message: "Tenant ID is required", + }; + } + + // NOTE: We can skip the check for the user being already associated + // since they will be associated again without any errors. + await implementation.associateAllLoginMethodsOfUserWithTenant(payload.tenantId, userId); + + return { + status: "OK", + message: "Request added", + }; + }), + }, + { + path: `${HANDLE_BASE_PATH}/switch-tenant`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + // NOTE: This is a special case where we cannot + // use role based claims for checking if user can + // switch to tenant. + }, + handler: withRequestHandler(async (req, res, session, userContext) => { + if (!session) { + throw new Error("Session not found"); + } + + const payload: { tenantId: string } | undefined = await req.getJSONBody(); + const tenantId = payload?.tenantId; + if (!tenantId) { + return { + status: "ERROR", + message: "Tenant ID is required", + }; + } + + // Check if the user has the role of member or admin in the tenant. + const roles = (await UserRoles.getRolesForUser(tenantId, session.getUserId())).roles; + if (roles.length === 0) { + return { + status: "ERROR_NOT_ALLOWED", + message: "Cannot switch to tenant", + }; + } + + // Since the user has a role, ensure that it is a valid one. + if (!roles.includes(ROLES.ADMIN) && !roles.includes(ROLES.MEMBER)) { + return { + status: "ERROR_NOT_ALLOWED", + message: "Requires member or higher role", + roles: roles, + }; + } + + // Do session.revoke and create a new session instead of the above + await session.revokeSession(); + await Session.createNewSession( + req, + res, + payload.tenantId, + session.getRecipeUserId(), + session.getAccessTokenPayload(), + undefined, + userContext, + ); + + return { + status: "OK", + message: "Session switched", + }; + }), + }, + ], + }; + }, + overrideMap: { + session: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + getGlobalClaimValidators: async function (input) { + if (!pluginConfig.requireNonPublicTenantAssociation) { + return input.claimValidatorsAddedByOtherRecipes; + } + + return [...input.claimValidatorsAddedByOtherRecipes, MultipleTenantsPresentClaim.validators.isTrue()]; + }, + createNewSession: async (input) => { + const userDetails = await supertokens.getUser(input.userId); + if (!userDetails) { + logDebugMessage(`User ${input.userId} not found, should never come here, throwing error`); + throw new Error("Should never come here"); + } + logDebugMessage(`User found for user id: ${input.userId}`); + + let tenantId = input.tenantId; + + // If they have a non public tenant, that gets the preference + // when creating the session. + + const firstNonPublicTenantId = userDetails.tenantIds.find((tenantId) => tenantId !== "public"); + if (firstNonPublicTenantId && firstNonPublicTenantId !== input.tenantId) { + logDebugMessage(`Creating new session with tenant: ${firstNonPublicTenantId}`); + return Session.createNewSessionWithoutRequestResponse( + firstNonPublicTenantId, + input.recipeUserId, + input.accessTokenPayload, + input.sessionDataInDatabase, + input.disableAntiCsrf, + input.userContext, + ); + } + + logDebugMessage(`Tenant ID to use for new session: ${tenantId}`); + + input.accessTokenPayload = { + ...input.accessTokenPayload, + ...(pluginConfig.requireNonPublicTenantAssociation + ? await MultipleTenantsPresentClaim.build( + input.userId, + input.recipeUserId, + tenantId, + input.accessTokenPayload, + input.userContext, + ) + : {}), + }; + + return originalImplementation.createNewSession({ + ...input, + tenantId, + }); + }, + }; + }, + }, + emailpassword: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + signInPOST: async (input) => { + const { code, tenantId, shouldAcceptInvite } = await extractInvitationCodeAndTenantId( + input.options.req, + ); + + const signInResponse = await originalImplementation.signInPOST!(input); + if (!shouldAcceptInvite || signInResponse.status !== "OK") return signInResponse; + + const invitationResponse = await implementation.acceptInvitation( + code, + tenantId, + signInResponse.session, + metadata, + ); + + return { + ...signInResponse, + invitation: invitationResponse, + }; + }, + signUpPOST: async (input) => { + const { code, tenantId, shouldAcceptInvite } = await extractInvitationCodeAndTenantId( + input.options.req, + ); + + const signUpResponse = await originalImplementation.signUpPOST!(input); + if (!shouldAcceptInvite || signUpResponse.status !== "OK") return signUpResponse; + + const invitationResponse = await implementation.acceptInvitation( + code, + tenantId, + signUpResponse.session, + metadata, + ); + + return { + ...signUpResponse, + invitation: invitationResponse, + }; + }, + }; + }, + }, + passwordless: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + consumeCodePOST: async (input) => { + const { code, tenantId, shouldAcceptInvite } = await extractInvitationCodeAndTenantId( + input.options.req, + ); + + const consumeCodeResponse = await originalImplementation.consumeCodePOST!(input); + if (!shouldAcceptInvite || consumeCodeResponse.status !== "OK") return consumeCodeResponse; + + const invitationResponse = await implementation.acceptInvitation( + code, + tenantId, + consumeCodeResponse.session, + metadata, + ); + + return { + ...consumeCodeResponse, + invitation: invitationResponse, + }; + }, + }; + }, + }, + thirdparty: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + signInUpPOST: async (input) => { + const { code, tenantId, shouldAcceptInvite } = await extractInvitationCodeAndTenantId( + input.options.req, + ); + + const signInUpResponse = await originalImplementation.signInUpPOST!(input); + if (!shouldAcceptInvite || signInUpResponse.status !== "OK") return signInUpResponse; + + const invitationResponse = await implementation.acceptInvitation( + code, + tenantId, + signInUpResponse.session, + metadata, + ); + + return { + ...signInUpResponse, + invitation: invitationResponse, + }; + }, + }; + }, + }, + }, + exports: { + associateAllLoginMethodsOfUserWithTenant: implementation.associateAllLoginMethodsOfUserWithTenant, + sendEmail: sendPluginEmail, + getUserIdsInTenantWithRole, + getAppUrl: implementation.getAppUrl, + }, + }; + }, + getOverrideableTenantFunctionImplementation, + (config) => ({ + requireNonPublicTenantAssociation: config.requireNonPublicTenantAssociation ?? false, + requireTenantCreationRequestApproval: config.requireTenantCreationRequestApproval ?? true, + }) +); diff --git a/packages/tenants-nodejs/src/recipeImplementation.ts b/packages/tenants-nodejs/src/recipeImplementation.ts new file mode 100644 index 0000000..8d7bb92 --- /dev/null +++ b/packages/tenants-nodejs/src/recipeImplementation.ts @@ -0,0 +1,480 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import supertokens from "supertokens-node"; +import { SessionContainerInterface } from "supertokens-node/recipe/session/types"; +import MultiTenancy from "supertokens-node/recipe/multitenancy"; +import { InviteeDetails, ROLES, TenantList } from "@shared/tenants"; +import { User } from "supertokens-node/types"; +import { + ErrorResponse, + MetadataType, + NonOkResponse, + OverrideableTenantFunctionImplementation, + SuperTokensPluginTenantPluginConfig, + TenantCreationRequestMetadataType, +} from "./types"; +import { logDebugMessage } from "supertokens-node/lib/build/logger"; +import UserRoles from "supertokens-node/recipe/userroles"; +import { LoginMethod } from "supertokens-node/lib/build/user"; +import { assignAdminToUserInTenant, getUserIdsInTenantWithRole } from "./roles"; +import { TENANT_CREATE_METADATA_REQUESTS_KEY } from "./constants"; + +export const getOverrideableTenantFunctionImplementation = ( + pluginConfig: SuperTokensPluginTenantPluginConfig, +): OverrideableTenantFunctionImplementation => { + const implementation: OverrideableTenantFunctionImplementation = { + getTenants: async ( + sessionOrUserId: SessionContainerInterface | string, + ): Promise<({ status: "OK" } & TenantList) | { status: "ERROR"; message: string }> => { + const userId = typeof sessionOrUserId === "string" ? sessionOrUserId : sessionOrUserId.getUserId(); + + const userDetails = await supertokens.getUser(userId); + if (!userDetails) { + return { + status: "ERROR", + message: "User not found", + }; + } + + const tenantDetails = await MultiTenancy.listAllTenants(); + + // Return the tenants that the user is not a member of + return { + ...tenantDetails, + joinedTenantIds: userDetails.tenantIds, + }; + }, + getTenantUsers: async (tenantId: string): Promise<{ status: "OK"; users: (User & { roles?: string[] })[] }> => { + const getUsersResponse = await supertokens.getUsersOldestFirst({ + tenantId: tenantId, + }); + + // Find all the users that have a role in the tenant + // and return details. + // Iterate through all the the available roles and find users. + const userIdToRoleMap: Record = {}; + for (const role of Object.values(ROLES)) { + const users = await getUserIdsInTenantWithRole(tenantId, role); + for (const user of users) { + userIdToRoleMap[user] = [...(userIdToRoleMap[user] || []), role]; + } + } + + return { + status: "OK", + users: getUsersResponse.users.map((user) => ({ + ...user, + roles: userIdToRoleMap[user.id] ?? [], + })), + }; + }, + addInvitation: async ( + email: string, + tenantId: string, + metadata: MetadataType, + ): Promise<{ status: "OK"; code: string } | NonOkResponse | ErrorResponse> => { + // Check if the user: + // 1. is already associated with the tenant + // 2. is already invited to the tenant + + const getUsersResponse = await supertokens.getUsersOldestFirst({ + tenantId: tenantId, + }); + + // TODO: Add support for role + + // We will have to find whether the user is already associated + // by searching with the email. + const userDetails = getUsersResponse.users.find((user) => user.emails.some((userEmail) => userEmail === email)); + if (userDetails) { + return { + status: "USER_ALREADY_ASSOCIATED", + message: "User already associated with tenant", + }; + } + + // Check if the user is already invited to the tenant + let tenantMetadata = await metadata.get(tenantId); + if (tenantMetadata?.invitees.some((invitee) => invitee.email === email)) { + return { + status: "USER_ALREADY_INVITED", + message: "User already invited to tenant", + }; + } + + if (tenantMetadata === undefined) { + tenantMetadata = { + invitees: [], + }; + } + + // Generate a random string for the code + const code = Math.random().toString(36).substring(2, 15); + + // Invite the user to the tenant + await metadata.set(tenantId, { + ...tenantMetadata, + invitees: [...tenantMetadata.invitees, { email, role: "user", code }], + }); + + return { + status: "OK", + message: "User invited to tenant", + code, + }; + }, + removeInvitation: async ( + email: string, + tenantId: string, + metadata: MetadataType, + ): Promise<{ status: "OK" } | NonOkResponse | ErrorResponse> => { + // Check if the user is invited to the tenant + const tenantMetadata = await metadata.get(tenantId); + if (!tenantMetadata) { + return { + status: "ERROR", + message: "Tenant not found", + }; + } + + // Check if the user is invited to the tenant + const isInvited = tenantMetadata.invitees.some((invitee) => invitee.email === email && invitee.role === "user"); + if (!isInvited) { + return { + status: "ERROR", + message: "User not invited to tenant", + }; + } + + // Remove the invitation from the tenants's metadata. + await metadata.set(tenantId, { + ...tenantMetadata, + invitees: tenantMetadata.invitees.filter((invitee) => invitee.email !== email), + }); + + return { + status: "OK", + message: "Invitation removed from tenant", + }; + }, + getInvitations: async ( + tenantId: string, + metadata: MetadataType, + ): Promise<{ status: "OK"; invitees: InviteeDetails[] } | NonOkResponse | ErrorResponse> => { + const tenantMetadata = await metadata.get(tenantId); + if (!tenantMetadata) { + return { + status: "ERROR", + message: "Tenant not found", + }; + } + + return { + status: "OK", + invitees: tenantMetadata.invitees, + }; + }, + acceptInvitation: async ( + code: string, + tenantId: string, + session: SessionContainerInterface, + metadata: MetadataType, + ): Promise<{ status: "OK" } | NonOkResponse | ErrorResponse> => { + // Check if the user is invited to the tenant + const tenantMetadata = await metadata.get(tenantId); + if (!tenantMetadata) { + return { + status: "ERROR", + message: "Tenant not found", + }; + } + + // Find the invitation details + const inviteeDetails = tenantMetadata.invitees.find((invitee) => invitee.code === code); + if (!inviteeDetails) { + return { + status: "ERROR", + message: "Invitation not found", + }; + } + + await implementation.associateAllLoginMethodsOfUserWithTenant( + tenantId, + session.getUserId(), + (loginMethod) => loginMethod.email === inviteeDetails.email, + ); + + // Remove the invitation from the tenants's metadata. + await metadata.set(tenantId, { + ...tenantMetadata, + invitees: tenantMetadata.invitees.filter((invitee) => invitee.email !== inviteeDetails.email), + }); + logDebugMessage(`Removed invitation from tenant ${tenantId}`); + + // TODO: Add the user with the role + + return { + status: "OK", + message: "Invitation accepted", + }; + }, + isAllowedToJoinTenant: async (user: User, session: SessionContainerInterface) => { + // By default we will allow all users to join a tenant. + return true; + }, + isAllowedToCreateTenant: async (session: SessionContainerInterface) => { + // By default we will allow all users to create a tenant. + return true; + }, + canCreateInvitation: async (user: User, role: string, session: SessionContainerInterface) => { + // By default, only owners can create invitations. + return role === ROLES.ADMIN; + }, + canApproveJoinRequest: async (user: User, role: string, session: SessionContainerInterface) => { + // By default, only owners can approve join requests. + return role === ROLES.ADMIN; + }, + canApproveTenantCreationRequest: async (user: User, role: string, session: SessionContainerInterface) => { + // By default, only owners can approve tenant creation requests. + return role === ROLES.ADMIN; + }, + canRemoveUserFromTenant: async (user: User, role: string, session: SessionContainerInterface) => { + // By default, only owners can remove users from a tenant. + return role === ROLES.ADMIN; + }, + associateAllLoginMethodsOfUserWithTenant: async ( + tenantId: string, + userId: string, + loginMethodFilter?: (loginMethod: LoginMethod) => boolean, + ) => { + const userDetails = await supertokens.getUser(userId); + if (!userDetails) { + throw new Error(`User ${userId} not found`); + } + + // Find all the loginMethods for the user that match the email for the + // invitation. + const loginMethods = userDetails.loginMethods.filter(loginMethodFilter ?? (() => true)); + logDebugMessage(`loginMethods: ${JSON.stringify(loginMethods)}`); + + // For each of the loginMethods, associate the user with the tenant + for (const loginMethod of loginMethods) { + await MultiTenancy.associateUserToTenant(tenantId, loginMethod.recipeUserId); + logDebugMessage(`Associated user ${userDetails.id} with tenant ${tenantId}`); + } + }, + doesTenantCreationRequireApproval: async (session: SessionContainerInterface) => { + // By default, tenant creation does not require approval. + return pluginConfig.requireTenantCreationRequestApproval ?? true; + }, + addTenantCreationRequest: async (session, tenantDetails, metadata, appUrl, userContext, sendEmail) => { + // Add tenant creation request to metadata + let tenantCreateRequestMetadata = await metadata.get(TENANT_CREATE_METADATA_REQUESTS_KEY); + + if (tenantCreateRequestMetadata === undefined) { + // Initialize it + tenantCreateRequestMetadata = { + requests: [], + }; + } + + // Add the new creation request + const requestId = Math.random().toString(36).substring(2, 15); + await metadata.set(TENANT_CREATE_METADATA_REQUESTS_KEY, { + ...tenantCreateRequestMetadata, + requests: [ + ...(tenantCreateRequestMetadata.requests ?? []), + { ...tenantDetails, userId: session.getUserId(), requestId }, + ], + }); + + // Extract the email of the user that is creating the tenant + const creatorUserId = session.getUserId(); + const userDetails = await supertokens.getUser(creatorUserId); + const creatorEmail = userDetails?.emails[0]; + + // Notify app admins + await implementation.sendTenantCreationRequestEmail( + tenantDetails.name, + creatorEmail ?? creatorUserId, + appUrl, + userContext, + sendEmail, + ); + + return { + status: "OK", + requestId, + }; + }, + getTenantCreationRequests: async (metadata: TenantCreationRequestMetadataType) => { + const tenantCreateRequestMetadata = await metadata.get(TENANT_CREATE_METADATA_REQUESTS_KEY); + return { + status: "OK", + requests: tenantCreateRequestMetadata?.requests ?? [], + }; + }, + acceptTenantCreationRequest: async (requestId, session, metadata) => { + /** + * Mark the request as accepted by creating the tenant + * and remove the create request. + * + * @param requestId - The id of the request to accept + * @param session - The session of the user accepting the request + * @param metadata - The metadata of the tenant + * @returns The status of the request + */ + const tenantCreateRequestMetadata = await metadata.get(TENANT_CREATE_METADATA_REQUESTS_KEY); + if (!tenantCreateRequestMetadata) { + return { + status: "ERROR", + message: "Tenant creation request not found", + }; + } + + // Find the request + const request = tenantCreateRequestMetadata.requests.find((request) => request.requestId === requestId); + if (!request) { + return { + status: "ERROR", + message: "Tenant creation request not found", + }; + } + + // Create the tenant and assign admin to the user that added the request. + const createResponse = await implementation.createTenantAndAssignAdmin( + { + name: request.name, + firstFactors: request.firstFactors, + }, + request.userId, + ); + + if (createResponse.status !== "OK") { + return createResponse; + } + + // Remove the request from the metadata + await metadata.set(TENANT_CREATE_METADATA_REQUESTS_KEY, { + ...tenantCreateRequestMetadata, + requests: tenantCreateRequestMetadata.requests.filter((request) => request.requestId !== requestId), + }); + + return { + status: "OK", + }; + }, + createTenantAndAssignAdmin: async (tenantDetails, userId) => { + const createResponse = await MultiTenancy.createOrUpdateTenant(tenantDetails.name, { + firstFactors: tenantDetails.firstFactors, + }); + + // Add the user as the admin of the tenant + await assignAdminToUserInTenant(tenantDetails.name, userId); + + return createResponse; + }, + rejectTenantCreationRequest: async (requestId, session, metadata) => { + const tenantCreateRequestMetadata = await metadata.get(TENANT_CREATE_METADATA_REQUESTS_KEY); + if (!tenantCreateRequestMetadata) { + return { + status: "ERROR", + message: "Tenant creation request not found", + }; + } + + // Remove the request from the metadata + await metadata.set(TENANT_CREATE_METADATA_REQUESTS_KEY, { + ...tenantCreateRequestMetadata, + requests: tenantCreateRequestMetadata.requests.filter((request) => request.requestId !== requestId), + }); + + return { + status: "OK", + }; + }, + sendTenantCreationRequestEmail: async (tenantId, creatorEmail, appUrl, userContext, sendEmail) => { + /** + * Send an email to all the admins of the app. + * + * @param tenantId - The id of the tenant that is being created + * @param creatorEmail - The email of the user that is creating the tenant + * @param appUrl - The url of the app + */ + const adminUsers = await getUserIdsInTenantWithRole("public", ROLES.APP_ADMIN); + + // For each of the users, we will need to find their email address. + const adminEmails = await Promise.all( + adminUsers.map(async (userId) => { + const userDetails = await supertokens.getUser(userId); + return userDetails?.emails[0]; + }), + ); + + // Send emails to all tenant admins using Promise.all + await Promise.all( + adminEmails + .filter((email) => email !== undefined) + .map(async (email) => { + await sendEmail( + { + type: "TENANT_CREATE_APPROVAL", + email, + tenantId, + creatorEmail, + appUrl, + }, + userContext, + ); + }), + ); + }, + getAppUrl: (appInfo, request, userContext) => { + /** + * Get the App URL using the app info, request and user context. + */ + const websiteDomain = appInfo.getTopLevelWebsiteDomain({ + request, + userContext, + }); + return `${websiteDomain ? "https://" : "http://"}${websiteDomain ?? "localhost"}${appInfo.websiteBasePath ?? ""}`; + }, + }; + + return implementation; +}; + +export const rejectRequestToJoinTenant = async ( + tenantId: string, + userId: string, +): Promise<{ status: "OK" } | NonOkResponse | ErrorResponse> => { + // We need to check that the user doesn't have an existing role, in which + // case we cannot "accept" the request. + const role = await UserRoles.getRolesForUser(tenantId, userId); + if (role.roles.length > 0) { + return { + status: "ERROR", + message: "Request already accepted", + }; + } + + // Find all the recipeUserIds for the user + // Remove the user from the tenant + const userDetails = await supertokens.getUser(userId); + if (!userDetails) { + return { + status: "ERROR", + message: "User not found", + }; + } + + // For each of the loginMethods, associate the user with the tenant + for (const loginMethod of userDetails.loginMethods) { + await MultiTenancy.disassociateUserFromTenant(tenantId, loginMethod.recipeUserId); + logDebugMessage(`Disassociated user ${userDetails.id} from tenant ${tenantId}`); + } + + return { + status: "OK", + message: "Request rejected", + }; +}; diff --git a/packages/tenants-nodejs/src/roles.ts b/packages/tenants-nodejs/src/roles.ts new file mode 100644 index 0000000..640f250 --- /dev/null +++ b/packages/tenants-nodejs/src/roles.ts @@ -0,0 +1,81 @@ +import UserRoles from "supertokens-node/recipe/userroles"; +import { logDebugMessage } from "supertokens-node/lib/build/logger"; + +import { PERMISSIONS, ROLES } from "@shared/tenants"; +import { GetUserIdsInTenantWithRole } from "./types"; + +export const createRoles = async () => { + // Create the roles + const adminCreateResponse = await UserRoles.createNewRoleOrAddPermissions(ROLES.ADMIN, [ + PERMISSIONS.READ, + PERMISSIONS.WRITE, + PERMISSIONS.DELETE, + ]); + const memberCreateResponse = await UserRoles.createNewRoleOrAddPermissions(ROLES.MEMBER, [ + PERMISSIONS.READ, + PERMISSIONS.WRITE, + ]); + const appAdminCreateResponse = await UserRoles.createNewRoleOrAddPermissions(ROLES.APP_ADMIN, [ + PERMISSIONS.READ, + PERMISSIONS.WRITE, + PERMISSIONS.DELETE, + ]); + + logDebugMessage(`Admin role created, already exists: ${!adminCreateResponse.createdNewRole}`); + logDebugMessage(`Member role created, already exists: ${!memberCreateResponse.createdNewRole}`); + logDebugMessage(`App admin role created, already exists: ${!appAdminCreateResponse.createdNewRole}`); +}; + +export const assignRoleToUserInTenant = async (tenantId: string, userId: string, role: string) => { + /** + * Function to assign the passed role to the passed user in the passed tenant. + * + * @param tenantId - The tenant id to assign the role to. + * @param userId - The user id to assign the role to. + * @param role - The role to assign to the user. + */ + const addRoleResponse = await UserRoles.addRoleToUser(tenantId, userId, role); + + logDebugMessage(`addRoleResponse: ${JSON.stringify(addRoleResponse)}`); + + if (addRoleResponse.status === "UNKNOWN_ROLE_ERROR") { + // NOTE: Should never come here + throw new Error("Role's not created yet, this should never happen"); + } + + logDebugMessage(`Did user already have role: ${addRoleResponse.didUserAlreadyHaveRole}`); +}; + +export const assignAdminToUserInTenant = async (tenantId: string, userId: string) => { + /** + * Function to assign the passed user as admin in the passed tenant. + * + * This function is useful when using the tenant management plugin in order + * to assign admin permissions to one user. + * + * @param tenantId - The tenant id to assign the admin to. + * @param userId - The user id to assign the admin to. + */ + return assignRoleToUserInTenant(tenantId, userId, ROLES.ADMIN); +}; + +export const getUserIdsInTenantWithRole: GetUserIdsInTenantWithRole = async ( + tenantId: string, + role: string, +): Promise => { + /** + * Get all the user ID's in the passed tenant with the specified role. + * + * @param tenantId - The tenant id to get the users from. + * @param role - The role to get the users with. + */ + const usersResponse = await UserRoles.getUsersThatHaveRole(tenantId, role); + + if (usersResponse.status === "UNKNOWN_ROLE_ERROR") { + // Should never happen since the role will be created before + // but need to handle it. + throw new Error("Role's not created yet, this should never happen"); + } + + return usersResponse.users; +}; diff --git a/packages/tenants-nodejs/src/types.ts b/packages/tenants-nodejs/src/types.ts new file mode 100644 index 0000000..c1f8cbb --- /dev/null +++ b/packages/tenants-nodejs/src/types.ts @@ -0,0 +1,159 @@ +import { SessionContainerInterface } from "supertokens-node/recipe/session/types"; +import { pluginUserMetadata } from "@shared/nodejs"; +import { + InviteeDetails, + TenantCreationRequest, + TenantCreationRequestMetadata, + TenantList, + TenantMetadata, +} from "@shared/tenants"; +import { NormalisedAppinfo, User, UserContext } from "supertokens-node/types"; +import { LoginMethod } from "supertokens-node/lib/build/user"; +import { EmailDeliveryInterface } from "supertokens-node/lib/build/ingredients/emaildelivery/types"; +import { BaseRequest } from "supertokens-node/lib/build/framework/index"; + +export type ErrorResponse = { + status: "ERROR"; + message: string; +}; + +export type NonOkResponse = ErrorResponse | { status: string; message: string }; +export type MetadataType = ReturnType>; +export type TenantCreationRequestMetadataType = ReturnType>; + +// Define custom email input type for the plugin +export type PluginEmailDeliveryInput = + | { + type: "TENANT_REQUEST_APPROVAL"; + email: string; + tenantId: string; + senderEmail: string; + customData?: Record; + appUrl: string; + } + | { + type: "TENANT_CREATE_APPROVAL"; + email: string; + tenantId: string; + creatorEmail: string; + customData?: Record; + appUrl: string; + }; + +export type SuperTokensPluginTenantPluginConfig = { + requireNonPublicTenantAssociation?: boolean; + requireTenantCreationRequestApproval?: boolean; + + // Email delivery configuration - service is optional, override can provide sendEmail implementation + emailDelivery?: { + service?: EmailDeliveryInterface; + override?: ( + originalImplementation: EmailDeliveryInterface, + ) => EmailDeliveryInterface; + }; +}; + +export type SuperTokensPluginTenantPluginNormalisedConfig = { + requireNonPublicTenantAssociation: boolean; + requireTenantCreationRequestApproval: boolean; + + // Email delivery configuration - service is optional, override can provide sendEmail implementation + emailDelivery?: { + service?: EmailDeliveryInterface; + override?: ( + originalImplementation: EmailDeliveryInterface, + ) => EmailDeliveryInterface; + }; +} + +export type SendPluginEmail = (input: PluginEmailDeliveryInput, userContext: UserContext) => Promise; + +export type AssociateAllLoginMethodsOfUserWithTenant = ( + tenantId: string, + userId: string, + loginMethodFilter?: (loginMethod: LoginMethod) => boolean, +) => Promise; + +export type GetUserIdsInTenantWithRole = (tenantId: string, role: string) => Promise; + +export type GetAppUrl = ( + appInfo: NormalisedAppinfo, + request: BaseRequest | undefined, + userContext: UserContext, +) => string; + +export type OverrideableTenantFunctionImplementation = { + getTenants: (session: SessionContainerInterface | string) => Promise<({ status: "OK" } & TenantList) | ErrorResponse>; + isAllowedToJoinTenant: (user: User, session: SessionContainerInterface) => Promise; + isAllowedToCreateTenant: (session: SessionContainerInterface) => Promise; + doesTenantCreationRequireApproval: (session: SessionContainerInterface) => Promise; + canCreateInvitation: (user: User, role: string, session: SessionContainerInterface) => Promise; + canApproveJoinRequest: (user: User, role: string, session: SessionContainerInterface) => Promise; + canApproveTenantCreationRequest: (user: User, role: string, session: SessionContainerInterface) => Promise; + canRemoveUserFromTenant: (user: User, role: string, session: SessionContainerInterface) => Promise; + createTenantAndAssignAdmin: ( + tenantDetails: { + name: string; + firstFactors?: string[] | null; + }, + userId: string, + ) => Promise<{ status: "OK"; createdNew: boolean } | ErrorResponse>; + getTenantUsers: ( + tenantId: string, + ) => Promise<{ status: "OK"; users: (User & { roles?: string[] })[] } | ErrorResponse>; + addInvitation: ( + email: string, + tenantId: string, + metadata: MetadataType, + ) => Promise<{ status: "OK"; code: string } | NonOkResponse | ErrorResponse>; + removeInvitation: ( + email: string, + tenantId: string, + metadata: MetadataType, + ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; + acceptInvitation: ( + code: string, + tenantId: string, + session: SessionContainerInterface, + metadata: MetadataType, + ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; + getInvitations: ( + tenantId: string, + metadata: MetadataType, + ) => Promise<{ status: "OK"; invitees: InviteeDetails[] } | NonOkResponse | ErrorResponse>; + associateAllLoginMethodsOfUserWithTenant: AssociateAllLoginMethodsOfUserWithTenant; + addTenantCreationRequest: ( + session: SessionContainerInterface, + tenantDetails: { + name: string; + firstFactors?: string[] | null; + }, + metadata: TenantCreationRequestMetadataType, + appUrl: string, + userContext: UserContext, + sendEmail: SendPluginEmail, + ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; + getTenantCreationRequests: ( + metadata: TenantCreationRequestMetadataType, + ) => Promise< + ({ status: "OK" } & { requests: (TenantCreationRequest & { userId: string })[] }) | NonOkResponse | ErrorResponse + >; + acceptTenantCreationRequest: ( + requestId: string, + session: SessionContainerInterface, + metadata: TenantCreationRequestMetadataType, + ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; + rejectTenantCreationRequest: ( + requestId: string, + session: SessionContainerInterface, + metadata: TenantCreationRequestMetadataType, + ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; + sendTenantCreationRequestEmail: ( + tenantId: string, + creatorEmail: string, + appUrl: string, + userContext: UserContext, + sendEmail: SendPluginEmail, + ) => Promise; + getAppUrl: GetAppUrl; +}; diff --git a/packages/tenants-nodejs/src/util.ts b/packages/tenants-nodejs/src/util.ts new file mode 100644 index 0000000..039bc4e --- /dev/null +++ b/packages/tenants-nodejs/src/util.ts @@ -0,0 +1,19 @@ +import { BaseRequest } from "supertokens-node/lib/build/framework/request"; +import Session from "supertokens-node/recipe/session"; + +export const validateWithoutClaim = (existingValidators: Session.SessionClaimValidator[], key: string) => { + return existingValidators.filter((validator) => validator.id !== key); +}; + +export const extractInvitationCodeAndTenantId = async (req: BaseRequest) => { + const body = await req.getJSONBody(); + const code = body.code; + const tenantId = body.tenantId; + const shouldAcceptInvite = !!code && !!tenantId; + + return { + code, + tenantId, + shouldAcceptInvite, + }; +}; diff --git a/packages/tenants-nodejs/tsconfig.json b/packages/tenants-nodejs/tsconfig.json new file mode 100644 index 0000000..06f8f7b --- /dev/null +++ b/packages/tenants-nodejs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@shared/tsconfig/node.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist", + "types": ["node"], + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/shared/tenants/.prettierrc.js b/shared/tenants/.prettierrc.js new file mode 100644 index 0000000..8986fc5 --- /dev/null +++ b/shared/tenants/.prettierrc.js @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + ...require("@shared/eslint/prettier"), +}; diff --git a/shared/tenants/package.json b/shared/tenants/package.json new file mode 100644 index 0000000..45d66bf --- /dev/null +++ b/shared/tenants/package.json @@ -0,0 +1,19 @@ +{ + "name": "@shared/tenants", + "version": "0.0.1", + "private": true, + "exports": { + ".": "./src/index.ts" + }, + "peerDependencies": { + "supertokens-node": "^23.0.1" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/tsconfig": "*", + "@types/debug": "^4.1.12" + }, + "dependencies": { + "debug": "^4.4.1" + } +} diff --git a/shared/tenants/src/constants.ts b/shared/tenants/src/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/shared/tenants/src/index.ts b/shared/tenants/src/index.ts new file mode 100644 index 0000000..2c6ce5b --- /dev/null +++ b/shared/tenants/src/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './roles'; diff --git a/shared/tenants/src/roles.ts b/shared/tenants/src/roles.ts new file mode 100644 index 0000000..80c7d17 --- /dev/null +++ b/shared/tenants/src/roles.ts @@ -0,0 +1,16 @@ +export const ROLES = { + // Admins of the tenant + ADMIN: 'tenant-admin', + // Member of the tenant + MEMBER: 'tenant-member', + // Admin of the app + APP_ADMIN: 'app-admin', + // Anyone else, i.e no assigned role will be considered + // a requester of the tenant. +}; + +export const PERMISSIONS = { + READ: 'read', + WRITE: 'write', + DELETE: 'delete', +}; diff --git a/shared/tenants/src/types.ts b/shared/tenants/src/types.ts new file mode 100644 index 0000000..69adb83 --- /dev/null +++ b/shared/tenants/src/types.ts @@ -0,0 +1,38 @@ +import { TenantConfig } from 'supertokens-node/lib/build/recipe/multitenancy/types'; + +export type TenantJoinData = { + tenantId: string; +}; + +export type TenantCreateData = { + name: string; +}; + +export type TenantDetails = { + tenantId: string; +} & TenantConfig; + +export type TenantList = { + tenants: TenantDetails[]; + joinedTenantIds: string[]; +}; + +export type InviteeDetails = { + email: string; + role: string; + code: string; +}; + +export type TenantCreationRequest = { + name: string; + firstFactors?: string[] | null; + requestId: string; +}; + +export type TenantMetadata = { + invitees: InviteeDetails[]; +}; + +export type TenantCreationRequestMetadata = { + requests: (TenantCreationRequest & { userId: string })[]; +}; diff --git a/shared/tenants/tsconfig.json b/shared/tenants/tsconfig.json new file mode 100644 index 0000000..31acfac --- /dev/null +++ b/shared/tenants/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@shared/tsconfig/node.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist", + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"] +} From 7469aa98d675e71f00e919b27316b6ed518907b7 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 1 Sep 2025 14:43:36 +0530 Subject: [PATCH 02/25] feat: migrate the tenants react repo --- packages/tenants-react/.eslintrc.js | 14 ++ packages/tenants-react/.prettierrc.js | 4 + packages/tenants-react/README.md | 7 + packages/tenants-react/package.json | 70 ++++++ packages/tenants-react/src/api.ts | 128 ++++++++++ .../components/details/details-wrapper.tsx | 52 ++++ .../components/details/details.module.scss | 96 ++++++++ .../tenants-react/src/components/index.ts | 3 + .../src/components/invitations/accept.tsx | 108 +++++++++ .../invitations/invitations.module.scss | 66 +++++ .../components/invitations/invitations.tsx | 162 +++++++++++++ .../src/components/page-wrapper/index.ts | 1 + .../components/page-wrapper/page-wrapper.css | 4 + .../components/page-wrapper/page-wrapper.tsx | 10 + .../src/components/tenant-card/index.ts | 1 + .../components/tenant-card/tenant-card.css | 26 ++ .../tenant-card/tenant-card.module.scss | 54 +++++ .../components/tenant-card/tenant-card.tsx | 98 ++++++++ .../src/components/tenant-management/index.ts | 1 + .../tenant-management.module.scss | 71 ++++++ .../tenant-management/tenant-management.tsx | 157 ++++++++++++ packages/tenants-react/src/constants.ts | 3 + packages/tenants-react/src/css.d.ts | 29 +++ packages/tenants-react/src/index.ts | 5 + .../src/invitation-accept-wrapper.tsx | 15 ++ packages/tenants-react/src/logger.ts | 5 + packages/tenants-react/src/plugin.tsx | 227 ++++++++++++++++++ .../tenants-react/src/select-tenant-page.tsx | 17 ++ .../src/tenant-details-wrapper.tsx | 21 ++ packages/tenants-react/src/tenant-wrapper.tsx | 84 +++++++ packages/tenants-react/src/types.ts | 7 + packages/tenants-react/tsconfig.json | 13 + packages/tenants-react/vite.config.ts | 38 +++ 33 files changed, 1597 insertions(+) create mode 100644 packages/tenants-react/.eslintrc.js create mode 100644 packages/tenants-react/.prettierrc.js create mode 100644 packages/tenants-react/README.md create mode 100644 packages/tenants-react/package.json create mode 100644 packages/tenants-react/src/api.ts create mode 100644 packages/tenants-react/src/components/details/details-wrapper.tsx create mode 100644 packages/tenants-react/src/components/details/details.module.scss create mode 100644 packages/tenants-react/src/components/index.ts create mode 100644 packages/tenants-react/src/components/invitations/accept.tsx create mode 100644 packages/tenants-react/src/components/invitations/invitations.module.scss create mode 100644 packages/tenants-react/src/components/invitations/invitations.tsx create mode 100644 packages/tenants-react/src/components/page-wrapper/index.ts create mode 100644 packages/tenants-react/src/components/page-wrapper/page-wrapper.css create mode 100644 packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx create mode 100644 packages/tenants-react/src/components/tenant-card/index.ts create mode 100644 packages/tenants-react/src/components/tenant-card/tenant-card.css create mode 100644 packages/tenants-react/src/components/tenant-card/tenant-card.module.scss create mode 100644 packages/tenants-react/src/components/tenant-card/tenant-card.tsx create mode 100644 packages/tenants-react/src/components/tenant-management/index.ts create mode 100644 packages/tenants-react/src/components/tenant-management/tenant-management.module.scss create mode 100644 packages/tenants-react/src/components/tenant-management/tenant-management.tsx create mode 100644 packages/tenants-react/src/constants.ts create mode 100644 packages/tenants-react/src/css.d.ts create mode 100644 packages/tenants-react/src/index.ts create mode 100644 packages/tenants-react/src/invitation-accept-wrapper.tsx create mode 100644 packages/tenants-react/src/logger.ts create mode 100644 packages/tenants-react/src/plugin.tsx create mode 100644 packages/tenants-react/src/select-tenant-page.tsx create mode 100644 packages/tenants-react/src/tenant-details-wrapper.tsx create mode 100644 packages/tenants-react/src/tenant-wrapper.tsx create mode 100644 packages/tenants-react/src/types.ts create mode 100644 packages/tenants-react/tsconfig.json create mode 100644 packages/tenants-react/vite.config.ts diff --git a/packages/tenants-react/.eslintrc.js b/packages/tenants-react/.eslintrc.js new file mode 100644 index 0000000..6a19d70 --- /dev/null +++ b/packages/tenants-react/.eslintrc.js @@ -0,0 +1,14 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: [require.resolve('@shared/eslint/react.js')], + parserOptions: { + project: true, + }, + rules: { + // Temporarily disable this rule due to a bug with mapped types + '@typescript-eslint/no-unused-vars': 'off', + // Disable global type warnings for third-party types + 'no-undef': 'off', + }, + ignorePatterns: ['**/*.test.ts', '**/*.spec.ts', 'tests/**/*'], +}; diff --git a/packages/tenants-react/.prettierrc.js b/packages/tenants-react/.prettierrc.js new file mode 100644 index 0000000..8986fc5 --- /dev/null +++ b/packages/tenants-react/.prettierrc.js @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + ...require("@shared/eslint/prettier"), +}; diff --git a/packages/tenants-react/README.md b/packages/tenants-react/README.md new file mode 100644 index 0000000..e4cdf84 --- /dev/null +++ b/packages/tenants-react/README.md @@ -0,0 +1,7 @@ +# @supertokens-plugin-profile/base + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test @supertokens-plugin-profile/tenants-frontend` to execute the unit tests via [Vitest](https://vitest.dev/). diff --git a/packages/tenants-react/package.json b/packages/tenants-react/package.json new file mode 100644 index 0000000..a77fe79 --- /dev/null +++ b/packages/tenants-react/package.json @@ -0,0 +1,70 @@ +{ + "name": "@supertokens-plugins/tenants-react", + "version": "0.0.1", + "description": "Tenants Base Plugin for SuperTokens", + "homepage": "https://github.com/supertokens/supertokens-plugins/blob/main/packages/tenants-react/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/supertokens/supertokens-plugins.git", + "directory": "packages/tenants-react" + }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest --watch", + "build": "vite build && npm run pretty", + "pretty": "npx pretty-quick .", + "pretty-check": "npx pretty-quick --check ." + }, + "keywords": [ + "tenants", + "base", + "plugin", + "supertokens" + ], + "dependencies": { + "supertokens-js-override": "^0.0.4" + }, + "peerDependencies": { + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "supertokens-auth-react": ">=0.50.0", + "supertokens-web-js": ">=0.16.0" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/tsconfig": "*", + "@testing-library/jest-dom": "^6.1.0", + "@types/react": "^17.0.20", + "jsdom": "^26.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^1.3.1", + "@shared/js": "*", + "@shared/react": "*", + "vite": "^6.3.5", + "@vitejs/plugin-react": "^4.5.2", + "vite-plugin-dts": "^4.5.4", + "rollup-plugin-peer-deps-external": "^2.2.4" + }, + "browser": { + "fs": false + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + }, + "./index": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + }, + "./index.js": { + "types": "dist/index.d.ts", + "default": "dist/index.js" + } + } +} diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts new file mode 100644 index 0000000..8cf0a77 --- /dev/null +++ b/packages/tenants-react/src/api.ts @@ -0,0 +1,128 @@ +import { getQuerier } from "@shared/react"; +import { InviteeDetails, TenantCreateData, TenantJoinData, TenantList } from "@shared/tenants"; +import Session from "supertokens-auth-react/recipe/session"; +import { User } from "supertokens-web-js/types"; + +export const getApi = (querier: ReturnType) => { + const fetchTenants = async () => { + const response = await querier.get<({ status: "OK" } & TenantList) | { status: "ERROR"; message: string }>( + "/list", + { + withSession: true, + }, + ); + + return response; + }; + + const joinTenant = + async (data: TenantJoinData) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/join", + { + ...data, + }, + { withSession: true }, + ); + + // Refresh the session if the status was OK + let wasSessionRefreshed = false; + if (response.status === "OK") { + wasSessionRefreshed = await Session.attemptRefreshingSession(); + } + + return { + ...response, + wasSessionRefreshed, + }; + }; + + const createTenant = + async (data: TenantCreateData) => { + const response = await querier.post< + { status: "OK"; pendingApproval: boolean; requestId: string } | { status: "ERROR"; message: string } + >( + "/create", + { + ...data, + }, + { withSession: true }, + ); + + return response; + }; + + const getUsers = async () => { + const response = await querier.post<{ status: "OK"; users: User[] } | { status: "ERROR"; message: string }>( + "/users", + {}, + { withSession: true }, + ); + + return response; + }; + + const getInvitations = async () => { + const response = await querier.post< + { status: "OK"; invitees: InviteeDetails[] } | { status: "ERROR"; message: string } + >("/invite/list", {}, { withSession: true }); + + return response; + }; + + const removeInvitation = + async (email: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/remove", + { email }, + { withSession: true }, + ); + + return response; + }; + + const addInvitation = + async (email: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/add", + { email }, + { withSession: true }, + ); + + return response; + }; + + const acceptInvitation = + async (code: string, tenantId: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/accept", + { code, tenantId }, + { withSession: true }, + ); + + return response; + }; + + const switchTenant = + async (tenantId: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/switch-tenant", + { tenantId }, + { withSession: true }, + ); + + return response; + }; + + return { + fetchTenants, + joinTenant, + createTenant, + getInvitations, + getUsers, + removeInvitation, + addInvitation, + acceptInvitation, + switchTenant, + }; +}; diff --git a/packages/tenants-react/src/components/details/details-wrapper.tsx b/packages/tenants-react/src/components/details/details-wrapper.tsx new file mode 100644 index 0000000..bc37160 --- /dev/null +++ b/packages/tenants-react/src/components/details/details-wrapper.tsx @@ -0,0 +1,52 @@ +import classNames from 'classnames/bind'; +import style from './details.module.scss'; +import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; +import { User } from 'supertokens-web-js/types'; +import { useCallback, useEffect, useState } from 'react'; + +const cx = classNames.bind(style); + +export const DetailsWrapper = ({ + section, + onFetch, +}: { + section: BaseFormSection; + onFetch: () => Promise<{ users: User[] }>; +}) => { + const [users, setUsers] = useState([]); + + const loadDetails = useCallback(async () => { + const details = await onFetch(); + setUsers(details.users); + }, [onFetch]); + + useEffect(() => { + loadDetails(); + }, [loadDetails]); + + return ( +
+
+

{section.label}

+

{section.description}

+
+ +
+ {users.length > 0 ? ( +
+ {users.map((user) => ( +
+
{user.emails[0]?.charAt(0).toUpperCase() || 'U'}
+
{user.emails[0]}
+
+ ))} +
+ ) : ( +
+

No users found

+
+ )} +
+
+ ); +}; diff --git a/packages/tenants-react/src/components/details/details.module.scss b/packages/tenants-react/src/components/details/details.module.scss new file mode 100644 index 0000000..12159c4 --- /dev/null +++ b/packages/tenants-react/src/components/details/details.module.scss @@ -0,0 +1,96 @@ +:global(.pluginProfile) { + .tenantDetailsSection { + display: flex; + flex-direction: column; + max-width: 800px; + margin: 0 auto; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, + Helvetica Neue, sans-serif; + color: #333; + width: 100%; + } + + .tenantDetailsHeader { + position: relative; + padding-bottom: 24px; + margin-bottom: 24px; + border-bottom: 1px solid #eee; + + h3 { + margin-bottom: 12px; + } + } + + .tenantDetailsContent { + display: flex; + flex-direction: column; + gap: 12px; + } + + .tenantDetailsUsers { + display: flex; + flex-direction: column; + gap: 8px; + } + + .userRow { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + border: 1px solid #eee; + border-radius: 6px; + background-color: #fafafa; + } + + .userAvatar { + width: 32px; + height: 32px; + border-radius: 50%; + background-color: #007bff; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + flex-shrink: 0; + } + + .userEmail { + flex: 1; + font-size: 14px; + color: #333; + } + + .removeButton { + width: 24px; + height: 24px; + border: none; + background-color: #dc3545; + color: white; + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: bold; + flex-shrink: 0; + transition: background-color 0.2s; + + &:hover { + background-color: #c82333; + } + } + + .tenantDetailsNoUsers { + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px; + border: 1px solid #eee; + border-radius: 4px; + } +} diff --git a/packages/tenants-react/src/components/index.ts b/packages/tenants-react/src/components/index.ts new file mode 100644 index 0000000..ee20c9e --- /dev/null +++ b/packages/tenants-react/src/components/index.ts @@ -0,0 +1,3 @@ +export * from './tenant-card'; +export * from './page-wrapper'; +export * from './tenant-management'; diff --git a/packages/tenants-react/src/components/invitations/accept.tsx b/packages/tenants-react/src/components/invitations/accept.tsx new file mode 100644 index 0000000..5d365f7 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/accept.tsx @@ -0,0 +1,108 @@ +import classNames from 'classnames/bind'; +import style from './invitations.module.scss'; +import { useEffect, useState } from 'react'; +import { useSessionContext } from 'supertokens-auth-react/recipe/session'; +import { redirectToAuth } from 'supertokens-auth-react'; +import { Card, Button } from '@supertokens-plugin-profile/common-frontend'; + +const cx = classNames.bind(style); + +export const AcceptInvitation = ({ + onAccept, +}: { + onAccept: (code: string, tenantId: string) => Promise<{ status: 'OK' } | { status: 'ERROR'; message: string }>; +}) => { + const [code, setCode] = useState(''); + const [tenantId, setTenantId] = useState(''); + const [isAccepting, setIsAccepting] = useState(false); + const [error, setError] = useState(''); + + const session = useSessionContext(); + + useEffect(() => { + // Parse the code from URL query parameters + const urlParams = new URLSearchParams((globalThis as any).location.search); + const inviteCode = urlParams.get('code'); + const tenantId = urlParams.get('tenantId'); + + if (!inviteCode || !tenantId) { + // Redirect to dashboard if no code is present + (globalThis as any).location.href = '/user/tenants'; + return; + } + + setCode(inviteCode); + setTenantId(tenantId); + }, []); + + if (session.loading) { + return
Loading...
; + } + + const handleAccept = async () => { + if (!code) return; + + setIsAccepting(true); + setError(''); + + try { + await onAccept(code, tenantId); + // Redirect to /user/tenants after successful acceptance + (globalThis as any).location.href = '/user/tenants'; + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to accept invitation'); + } finally { + setIsAccepting(false); + } + }; + + const handleRedirectToAuth = () => { + redirectToAuth({ + queryParams: { + code, + tenantId, + }, + redirectBack: false, + }); + }; + + if (!code) { + return ( +
+
+

Invalid Invitation

+

No invitation code found. Redirecting to dashboard...

+
+
+ ); + } + + return ( + +
+ Accept Invitation +
+ +
+ You have been invited to join "{tenantId}" tenant. Click the button + below to accept the invitation. +
+
+
Invitation code:
+
{code}
+
+
+
+ {session.doesSessionExist ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/packages/tenants-react/src/components/invitations/invitations.module.scss b/packages/tenants-react/src/components/invitations/invitations.module.scss new file mode 100644 index 0000000..ad5a9d0 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/invitations.module.scss @@ -0,0 +1,66 @@ +.invitationDetailsChild { + border-radius: 12px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + border: 1px solid #dddde3; + background-color: #f9f9f8; + + .invitationDetailsChildHeader { + padding: 12px; + color: #60646c; + font-weight: var(--wa-font-weight-normal); + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + + .tenantName { + font-weight: var(--wa-font-weight-semibold); + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + } + } + + .invitationDetailsCodeContainer { + display: flex; + align-items: center; + color: #60646c; + padding: 12px; + background-color: #f2f2f0; + border-top: 1px solid #dddde3; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + + .invitationCodeContainer { + margin-left: 10px; + padding: 2px 8px; + border-radius: 9999px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + background: #00003b0d; + color: #0007139f; + } + } +} + +.invitationDetailsFooter { + display: flex; + justify-content: end; +} + +.invitationAcceptHeader { + font-weight: var(--wa-font-weight-extrabold); + font-style: Bold; + font-size: 28px; + line-height: 36px; + letter-spacing: -0.12px; + color: #1c2024; +} + +.invitationDetailsChild::part(header) { + padding: 0 !important; +} + +.invitationDetailsChild::part(body) { + padding: 0 !important; +} diff --git a/packages/tenants-react/src/components/invitations/invitations.tsx b/packages/tenants-react/src/components/invitations/invitations.tsx new file mode 100644 index 0000000..a22d427 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/invitations.tsx @@ -0,0 +1,162 @@ +import classNames from 'classnames/bind'; +import style from './invitations.module.scss'; +import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; +import { useCallback, useEffect, useState } from 'react'; +import { InviteeDetails } from '@supertokens-plugin-profile/tenants-shared'; + +const cx = classNames.bind(style); + +export const InvitationsWrapper = ({ + section, + onFetch, + onRemove, + onCreate, + selectedTenantId, +}: { + section: BaseFormSection; + onFetch: (tenantId?: string) => Promise<{ invitations: InviteeDetails[] }>; + onRemove: (email: string, tenantId?: string) => Promise; + onCreate?: (email: string, tenantId: string) => Promise; + selectedTenantId: string; +}) => { + const [invitations, setInvitations] = useState([]); + const [showInviteForm, setShowInviteForm] = useState(false); + const [inviteEmail, setInviteEmail] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showCode, setShowCode] = useState(null); + const loadDetails = useCallback( + async (tenantId?: string) => { + const details = await onFetch(tenantId || selectedTenantId); + setInvitations(details.invitations); + }, + [onFetch, selectedTenantId], + ); + + const handleShowCode = (code: string) => { + setShowCode(code); + }; + + useEffect(() => { + if (selectedTenantId) { + loadDetails(selectedTenantId); + } + }, [selectedTenantId, loadDetails]); + + const handleInviteSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!onCreate || !inviteEmail.trim()) return; + + setIsSubmitting(true); + try { + await onCreate(inviteEmail.trim(), selectedTenantId); + setInviteEmail(''); + setShowInviteForm(false); + // Reload the invitations list + await loadDetails(selectedTenantId); + } catch (error) { + console.error('Failed to create invitation:', error); + } finally { + setIsSubmitting(false); + } + }; + + const handleRemoveInvitation = async (email: string, tenantId: string) => { + await onRemove(email, tenantId); + await loadDetails(tenantId); + }; + + const handleCancelInvite = () => { + setShowInviteForm(false); + setInviteEmail(''); + }; + + return ( +
+
+

{section.label}

+

{section.description}

+ {onCreate && ( + + )} +
+ + {showInviteForm && onCreate && ( +
+
+
+ setInviteEmail(e.currentTarget.value)} + className={cx('inviteEmailInput')} + required + disabled={isSubmitting} + /> +
+ + +
+
+
+
+ )} + +
+ {invitations.length > 0 ? ( +
+ {invitations.map((invitation) => ( +
+
{invitation.email.charAt(0).toUpperCase() || 'U'}
+
{invitation.email}
+ + +
+ ))} +
+ ) : ( +
+

No invitations found

+
+ )} +
+ + {showCode && ( +
+

{showCode}

+ +
+ )} +
+ ); +}; diff --git a/packages/tenants-react/src/components/page-wrapper/index.ts b/packages/tenants-react/src/components/page-wrapper/index.ts new file mode 100644 index 0000000..10664de --- /dev/null +++ b/packages/tenants-react/src/components/page-wrapper/index.ts @@ -0,0 +1 @@ +export * from './page-wrapper'; diff --git a/packages/tenants-react/src/components/page-wrapper/page-wrapper.css b/packages/tenants-react/src/components/page-wrapper/page-wrapper.css new file mode 100644 index 0000000..06ab172 --- /dev/null +++ b/packages/tenants-react/src/components/page-wrapper/page-wrapper.css @@ -0,0 +1,4 @@ +.page-wrapper { + width: 100%; + height: auto; +} diff --git a/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx b/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx new file mode 100644 index 0000000..4970094 --- /dev/null +++ b/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from 'react'; +import './page-wrapper.css'; + +export const PageWrapper = ({ children, style }: { children: ReactNode; style?: React.CSSProperties }) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/tenants-react/src/components/tenant-card/index.ts b/packages/tenants-react/src/components/tenant-card/index.ts new file mode 100644 index 0000000..b35ab18 --- /dev/null +++ b/packages/tenants-react/src/components/tenant-card/index.ts @@ -0,0 +1 @@ +export * from './tenant-card'; diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.css b/packages/tenants-react/src/components/tenant-card/tenant-card.css new file mode 100644 index 0000000..c9bc492 --- /dev/null +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.css @@ -0,0 +1,26 @@ +.plugin-tenant-card { + display: flex; + width: 100%; + min-width: 600px; + min-height: 400px; + background: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border: 1px solid #eee; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', + 'Helvetica Neue', sans-serif; +} + +.plugin-tenant-content { + flex: 1; + padding: 24px 32px; +} + +.plugin-tenant-content h2 { + margin-top: 0; + margin-bottom: 24px; +} + +.plugin-tenant-content-body { + width: 100%; +} diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss b/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss new file mode 100644 index 0000000..5322d01 --- /dev/null +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss @@ -0,0 +1,54 @@ +.createTenantInputContainer { + border-radius: 12px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + border: 1px solid #dddde3; + background-color: #f2f2f0; + + .createTenantInputCardText { + padding: 12px; + color: #60646c; + font-weight: var(--wa-font-weight-normal); + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + } + + .createTenantInputWrapper { + display: flex; + align-items: center; + color: #60646c; + padding: 12px; + background-color: #f9f9f8; + border-top: 1px solid #dddde3; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + + .createTenantInput { + width: 100%; + } + } +} + +.createTenantHeader { + font-weight: var(--wa-font-weight-extrabold); + font-style: Bold; + font-size: 28px; + line-height: 36px; + letter-spacing: -0.12px; + color: #1c2024; +} + +.createTenantFooter { + display: flex; + justify-content: end; + align-items: center; +} + +.createTenantInputContainer::part(header) { + padding: 0 !important; +} + +.createTenantInputContainer::part(body) { + padding: 0 !important; +} diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.tsx b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx new file mode 100644 index 0000000..fe6431e --- /dev/null +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx @@ -0,0 +1,98 @@ +import classNames from 'classnames/bind'; +import { Button, Card, TextInput, usePrettyAction } from '@supertokens-plugin-profile/common-frontend'; +import { TenantCreateData, TenantJoinData, TenantList } from '@supertokens-plugin-profile/tenants-shared'; +import { useState } from 'react'; +import styles from './tenant-card.module.scss'; + +const cx = classNames.bind(styles); + +interface TenantCardProps { + data: TenantList; + onJoin: (data: TenantJoinData) => Promise<{ status: 'OK' } | { status: 'ERROR'; message: string }>; + onCreate: ( + data: TenantCreateData, + ) => Promise<{ status: 'OK'; pendingApproval: boolean; requestId: string } | { status: 'ERROR'; message: string }>; + isLoading: boolean; +} + +export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProps) => { + if (isLoading) { + return ; + } + + const [newTenantName, setNewTenantName] = useState(''); + + const onSuccess = () => { + // Redirect the user to the app. + console.log('Redirecting...'); + }; + + const handleCreateAndJoin = usePrettyAction( + async () => { + if (newTenantName.trim().length === 0) { + console.warn('No tenant name provided'); + return; + } + + const createResponse = await onCreate({ name: newTenantName }); + if (createResponse.status !== 'OK') { + throw new Error(createResponse.message); + } + + // If creation is pending approval, show a message to the user + if (createResponse.pendingApproval) { + throw new Error('Tenant creation request is pending approval'); + return; + } + + // If creation is successful, join the tenant + await onJoin({ tenantId: newTenantName }); + }, + [onCreate, newTenantName], + { + successMessage: 'Tenant created, redirecting...', + errorMessage: 'Failed to create tenant', + onSuccess: async () => { + onSuccess(); + }, + }, + ); + + return ( + +
+ Create Tenant +
+
+ +
+
+ +
+ Enter name of your tenant +
+
+ { + setNewTenantName(value); + }} + type="text" + appearance="outlined" + className={cx('createTenantInput')} + /> +
+
+
+
+ ); +}; diff --git a/packages/tenants-react/src/components/tenant-management/index.ts b/packages/tenants-react/src/components/tenant-management/index.ts new file mode 100644 index 0000000..e489b8c --- /dev/null +++ b/packages/tenants-react/src/components/tenant-management/index.ts @@ -0,0 +1 @@ +export { TenantManagement } from './tenant-management'; \ No newline at end of file diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.module.scss b/packages/tenants-react/src/components/tenant-management/tenant-management.module.scss new file mode 100644 index 0000000..f97233d --- /dev/null +++ b/packages/tenants-react/src/components/tenant-management/tenant-management.module.scss @@ -0,0 +1,71 @@ +.tenantManagement { + display: flex; + flex-direction: column; + max-width: 800px; + margin: 0 auto; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, + sans-serif; + color: #333; + width: 100%; +} + +.tenantManagementHeader { + padding-bottom: 24px; + margin-bottom: 24px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + + h3 { + margin-bottom: 12px; + font-size: 24px; + font-weight: 600; + } + + p { + margin-bottom: 16px; + color: #666; + } +} + +.tenantSwitcherWrapper wa-select::part(form-control) { + display: flex; + align-items: center; +} + +.tabNavigation { + display: flex; + gap: 0; + margin-bottom: 24px; + border-bottom: 1px solid #eee; +} + +.tabButton { + padding: 12px 24px; + background: none; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + font-size: 14px; + font-weight: 500; + color: #666; + transition: all 0.2s; + + &:hover { + color: #333; + background-color: #f8f9fa; + } + + &.active { + color: #007bff; + border-bottom-color: #007bff; + background-color: #f8f9fa; + } +} + +.tabContent { + flex: 1; + min-height: 400px; +} diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx new file mode 100644 index 0000000..3a07310 --- /dev/null +++ b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx @@ -0,0 +1,157 @@ +// DEBUG: Modified at 17:30 on 2025-07-28 - CHECK IF THIS APPEARS IN BROWSER +import { useState, useEffect, useCallback } from 'react'; +import classNames from 'classnames/bind'; +import style from './tenant-management.module.scss'; +import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; +import { usePlugin } from '../../use-plugin'; +import { TenantDetails } from '@supertokens-plugin-profile/tenants-shared'; +import { DetailsWrapper } from '../details/details-wrapper'; +import { InvitationsWrapper } from '../invitations/invitations'; +import { SelectInput } from '@supertokens-plugin-profile/common-frontend'; + +const cx = classNames.bind(style); + +export const TenantManagement = ({ section }: { section: BaseFormSection }) => { + const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant } = usePlugin(); + const [tenants, setTenants] = useState([]); + const [selectedTenantId, setSelectedTenantId] = useState('public'); + const [activeTab, setActiveTab] = useState<'users' | 'invitations'>('users'); + + // Load tenants on component mount + useEffect(() => { + const loadTenants = async () => { + const response = await fetchTenants(); + if (response.status === 'OK') { + setTenants(response.tenants); + if (response.tenants.length > 0) { + setSelectedTenantId(response.tenants[0].tenantId); + } + } + }; + loadTenants(); + }, [fetchTenants]); + + // Users tab functions + const onFetchUsers = useCallback(async () => { + const response = await getUsers(selectedTenantId); + if (response.status === 'ERROR') { + throw new Error(response.message); + } + return { users: response.users }; + }, [getUsers, selectedTenantId]); + + // Invitations tab functions + const onFetchInvitations = useCallback( + async (tenantId?: string) => { + const response = await getInvitations(tenantId || selectedTenantId); + if (response.status === 'ERROR') { + throw new Error(response.message); + } + return { invitations: response.invitees }; + }, + [getInvitations, selectedTenantId], + ); + + const onRemoveInvite = useCallback( + async (email: string, tenantId?: string) => { + const response = await removeInvitation(email, tenantId || selectedTenantId); + if (response.status === 'ERROR') { + throw new Error(response.message); + } + }, + [removeInvitation, selectedTenantId], + ); + + const onCreateInvite = useCallback( + async (email: string, tenantId: string) => { + const response = await addInvitation(email, tenantId); + if (response.status === 'ERROR') { + throw new Error(response.message); + } + }, + [addInvitation], + ); + + const handleTenantSwitch = useCallback( + async (tenantId: string) => { + const response = await switchTenant(tenantId); + if (response.status === 'OK') { + setSelectedTenantId(tenantId); + } else { + console.error('Failed to switch tenant:', response.message); + } + }, + [switchTenant], + ); + + return ( +
+
+
+

{section.label}

+

{section.description}

+
+ + {/* Tenant Switcher */} + {tenants.length > 0 && ( +
+ handleTenantSwitch(e.target.value)} + name="Tenant Switcher" + options={tenants.map(({ tenantId }) => ({ + label: tenantId === 'public' ? 'Public' : tenantId, + value: tenantId, + }))} + /> +
+ )} +
+ + {/* Tab Navigation */} +
+ + +
+ + {/* Tab Content */} +
+ {activeTab === 'users' && selectedTenantId && ( + + )} + + {activeTab === 'invitations' && selectedTenantId && ( + + )} +
+
+ ); +}; diff --git a/packages/tenants-react/src/constants.ts b/packages/tenants-react/src/constants.ts new file mode 100644 index 0000000..b754188 --- /dev/null +++ b/packages/tenants-react/src/constants.ts @@ -0,0 +1,3 @@ +export const PLUGIN_ID = "supertokens-plugin-tenants"; +export const PLUGIN_VERSION = "0.0.1"; +export const API_PATH = `plugin/${PLUGIN_ID}`; diff --git a/packages/tenants-react/src/css.d.ts b/packages/tenants-react/src/css.d.ts new file mode 100644 index 0000000..93c8235 --- /dev/null +++ b/packages/tenants-react/src/css.d.ts @@ -0,0 +1,29 @@ +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.scss" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.sass" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.less" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.styl" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.css" { + const css: string; + export default css; +} diff --git a/packages/tenants-react/src/index.ts b/packages/tenants-react/src/index.ts new file mode 100644 index 0000000..5ce4ccc --- /dev/null +++ b/packages/tenants-react/src/index.ts @@ -0,0 +1,5 @@ +import { PLUGIN_ID } from "./constants"; +import { init } from "./plugin"; + +export { init, PLUGIN_ID }; +export default { init, PLUGIN_ID }; diff --git a/packages/tenants-react/src/invitation-accept-wrapper.tsx b/packages/tenants-react/src/invitation-accept-wrapper.tsx new file mode 100644 index 0000000..e6010ac --- /dev/null +++ b/packages/tenants-react/src/invitation-accept-wrapper.tsx @@ -0,0 +1,15 @@ +import { SuperTokensWrapper } from "supertokens-auth-react"; + +import { AcceptInvitation } from "./components/invitations/accept"; +import { usePluginContext } from "./plugin"; +// import { SessionAuth } from 'supertokens-auth-react/recipe/session'; + +export const InvitationAcceptWrapper = () => { + const { api } = usePluginContext(); + + return ( + + + + ); +}; diff --git a/packages/tenants-react/src/logger.ts b/packages/tenants-react/src/logger.ts new file mode 100644 index 0000000..37cdd77 --- /dev/null +++ b/packages/tenants-react/src/logger.ts @@ -0,0 +1,5 @@ +import { buildLogger } from "@shared/react"; + +import { PLUGIN_ID, PLUGIN_VERSION } from "./constants"; + +export const { logDebugMessage, enableDebugLogs } = buildLogger(PLUGIN_ID, PLUGIN_VERSION); diff --git a/packages/tenants-react/src/plugin.tsx b/packages/tenants-react/src/plugin.tsx new file mode 100644 index 0000000..42d6455 --- /dev/null +++ b/packages/tenants-react/src/plugin.tsx @@ -0,0 +1,227 @@ +import { createPluginInitFunction } from "@shared/js"; +import { buildContext, getQuerier } from "@shared/react"; +import { SuperTokensPlugin, SuperTokensPublicConfig, SuperTokensPublicPlugin } from "supertokens-auth-react"; +import { BooleanClaim } from "supertokens-auth-react/recipe/session"; + +import { getApi } from "./api"; +import { TenantManagement } from "./components"; +import { API_PATH, PLUGIN_ID } from "./constants"; +import { InvitationAcceptWrapper } from "./invitation-accept-wrapper"; +import { enableDebugLogs } from "./logger"; +import { SelectTenantPage } from "./select-tenant-page"; +import { SuperTokensPluginTenantsPluginConfig, SuperTokensPluginTenantsPluginNormalisedConfig } from "./types"; + +const { usePluginContext, setContext } = buildContext<{ + plugins: SuperTokensPublicPlugin[]; + sdkVersion: string; + appConfig: SuperTokensPublicConfig; + pluginConfig: SuperTokensPluginTenantsPluginNormalisedConfig; + querier: ReturnType; + api: ReturnType; + t: (key: any) => string; // TODO: Update with correct translations type +}>(); +export { usePluginContext }; + +export const init = createPluginInitFunction< + SuperTokensPlugin, + SuperTokensPluginTenantsPluginConfig, + undefined, + SuperTokensPluginTenantsPluginNormalisedConfig +>((pluginConfig) => { + const MultipleTenantsPresentClaim = new BooleanClaim({ + id: `${PLUGIN_ID}-multiple-tenants-present`, + refresh: async () => {}, + onFailureRedirection: async ({ reason }) => { + return "/user/tenants/select"; + }, + }); + + // TODO: Update this to parse it from the exports of that + // plugin so that we don't have to depend on that plugin. + // Essentially, if that plugin is enabled, only then we need to use + // the ID. + const PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID = "some-id"; + + const extractCodeAndTenantId = (url: string) => { + const urlParams = new URLSearchParams(url); + const code = urlParams.get("code"); + const tenantId = urlParams.get("tenantId"); + return { code, tenantId, shouldAcceptInvite: Boolean(code) && Boolean(tenantId) }; + }; + + const extractAndInjectCodeAndTenantId = (context: any) => { + const { code, tenantId, shouldAcceptInvite } = extractCodeAndTenantId(context.url); + + if (!shouldAcceptInvite) { + return { + requestInit: context.requestInit, + url: context.url, + }; + } + + let requestInit = context.requestInit; + let body = context.requestInit.body; + if (body !== undefined) { + let bodyJson = JSON.parse(body as string); + bodyJson.code = code; + bodyJson.tenantId = tenantId; + requestInit.body = JSON.stringify(bodyJson); + } + + return { + requestInit, + url: context.url, + }; + }; + + return { + id: PLUGIN_ID, + init: (config, plugins, sdkVersion) => { + if (config.enableDebugLogs) { + enableDebugLogs(); + } + + const baseProfilePlugin = plugins.find((plugin: any) => plugin.id === "supertokens-plugin-profile-base"); + if (!baseProfilePlugin) { + console.warn("Base profile plugin not found. Not adding common details profile plugin."); + return; + } + + if (!baseProfilePlugin.exports) { + console.warn("Base profile plugin does not export anything. Not adding common details profile plugin."); + return; + } + + const registerSection = baseProfilePlugin.exports?.registerSection; + if (!registerSection) { + console.warn("Base profile plugin does not export registerSection. Not adding common details profile plugin."); + return; + } + + registerSection(async () => ({ + id: "tenant-management", + title: "Tenants", + order: 1, + component: () => + TenantManagement.call(null, { + section: { + id: "tenant-management", + label: "Tenant Management", + description: "Manage users and invitations for your tenants", + fields: [], + }, + }), + })); + + const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); + const api = getApi(querier); + + setContext({ + plugins, + sdkVersion, + appConfig: config, + pluginConfig, + querier, + api, + t: (t: undefined) => "", // TODO: Update this + }); + }, + routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { + return { + status: "OK", + routeHandlers: [ + { + path: "/user/tenants/select", + handler: () => SelectTenantPage.call(null), + }, + { + path: "/user/invite/accept", + handler: () => InvitationAcceptWrapper.call(null), + }, + ], + }; + }, + overrideMap: { + session: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + getGlobalClaimValidators(input) { + // If the profile claim is present, make sure the tenant + // one is added after it. + const profileClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( + (validator) => validator.id === PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, + ); + const otherClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( + (validator) => validator.id !== PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, + ); + + const claimValidators = [ + ...otherClaimValidators, + ...profileClaimValidators, + ...(pluginConfig.requireTenantCreation ? [MultipleTenantsPresentClaim.validators.isTrue()] : []), + ]; + + return claimValidators; + }, + }; + }, + }, + emailpassword: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "EMAIL_PASSWORD_SIGN_IN" || context.action === "EMAIL_PASSWORD_SIGN_UP") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, + passwordless: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "PASSWORDLESS_CONSUME_CODE") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, + thirdparty: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "THIRD_PARTY_SIGN_IN_UP") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, + }, + generalAuthRecipeComponentOverrides: { + AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { + // If the code and tenantId, we need to show the message that + // the invitation will be accepted automatically. + const { shouldAcceptInvite } = extractCodeAndTenantId((globalThis as any).location.search); + + return ( +
+ {shouldAcceptInvite && "If you authenticate, invitation will be accepted automatically."} + {/* @ts-ignore */} + +
+ ); + }, + }, + exports: { + }, + }; +}, + undefined, + (pluginConfig) => ({ + requireTenantCreation: pluginConfig.requireTenantCreation ?? true, + }) +); diff --git a/packages/tenants-react/src/select-tenant-page.tsx b/packages/tenants-react/src/select-tenant-page.tsx new file mode 100644 index 0000000..02e9acf --- /dev/null +++ b/packages/tenants-react/src/select-tenant-page.tsx @@ -0,0 +1,17 @@ +import { SuperTokensWrapper } from "supertokens-auth-react"; +import { SessionAuth } from "supertokens-auth-react/recipe/session"; + +import { PageWrapper } from "./components"; +import TenantCardWrapper from "./tenant-wrapper"; + +export const SelectTenantPage = () => { + return ( + + + + + + + + ); +}; diff --git a/packages/tenants-react/src/tenant-details-wrapper.tsx b/packages/tenants-react/src/tenant-details-wrapper.tsx new file mode 100644 index 0000000..18a7210 --- /dev/null +++ b/packages/tenants-react/src/tenant-details-wrapper.tsx @@ -0,0 +1,21 @@ +import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; +import { useCallback } from "react"; + +import { DetailsWrapper } from "./components/details/details-wrapper"; +import { usePluginContext } from "./plugin"; + +export const TenantDetailsWrapper = ({ section }: { section: BaseFormSection }) => { + const { api } = usePluginContext(); + + const onFetch = useCallback(async () => { + // Use the `tid` from the users access token payload. + + const response = await api.getUsers(); + if (response.status === "ERROR") { + throw new Error(response.message); + } + return { users: response.users }; + }, [api.getUsers, section.id]); + + return ; +}; diff --git a/packages/tenants-react/src/tenant-wrapper.tsx b/packages/tenants-react/src/tenant-wrapper.tsx new file mode 100644 index 0000000..1b88192 --- /dev/null +++ b/packages/tenants-react/src/tenant-wrapper.tsx @@ -0,0 +1,84 @@ +import { TenantCreateData, TenantJoinData, TenantList } from "@shared/tenants"; +import { ToastProvider, ToastContainer } from "@shared/ui"; +import { useEffect, useState } from "react"; + +import { TenantCard } from "./components"; +import { logDebugMessage } from "./logger"; +import { usePluginContext } from "./plugin"; + +const TenantCardWrapper = () => { + const { api } = usePluginContext(); + const { fetchTenants, joinTenant, createTenant } = api; + const [data, setData] = useState({ + tenants: [], + joinedTenantIds: [], + }); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + setIsLoading(true); + fetchTenants().then((result) => { + if (result.status === "OK") { + setData(result); + } + setIsLoading(false); + }); + }, []); + + const handleOnJoin = async (data: TenantJoinData) => { + setIsLoading(true); + try { + const result = await joinTenant(data); + + // If there was an error, show that + if (result.status === "ERROR") { + console.error(result.message); + return result; + } + + // If it was successful, redirect the user to `/user/profile`. + if (result.status === "OK") { + logDebugMessage("Successfully joined tenant"); + if (result.wasSessionRefreshed) { + logDebugMessage("Session was refreshed"); + } else { + logDebugMessage("Please go to `/user/profile` to continue"); + } + } + + return result; + } finally { + setIsLoading(false); + } + }; + + const handleOnCreate = async (data: TenantCreateData) => { + setIsLoading(true); + try { + const result = await createTenant(data); + + // If there was an error, show that + if (result.status === "ERROR") { + console.error(result.message); + return result; + } + + return result; + } finally { + setIsLoading(false); + } + }; + + return ; +}; + +const TenantCardWrapperWithToast = () => { + return ( + + + + + ); +}; + +export default TenantCardWrapperWithToast; diff --git a/packages/tenants-react/src/types.ts b/packages/tenants-react/src/types.ts new file mode 100644 index 0000000..d300463 --- /dev/null +++ b/packages/tenants-react/src/types.ts @@ -0,0 +1,7 @@ +export type SuperTokensPluginTenantsPluginConfig = { + requireTenantCreation?: boolean; +}; + +export type SuperTokensPluginTenantsPluginNormalisedConfig = { + requireTenantCreation?: boolean; +}; diff --git a/packages/tenants-react/tsconfig.json b/packages/tenants-react/tsconfig.json new file mode 100644 index 0000000..9ce578d --- /dev/null +++ b/packages/tenants-react/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@shared/tsconfig/react.json", + "compilerOptions": { + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist", + "noUnusedLocals": false, + "noImplicitAny": false + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.tsx", "src/**/*.spec.tsx", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/packages/tenants-react/vite.config.ts b/packages/tenants-react/vite.config.ts new file mode 100644 index 0000000..7d43469 --- /dev/null +++ b/packages/tenants-react/vite.config.ts @@ -0,0 +1,38 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import dts from 'vite-plugin-dts'; +import peerDepsExternal from 'rollup-plugin-peer-deps-external'; +import * as path from 'path'; +import packageJson from './package.json'; + +export default defineConfig(() => { + return { + root: __dirname, + plugins: [ + react(), + dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.json') }), + peerDepsExternal(), + ], + + build: { + outDir: 'dist', + sourcemap: false, + emptyOutDir: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + fileName: 'index', + name: packageJson.name, + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es' as const, 'cjs' as const], + }, + rollupOptions: { + cache: false, + }, + }, + }; +}); From 5226d7f6333dc1f0108cbd143812ef0adfa484f1 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 5 Sep 2025 11:55:26 +0530 Subject: [PATCH 03/25] feat: add changes to the tenants tab --- package-lock.json | 1563 ++++++++++++++--- package.json | 6 +- .../components/details/details-wrapper.tsx | 36 +- .../src/components/invitations/accept.tsx | 57 +- .../invitations/invitations.module.scss | 66 - .../components/invitations/invitations.tsx | 84 +- .../src/components/page-wrapper/index.ts | 2 +- .../components/page-wrapper/page-wrapper.tsx | 6 +- .../tenant-card/tenant-card.module.scss | 54 - .../components/tenant-card/tenant-card.tsx | 46 +- ...agement.module.scss => styles.module.scss} | 11 +- .../tenant-management/tenant-management.tsx | 90 +- packages/tenants-react/src/plugin.tsx | 366 ++-- .../src/tenant-details-wrapper.tsx | 4 +- shared/ui/src/components/index.ts | 1 + shared/ui/src/components/tab/index.ts | 5 + shared/ui/src/components/tab/tab-group.tsx | 27 + shared/ui/src/components/tab/tab-panel.tsx | 25 + shared/ui/src/components/tab/tab.module.scss | 11 + shared/ui/src/components/tab/tab.stories.tsx | 46 + shared/ui/src/components/tab/tab.tsx | 25 + shared/ui/src/theme/wa/common.css | 35 +- 22 files changed, 1827 insertions(+), 739 deletions(-) delete mode 100644 packages/tenants-react/src/components/invitations/invitations.module.scss delete mode 100644 packages/tenants-react/src/components/tenant-card/tenant-card.module.scss rename packages/tenants-react/src/components/tenant-management/{tenant-management.module.scss => styles.module.scss} (87%) create mode 100644 shared/ui/src/components/tab/index.ts create mode 100644 shared/ui/src/components/tab/tab-group.tsx create mode 100644 shared/ui/src/components/tab/tab-panel.tsx create mode 100644 shared/ui/src/components/tab/tab.module.scss create mode 100644 shared/ui/src/components/tab/tab.stories.tsx create mode 100644 shared/ui/src/components/tab/tab.tsx diff --git a/package-lock.json b/package-lock.json index 6611f5b..2fbfac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "prettier": "^3.2.5", "rollup-plugin-peer-deps-external": "^2.2.4", + "sass-embedded": "^1.92.0", "turbo": "^2.5.5", "vite": "^6.3.5", "vite-plugin-dts": "^4.5.4" @@ -1197,6 +1198,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.7.0.tgz", + "integrity": "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@changesets/apply-release-plan": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.12.tgz", @@ -2695,6 +2703,316 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3967,6 +4285,10 @@ "resolved": "packages/tenants-nodejs", "link": true }, + "node_modules/@supertokens-plugins/tenants-react": { + "resolved": "packages/tenants-react", + "link": true + }, "node_modules/@supertokens-plugins/user-banning-nodejs": { "resolved": "packages/user-banning-nodejs", "link": true @@ -5949,6 +6271,13 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "license": "MIT/X11" + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -6273,6 +6602,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6795,15 +7131,29 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, "node_modules/diff-sequences": { "version": "29.6.3", @@ -8835,6 +9185,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -10510,6 +10867,14 @@ "lower-case": "^1.1.1" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -12219,205 +12584,592 @@ "dev": true, "license": "MIT" }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/sass": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.92.0.tgz", + "integrity": "sha512-KDNI0BxgIRDAfJgzNm5wuy+4yOCIZyrUbjSpiU/JItfih+KGXAVefKL53MTml054MmBA3DDKIBMSI/7XLxZJ3A==", "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "xmlchars": "^2.2.0" + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT", - "peer": true - }, - "node_modules/scmp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", - "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", "bin": { - "semver": "bin/semver.js" + "sass": "sass.js" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "node_modules/sass-embedded": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.92.0.tgz", + "integrity": "sha512-daqnoAA+AmXvcL1fvJRMd4RDPZM2s27qYxb51c5TYc1B1Zugu0gVGyA5leoXQJEzo6sDTQ95J8X0yFcdBNGNtw==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" }, "engines": { - "node": ">= 18" + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.92.0", + "sass-embedded-android-arm": "1.92.0", + "sass-embedded-android-arm64": "1.92.0", + "sass-embedded-android-riscv64": "1.92.0", + "sass-embedded-android-x64": "1.92.0", + "sass-embedded-darwin-arm64": "1.92.0", + "sass-embedded-darwin-x64": "1.92.0", + "sass-embedded-linux-arm": "1.92.0", + "sass-embedded-linux-arm64": "1.92.0", + "sass-embedded-linux-musl-arm": "1.92.0", + "sass-embedded-linux-musl-arm64": "1.92.0", + "sass-embedded-linux-musl-riscv64": "1.92.0", + "sass-embedded-linux-musl-x64": "1.92.0", + "sass-embedded-linux-riscv64": "1.92.0", + "sass-embedded-linux-x64": "1.92.0", + "sass-embedded-unknown-all": "1.92.0", + "sass-embedded-win32-arm64": "1.92.0", + "sass-embedded-win32-x64": "1.92.0" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.92.0.tgz", + "integrity": "sha512-0VcRBilndf8Iot7zfKKEYH7Ig4JBRjltf7Ba9dNL6wtv0m1a36cm8FgZFofrXtDjUgVTV/aEH/Xw4zBUs6vFYA==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.92.0" } }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "node_modules/sass-embedded-android-arm": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.92.0.tgz", + "integrity": "sha512-0NH0zElKL5gLdNcWFzYX/bqtpoFq5ogcU+4vLdmpBXA9Zl5NFPXAPRA6K8pgjQNWpnV7bG05JSIVPuNKZ60Ptg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.6" + "node": ">=14.0.0" } }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "node_modules/sass-embedded-android-arm64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.92.0.tgz", + "integrity": "sha512-m0JY0QyskN77AFpA8FKxqXZNWSzrPvKOvZqOu1DwEEipyHuSdiAVFDHZ6EpI3aABxCXE3jgkP+Ij2mb4hiLxFw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.6" + "node": ">=14.0.0" } }, - "node_modules/sentence-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", - "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "node_modules/sass-embedded-android-riscv64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.92.0.tgz", + "integrity": "sha512-wfVRf2PFR15vCgJE3SWLZQRo+98xm7vKvpHiaPU4satutwMKC8yXxDvsc7hFBqQYBtqHNK5ap5dZSXlgdGGZrA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "no-case": "^2.2.0", - "upper-case-first": "^1.1.2" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" } }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "node_modules/sass-embedded-android-x64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.92.0.tgz", + "integrity": "sha512-bc1c3OMdfrYoiIzVzsMI3KBnIa4mEumg7jHzonkDUIUWrfUNsbzlO+UXX1CcRyfaOIqyKNNZLVvBCz8EwmUsbQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 18" + "node": ">=14.0.0" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.92.0.tgz", + "integrity": "sha512-vZh1WCL2QlQyTlAD8snmC8W90XBZI/125o15bfKkGbUzV58dkZJf413hk6JVQS2+a0lZT4GxvrlGH1fSaSNTug==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "node_modules/sass-embedded-darwin-x64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.92.0.tgz", + "integrity": "sha512-4vJsXpOQAgU+KrrW/3POvhnfkG9iJ4gU8ujeEDXqCSSY2M+5B/j0S6iXB7nu3Z9MfmCl8V4B6xyeB0EWE5Ul0g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" } }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "node_modules/sass-embedded-linux-arm": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.92.0.tgz", + "integrity": "sha512-HMjTDjIT8bHwAVd0c8r8QvGxGZwJg3H06/Y5ZiaiwVGiZtYS9jfef6LnrPw8LY5cOG8wm9RZ9AgVqvkL7E2jBQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "node_modules/sass-embedded-linux-arm64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.92.0.tgz", + "integrity": "sha512-HH4LNY1svM2Lv6NCxqOQca42hzG/o55ON9X3T0R18Rl9kVb3y5qiJpdrHh7sSlZWF4qhHYbRc9BIc+Tw142oog==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.92.0.tgz", + "integrity": "sha512-qJDCXm379yRT9+8wKSi6nHFCOODTmD6XmE8rqmMozKo6kvCM+Y3sAMlHrT/0+pfzlGh1JSamkoYIo/ODn+LRVA==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.92.0.tgz", + "integrity": "sha512-xYzZDmcPb3BsaD6qlRTqZqtyMOZfGCSKJBZYj2ZRJiKDDr1sqPSIqKx6G8jc1wJAVdvoNp5tzENnCfY7NRkxNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.92.0.tgz", + "integrity": "sha512-ZD3a6c7YvAjp1lEkKyaQpHc5EuetQ0RU3YoTfjwHiyWwezsuJHZc4hkS7SXWbZNEvi7tc2U1bdt4nSdx9c5Qxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.92.0.tgz", + "integrity": "sha512-ShivGoEKmpyL57hQB9K+EMEOWOo+LuwH5eIM2T0sRIHW5n28IS6h12R3WEJVf+PYtSi9FKWazy7kzeLefya6fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.92.0.tgz", + "integrity": "sha512-CTZF8rMYBS4JsGGFMUwdPExq6DxhONXQv9omKpVmuleRw52Isx37GaMTQg5zSxunS6QfwqCyUysjWXTLe8khHA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.92.0.tgz", + "integrity": "sha512-jAY4tzhSUUDUYSl0m+GQub/ZpVk00Pn4ybHeUICAYSQj043A9rkag+LSKDGCvC/0MptMM+/HkIDAC06tRY4PeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.92.0.tgz", + "integrity": "sha512-s0UF1jquqhrxg0dl/0E+L5tCH1zv1ueF+m3VgJukDkDSTW+nb7wpCGcm8csGoSXnP8+dq53jtUXVtt8sPLr8ZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.92.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.92.0.tgz", + "integrity": "sha512-K8x+q2W0VyGPBtO3b0AlpecGOk47ce2FkEX0WD1gEexpbRCytQ+udDACHQGXpwWYPgSIT9ky0IASzDVT1fjMcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.92.0.tgz", + "integrity": "sha512-b0051n7EwSvH580u8LjsCAj2US1F59FY6/GbWJWlE2bidzY86/f8ovl4LsGY/uM3lNzWQlA/0BGmdAm44d/qJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sentence-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -13094,6 +13846,29 @@ "dev": true, "license": "MIT" }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/synckit": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", @@ -14410,6 +15185,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -16449,28 +17231,244 @@ "happy-dom": "*", "jsdom": "*" }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/captcha-react/node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-discovery-nodejs": { + "name": "@supertokens-plugins/tenant-discovery-nodejs", + "version": "0.0.1-beta.1", + "devDependencies": { + "@shared/eslint": "*", + "@shared/nodejs": "*", + "@shared/tsconfig": "*", + "@types/react": "^17.0.20", + "express": "^5.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "supertokens-node": ">=23.0.0" + } + }, + "packages/tenant-discovery-nodejs/node_modules/@types/react": { + "version": "17.0.88", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", + "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "packages/tenant-discovery-react": { + "name": "@supertokens-plugins/tenant-discovery-react", + "version": "0.0.1-beta.1", + "dependencies": { + "supertokens-js-override": "^0.0.4" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/js": "*", + "@shared/react": "*", + "@shared/tsconfig": "*", + "@testing-library/jest-dom": "^6.1.0", + "@types/react": "^17.0.20", + "@vitejs/plugin-react": "^4.5.2", + "jsdom": "^26.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "rollup-plugin-peer-deps-external": "^2.2.4", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vite-plugin-dts": "^4.5.4", + "vitest": "^1.3.1" + }, + "peerDependencies": { + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "supertokens-auth-react": ">=0.50.0", + "supertokens-web-js": ">=0.16.0" } }, - "packages/captcha-react/node_modules/vitest/node_modules/execa": { + "packages/tenant-discovery-react/node_modules/@types/react": { + "version": "17.0.88", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", + "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "packages/tenant-discovery-react/node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", @@ -16494,7 +17492,7 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "packages/captcha-react/node_modules/vitest/node_modules/get-stream": { + "packages/tenant-discovery-react/node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", @@ -16507,7 +17505,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/human-signals": { + "packages/tenant-discovery-react/node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", @@ -16517,7 +17515,7 @@ "node": ">=16.17.0" } }, - "packages/captcha-react/node_modules/vitest/node_modules/is-stream": { + "packages/tenant-discovery-react/node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", @@ -16530,7 +17528,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/mimic-fn": { + "packages/tenant-discovery-react/node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", @@ -16543,7 +17541,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/npm-run-path": { + "packages/tenant-discovery-react/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", @@ -16559,7 +17557,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/onetime": { + "packages/tenant-discovery-react/node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", @@ -16575,7 +17573,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/path-key": { + "packages/tenant-discovery-react/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", @@ -16588,39 +17586,154 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "packages/tenant-discovery-react/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/captcha-react/node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "packages/tenant-discovery-react/node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", "dev": true, "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": ">=12" + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "packages/tenant-discovery-nodejs": { - "name": "@supertokens-plugins/tenant-discovery-nodejs", - "version": "0.0.1-beta.1", + "packages/tenant-discovery-react/node_modules/vitest/node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "packages/tenants-nodejs": { + "name": "@supertokens-plugins/tenants-nodejs", + "version": "0.0.1", "devDependencies": { "@shared/eslint": "*", "@shared/nodejs": "*", + "@shared/tenants": "*", "@shared/tsconfig": "*", + "@types/nodemailer": "^7.0.1", "@types/react": "^17.0.20", "express": "^5.1.0", "prettier": "3.6.2", @@ -16629,10 +17742,11 @@ "vitest": "^3.2.4" }, "peerDependencies": { + "nodemailer": "^6.0.0", "supertokens-node": ">=23.0.0" } }, - "packages/tenant-discovery-nodejs/node_modules/@types/react": { + "packages/tenants-nodejs/node_modules/@types/react": { "version": "17.0.88", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", @@ -16644,9 +17758,9 @@ "csstype": "^3.0.2" } }, - "packages/tenant-discovery-react": { - "name": "@supertokens-plugins/tenant-discovery-react", - "version": "0.0.1-beta.1", + "packages/tenants-react": { + "name": "@supertokens-plugins/tenants-react", + "version": "0.0.1", "dependencies": { "supertokens-js-override": "^0.0.4" }, @@ -16674,7 +17788,7 @@ "supertokens-web-js": ">=0.16.0" } }, - "packages/tenant-discovery-react/node_modules/@types/react": { + "packages/tenants-react/node_modules/@types/react": { "version": "17.0.88", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", @@ -16686,7 +17800,7 @@ "csstype": "^3.0.2" } }, - "packages/tenant-discovery-react/node_modules/execa": { + "packages/tenants-react/node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", @@ -16710,7 +17824,7 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "packages/tenant-discovery-react/node_modules/get-stream": { + "packages/tenants-react/node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", @@ -16723,7 +17837,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/human-signals": { + "packages/tenants-react/node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", @@ -16733,7 +17847,7 @@ "node": ">=16.17.0" } }, - "packages/tenant-discovery-react/node_modules/is-stream": { + "packages/tenants-react/node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", @@ -16746,7 +17860,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/mimic-fn": { + "packages/tenants-react/node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", @@ -16759,7 +17873,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/npm-run-path": { + "packages/tenants-react/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", @@ -16775,7 +17889,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/onetime": { + "packages/tenants-react/node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", @@ -16791,7 +17905,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/path-key": { + "packages/tenants-react/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", @@ -16804,7 +17918,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/strip-final-newline": { + "packages/tenants-react/node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", @@ -16817,7 +17931,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/tenant-discovery-react/node_modules/vitest": { + "packages/tenants-react/node_modules/vitest": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", @@ -16883,7 +17997,7 @@ } } }, - "packages/tenant-discovery-react/node_modules/vitest/node_modules/vite": { + "packages/tenants-react/node_modules/vitest/node_modules/vite": { "version": "5.4.19", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", @@ -16943,39 +18057,6 @@ } } }, - "packages/tenants-nodejs": { - "name": "@supertokens-plugins/tenants-nodejs", - "version": "0.0.1", - "devDependencies": { - "@shared/eslint": "*", - "@shared/nodejs": "*", - "@shared/tenants": "*", - "@shared/tsconfig": "*", - "@types/nodemailer": "^7.0.1", - "@types/react": "^17.0.20", - "express": "^5.1.0", - "prettier": "3.6.2", - "pretty-quick": "^4.2.2", - "typescript": "^5.8.3", - "vitest": "^3.2.4" - }, - "peerDependencies": { - "nodemailer": "^6.0.0", - "supertokens-node": ">=23.0.0" - } - }, - "packages/tenants-nodejs/node_modules/@types/react": { - "version": "17.0.88", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", - "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" - } - }, "packages/user-banning-nodejs": { "name": "@supertokens-plugins/user-banning-nodejs", "version": "0.0.2-beta.2", diff --git a/package.json b/package.json index a0679dd..ad35b8d 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,14 @@ "devDependencies": { "@changesets/cli": "^2.27.1", "@typescript-eslint/eslint-plugin": "^8.39.1", + "@vitejs/plugin-react": "^4.5.2", "eslint": "^8.57.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react-hooks": "^5.2.0", "prettier": "^3.2.5", - "turbo": "^2.5.5", - "@vitejs/plugin-react": "^4.5.2", "rollup-plugin-peer-deps-external": "^2.2.4", + "sass-embedded": "^1.92.0", + "turbo": "^2.5.5", "vite": "^6.3.5", "vite-plugin-dts": "^4.5.4" }, @@ -43,4 +44,3 @@ "tsup": "^8.5.0" } } - diff --git a/packages/tenants-react/src/components/details/details-wrapper.tsx b/packages/tenants-react/src/components/details/details-wrapper.tsx index bc37160..1e5f3e7 100644 --- a/packages/tenants-react/src/components/details/details-wrapper.tsx +++ b/packages/tenants-react/src/components/details/details-wrapper.tsx @@ -1,18 +1,14 @@ -import classNames from 'classnames/bind'; -import style from './details.module.scss'; -import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; -import { User } from 'supertokens-web-js/types'; -import { useCallback, useEffect, useState } from 'react'; +import classNames from "classnames/bind"; +import { useCallback, useEffect, useState } from "react"; +import { User } from "supertokens-web-js/types"; + +import style from "./details.module.scss"; + +// import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; const cx = classNames.bind(style); -export const DetailsWrapper = ({ - section, - onFetch, -}: { - section: BaseFormSection; - onFetch: () => Promise<{ users: User[] }>; -}) => { +export const DetailsWrapper = ({ section, onFetch }: { section: any; onFetch: () => Promise<{ users: User[] }> }) => { const [users, setUsers] = useState([]); const loadDetails = useCallback(async () => { @@ -25,24 +21,24 @@ export const DetailsWrapper = ({ }, [loadDetails]); return ( -
-
+
+

{section.label}

{section.description}

-
+
{users.length > 0 ? ( -
+
{users.map((user) => ( -
-
{user.emails[0]?.charAt(0).toUpperCase() || 'U'}
-
{user.emails[0]}
+
+
{user.emails[0]?.charAt(0).toUpperCase() || "U"}
+
{user.emails[0]}
))}
) : ( -
+

No users found

)} diff --git a/packages/tenants-react/src/components/invitations/accept.tsx b/packages/tenants-react/src/components/invitations/accept.tsx index 5d365f7..1b3395e 100644 --- a/packages/tenants-react/src/components/invitations/accept.tsx +++ b/packages/tenants-react/src/components/invitations/accept.tsx @@ -1,33 +1,34 @@ -import classNames from 'classnames/bind'; -import style from './invitations.module.scss'; -import { useEffect, useState } from 'react'; -import { useSessionContext } from 'supertokens-auth-react/recipe/session'; -import { redirectToAuth } from 'supertokens-auth-react'; -import { Card, Button } from '@supertokens-plugin-profile/common-frontend'; +import { Card, Button } from "@shared/ui"; +import classNames from "classnames/bind"; +import { useEffect, useState } from "react"; +import { redirectToAuth } from "supertokens-auth-react"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; + +import style from "./invitations.module.scss"; const cx = classNames.bind(style); export const AcceptInvitation = ({ onAccept, }: { - onAccept: (code: string, tenantId: string) => Promise<{ status: 'OK' } | { status: 'ERROR'; message: string }>; + onAccept: (code: string, tenantId: string) => Promise<{ status: "OK" } | { status: "ERROR"; message: string }>; }) => { - const [code, setCode] = useState(''); - const [tenantId, setTenantId] = useState(''); + const [code, setCode] = useState(""); + const [tenantId, setTenantId] = useState(""); const [isAccepting, setIsAccepting] = useState(false); - const [error, setError] = useState(''); + const [error, setError] = useState(""); const session = useSessionContext(); useEffect(() => { // Parse the code from URL query parameters const urlParams = new URLSearchParams((globalThis as any).location.search); - const inviteCode = urlParams.get('code'); - const tenantId = urlParams.get('tenantId'); + const inviteCode = urlParams.get("code"); + const tenantId = urlParams.get("tenantId"); if (!inviteCode || !tenantId) { // Redirect to dashboard if no code is present - (globalThis as any).location.href = '/user/tenants'; + (globalThis as any).location.href = "/user/tenants"; return; } @@ -40,17 +41,19 @@ export const AcceptInvitation = ({ } const handleAccept = async () => { - if (!code) return; + if (!code) { + return; + } setIsAccepting(true); - setError(''); + setError(""); try { await onAccept(code, tenantId); // Redirect to /user/tenants after successful acceptance - (globalThis as any).location.href = '/user/tenants'; + (globalThis as any).location.href = "/user/tenants"; } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to accept invitation'); + setError(err instanceof Error ? err.message : "Failed to accept invitation"); } finally { setIsAccepting(false); } @@ -68,8 +71,8 @@ export const AcceptInvitation = ({ if (!code) { return ( -
-
+
+

Invalid Invitation

No invitation code found. Redirecting to dashboard...

@@ -79,23 +82,23 @@ export const AcceptInvitation = ({ return ( -
+
Accept Invitation
- -
- You have been invited to join "{tenantId}" tenant. Click the button + +
+ You have been invited to join "{tenantId}" tenant. Click the button below to accept the invitation.
-
+
Invitation code:
-
{code}
+
{code}
-
+
{session.doesSessionExist ? ( ) : ( )}
{showInviteForm && onCreate && ( -
+
-
+
setInviteEmail(e.currentTarget.value)} - className={cx('inviteEmailInput')} + className="" required disabled={isSubmitting} /> -
- -
@@ -122,39 +106,37 @@ export const InvitationsWrapper = ({
)} -
+
{invitations.length > 0 ? ( -
- {invitations.map((invitation) => ( -
-
{invitation.email.charAt(0).toUpperCase() || 'U'}
-
{invitation.email}
-
- ))} + ))} */}
) : ( -
+

No invitations found

)}
{showCode && ( -
+

{showCode}

- +
)}
diff --git a/packages/tenants-react/src/components/page-wrapper/index.ts b/packages/tenants-react/src/components/page-wrapper/index.ts index 10664de..a66493a 100644 --- a/packages/tenants-react/src/components/page-wrapper/index.ts +++ b/packages/tenants-react/src/components/page-wrapper/index.ts @@ -1 +1 @@ -export * from './page-wrapper'; +export * from "./page-wrapper"; diff --git a/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx b/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx index 4970094..f5f3fa5 100644 --- a/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx +++ b/packages/tenants-react/src/components/page-wrapper/page-wrapper.tsx @@ -1,9 +1,9 @@ -import { ReactNode } from 'react'; -import './page-wrapper.css'; +import { ReactNode } from "react"; +import "./page-wrapper.css"; export const PageWrapper = ({ children, style }: { children: ReactNode; style?: React.CSSProperties }) => { return ( -
+
{children}
); diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss b/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss deleted file mode 100644 index 5322d01..0000000 --- a/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss +++ /dev/null @@ -1,54 +0,0 @@ -.createTenantInputContainer { - border-radius: 12px; - box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; - border: 1px solid #dddde3; - background-color: #f2f2f0; - - .createTenantInputCardText { - padding: 12px; - color: #60646c; - font-weight: var(--wa-font-weight-normal); - font-style: Regular; - font-size: 14px; - line-height: 20px; - letter-spacing: 0px; - } - - .createTenantInputWrapper { - display: flex; - align-items: center; - color: #60646c; - padding: 12px; - background-color: #f9f9f8; - border-top: 1px solid #dddde3; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - - .createTenantInput { - width: 100%; - } - } -} - -.createTenantHeader { - font-weight: var(--wa-font-weight-extrabold); - font-style: Bold; - font-size: 28px; - line-height: 36px; - letter-spacing: -0.12px; - color: #1c2024; -} - -.createTenantFooter { - display: flex; - justify-content: end; - align-items: center; -} - -.createTenantInputContainer::part(header) { - padding: 0 !important; -} - -.createTenantInputContainer::part(body) { - padding: 0 !important; -} diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.tsx b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx index fe6431e..22a37cf 100644 --- a/packages/tenants-react/src/components/tenant-card/tenant-card.tsx +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx @@ -1,17 +1,14 @@ -import classNames from 'classnames/bind'; -import { Button, Card, TextInput, usePrettyAction } from '@supertokens-plugin-profile/common-frontend'; -import { TenantCreateData, TenantJoinData, TenantList } from '@supertokens-plugin-profile/tenants-shared'; -import { useState } from 'react'; -import styles from './tenant-card.module.scss'; - -const cx = classNames.bind(styles); +import { TenantCreateData, TenantJoinData, TenantList } from "@shared/tenants"; +import { Button, Card, TextInput, usePrettyAction } from "@shared/ui"; +import classNames from "classnames/bind"; +import { useState } from "react"; interface TenantCardProps { data: TenantList; - onJoin: (data: TenantJoinData) => Promise<{ status: 'OK' } | { status: 'ERROR'; message: string }>; + onJoin: (data: TenantJoinData) => Promise<{ status: "OK" } | { status: "ERROR"; message: string }>; onCreate: ( data: TenantCreateData, - ) => Promise<{ status: 'OK'; pendingApproval: boolean; requestId: string } | { status: 'ERROR'; message: string }>; + ) => Promise<{ status: "OK"; pendingApproval: boolean; requestId: string } | { status: "ERROR"; message: string }>; isLoading: boolean; } @@ -20,29 +17,28 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp return ; } - const [newTenantName, setNewTenantName] = useState(''); + const [newTenantName, setNewTenantName] = useState(""); const onSuccess = () => { // Redirect the user to the app. - console.log('Redirecting...'); + console.log("Redirecting..."); }; const handleCreateAndJoin = usePrettyAction( async () => { if (newTenantName.trim().length === 0) { - console.warn('No tenant name provided'); + console.warn("No tenant name provided"); return; } const createResponse = await onCreate({ name: newTenantName }); - if (createResponse.status !== 'OK') { + if (createResponse.status !== "OK") { throw new Error(createResponse.message); } // If creation is pending approval, show a message to the user if (createResponse.pendingApproval) { - throw new Error('Tenant creation request is pending approval'); - return; + throw new Error("Tenant creation request is pending approval"); } // If creation is successful, join the tenant @@ -50,8 +46,8 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp }, [onCreate, newTenantName], { - successMessage: 'Tenant created, redirecting...', - errorMessage: 'Failed to create tenant', + successMessage: "Tenant created, redirecting...", + errorMessage: "Failed to create tenant", onSuccess: async () => { onSuccess(); }, @@ -60,13 +56,13 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp return ( -
+ {/*
Create Tenant
-
+
- -
+ +
Enter name of your tenant
-
+
-
+
*/}
); }; diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.module.scss b/packages/tenants-react/src/components/tenant-management/styles.module.scss similarity index 87% rename from packages/tenants-react/src/components/tenant-management/tenant-management.module.scss rename to packages/tenants-react/src/components/tenant-management/styles.module.scss index f97233d..9c7626e 100644 --- a/packages/tenants-react/src/components/tenant-management/tenant-management.module.scss +++ b/packages/tenants-react/src/components/tenant-management/styles.module.scss @@ -4,7 +4,16 @@ max-width: 800px; margin: 0 auto; padding: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Open Sans, + Helvetica Neue, sans-serif; color: #333; width: 100%; diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx index 3a07310..fd23396 100644 --- a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx @@ -1,27 +1,29 @@ -// DEBUG: Modified at 17:30 on 2025-07-28 - CHECK IF THIS APPEARS IN BROWSER -import { useState, useEffect, useCallback } from 'react'; -import classNames from 'classnames/bind'; -import style from './tenant-management.module.scss'; -import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; -import { usePlugin } from '../../use-plugin'; -import { TenantDetails } from '@supertokens-plugin-profile/tenants-shared'; -import { DetailsWrapper } from '../details/details-wrapper'; -import { InvitationsWrapper } from '../invitations/invitations'; -import { SelectInput } from '@supertokens-plugin-profile/common-frontend'; +import { TenantDetails } from "@shared/tenants"; +import { SelectInput, TabGroup, Tab } from "@shared/ui"; +// import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; +import classNames from "classnames/bind"; +import { useState, useEffect, useCallback } from "react"; + +import { usePluginContext } from "../../plugin"; +import { DetailsWrapper } from "../details/details-wrapper"; +import { InvitationsWrapper } from "../invitations/invitations"; + +import style from "./styles.module.scss"; const cx = classNames.bind(style); -export const TenantManagement = ({ section }: { section: BaseFormSection }) => { - const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant } = usePlugin(); +export const TenantManagement = ({ section }: { section: any }) => { + const { api } = usePluginContext(); + const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant } = api; const [tenants, setTenants] = useState([]); - const [selectedTenantId, setSelectedTenantId] = useState('public'); - const [activeTab, setActiveTab] = useState<'users' | 'invitations'>('users'); + const [selectedTenantId, setSelectedTenantId] = useState("public"); + const [activeTab, setActiveTab] = useState<"users" | "invitations">("users"); // Load tenants on component mount useEffect(() => { const loadTenants = async () => { const response = await fetchTenants(); - if (response.status === 'OK') { + if (response.status === "OK") { setTenants(response.tenants); if (response.tenants.length > 0) { setSelectedTenantId(response.tenants[0].tenantId); @@ -34,7 +36,7 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { // Users tab functions const onFetchUsers = useCallback(async () => { const response = await getUsers(selectedTenantId); - if (response.status === 'ERROR') { + if (response.status === "ERROR") { throw new Error(response.message); } return { users: response.users }; @@ -43,8 +45,8 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { // Invitations tab functions const onFetchInvitations = useCallback( async (tenantId?: string) => { - const response = await getInvitations(tenantId || selectedTenantId); - if (response.status === 'ERROR') { + const response = await getInvitations(); + if (response.status === "ERROR") { throw new Error(response.message); } return { invitations: response.invitees }; @@ -55,7 +57,7 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { const onRemoveInvite = useCallback( async (email: string, tenantId?: string) => { const response = await removeInvitation(email, tenantId || selectedTenantId); - if (response.status === 'ERROR') { + if (response.status === "ERROR") { throw new Error(response.message); } }, @@ -65,7 +67,7 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { const onCreateInvite = useCallback( async (email: string, tenantId: string) => { const response = await addInvitation(email, tenantId); - if (response.status === 'ERROR') { + if (response.status === "ERROR") { throw new Error(response.message); } }, @@ -75,18 +77,18 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { const handleTenantSwitch = useCallback( async (tenantId: string) => { const response = await switchTenant(tenantId); - if (response.status === 'OK') { + if (response.status === "OK") { setSelectedTenantId(tenantId); } else { - console.error('Failed to switch tenant:', response.message); + console.error("Failed to switch tenant:", response.message); } }, [switchTenant], ); return ( -
-
+
+

{section.label}

{section.description}

@@ -94,7 +96,7 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => { {/* Tenant Switcher */} {tenants.length > 0 && ( -
+
{ onChange={(e: any) => handleTenantSwitch(e.target.value)} name="Tenant Switcher" options={tenants.map(({ tenantId }) => ({ - label: tenantId === 'public' ? 'Public' : tenantId, + label: tenantId === "public" ? "Public" : tenantId, value: tenantId, }))} /> @@ -111,38 +113,46 @@ export const TenantManagement = ({ section }: { section: BaseFormSection }) => {
{/* Tab Navigation */} -
-
{/* Tab Content */} -
- {activeTab === 'users' && selectedTenantId && ( +
+ {activeTab === "users" && selectedTenantId && ( )} - {activeTab === 'invitations' && selectedTenantId && ( + {activeTab === "invitations" && selectedTenantId && ( ((pluginConfig) => { - const MultipleTenantsPresentClaim = new BooleanClaim({ - id: `${PLUGIN_ID}-multiple-tenants-present`, - refresh: async () => {}, - onFailureRedirection: async ({ reason }) => { - return "/user/tenants/select"; - }, - }); - - // TODO: Update this to parse it from the exports of that - // plugin so that we don't have to depend on that plugin. - // Essentially, if that plugin is enabled, only then we need to use - // the ID. - const PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID = "some-id"; - - const extractCodeAndTenantId = (url: string) => { - const urlParams = new URLSearchParams(url); - const code = urlParams.get("code"); - const tenantId = urlParams.get("tenantId"); - return { code, tenantId, shouldAcceptInvite: Boolean(code) && Boolean(tenantId) }; - }; - - const extractAndInjectCodeAndTenantId = (context: any) => { - const { code, tenantId, shouldAcceptInvite } = extractCodeAndTenantId(context.url); - - if (!shouldAcceptInvite) { - return { - requestInit: context.requestInit, - url: context.url, - }; - } - - let requestInit = context.requestInit; - let body = context.requestInit.body; - if (body !== undefined) { - let bodyJson = JSON.parse(body as string); - bodyJson.code = code; - bodyJson.tenantId = tenantId; - requestInit.body = JSON.stringify(bodyJson); - } - - return { - requestInit, - url: context.url, +>( + (pluginConfig) => { + const MultipleTenantsPresentClaim = new BooleanClaim({ + id: `${PLUGIN_ID}-multiple-tenants-present`, + refresh: async () => {}, + onFailureRedirection: async ({ reason }) => { + return "/user/tenants/select"; + }, + }); + + // TODO: Update this to parse it from the exports of that + // plugin so that we don't have to depend on that plugin. + // Essentially, if that plugin is enabled, only then we need to use + // the ID. + const PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID = "some-id"; + + const extractCodeAndTenantId = (url: string) => { + const urlParams = new URLSearchParams(url); + const code = urlParams.get("code"); + const tenantId = urlParams.get("tenantId"); + return { code, tenantId, shouldAcceptInvite: Boolean(code) && Boolean(tenantId) }; }; - }; - return { - id: PLUGIN_ID, - init: (config, plugins, sdkVersion) => { - if (config.enableDebugLogs) { - enableDebugLogs(); - } - - const baseProfilePlugin = plugins.find((plugin: any) => plugin.id === "supertokens-plugin-profile-base"); - if (!baseProfilePlugin) { - console.warn("Base profile plugin not found. Not adding common details profile plugin."); - return; - } + const extractAndInjectCodeAndTenantId = (context: any) => { + const { code, tenantId, shouldAcceptInvite } = extractCodeAndTenantId(context.url); - if (!baseProfilePlugin.exports) { - console.warn("Base profile plugin does not export anything. Not adding common details profile plugin."); - return; + if (!shouldAcceptInvite) { + return { + requestInit: context.requestInit, + url: context.url, + }; } - const registerSection = baseProfilePlugin.exports?.registerSection; - if (!registerSection) { - console.warn("Base profile plugin does not export registerSection. Not adding common details profile plugin."); - return; + let requestInit = context.requestInit; + let body = context.requestInit.body; + if (body !== undefined) { + let bodyJson = JSON.parse(body as string); + bodyJson.code = code; + bodyJson.tenantId = tenantId; + requestInit.body = JSON.stringify(bodyJson); } - registerSection(async () => ({ - id: "tenant-management", - title: "Tenants", - order: 1, - component: () => - TenantManagement.call(null, { - section: { - id: "tenant-management", - label: "Tenant Management", - description: "Manage users and invitations for your tenants", - fields: [], - }, - }), - })); - - const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); - const api = getApi(querier); - - setContext({ - plugins, - sdkVersion, - appConfig: config, - pluginConfig, - querier, - api, - t: (t: undefined) => "", // TODO: Update this - }); - }, - routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { return { - status: "OK", - routeHandlers: [ - { - path: "/user/tenants/select", - handler: () => SelectTenantPage.call(null), - }, - { - path: "/user/invite/accept", - handler: () => InvitationAcceptWrapper.call(null), - }, - ], + requestInit, + url: context.url, }; - }, - overrideMap: { - session: { - functions: (originalImplementation) => { - return { - ...originalImplementation, - getGlobalClaimValidators(input) { - // If the profile claim is present, make sure the tenant - // one is added after it. - const profileClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( - (validator) => validator.id === PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, - ); - const otherClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( - (validator) => validator.id !== PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, - ); - - const claimValidators = [ - ...otherClaimValidators, - ...profileClaimValidators, - ...(pluginConfig.requireTenantCreation ? [MultipleTenantsPresentClaim.validators.isTrue()] : []), - ]; - - return claimValidators; - }, - }; - }, - }, - emailpassword: { - config: (config) => ({ - ...config, - preAPIHook: async (context) => { - if (context.action === "EMAIL_PASSWORD_SIGN_IN" || context.action === "EMAIL_PASSWORD_SIGN_UP") { - return extractAndInjectCodeAndTenantId(context); - } - return context; - }, - }), + }; + + return { + id: PLUGIN_ID, + init: (config, plugins, sdkVersion) => { + if (config.enableDebugLogs) { + enableDebugLogs(); + } + + const baseProfilePlugin = plugins.find((plugin: any) => plugin.id === "supertokens-plugin-profile-base"); + if (!baseProfilePlugin) { + console.warn("Base profile plugin not found. Not adding common details profile plugin."); + return; + } + + if (!baseProfilePlugin.exports) { + console.warn("Base profile plugin does not export anything. Not adding common details profile plugin."); + return; + } + + const registerSection = baseProfilePlugin.exports?.registerSection; + if (!registerSection) { + console.warn( + "Base profile plugin does not export registerSection. Not adding common details profile plugin.", + ); + return; + } + + registerSection(async () => ({ + id: "tenant-management", + title: "Tenants", + order: 1, + component: () => + TenantManagement.call(null, { + section: { + id: "tenant-management", + label: "Tenant Management", + description: "Manage users and invitations for your tenants", + fields: [], + }, + }), + })); + + const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); + const api = getApi(querier); + + setContext({ + plugins, + sdkVersion, + appConfig: config, + pluginConfig, + querier, + api, + t: (t: undefined) => "", // TODO: Update this + }); }, - passwordless: { - config: (config) => ({ - ...config, - preAPIHook: async (context) => { - if (context.action === "PASSWORDLESS_CONSUME_CODE") { - return extractAndInjectCodeAndTenantId(context); - } - return context; - }, - }), + routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { + return { + status: "OK", + routeHandlers: [ + { + path: "/user/tenants/select", + handler: () => SelectTenantPage.call(null), + }, + { + path: "/user/invite/accept", + handler: () => InvitationAcceptWrapper.call(null), + }, + ], + }; }, - thirdparty: { - config: (config) => ({ - ...config, - preAPIHook: async (context) => { - if (context.action === "THIRD_PARTY_SIGN_IN_UP") { - return extractAndInjectCodeAndTenantId(context); - } - return context; + overrideMap: { + session: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + getGlobalClaimValidators(input) { + // If the profile claim is present, make sure the tenant + // one is added after it. + const profileClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( + (validator) => validator.id === PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, + ); + const otherClaimValidators = input.claimValidatorsAddedByOtherRecipes.filter( + (validator) => validator.id !== PROGRESSIVE_PROFILING_COMPLETED_CLAIM_ID, + ); + + const claimValidators = [ + ...otherClaimValidators, + ...profileClaimValidators, + ...(pluginConfig.requireTenantCreation ? [MultipleTenantsPresentClaim.validators.isTrue()] : []), + ]; + + return claimValidators; + }, + }; }, - }), + }, + emailpassword: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "EMAIL_PASSWORD_SIGN_IN" || context.action === "EMAIL_PASSWORD_SIGN_UP") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, + passwordless: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "PASSWORDLESS_CONSUME_CODE") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, + thirdparty: { + config: (config) => ({ + ...config, + preAPIHook: async (context) => { + if (context.action === "THIRD_PARTY_SIGN_IN_UP") { + return extractAndInjectCodeAndTenantId(context); + } + return context; + }, + }), + }, }, - }, - generalAuthRecipeComponentOverrides: { - AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { - // If the code and tenantId, we need to show the message that - // the invitation will be accepted automatically. - const { shouldAcceptInvite } = extractCodeAndTenantId((globalThis as any).location.search); - - return ( -
- {shouldAcceptInvite && "If you authenticate, invitation will be accepted automatically."} - {/* @ts-ignore */} - -
- ); + generalAuthRecipeComponentOverrides: { + AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { + // If the code and tenantId, we need to show the message that + // the invitation will be accepted automatically. + const { shouldAcceptInvite } = extractCodeAndTenantId((globalThis as any).location.search); + + return ( +
+ {shouldAcceptInvite && "If you authenticate, invitation will be accepted automatically."} + {/* @ts-ignore */} + +
+ ); + }, }, - }, - exports: { - }, - }; -}, + exports: {}, + }; + }, undefined, (pluginConfig) => ({ requireTenantCreation: pluginConfig.requireTenantCreation ?? true, - }) + }), ); diff --git a/packages/tenants-react/src/tenant-details-wrapper.tsx b/packages/tenants-react/src/tenant-details-wrapper.tsx index 18a7210..31fd996 100644 --- a/packages/tenants-react/src/tenant-details-wrapper.tsx +++ b/packages/tenants-react/src/tenant-details-wrapper.tsx @@ -1,10 +1,10 @@ -import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; +// import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; import { useCallback } from "react"; import { DetailsWrapper } from "./components/details/details-wrapper"; import { usePluginContext } from "./plugin"; -export const TenantDetailsWrapper = ({ section }: { section: BaseFormSection }) => { +export const TenantDetailsWrapper = ({ section }: { section: any }) => { const { api } = usePluginContext(); const onFetch = useCallback(async () => { diff --git a/shared/ui/src/components/index.ts b/shared/ui/src/components/index.ts index 181a2a9..d56ebfa 100644 --- a/shared/ui/src/components/index.ts +++ b/shared/ui/src/components/index.ts @@ -7,3 +7,4 @@ export * from "./toast"; export * from "./tag"; export * from "./theme-provider"; export * from "./callout"; +export * from "./tab"; diff --git a/shared/ui/src/components/tab/index.ts b/shared/ui/src/components/tab/index.ts new file mode 100644 index 0000000..efa2a59 --- /dev/null +++ b/shared/ui/src/components/tab/index.ts @@ -0,0 +1,5 @@ +export * from "./tab-group"; +export * from "./tab"; +export * from "./tab-panel"; + +export type {}; diff --git a/shared/ui/src/components/tab/tab-group.tsx b/shared/ui/src/components/tab/tab-group.tsx new file mode 100644 index 0000000..f2ad9a4 --- /dev/null +++ b/shared/ui/src/components/tab/tab-group.tsx @@ -0,0 +1,27 @@ +import classNames from "classnames/bind"; +import styles from "./tab.module.scss"; +import { useWebComponent } from "../utils"; +import { BaseWaProps, HTMLElementProps } from "../types"; + +const cx = classNames.bind(styles); + +export interface TabGroupProps extends HTMLElementProps, Pick { + active?: string; + placement?: "top" | "bottom" | "start" | "end"; + activation?: "auto" | "manual"; + noScrollControls?: boolean; + children?: React.ReactNode; +} + +export const TabGroup = (_props: TabGroupProps) => { + const { isDefined, props } = useWebComponent({ + name: "wa-tab-group", + className: cx("st-tab-group"), + props: _props, + importCallback: () => import("@awesome.me/webawesome/dist/components/tab-group/tab-group.js"), + }); + + if (!isDefined) return null; + + return {props.children}; +}; diff --git a/shared/ui/src/components/tab/tab-panel.tsx b/shared/ui/src/components/tab/tab-panel.tsx new file mode 100644 index 0000000..001b9d0 --- /dev/null +++ b/shared/ui/src/components/tab/tab-panel.tsx @@ -0,0 +1,25 @@ +import classNames from "classnames/bind"; +import styles from "./tab.module.scss"; +import { useWebComponent } from "../utils"; +import { HTMLElementProps } from "../types"; + +const cx = classNames.bind(styles); + +export interface TabPanelProps extends HTMLElementProps { + children?: React.ReactNode; + name?: string; + active?: boolean; +} + +export const TabPanel = (_props: TabPanelProps) => { + const { isDefined, props } = useWebComponent({ + name: "wa-tab-panel", + className: cx("st-tab-panel"), + props: _props, + importCallback: () => import("@awesome.me/webawesome/dist/components/tab-panel/tab-panel.js"), + }); + + if (!isDefined) return null; + + return {props.children}; +}; diff --git a/shared/ui/src/components/tab/tab.module.scss b/shared/ui/src/components/tab/tab.module.scss new file mode 100644 index 0000000..d905087 --- /dev/null +++ b/shared/ui/src/components/tab/tab.module.scss @@ -0,0 +1,11 @@ +:global(.plugin-profile) { + .st-tab-group { + } + + .st-tab { + } + + .st-tab-panel { + // Add custom styles for tab panels + } +} diff --git a/shared/ui/src/components/tab/tab.stories.tsx b/shared/ui/src/components/tab/tab.stories.tsx new file mode 100644 index 0000000..be38922 --- /dev/null +++ b/shared/ui/src/components/tab/tab.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { TabGroup } from "./tab-group"; +import { Tab } from "./tab"; +import { TabPanel } from "./tab-panel"; +import { ThemeProvider } from "../theme-provider/theme-provider"; + +const meta: Meta = { + component: TabGroup, + title: "Component/TabGroup", + parameters: { + layout: "centered", + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + active: "general", + placement: "top", + activation: "auto", + size: "medium", + noScrollControls: true, + }, + render: (args) => ( + + General + Billing + Security + + + General content goes here. + + Billing content goes here. + Security content goes here. + + ), +}; diff --git a/shared/ui/src/components/tab/tab.tsx b/shared/ui/src/components/tab/tab.tsx new file mode 100644 index 0000000..6ba90e1 --- /dev/null +++ b/shared/ui/src/components/tab/tab.tsx @@ -0,0 +1,25 @@ +import classNames from "classnames/bind"; +import styles from "./tab.module.scss"; +import { useWebComponent } from "../utils"; +import { HTMLElementProps } from "../types"; + +const cx = classNames.bind(styles); + +export interface TabProps extends HTMLElementProps { + children?: React.ReactNode; + panel?: string; + disabled?: boolean; +} + +export const Tab = (_props: TabProps) => { + const { isDefined, props } = useWebComponent({ + name: "wa-tab", + className: cx("st-tab"), + props: _props, + importCallback: () => import("@awesome.me/webawesome/dist/components/tab/tab.js"), + }); + + if (!isDefined) return null; + + return {props.children}; +}; diff --git a/shared/ui/src/theme/wa/common.css b/shared/ui/src/theme/wa/common.css index 2aae258..de59744 100644 --- a/shared/ui/src/theme/wa/common.css +++ b/shared/ui/src/theme/wa/common.css @@ -15,6 +15,7 @@ --wa-font-weight-normal: 400; --wa-font-weight-semibold: 500; --wa-font-weight-bold: 600; + --wa-font-weight-extrabold: 700; --wa-font-weight-body: var(--wa-font-weight-normal); --wa-font-weight-heading: var(--wa-font-weight-bold); @@ -26,8 +27,7 @@ --wa-line-height-normal: 1.6; --wa-line-height-expanded: 2; - --wa-link-decoration-default: underline - color-mix(in oklab, currentColor 70%, transparent) dotted; + --wa-link-decoration-default: underline color-mix(in oklab, currentColor 70%, transparent) dotted; --wa-link-decoration-hover: underline; /* #endregion */ @@ -70,8 +70,7 @@ /* #region Focus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ --wa-focus-ring-style: solid; --wa-focus-ring-width: 0.1875rem; /* 3px */ - --wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width) - var(--wa-color-focus); + --wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width) var(--wa-color-focus); --wa-focus-ring-offset: 0.0625rem; /* 1px */ /* #endregion */ @@ -92,12 +91,11 @@ --wa-shadow-spread-m: calc(var(--wa-shadow-spread-scale) * 0.25rem); --wa-shadow-spread-l: calc(var(--wa-shadow-spread-scale) * 0.5rem); - --wa-shadow-s: 0 1px 4px 0 rgba(0, 0, 61, 0.05), - 0 2px 1px -1px rgba(0, 0, 61, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.05); /* done */ - --wa-shadow-m: var(--wa-shadow-offset-x-m) var(--wa-shadow-offset-y-m) - var(--wa-shadow-blur-m) var(--wa-shadow-spread-m) var(--wa-color-shadow); - --wa-shadow-l: 0 12px 32px -16px rgba(0, 0, 0, 0.05), - 0 8px 40px 0 rgba(0, 0, 61, 0.05); /* done */ + --wa-shadow-s: + 0 1px 4px 0 rgba(0, 0, 61, 0.05), 0 2px 1px -1px rgba(0, 0, 61, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.05); /* done */ + --wa-shadow-m: var(--wa-shadow-offset-x-m) var(--wa-shadow-offset-y-m) var(--wa-shadow-blur-m) + var(--wa-shadow-spread-m) var(--wa-color-shadow); + --wa-shadow-l: 0 12px 32px -16px rgba(0, 0, 0, 0.05), 0 8px 40px 0 rgba(0, 0, 61, 0.05); /* done */ /* #endregion */ /* #region Transitions ~~~~~~~~~~~~~~~~~~~~~~ */ @@ -112,12 +110,8 @@ --wa-form-control-background-color: var(--wa-color-surface-default); --wa-form-control-border-color: var(--wa-color-neutral-border-loud); - --wa-form-control-border-color-hover: var( - --st-color-neutral-border-loud-hover - ); /* done */ - --wa-form-control-border-color-focus: var( - --st-color-neutral-border-loud-focus - ); /* done */ + --wa-form-control-border-color-hover: var(--st-color-neutral-border-loud-hover); /* done */ + --wa-form-control-border-color-focus: var(--st-color-neutral-border-loud-focus); /* done */ --wa-form-control-border-style: var(--wa-border-style); --wa-form-control-border-width: var(--wa-border-width-s); --wa-form-control-border-radius: var(--wa-border-radius-m); @@ -136,9 +130,7 @@ --wa-form-control-hint-font-weight: var(--wa-font-weight-body); --wa-form-control-hint-line-height: var(--wa-line-height-normal); - --wa-form-control-placeholder-color: var( - --wa-color-neutral-alpha-80 - ); /* done */ + --wa-form-control-placeholder-color: var(--wa-color-neutral-alpha-80); /* done */ --wa-form-control-required-content: "*"; --wa-form-control-required-content-color: var(--wa-color-danger-90); @@ -147,10 +139,7 @@ --wa-form-control-padding-block: 0.75em; --wa-form-control-padding-inline: 1em; --wa-form-control-height: round( - calc( - 2 * var(--wa-form-control-padding-block) + 1em * - var(--wa-form-control-value-line-height) - ), + calc(2 * var(--wa-form-control-padding-block) + 1em * var(--wa-form-control-value-line-height)), 1px ); --wa-form-control-toggle-size: round(1.25em, 1px); From 94b6369b14837b4cbe12a43128f0a59362299469 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 5 Sep 2025 14:18:37 +0530 Subject: [PATCH 04/25] feat: update tenant management with UI components --- .../tenant-management/tenant-management.tsx | 96 ++++++++----------- packages/tenants-react/src/plugin.tsx | 12 ++- packages/tenants-react/src/translations.ts | 7 ++ packages/tenants-react/src/types.ts | 5 + 4 files changed, 59 insertions(+), 61 deletions(-) create mode 100644 packages/tenants-react/src/translations.ts diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx index fd23396..8f6d1ad 100644 --- a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/components/tenant-management/tenant-management.tsx @@ -13,11 +13,10 @@ import style from "./styles.module.scss"; const cx = classNames.bind(style); export const TenantManagement = ({ section }: { section: any }) => { - const { api } = usePluginContext(); + const { api, t } = usePluginContext(); const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant } = api; const [tenants, setTenants] = useState([]); const [selectedTenantId, setSelectedTenantId] = useState("public"); - const [activeTab, setActiveTab] = useState<"users" | "invitations">("users"); // Load tenants on component mount useEffect(() => { @@ -25,8 +24,10 @@ export const TenantManagement = ({ section }: { section: any }) => { const response = await fetchTenants(); if (response.status === "OK") { setTenants(response.tenants); + + // TODO: Set the selected tenant from the user details if (response.tenants.length > 0) { - setSelectedTenantId(response.tenants[0].tenantId); + setSelectedTenantId(response.tenants[0]!.tenantId); } } }; @@ -35,38 +36,38 @@ export const TenantManagement = ({ section }: { section: any }) => { // Users tab functions const onFetchUsers = useCallback(async () => { - const response = await getUsers(selectedTenantId); + const response = await getUsers(); if (response.status === "ERROR") { throw new Error(response.message); } return { users: response.users }; - }, [getUsers, selectedTenantId]); + }, [getUsers]); // Invitations tab functions const onFetchInvitations = useCallback( - async (tenantId?: string) => { + async () => { const response = await getInvitations(); if (response.status === "ERROR") { throw new Error(response.message); } return { invitations: response.invitees }; }, - [getInvitations, selectedTenantId], + [getInvitations], ); const onRemoveInvite = useCallback( - async (email: string, tenantId?: string) => { - const response = await removeInvitation(email, tenantId || selectedTenantId); + async (email: string) => { + const response = await removeInvitation(email); if (response.status === "ERROR") { throw new Error(response.message); } }, - [removeInvitation, selectedTenantId], + [removeInvitation], ); const onCreateInvite = useCallback( - async (email: string, tenantId: string) => { - const response = await addInvitation(email, tenantId); + async (email: string) => { + const response = await addInvitation(email); if (response.status === "ERROR") { throw new Error(response.message); } @@ -113,55 +114,36 @@ export const TenantManagement = ({ section }: { section: any }) => {
{/* Tab Navigation */} -
- - - + + + + + + +
- -
- - -
- - {/* Tab Content */} -
- {activeTab === "users" && selectedTenantId && ( - - )} - - {activeTab === "invitations" && selectedTenantId && ( - - )} -
); }; diff --git a/packages/tenants-react/src/plugin.tsx b/packages/tenants-react/src/plugin.tsx index cd156d3..aa56020 100644 --- a/packages/tenants-react/src/plugin.tsx +++ b/packages/tenants-react/src/plugin.tsx @@ -1,6 +1,6 @@ import { createPluginInitFunction } from "@shared/js"; import { buildContext, getQuerier } from "@shared/react"; -import { SuperTokensPlugin, SuperTokensPublicConfig, SuperTokensPublicPlugin } from "supertokens-auth-react"; +import { getTranslationFunction, SuperTokensPlugin, SuperTokensPublicConfig, SuperTokensPublicPlugin } from "supertokens-auth-react"; import { BooleanClaim } from "supertokens-auth-react/recipe/session"; import { getApi } from "./api"; @@ -9,7 +9,8 @@ import { API_PATH, PLUGIN_ID } from "./constants"; // import { InvitationAcceptWrapper } from "./invitation-accept-wrapper"; import { enableDebugLogs } from "./logger"; // import { SelectTenantPage } from "./select-tenant-page"; -import { SuperTokensPluginTenantsPluginConfig, SuperTokensPluginTenantsPluginNormalisedConfig } from "./types"; +import { defaultTranslationsTenants } from "./translations"; +import { SuperTokensPluginTenantsPluginConfig, SuperTokensPluginTenantsPluginNormalisedConfig, TranslationKeys } from "./types"; const { usePluginContext, setContext } = buildContext<{ plugins: SuperTokensPublicPlugin[]; @@ -18,7 +19,7 @@ const { usePluginContext, setContext } = buildContext<{ pluginConfig: SuperTokensPluginTenantsPluginNormalisedConfig; querier: ReturnType; api: ReturnType; - t: (key: any) => string; // TODO: Update with correct translations type + t: (key: TranslationKeys) => string; }>(); export { usePluginContext }; @@ -75,6 +76,8 @@ export const init = createPluginInitFunction< }; }; + let translations: ReturnType>; + return { id: PLUGIN_ID, init: (config, plugins, sdkVersion) => { @@ -118,6 +121,7 @@ export const init = createPluginInitFunction< const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); const api = getApi(querier); + translations = getTranslationFunction(defaultTranslationsTenants); setContext({ plugins, @@ -126,7 +130,7 @@ export const init = createPluginInitFunction< pluginConfig, querier, api, - t: (t: undefined) => "", // TODO: Update this + t: translations, }); }, routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts new file mode 100644 index 0000000..8db5d0d --- /dev/null +++ b/packages/tenants-react/src/translations.ts @@ -0,0 +1,7 @@ +export const defaultTranslationsTenants = { + en: { + PL_TB_USERS_TAB_LABEL: "Users", + PL_TB_INVITATIONS_TAB_LABEL: "Invitations", + PL_TB_REQUESTS_TAB_LABEL: "Requests", + }, +} as const; diff --git a/packages/tenants-react/src/types.ts b/packages/tenants-react/src/types.ts index d300463..5d41487 100644 --- a/packages/tenants-react/src/types.ts +++ b/packages/tenants-react/src/types.ts @@ -1,3 +1,6 @@ +import { defaultTranslationsTenants } from "./translations"; + + export type SuperTokensPluginTenantsPluginConfig = { requireTenantCreation?: boolean; }; @@ -5,3 +8,5 @@ export type SuperTokensPluginTenantsPluginConfig = { export type SuperTokensPluginTenantsPluginNormalisedConfig = { requireTenantCreation?: boolean; }; + +export type TranslationKeys = keyof (typeof defaultTranslationsTenants)["en"]; From ba43413f8f1d677aa16bba8a2aae7a7af9120151 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 5 Sep 2025 15:54:59 +0530 Subject: [PATCH 05/25] feat: add support for tenants tab/table structure and use in users tab --- .../tenants-react/src/components/index.ts | 1 - .../src/components/tab/TenantTab.tsx | 15 ++++++++ .../src/components/table/TenantTable.tsx | 25 +++++++++++++ .../TenantUsers.tsx} | 29 ++++++++------- .../{details => users}/details.module.scss | 0 .../tenant-management/index.ts | 0 .../tenant-management/styles.module.scss | 0 .../tenant-management/tenant-management.tsx | 36 ++++++++----------- packages/tenants-react/src/plugin.tsx | 2 +- .../src/tenant-details-wrapper.tsx | 8 ++--- 10 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 packages/tenants-react/src/components/tab/TenantTab.tsx create mode 100644 packages/tenants-react/src/components/table/TenantTable.tsx rename packages/tenants-react/src/components/{details/details-wrapper.tsx => users/TenantUsers.tsx} (60%) rename packages/tenants-react/src/components/{details => users}/details.module.scss (100%) rename packages/tenants-react/src/{components => pages}/tenant-management/index.ts (100%) rename packages/tenants-react/src/{components => pages}/tenant-management/styles.module.scss (100%) rename packages/tenants-react/src/{components => pages}/tenant-management/tenant-management.tsx (85%) diff --git a/packages/tenants-react/src/components/index.ts b/packages/tenants-react/src/components/index.ts index ee20c9e..16eeb02 100644 --- a/packages/tenants-react/src/components/index.ts +++ b/packages/tenants-react/src/components/index.ts @@ -1,3 +1,2 @@ export * from './tenant-card'; export * from './page-wrapper'; -export * from './tenant-management'; diff --git a/packages/tenants-react/src/components/tab/TenantTab.tsx b/packages/tenants-react/src/components/tab/TenantTab.tsx new file mode 100644 index 0000000..4cc0702 --- /dev/null +++ b/packages/tenants-react/src/components/tab/TenantTab.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +type TenantTabProps = { + description: string; + children: React.ReactNode; +} + +export const TenantTab: React.FC = ({ description, children }) => { + return ( +
+
{description}
+
{children}
+
+ ); +}; diff --git a/packages/tenants-react/src/components/table/TenantTable.tsx b/packages/tenants-react/src/components/table/TenantTable.tsx new file mode 100644 index 0000000..2698d31 --- /dev/null +++ b/packages/tenants-react/src/components/table/TenantTable.tsx @@ -0,0 +1,25 @@ +type TableProps = { + columns: { + emailComponent: React.ReactNode; + extraComponent?: React.ReactNode; + }[]; +}; + +export const TenantTable: React.FC = ({ columns }) => { + return ( +
+
+
Email
+
Role
+
+
+ {columns.map((column) => { +
+
{column.emailComponent}
+ {column.extraComponent &&
{column.extraComponent}
} +
; + })} +
+
+ ); +}; diff --git a/packages/tenants-react/src/components/details/details-wrapper.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx similarity index 60% rename from packages/tenants-react/src/components/details/details-wrapper.tsx rename to packages/tenants-react/src/components/users/TenantUsers.tsx index 1e5f3e7..e742714 100644 --- a/packages/tenants-react/src/components/details/details-wrapper.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -2,13 +2,19 @@ import classNames from "classnames/bind"; import { useCallback, useEffect, useState } from "react"; import { User } from "supertokens-web-js/types"; +import { TenantTable } from "../table/TenantTable"; + import style from "./details.module.scss"; // import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; const cx = classNames.bind(style); -export const DetailsWrapper = ({ section, onFetch }: { section: any; onFetch: () => Promise<{ users: User[] }> }) => { +type TenantUsersProps = { + onFetch: () => Promise<{ users: User[] }> +}; + +export const TenantUsers: React.FC = ({ onFetch }) => { const [users, setUsers] = useState([]); const loadDetails = useCallback(async () => { @@ -22,20 +28,19 @@ export const DetailsWrapper = ({ section, onFetch }: { section: any; onFetch: () return (
-
-

{section.label}

-

{section.description}

-
-
{users.length > 0 ? (
- {users.map((user) => ( -
-
{user.emails[0]?.charAt(0).toUpperCase() || "U"}
-
{user.emails[0]}
-
- ))} + ({ + emailComponent: ( +
+
{user.emails[0]?.charAt(0).toUpperCase() || "U"}
+
{user.emails[0]}
+
+ ) + }))} + />
) : (
diff --git a/packages/tenants-react/src/components/details/details.module.scss b/packages/tenants-react/src/components/users/details.module.scss similarity index 100% rename from packages/tenants-react/src/components/details/details.module.scss rename to packages/tenants-react/src/components/users/details.module.scss diff --git a/packages/tenants-react/src/components/tenant-management/index.ts b/packages/tenants-react/src/pages/tenant-management/index.ts similarity index 100% rename from packages/tenants-react/src/components/tenant-management/index.ts rename to packages/tenants-react/src/pages/tenant-management/index.ts diff --git a/packages/tenants-react/src/components/tenant-management/styles.module.scss b/packages/tenants-react/src/pages/tenant-management/styles.module.scss similarity index 100% rename from packages/tenants-react/src/components/tenant-management/styles.module.scss rename to packages/tenants-react/src/pages/tenant-management/styles.module.scss diff --git a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx similarity index 85% rename from packages/tenants-react/src/components/tenant-management/tenant-management.tsx rename to packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index 8f6d1ad..2f58d78 100644 --- a/packages/tenants-react/src/components/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -4,9 +4,10 @@ import { SelectInput, TabGroup, Tab } from "@shared/ui"; import classNames from "classnames/bind"; import { useState, useEffect, useCallback } from "react"; +import { InvitationsWrapper } from "../../components/invitations/invitations"; +import { TenantTab } from "../../components/tab/TenantTab"; +import { TenantUsers } from "../../components/users/TenantUsers"; import { usePluginContext } from "../../plugin"; -import { DetailsWrapper } from "../details/details-wrapper"; -import { InvitationsWrapper } from "../invitations/invitations"; import style from "./styles.module.scss"; @@ -44,16 +45,13 @@ export const TenantManagement = ({ section }: { section: any }) => { }, [getUsers]); // Invitations tab functions - const onFetchInvitations = useCallback( - async () => { - const response = await getInvitations(); - if (response.status === "ERROR") { - throw new Error(response.message); - } - return { invitations: response.invitees }; - }, - [getInvitations], - ); + const onFetchInvitations = useCallback(async () => { + const response = await getInvitations(); + if (response.status === "ERROR") { + throw new Error(response.message); + } + return { invitations: response.invitees }; + }, [getInvitations]); const onRemoveInvite = useCallback( async (email: string) => { @@ -117,15 +115,11 @@ export const TenantManagement = ({ section }: { section: any }) => {
- + + + { const { api } = usePluginContext(); @@ -11,7 +11,7 @@ export const TenantDetailsWrapper = ({ section }: { section: any }) => { // Use the `tid` from the users access token payload. const response = await api.getUsers(); - if (response.status === "ERROR") { + if (response.status === 'ERROR') { throw new Error(response.message); } return { users: response.users }; From b4eee3d2a768c18fccce9f596e50ee35850862ca Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 8 Sep 2025 13:43:36 +0530 Subject: [PATCH 06/25] fix: issues with tab rendering for tenant management --- .../tenant-management/tenant-management.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index 2f58d78..fc36e90 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -1,5 +1,5 @@ import { TenantDetails } from "@shared/tenants"; -import { SelectInput, TabGroup, Tab } from "@shared/ui"; +import { SelectInput, TabGroup, Tab, TabPanel } from "@shared/ui"; // import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; import classNames from "classnames/bind"; import { useState, useEffect, useCallback } from "react"; @@ -114,14 +114,17 @@ export const TenantManagement = ({ section }: { section: any }) => { {/* Tab Navigation */}
- + {t("PL_TB_USERS_TAB_LABEL")} + {t("PL_TB_INVITATIONS_TAB_LABEL")} + {t("PL_TB_REQUESTS_TAB_LABEL")} + + {/* Tab Content */} + - + - - + + { onCreate={onCreateInvite} selectedTenantId={selectedTenantId} /> - - +
From bc245256cfc37fb512c7e3eef7f16c9c0b8b54e3 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 8 Sep 2025 16:21:41 +0530 Subject: [PATCH 07/25] fix: styling issues with wa tab components --- package-lock.json | 11 +++++++ packages/tenants-react/package.json | 11 +++---- .../src/components/tab/TenantTab.tsx | 10 +++---- packages/tenants-react/src/plugin.tsx | 30 ++++++++++++------- packages/tenants-react/src/styles/global.css | 7 +++++ packages/tenants-react/vite.config.ts | 24 ++++++++------- 6 files changed, 62 insertions(+), 31 deletions(-) create mode 100644 packages/tenants-react/src/styles/global.css diff --git a/package-lock.json b/package-lock.json index 2fbfac7..de347b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15360,6 +15360,16 @@ } } }, + "node_modules/vite-plugin-css-injected-by-js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz", + "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": ">2.0.0-0" + } + }, "node_modules/vite-plugin-dts": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", @@ -17778,6 +17788,7 @@ "rollup-plugin-peer-deps-external": "^2.2.4", "typescript": "^5.8.3", "vite": "^6.3.5", + "vite-plugin-css-injected-by-js": "^3.5.2", "vite-plugin-dts": "^4.5.4", "vitest": "^1.3.1" }, diff --git a/packages/tenants-react/package.json b/packages/tenants-react/package.json index a77fe79..ecd9bed 100644 --- a/packages/tenants-react/package.json +++ b/packages/tenants-react/package.json @@ -32,20 +32,21 @@ }, "devDependencies": { "@shared/eslint": "*", + "@shared/js": "*", + "@shared/react": "*", "@shared/tsconfig": "*", "@testing-library/jest-dom": "^6.1.0", "@types/react": "^17.0.20", + "@vitejs/plugin-react": "^4.5.2", "jsdom": "^26.1.0", "prettier": "3.6.2", "pretty-quick": "^4.2.2", + "rollup-plugin-peer-deps-external": "^2.2.4", "typescript": "^5.8.3", - "vitest": "^1.3.1", - "@shared/js": "*", - "@shared/react": "*", "vite": "^6.3.5", - "@vitejs/plugin-react": "^4.5.2", + "vite-plugin-css-injected-by-js": "^3.5.2", "vite-plugin-dts": "^4.5.4", - "rollup-plugin-peer-deps-external": "^2.2.4" + "vitest": "^1.3.1" }, "browser": { "fs": false diff --git a/packages/tenants-react/src/components/tab/TenantTab.tsx b/packages/tenants-react/src/components/tab/TenantTab.tsx index 4cc0702..877bdea 100644 --- a/packages/tenants-react/src/components/tab/TenantTab.tsx +++ b/packages/tenants-react/src/components/tab/TenantTab.tsx @@ -1,15 +1,15 @@ import React from "react"; type TenantTabProps = { - description: string; - children: React.ReactNode; -} + description: string; + children: React.ReactNode; +}; export const TenantTab: React.FC = ({ description, children }) => { return (
-
{description}
-
{children}
+
{description}
+
{children}
); }; diff --git a/packages/tenants-react/src/plugin.tsx b/packages/tenants-react/src/plugin.tsx index bd2f5b5..7b7a528 100644 --- a/packages/tenants-react/src/plugin.tsx +++ b/packages/tenants-react/src/plugin.tsx @@ -1,16 +1,26 @@ import { createPluginInitFunction } from "@shared/js"; import { buildContext, getQuerier } from "@shared/react"; -import { getTranslationFunction, SuperTokensPlugin, SuperTokensPublicConfig, SuperTokensPublicPlugin } from "supertokens-auth-react"; +import { + getTranslationFunction, + SuperTokensPlugin, + SuperTokensPublicConfig, + SuperTokensPublicPlugin, +} from "supertokens-auth-react"; import { BooleanClaim } from "supertokens-auth-react/recipe/session"; import { getApi } from "./api"; import { API_PATH, PLUGIN_ID } from "./constants"; +import "./styles/global.css"; // import { InvitationAcceptWrapper } from "./invitation-accept-wrapper"; import { enableDebugLogs } from "./logger"; import { TenantManagement } from "./pages/tenant-management/tenant-management"; // import { SelectTenantPage } from "./select-tenant-page"; import { defaultTranslationsTenants } from "./translations"; -import { SuperTokensPluginTenantsPluginConfig, SuperTokensPluginTenantsPluginNormalisedConfig, TranslationKeys } from "./types"; +import { + SuperTokensPluginTenantsPluginConfig, + SuperTokensPluginTenantsPluginNormalisedConfig, + TranslationKeys, +} from "./types"; const { usePluginContext, setContext } = buildContext<{ plugins: SuperTokensPublicPlugin[]; @@ -137,14 +147,14 @@ export const init = createPluginInitFunction< return { status: "OK", routeHandlers: [ - { - path: "/user/tenants/select", - handler: () => SelectTenantPage.call(null), - }, - { - path: "/user/invite/accept", - handler: () => InvitationAcceptWrapper.call(null), - }, + // { + // path: "/user/tenants/select", + // handler: () => SelectTenantPage.call(null), + // }, + // { + // path: "/user/invite/accept", + // handler: () => InvitationAcceptWrapper.call(null), + // }, ], }; }, diff --git a/packages/tenants-react/src/styles/global.css b/packages/tenants-react/src/styles/global.css new file mode 100644 index 0000000..152e195 --- /dev/null +++ b/packages/tenants-react/src/styles/global.css @@ -0,0 +1,7 @@ +/* Tab related styling */ + +.st-tab-group::part(base) { + border: 1px solid #dddde3; + border-radius: 12px; + background-color: #f9f9f8; +} diff --git a/packages/tenants-react/vite.config.ts b/packages/tenants-react/vite.config.ts index 7d43469..03cff1c 100644 --- a/packages/tenants-react/vite.config.ts +++ b/packages/tenants-react/vite.config.ts @@ -1,21 +1,23 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import dts from 'vite-plugin-dts'; -import peerDepsExternal from 'rollup-plugin-peer-deps-external'; -import * as path from 'path'; -import packageJson from './package.json'; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import dts from "vite-plugin-dts"; +import peerDepsExternal from "rollup-plugin-peer-deps-external"; +import * as path from "path"; +import packageJson from "./package.json"; +import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; export default defineConfig(() => { return { root: __dirname, plugins: [ react(), - dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.json') }), + dts({ entryRoot: "src", tsconfigPath: path.join(__dirname, "tsconfig.json") }), peerDepsExternal(), + cssInjectedByJsPlugin(), ], build: { - outDir: 'dist', + outDir: "dist", sourcemap: false, emptyOutDir: true, commonjsOptions: { @@ -23,12 +25,12 @@ export default defineConfig(() => { }, lib: { // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - fileName: 'index', + entry: "src/index.ts", + fileName: "index", name: packageJson.name, // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es' as const, 'cjs' as const], + formats: ["es" as const, "cjs" as const], }, rollupOptions: { cache: false, From b6e0a8bd062ba200c16b4b0ace61b5cd68eec786 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 9 Sep 2025 13:59:09 +0530 Subject: [PATCH 08/25] feat: add UI changes for tenant table and users management --- packages/tenants-nodejs/src/constants.ts | 2 +- .../src/recipeImplementation.ts | 3 +- .../src/components/tab/TenantTab.tsx | 9 +- .../src/components/tab/tenant-tab.module.scss | 18 +++ .../src/components/table/TenantTable.tsx | 42 ++++-- .../src/components/table/table.module.scss | 44 +++++++ .../src/components/users/TenantUsers.tsx | 9 +- .../src/components/users/details.module.scss | 123 ++++++------------ packages/tenants-react/src/styles/global.css | 25 +++- packages/tenants-react/src/translations.ts | 1 + shared/nodejs/src/withRequestHandler.ts | 3 +- shared/tenants/src/types.ts | 5 +- 12 files changed, 176 insertions(+), 108 deletions(-) create mode 100644 packages/tenants-react/src/components/tab/tenant-tab.module.scss create mode 100644 packages/tenants-react/src/components/table/table.module.scss diff --git a/packages/tenants-nodejs/src/constants.ts b/packages/tenants-nodejs/src/constants.ts index 405960a..cda004a 100644 --- a/packages/tenants-nodejs/src/constants.ts +++ b/packages/tenants-nodejs/src/constants.ts @@ -1,4 +1,4 @@ -export const PLUGIN_ID = "supertokens-plugin-tenant-discovery"; +export const PLUGIN_ID = "supertokens-plugin-tenants"; export const PLUGIN_VERSION = "0.0.1"; export const PLUGIN_SDK_VERSION = ["23.0.0", "23.0.1", ">=23.0.1"]; diff --git a/packages/tenants-nodejs/src/recipeImplementation.ts b/packages/tenants-nodejs/src/recipeImplementation.ts index 8d7bb92..15c4ea3 100644 --- a/packages/tenants-nodejs/src/recipeImplementation.ts +++ b/packages/tenants-nodejs/src/recipeImplementation.ts @@ -39,7 +39,8 @@ export const getOverrideableTenantFunctionImplementation = ( // Return the tenants that the user is not a member of return { - ...tenantDetails, + status: "OK", + tenants: tenantDetails.tenants.map((tenant) => ({ tenantId: tenant.tenantId, displayName: tenant.tenantId })), joinedTenantIds: userDetails.tenantIds, }; }, diff --git a/packages/tenants-react/src/components/tab/TenantTab.tsx b/packages/tenants-react/src/components/tab/TenantTab.tsx index 877bdea..6915af7 100644 --- a/packages/tenants-react/src/components/tab/TenantTab.tsx +++ b/packages/tenants-react/src/components/tab/TenantTab.tsx @@ -1,5 +1,10 @@ +import classNames from "classnames/bind"; import React from "react"; +import style from "./tenant-tab.module.scss"; + +const cx = classNames.bind(style); + type TenantTabProps = { description: string; children: React.ReactNode; @@ -8,8 +13,8 @@ type TenantTabProps = { export const TenantTab: React.FC = ({ description, children }) => { return (
-
{description}
-
{children}
+
{description}
+
{children}
); }; diff --git a/packages/tenants-react/src/components/tab/tenant-tab.module.scss b/packages/tenants-react/src/components/tab/tenant-tab.module.scss new file mode 100644 index 0000000..b4bfa5c --- /dev/null +++ b/packages/tenants-react/src/components/tab/tenant-tab.module.scss @@ -0,0 +1,18 @@ +.tabDescription { + background-color: #f9f9f8; + border-bottom: 1px solid #dddde3; + padding: 14px; + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: #60646c; +} + +.tabChildrenWrapper { + padding: 20px; + background-color: #ffffff; + border-bottom-left-radius: 16px; + border-bottom-right-radius: 16px; +} diff --git a/packages/tenants-react/src/components/table/TenantTable.tsx b/packages/tenants-react/src/components/table/TenantTable.tsx index 2698d31..787455d 100644 --- a/packages/tenants-react/src/components/table/TenantTable.tsx +++ b/packages/tenants-react/src/components/table/TenantTable.tsx @@ -1,24 +1,42 @@ +import classNames from "classnames/bind"; + +import style from "./table.module.scss"; + +const cx = classNames.bind(style); + type TableProps = { + emailComponentTitle?: string; + extraComponentTitle?: string; columns: { emailComponent: React.ReactNode; extraComponent?: React.ReactNode; }[]; }; -export const TenantTable: React.FC = ({ columns }) => { +export const TenantTable: React.FC = ({ + emailComponentTitle = "Email", + extraComponentTitle = "Role", + columns, +}) => { return ( -
-
-
Email
-
Role
+
+
+
{emailComponentTitle}
+
{extraComponentTitle}
-
- {columns.map((column) => { -
-
{column.emailComponent}
- {column.extraComponent &&
{column.extraComponent}
} -
; - })} +
+ {columns.map((column) => ( +
+
+ {column.emailComponent} +
+ {column.extraComponent && ( +
+ {column.extraComponent} +
+ )} +
+ ))}
); diff --git a/packages/tenants-react/src/components/table/table.module.scss b/packages/tenants-react/src/components/table/table.module.scss new file mode 100644 index 0000000..aed3d1d --- /dev/null +++ b/packages/tenants-react/src/components/table/table.module.scss @@ -0,0 +1,44 @@ +.tableContainer { + border: 1px solid #b9bbc6; + border-radius: 8px; + + .tableHead { + padding: 12px; + display: flex; + align-items: center; + background-color: #f9f9f8; + border-radius: 8px; + + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: #60646c; + + .emailHeader { + width: 65%; + } + + .extraHeader { + width: 35%; + } + } + + .tableColumns { + .tableColumn { + border-top: 1px solid #b9bbc6; + padding: 16px 8px; + display: flex; + align-items: center; + + .emailComponentWrapper { + width: 65%; + } + + .extraComponentWrapper { + width: 35%; + } + } + } +} diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index e742714..b0d391e 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -2,6 +2,7 @@ import classNames from "classnames/bind"; import { useCallback, useEffect, useState } from "react"; import { User } from "supertokens-web-js/types"; +import { usePluginContext } from "../../plugin"; import { TenantTable } from "../table/TenantTable"; import style from "./details.module.scss"; @@ -11,10 +12,11 @@ import style from "./details.module.scss"; const cx = classNames.bind(style); type TenantUsersProps = { - onFetch: () => Promise<{ users: User[] }> + onFetch: () => Promise<{ users: User[] }>; }; export const TenantUsers: React.FC = ({ onFetch }) => { + const { t } = usePluginContext(); const [users, setUsers] = useState([]); const loadDetails = useCallback(async () => { @@ -38,13 +40,14 @@ export const TenantUsers: React.FC = ({ onFetch }) => {
{user.emails[0]?.charAt(0).toUpperCase() || "U"}
{user.emails[0]}
- ) + ), }))} />
) : (
-

No users found

+
+
{t("PL_TB_NO_USERS_FOUND_TEXT")}
)}
diff --git a/packages/tenants-react/src/components/users/details.module.scss b/packages/tenants-react/src/components/users/details.module.scss index 12159c4..a8993d2 100644 --- a/packages/tenants-react/src/components/users/details.module.scss +++ b/packages/tenants-react/src/components/users/details.module.scss @@ -1,96 +1,49 @@ -:global(.pluginProfile) { - .tenantDetailsSection { - display: flex; - flex-direction: column; - max-width: 800px; - margin: 0 auto; - padding: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, - Helvetica Neue, sans-serif; - color: #333; - width: 100%; - } - - .tenantDetailsHeader { - position: relative; - padding-bottom: 24px; - margin-bottom: 24px; - border-bottom: 1px solid #eee; - - h3 { - margin-bottom: 12px; - } - } - - .tenantDetailsContent { - display: flex; - flex-direction: column; - gap: 12px; - } - - .tenantDetailsUsers { - display: flex; - flex-direction: column; - gap: 8px; - } - - .userRow { - display: flex; - align-items: center; - gap: 12px; - padding: 8px 12px; - border: 1px solid #eee; - border-radius: 6px; - background-color: #fafafa; - } - - .userAvatar { - width: 32px; - height: 32px; - border-radius: 50%; - background-color: #007bff; - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; +.tenantDetailsNoUsers { + background-color: #0144ff0f; + padding: 12px; + display: flex; + align-items: center; + gap: 8px; + border-radius: 6px; + + .text { + font-weight: 400; + font-style: Regular; font-size: 14px; - flex-shrink: 0; + line-height: 20px; + letter-spacing: 0px; + color: #1c2024; } +} - .userEmail { - flex: 1; - font-size: 14px; - color: #333; - } +.userRow { + display: flex; + align-items: center; + gap: 18px; - .removeButton { + .userAvatar { + background-color: #3e63dd; + color: #fff; + text-transform: uppercase; + border-radius: 4px; width: 24px; height: 24px; - border: none; - background-color: #dc3545; - color: white; - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - font-weight: bold; - flex-shrink: 0; - transition: background-color 0.2s; + padding: 4px 8px; - &:hover { - background-color: #c82333; - } + font-weight: 500; + font-style: Medium; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + text-align: center; } - .tenantDetailsNoUsers { - display: flex; - flex-direction: column; - gap: 12px; - padding: 12px; - border: 1px solid #eee; - border-radius: 4px; + .userEmail { + font-weight: 400; + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: #1c2024; } } diff --git a/packages/tenants-react/src/styles/global.css b/packages/tenants-react/src/styles/global.css index 152e195..30651a1 100644 --- a/packages/tenants-react/src/styles/global.css +++ b/packages/tenants-react/src/styles/global.css @@ -3,5 +3,28 @@ .st-tab-group::part(base) { border: 1px solid #dddde3; border-radius: 12px; - background-color: #f9f9f8; + background-color: #ffffff; + + --indicator-color: #3e63dd; +} + +.st-tab-group::part(tabs) { + gap: 30px; +} + +.st-tab::part(base) { + padding: 12px 16px; + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; +} + +.st-tab:not([active])::part(base) { + color: #1c2024; +} + +.st-tab-panel::part(base) { + padding: 0; } diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts index 8db5d0d..c56c74c 100644 --- a/packages/tenants-react/src/translations.ts +++ b/packages/tenants-react/src/translations.ts @@ -3,5 +3,6 @@ export const defaultTranslationsTenants = { PL_TB_USERS_TAB_LABEL: "Users", PL_TB_INVITATIONS_TAB_LABEL: "Invitations", PL_TB_REQUESTS_TAB_LABEL: "Requests", + PL_TB_NO_USERS_FOUND_TEXT: "No users have been found linked to this tenant.", }, } as const; diff --git a/shared/nodejs/src/withRequestHandler.ts b/shared/nodejs/src/withRequestHandler.ts index ec50ed8..f84cb4d 100644 --- a/shared/nodejs/src/withRequestHandler.ts +++ b/shared/nodejs/src/withRequestHandler.ts @@ -6,7 +6,8 @@ export const withRequestHandler = ( ) => Promise< | ({ status: "OK" } & JSONObject) | ({ status: string; code?: number } & JSONObject) - > + | ({ status: string; code?: number } & Object) + >, ): PluginRouteHandler["handler"] => { return async (req, res, session, userContext) => { const result = await fn(req, res, session, userContext); diff --git a/shared/tenants/src/types.ts b/shared/tenants/src/types.ts index 69adb83..c9d2ef5 100644 --- a/shared/tenants/src/types.ts +++ b/shared/tenants/src/types.ts @@ -1,4 +1,4 @@ -import { TenantConfig } from 'supertokens-node/lib/build/recipe/multitenancy/types'; +import { TenantConfig } from "supertokens-node/lib/build/recipe/multitenancy/types"; export type TenantJoinData = { tenantId: string; @@ -10,7 +10,8 @@ export type TenantCreateData = { export type TenantDetails = { tenantId: string; -} & TenantConfig; + displayName: string; +}; export type TenantList = { tenants: TenantDetails[]; From 2db06a825403416207ea94762548c84c7d68a3bb Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Wed, 10 Sep 2025 12:36:14 +0530 Subject: [PATCH 09/25] feat: add support for user roles in users in tenants tab --- .../src/components/users/TenantUsers.tsx | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index b0d391e..57fe00c 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -1,7 +1,11 @@ import classNames from "classnames/bind"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import Session from "supertokens-auth-react/recipe/session"; import { User } from "supertokens-web-js/types"; +import { ROLES } from "../../../../../shared/tenants/src/roles"; +import { SelectInput } from "../../../../../shared/ui/src/components"; +import { logDebugMessage } from "../../logger"; import { usePluginContext } from "../../plugin"; import { TenantTable } from "../table/TenantTable"; @@ -11,23 +15,77 @@ import style from "./details.module.scss"; const cx = classNames.bind(style); +type UserWithRole = { roles: string[] } & User; + type TenantUsersProps = { - onFetch: () => Promise<{ users: User[] }>; + onFetch: () => Promise<{ users: UserWithRole[] }>; }; export const TenantUsers: React.FC = ({ onFetch }) => { const { t } = usePluginContext(); - const [users, setUsers] = useState([]); + const [users, setUsers] = useState([]); + const [userId, setUserId] = useState(null); + const [currentUserDetails, setCurrentUserDetails] = useState(null); + const isCurrentUserAdmin = useMemo(() => currentUserDetails?.roles.includes(ROLES.ADMIN), [currentUserDetails]); const loadDetails = useCallback(async () => { const details = await onFetch(); - setUsers(details.users); + // Show the users that have a valid role + setUsers(details.users.filter((user) => user.roles.length !== 0)); + + const userIdFromSession = await Session.getUserId(); + setUserId(userIdFromSession); + + // determine if the current user is admin or not. + const currentUserDetailsWithRole = details.users.find((user) => user.id === userIdFromSession); + setCurrentUserDetails(currentUserDetailsWithRole ?? null); }, [onFetch]); useEffect(() => { loadDetails(); }, [loadDetails]); + const availableRoles = [ + { + label: "Admin", + value: ROLES.ADMIN, + }, + { + label: "Member", + value: ROLES.MEMBER, + }, + ]; + + const getExtraComponent = useCallback( + (user: { roles: string[] } & User) => { + // It's safe to assume that they would have one role + const currentRole = user.roles[0] ?? ROLES.MEMBER; + + const handleRoleChange = (newValue) => { + logDebugMessage(`Changing role for user to: ${newValue}`); + }; + + return ( +
+
+ handleRoleChange(e.target.value)} + options={availableRoles} + disabled={!isCurrentUserAdmin} + /> +
+
+ ); + }, + [isCurrentUserAdmin], + ); + + if (!userId) { + return
Loading....
; + } + return (
@@ -41,6 +99,7 @@ export const TenantUsers: React.FC = ({ onFetch }) => {
{user.emails[0]}
), + extraComponent: getExtraComponent(user), }))} />
From 8a0028d321dc09dfcc75877f81da8c1346c5d5f5 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Wed, 10 Sep 2025 13:11:04 +0530 Subject: [PATCH 10/25] feat: add support for role change in tenants users --- packages/tenants-nodejs/src/plugin.ts | 61 +++++++- packages/tenants-react/src/api.ts | 145 +++++++++--------- .../src/components/users/TenantUsers.tsx | 32 +++- .../tenant-management/tenant-management.tsx | 30 +++- 4 files changed, 180 insertions(+), 88 deletions(-) diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts index a5a2aa5..3d39e97 100644 --- a/packages/tenants-nodejs/src/plugin.ts +++ b/packages/tenants-nodejs/src/plugin.ts @@ -13,7 +13,7 @@ import { SuperTokensPluginTenantPluginConfig, PluginEmailDeliveryInput, SendPluginEmail, - SuperTokensPluginTenantPluginNormalisedConfig + SuperTokensPluginTenantPluginNormalisedConfig, } from "./types"; import { HANDLE_BASE_PATH, METADATA_KEY, PLUGIN_ID, PLUGIN_SDK_VERSION } from "./constants"; import { BooleanClaim } from "supertokens-node/lib/build/recipe/session/claims"; @@ -761,6 +761,51 @@ export const init = createPluginInitFunction< }; }), }, + { + path: `${HANDLE_BASE_PATH}/role/change`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; + }, + }, + handler: withRequestHandler(async (req, res, session) => { + if (!session) { + throw new Error("Session not found"); + } + + // Parse the tenantId from the body + const tenantIdToUse = session.getTenantId(); + const payload: { userId: string; role: string } | undefined = await req.getJSONBody(); + + if (!payload?.userId || !payload?.role) { + return { + status: "ERROR", + message: "User ID and role is required", + }; + } + + // We need to remove any existing role from the tenant for the user + // and assign the new one to them. + + // We need to check that the user doesn't have an existing role, in which + // case we cannot "accept" the request. + const roleDetails = await UserRoles.getRolesForUser(tenantIdToUse, payload.userId); + for (const role of roleDetails.roles) { + UserRoles.removeUserRole(tenantIdToUse, payload.userId, role); + } + + // NOTE: We are assuming that the role passed in the payload + // is a valid one. + await assignRoleToUserInTenant(tenantIdToUse, payload.userId, payload.role); + + return { + status: "OK", + message: "Role changed", + }; + }), + }, ], }; }, @@ -808,12 +853,12 @@ export const init = createPluginInitFunction< ...input.accessTokenPayload, ...(pluginConfig.requireNonPublicTenantAssociation ? await MultipleTenantsPresentClaim.build( - input.userId, - input.recipeUserId, - tenantId, - input.accessTokenPayload, - input.userContext, - ) + input.userId, + input.recipeUserId, + tenantId, + input.accessTokenPayload, + input.userContext, + ) : {}), }; @@ -939,5 +984,5 @@ export const init = createPluginInitFunction< (config) => ({ requireNonPublicTenantAssociation: config.requireNonPublicTenantAssociation ?? false, requireTenantCreationRequestApproval: config.requireTenantCreationRequestApproval ?? true, - }) + }), ); diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts index 8cf0a77..c580404 100644 --- a/packages/tenants-react/src/api.ts +++ b/packages/tenants-react/src/api.ts @@ -15,42 +15,40 @@ export const getApi = (querier: ReturnType) => { return response; }; - const joinTenant = - async (data: TenantJoinData) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( - "/join", - { - ...data, - }, - { withSession: true }, - ); - - // Refresh the session if the status was OK - let wasSessionRefreshed = false; - if (response.status === "OK") { - wasSessionRefreshed = await Session.attemptRefreshingSession(); - } - - return { - ...response, - wasSessionRefreshed, - }; - }; + const joinTenant = async (data: TenantJoinData) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/join", + { + ...data, + }, + { withSession: true }, + ); + + // Refresh the session if the status was OK + let wasSessionRefreshed = false; + if (response.status === "OK") { + wasSessionRefreshed = await Session.attemptRefreshingSession(); + } - const createTenant = - async (data: TenantCreateData) => { - const response = await querier.post< - { status: "OK"; pendingApproval: boolean; requestId: string } | { status: "ERROR"; message: string } - >( - "/create", - { - ...data, - }, - { withSession: true }, - ); - - return response; + return { + ...response, + wasSessionRefreshed, }; + }; + + const createTenant = async (data: TenantCreateData) => { + const response = await querier.post< + { status: "OK"; pendingApproval: boolean; requestId: string } | { status: "ERROR"; message: string } + >( + "/create", + { + ...data, + }, + { withSession: true }, + ); + + return response; + }; const getUsers = async () => { const response = await querier.post<{ status: "OK"; users: User[] } | { status: "ERROR"; message: string }>( @@ -70,49 +68,55 @@ export const getApi = (querier: ReturnType) => { return response; }; - const removeInvitation = - async (email: string) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( - "/invite/remove", - { email }, - { withSession: true }, - ); + const removeInvitation = async (email: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/remove", + { email }, + { withSession: true }, + ); - return response; - }; + return response; + }; - const addInvitation = - async (email: string) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( - "/invite/add", - { email }, - { withSession: true }, - ); + const addInvitation = async (email: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/add", + { email }, + { withSession: true }, + ); - return response; - }; + return response; + }; - const acceptInvitation = - async (code: string, tenantId: string) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( - "/invite/accept", - { code, tenantId }, - { withSession: true }, - ); + const acceptInvitation = async (code: string, tenantId: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/invite/accept", + { code, tenantId }, + { withSession: true }, + ); - return response; - }; + return response; + }; - const switchTenant = - async (tenantId: string) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( - "/switch-tenant", - { tenantId }, - { withSession: true }, - ); + const switchTenant = async (tenantId: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/switch-tenant", + { tenantId }, + { withSession: true }, + ); - return response; - }; + return response; + }; + + const changeRole = async (userId: string, role: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/role/change", + { userId, role }, + { withSession: true }, + ); + + return response; + }; return { fetchTenants, @@ -124,5 +128,6 @@ export const getApi = (querier: ReturnType) => { addInvitation, acceptInvitation, switchTenant, + changeRole, }; }; diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index 57fe00c..585abcc 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -5,6 +5,7 @@ import { User } from "supertokens-web-js/types"; import { ROLES } from "../../../../../shared/tenants/src/roles"; import { SelectInput } from "../../../../../shared/ui/src/components"; +import { usePrettyAction } from "../../../../../shared/ui/src/hooks"; import { logDebugMessage } from "../../logger"; import { usePluginContext } from "../../plugin"; import { TenantTable } from "../table/TenantTable"; @@ -19,14 +20,16 @@ type UserWithRole = { roles: string[] } & User; type TenantUsersProps = { onFetch: () => Promise<{ users: UserWithRole[] }>; + onRoleChange: (userId: string, role: string) => Promise; }; -export const TenantUsers: React.FC = ({ onFetch }) => { +export const TenantUsers: React.FC = ({ onFetch, onRoleChange }) => { const { t } = usePluginContext(); const [users, setUsers] = useState([]); const [userId, setUserId] = useState(null); const [currentUserDetails, setCurrentUserDetails] = useState(null); const isCurrentUserAdmin = useMemo(() => currentUserDetails?.roles.includes(ROLES.ADMIN), [currentUserDetails]); + const [isRoleChanging, setRoleChanging] = useState(false); const loadDetails = useCallback(async () => { const details = await onFetch(); @@ -56,13 +59,30 @@ export const TenantUsers: React.FC = ({ onFetch }) => { }, ]; + const handleRoleChange = usePrettyAction( + async (userId: string, newValue: string) => { + logDebugMessage(`Changing role for user to: ${newValue}`); + setRoleChanging(true); + + const wasChanged = await onRoleChange(userId, newValue); + setRoleChanging(false); + if (!wasChanged) { + throw new Error("Failed to change role"); + } + }, + [onRoleChange], + { errorMessage: "Failed to change role" }, + ); + const getExtraComponent = useCallback( (user: { roles: string[] } & User) => { // It's safe to assume that they would have one role const currentRole = user.roles[0] ?? ROLES.MEMBER; - const handleRoleChange = (newValue) => { - logDebugMessage(`Changing role for user to: ${newValue}`); + const getRoleChangeWrapper = (userId: string) => { + return async (newValue: string) => { + return await handleRoleChange(userId, newValue); + }; }; return ( @@ -71,15 +91,15 @@ export const TenantUsers: React.FC = ({ onFetch }) => { handleRoleChange(e.target.value)} + onChange={(e: any) => getRoleChangeWrapper(user.id)(e)} options={availableRoles} - disabled={!isCurrentUserAdmin} + disabled={!isCurrentUserAdmin || isRoleChanging} />
); }, - [isCurrentUserAdmin], + [isCurrentUserAdmin, isRoleChanging, handleRoleChange], ); if (!userId) { diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index fc36e90..c708100 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -1,5 +1,5 @@ import { TenantDetails } from "@shared/tenants"; -import { SelectInput, TabGroup, Tab, TabPanel } from "@shared/ui"; +import { SelectInput, TabGroup, Tab, TabPanel, ToastProvider, ToastContainer } from "@shared/ui"; // import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; import classNames from "classnames/bind"; import { useState, useEffect, useCallback } from "react"; @@ -7,15 +7,16 @@ import { useState, useEffect, useCallback } from "react"; import { InvitationsWrapper } from "../../components/invitations/invitations"; import { TenantTab } from "../../components/tab/TenantTab"; import { TenantUsers } from "../../components/users/TenantUsers"; +import { logDebugMessage } from "../../logger"; import { usePluginContext } from "../../plugin"; import style from "./styles.module.scss"; const cx = classNames.bind(style); -export const TenantManagement = ({ section }: { section: any }) => { +export const TenantManagementWithoutToastWrapper = ({ section }: { section: any }) => { const { api, t } = usePluginContext(); - const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant } = api; + const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant, changeRole } = api; const [tenants, setTenants] = useState([]); const [selectedTenantId, setSelectedTenantId] = useState("public"); @@ -44,6 +45,18 @@ export const TenantManagement = ({ section }: { section: any }) => { return { users: response.users }; }, [getUsers]); + const onRoleChange = useCallback( + async (userId: string, role: string) => { + const response = await changeRole(userId, role); + if (response.status === "ERROR") { + logDebugMessage(`Got error while changing role: ${response.message}`); + return false; + } + return true; + }, + [changeRole], + ); + // Invitations tab functions const onFetchInvitations = useCallback(async () => { const response = await getInvitations(); @@ -121,7 +134,7 @@ export const TenantManagement = ({ section }: { section: any }) => { {/* Tab Content */} - + @@ -143,3 +156,12 @@ export const TenantManagement = ({ section }: { section: any }) => {
); }; + +export const TenantManagement = (props) => { + return ( + + + + + ); +}; From 6b5382e423bfef3316ea77ce4bdaa8044fc12c4b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 11 Sep 2025 12:43:00 +0530 Subject: [PATCH 11/25] feat: add support for adding invites with the new UI --- packages/tenants-react/src/api.ts | 2 +- .../components/invitations/AddInvitation.tsx | 49 ++++++ .../components/invitations/InvitedUsers.tsx | 32 ++++ .../invitations/add-invitation.module.scss | 5 + .../components/invitations/invitations.tsx | 158 +++++------------- .../components/invitations/invitationsOld.tsx | 144 ++++++++++++++++ .../src/components/tab/TenantTab.tsx | 8 +- .../src/components/tab/tenant-tab.module.scss | 18 +- .../src/components/table/TenantTable.tsx | 6 +- .../src/components/users/NoUsers.tsx | 18 ++ .../src/components/users/TenantUsers.tsx | 45 ++--- .../src/components/users/UserDetails.tsx | 18 ++ .../src/components/users/details.module.scss | 18 -- .../src/components/users/empty.module.scss | 17 ++ .../tenant-management/tenant-management.tsx | 27 +-- packages/tenants-react/src/styles/global.css | 7 + packages/tenants-react/src/translations.ts | 2 + shared/tenants/src/types.ts | 1 - 18 files changed, 372 insertions(+), 203 deletions(-) create mode 100644 packages/tenants-react/src/components/invitations/AddInvitation.tsx create mode 100644 packages/tenants-react/src/components/invitations/InvitedUsers.tsx create mode 100644 packages/tenants-react/src/components/invitations/add-invitation.module.scss create mode 100644 packages/tenants-react/src/components/invitations/invitationsOld.tsx create mode 100644 packages/tenants-react/src/components/users/NoUsers.tsx create mode 100644 packages/tenants-react/src/components/users/UserDetails.tsx create mode 100644 packages/tenants-react/src/components/users/empty.module.scss diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts index c580404..cdf4600 100644 --- a/packages/tenants-react/src/api.ts +++ b/packages/tenants-react/src/api.ts @@ -79,7 +79,7 @@ export const getApi = (querier: ReturnType) => { }; const addInvitation = async (email: string) => { - const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + const response = await querier.post<{ status: "OK"; code: string } | { status: "ERROR"; message: string }>( "/invite/add", { email }, { withSession: true }, diff --git a/packages/tenants-react/src/components/invitations/AddInvitation.tsx b/packages/tenants-react/src/components/invitations/AddInvitation.tsx new file mode 100644 index 0000000..e6d3438 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/AddInvitation.tsx @@ -0,0 +1,49 @@ +import { Button, TextInput, usePrettyAction } from "@shared/ui"; +import classNames from "classnames/bind"; +import { useEffect, useState } from "react"; + +import { usePluginContext } from "../../plugin"; + +import style from "./add-invitation.module.scss"; + +const cx = classNames.bind(style); + +export type AddInvitationProps = { + onCreate: (email: string, tenantId: string) => Promise; + selectedTenantId: string; +}; + +export const AddInvitation: React.FC = ({ onCreate, selectedTenantId }) => { + const { t } = usePluginContext(); + const [inviteEmail, setInviteEmail] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleAddInvite = usePrettyAction( + async () => { + setIsSubmitting(true); + await onCreate(inviteEmail, selectedTenantId); + setIsSubmitting(false); + setInviteEmail(""); + }, + [onCreate, inviteEmail, selectedTenantId], + { errorMessage: "Failed to add invite, please try again" }, + ); + + return ( +
+ setInviteEmail(e)} + className="" + required + disabled={isSubmitting} + /> + +
+ ); +}; diff --git a/packages/tenants-react/src/components/invitations/InvitedUsers.tsx b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx new file mode 100644 index 0000000..279adc0 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx @@ -0,0 +1,32 @@ +import { InviteeDetails } from "@shared/tenants"; + +import { usePluginContext } from "../../plugin"; +import { TenantUsersTable } from "../table/TenantTable"; +import { NoUsers } from "../users/NoUsers"; +import { UserDetails } from "../users/UserDetails"; + +export type InvitedUsersProps = { + selectedTenantId: string; + invitations: InviteeDetails[]; +}; + +export const InvitedUsers: React.FC = ({ selectedTenantId, invitations }) => { + const { t } = usePluginContext(); + + return ( +
+ {invitations.length > 0 ? ( +
+ ({ + emailComponent: , + // extraComponent: getExtraComponent(user), + }))} + /> +
+ ) : ( + + )} +
+ ); +}; diff --git a/packages/tenants-react/src/components/invitations/add-invitation.module.scss b/packages/tenants-react/src/components/invitations/add-invitation.module.scss new file mode 100644 index 0000000..1496570 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/add-invitation.module.scss @@ -0,0 +1,5 @@ +.addInvitationWrapper { + display: flex; + align-items: center; + gap: 10px; +} diff --git a/packages/tenants-react/src/components/invitations/invitations.tsx b/packages/tenants-react/src/components/invitations/invitations.tsx index 8daec9f..415dcc1 100644 --- a/packages/tenants-react/src/components/invitations/invitations.tsx +++ b/packages/tenants-react/src/components/invitations/invitations.tsx @@ -1,25 +1,23 @@ import { InviteeDetails } from "@shared/tenants"; -// import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; import { useCallback, useEffect, useState } from "react"; -export const InvitationsWrapper = ({ - section, - onFetch, - onRemove, - onCreate, - selectedTenantId, -}: { - section: any; +import { usePluginContext } from "../../plugin"; +import { TenantTab } from "../tab/TenantTab"; + +import { AddInvitation } from "./AddInvitation"; +import { InvitedUsers } from "./InvitedUsers"; + +type InvitationsProps = { onFetch: (tenantId?: string) => Promise<{ invitations: InviteeDetails[] }>; - onRemove: (email: string, tenantId?: string) => Promise; - onCreate?: (email: string, tenantId: string) => Promise; selectedTenantId: string; -}) => { +}; + +export const Invitations: React.FC = ({ selectedTenantId, onFetch }) => { + const { api } = usePluginContext(); + const { addInvitation } = api; + const [invitations, setInvitations] = useState([]); - const [showInviteForm, setShowInviteForm] = useState(false); - const [inviteEmail, setInviteEmail] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - const [showCode, setShowCode] = useState(null); + const loadDetails = useCallback( async (tenantId?: string) => { const details = await onFetch(tenantId || selectedTenantId); @@ -28,117 +26,37 @@ export const InvitationsWrapper = ({ [onFetch, selectedTenantId], ); - const handleShowCode = (code: string) => { - setShowCode(code); - }; - useEffect(() => { if (selectedTenantId) { loadDetails(selectedTenantId); } }, [selectedTenantId, loadDetails]); - const handleInviteSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!onCreate || !inviteEmail.trim()) { - return; - } - - setIsSubmitting(true); - try { - await onCreate(inviteEmail.trim(), selectedTenantId); - setInviteEmail(""); - setShowInviteForm(false); - // Reload the invitations list - await loadDetails(selectedTenantId); - } catch (error) { - console.error("Failed to create invitation:", error); - } finally { - setIsSubmitting(false); - } - }; - - const handleRemoveInvitation = async (email: string, tenantId: string) => { - await onRemove(email, tenantId); - await loadDetails(tenantId); - }; - - const handleCancelInvite = () => { - setShowInviteForm(false); - setInviteEmail(""); - }; + const onCreateInvite = useCallback( + async (email: string) => { + const response = await addInvitation(email); + if (response.status === "ERROR") { + throw new Error(response.message); + } + + // If `OK` status, add the newly added invitation to the + // list of invitations. + setInvitations((currentInvitations) => [ + ...currentInvitations, + { + email, + code: response.code, + }, + ]); + }, + [addInvitation], + ); return ( -
-
-

{section.label}

-

{section.description}

- {onCreate && ( - - )} -
- - {showInviteForm && onCreate && ( -
- -
- setInviteEmail(e.currentTarget.value)} - className="" - required - disabled={isSubmitting} - /> -
- - -
-
- -
- )} - -
- {invitations.length > 0 ? ( -
- {/* {invitations.map((invitation) => ( -
-
-
- - -
- ))} */} -
- ) : ( -
-

No invitations found

-
- )} -
- - {showCode && ( -
-

{showCode}

- -
- )} -
+ }> + + ); }; diff --git a/packages/tenants-react/src/components/invitations/invitationsOld.tsx b/packages/tenants-react/src/components/invitations/invitationsOld.tsx new file mode 100644 index 0000000..8daec9f --- /dev/null +++ b/packages/tenants-react/src/components/invitations/invitationsOld.tsx @@ -0,0 +1,144 @@ +import { InviteeDetails } from "@shared/tenants"; +// import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; +import { useCallback, useEffect, useState } from "react"; + +export const InvitationsWrapper = ({ + section, + onFetch, + onRemove, + onCreate, + selectedTenantId, +}: { + section: any; + onFetch: (tenantId?: string) => Promise<{ invitations: InviteeDetails[] }>; + onRemove: (email: string, tenantId?: string) => Promise; + onCreate?: (email: string, tenantId: string) => Promise; + selectedTenantId: string; +}) => { + const [invitations, setInvitations] = useState([]); + const [showInviteForm, setShowInviteForm] = useState(false); + const [inviteEmail, setInviteEmail] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showCode, setShowCode] = useState(null); + const loadDetails = useCallback( + async (tenantId?: string) => { + const details = await onFetch(tenantId || selectedTenantId); + setInvitations(details.invitations); + }, + [onFetch, selectedTenantId], + ); + + const handleShowCode = (code: string) => { + setShowCode(code); + }; + + useEffect(() => { + if (selectedTenantId) { + loadDetails(selectedTenantId); + } + }, [selectedTenantId, loadDetails]); + + const handleInviteSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!onCreate || !inviteEmail.trim()) { + return; + } + + setIsSubmitting(true); + try { + await onCreate(inviteEmail.trim(), selectedTenantId); + setInviteEmail(""); + setShowInviteForm(false); + // Reload the invitations list + await loadDetails(selectedTenantId); + } catch (error) { + console.error("Failed to create invitation:", error); + } finally { + setIsSubmitting(false); + } + }; + + const handleRemoveInvitation = async (email: string, tenantId: string) => { + await onRemove(email, tenantId); + await loadDetails(tenantId); + }; + + const handleCancelInvite = () => { + setShowInviteForm(false); + setInviteEmail(""); + }; + + return ( +
+
+

{section.label}

+

{section.description}

+ {onCreate && ( + + )} +
+ + {showInviteForm && onCreate && ( +
+
+
+ setInviteEmail(e.currentTarget.value)} + className="" + required + disabled={isSubmitting} + /> +
+ + +
+
+
+
+ )} + +
+ {invitations.length > 0 ? ( +
+ {/* {invitations.map((invitation) => ( +
+
+
+ + +
+ ))} */} +
+ ) : ( +
+

No invitations found

+
+ )} +
+ + {showCode && ( +
+

{showCode}

+ +
+ )} +
+ ); +}; diff --git a/packages/tenants-react/src/components/tab/TenantTab.tsx b/packages/tenants-react/src/components/tab/TenantTab.tsx index 6915af7..ab07ec3 100644 --- a/packages/tenants-react/src/components/tab/TenantTab.tsx +++ b/packages/tenants-react/src/components/tab/TenantTab.tsx @@ -7,13 +7,17 @@ const cx = classNames.bind(style); type TenantTabProps = { description: string; + descriptionComponent?: React.ReactNode; children: React.ReactNode; }; -export const TenantTab: React.FC = ({ description, children }) => { +export const TenantTab: React.FC = ({ description, descriptionComponent, children }) => { return (
-
{description}
+
+
{description}
+ {descriptionComponent &&
{descriptionComponent}
} +
{children}
); diff --git a/packages/tenants-react/src/components/tab/tenant-tab.module.scss b/packages/tenants-react/src/components/tab/tenant-tab.module.scss index b4bfa5c..872ab6c 100644 --- a/packages/tenants-react/src/components/tab/tenant-tab.module.scss +++ b/packages/tenants-react/src/components/tab/tenant-tab.module.scss @@ -2,12 +2,18 @@ background-color: #f9f9f8; border-bottom: 1px solid #dddde3; padding: 14px; - font-weight: 500; - font-style: Medium; - font-size: 14px; - line-height: 20px; - letter-spacing: 0px; - color: #60646c; + display: flex; + align-items: center; + justify-content: space-between; + + .tabDescriptionText { + font-weight: 500; + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: #60646c; + } } .tabChildrenWrapper { diff --git a/packages/tenants-react/src/components/table/TenantTable.tsx b/packages/tenants-react/src/components/table/TenantTable.tsx index 787455d..5a71286 100644 --- a/packages/tenants-react/src/components/table/TenantTable.tsx +++ b/packages/tenants-react/src/components/table/TenantTable.tsx @@ -13,16 +13,16 @@ type TableProps = { }[]; }; -export const TenantTable: React.FC = ({ +export const TenantUsersTable: React.FC = ({ emailComponentTitle = "Email", - extraComponentTitle = "Role", + extraComponentTitle = undefined, columns, }) => { return (
{emailComponentTitle}
-
{extraComponentTitle}
+ {extraComponentTitle &&
{extraComponentTitle}
}
{columns.map((column) => ( diff --git a/packages/tenants-react/src/components/users/NoUsers.tsx b/packages/tenants-react/src/components/users/NoUsers.tsx new file mode 100644 index 0000000..37cc9da --- /dev/null +++ b/packages/tenants-react/src/components/users/NoUsers.tsx @@ -0,0 +1,18 @@ +import classNames from "classnames/bind"; + +import style from "./empty.module.scss"; + +const cx = classNames.bind(style); + +type NoUsersProps = { + text: string; +}; + +export const NoUsers: React.FC = ({ text }) => { + return ( +
+
+
{text}
+
+ ); +}; diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index 585abcc..dd9b62f 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -1,4 +1,3 @@ -import classNames from "classnames/bind"; import { useCallback, useEffect, useMemo, useState } from "react"; import Session from "supertokens-auth-react/recipe/session"; import { User } from "supertokens-web-js/types"; @@ -8,14 +7,13 @@ import { SelectInput } from "../../../../../shared/ui/src/components"; import { usePrettyAction } from "../../../../../shared/ui/src/hooks"; import { logDebugMessage } from "../../logger"; import { usePluginContext } from "../../plugin"; -import { TenantTable } from "../table/TenantTable"; +import { TenantUsersTable } from "../table/TenantTable"; -import style from "./details.module.scss"; +import { NoUsers } from "./NoUsers"; +import { UserDetails } from "./UserDetails"; // import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; -const cx = classNames.bind(style); - type UserWithRole = { roles: string[] } & User; type TenantUsersProps = { @@ -107,29 +105,20 @@ export const TenantUsers: React.FC = ({ onFetch, onRoleChange } return ( -
-
- {users.length > 0 ? ( -
- ({ - emailComponent: ( -
-
{user.emails[0]?.charAt(0).toUpperCase() || "U"}
-
{user.emails[0]}
-
- ), - extraComponent: getExtraComponent(user), - }))} - /> -
- ) : ( -
-
-
{t("PL_TB_NO_USERS_FOUND_TEXT")}
-
- )} -
+
+ {users.length > 0 ? ( +
+ ({ + emailComponent: , + extraComponent: getExtraComponent(user), + }))} + /> +
+ ) : ( + + )}
); }; diff --git a/packages/tenants-react/src/components/users/UserDetails.tsx b/packages/tenants-react/src/components/users/UserDetails.tsx new file mode 100644 index 0000000..0e238fa --- /dev/null +++ b/packages/tenants-react/src/components/users/UserDetails.tsx @@ -0,0 +1,18 @@ +import classNames from "classnames/bind"; + +import style from "./details.module.scss"; + +const cx = classNames.bind(style); + +type UserDetailsProps = { + email: string; +}; + +export const UserDetails: React.FC = ({ email }) => { + return ( +
+
{email.charAt(0).toUpperCase() || "U"}
+
{email}
+
+ ); +}; diff --git a/packages/tenants-react/src/components/users/details.module.scss b/packages/tenants-react/src/components/users/details.module.scss index a8993d2..d94387a 100644 --- a/packages/tenants-react/src/components/users/details.module.scss +++ b/packages/tenants-react/src/components/users/details.module.scss @@ -1,21 +1,3 @@ -.tenantDetailsNoUsers { - background-color: #0144ff0f; - padding: 12px; - display: flex; - align-items: center; - gap: 8px; - border-radius: 6px; - - .text { - font-weight: 400; - font-style: Regular; - font-size: 14px; - line-height: 20px; - letter-spacing: 0px; - color: #1c2024; - } -} - .userRow { display: flex; align-items: center; diff --git a/packages/tenants-react/src/components/users/empty.module.scss b/packages/tenants-react/src/components/users/empty.module.scss new file mode 100644 index 0000000..4f85896 --- /dev/null +++ b/packages/tenants-react/src/components/users/empty.module.scss @@ -0,0 +1,17 @@ +.tenantDetailsNoUsers { + background-color: #0144ff0f; + padding: 12px; + display: flex; + align-items: center; + gap: 8px; + border-radius: 6px; + + .text { + font-weight: 400; + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: #1c2024; + } +} diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index c708100..4ed465f 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -4,7 +4,7 @@ import { SelectInput, TabGroup, Tab, TabPanel, ToastProvider, ToastContainer } f import classNames from "classnames/bind"; import { useState, useEffect, useCallback } from "react"; -import { InvitationsWrapper } from "../../components/invitations/invitations"; +import { Invitations } from "../../components/invitations/invitations"; import { TenantTab } from "../../components/tab/TenantTab"; import { TenantUsers } from "../../components/users/TenantUsers"; import { logDebugMessage } from "../../logger"; @@ -16,7 +16,7 @@ const cx = classNames.bind(style); export const TenantManagementWithoutToastWrapper = ({ section }: { section: any }) => { const { api, t } = usePluginContext(); - const { getUsers, getInvitations, removeInvitation, addInvitation, fetchTenants, switchTenant, changeRole } = api; + const { getUsers, getInvitations, removeInvitation, fetchTenants, switchTenant, changeRole } = api; const [tenants, setTenants] = useState([]); const [selectedTenantId, setSelectedTenantId] = useState("public"); @@ -76,16 +76,6 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any [removeInvitation], ); - const onCreateInvite = useCallback( - async (email: string) => { - const response = await addInvitation(email); - if (response.status === "ERROR") { - throw new Error(response.message); - } - }, - [addInvitation], - ); - const handleTenantSwitch = useCallback( async (tenantId: string) => { const response = await switchTenant(tenantId); @@ -138,18 +128,7 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any - +
diff --git a/packages/tenants-react/src/styles/global.css b/packages/tenants-react/src/styles/global.css index 30651a1..5192b42 100644 --- a/packages/tenants-react/src/styles/global.css +++ b/packages/tenants-react/src/styles/global.css @@ -28,3 +28,10 @@ .st-tab-panel::part(base) { padding: 0; } + +/* Style the button */ + +wa-button::part(base) { + background-color: var(--wa-color-brand-80); + color: var(--wa-color-neutral-00); +} diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts index c56c74c..450d62f 100644 --- a/packages/tenants-react/src/translations.ts +++ b/packages/tenants-react/src/translations.ts @@ -4,5 +4,7 @@ export const defaultTranslationsTenants = { PL_TB_INVITATIONS_TAB_LABEL: "Invitations", PL_TB_REQUESTS_TAB_LABEL: "Requests", PL_TB_NO_USERS_FOUND_TEXT: "No users have been found linked to this tenant.", + PL_TB_NO_INVITATIONS_FOUND_TEXT: "There are currently no pending invites.", + PL_TB_ADD_INVITE_BUTTON_TEXT: "+ Invite", }, } as const; diff --git a/shared/tenants/src/types.ts b/shared/tenants/src/types.ts index c9d2ef5..c50e341 100644 --- a/shared/tenants/src/types.ts +++ b/shared/tenants/src/types.ts @@ -20,7 +20,6 @@ export type TenantList = { export type InviteeDetails = { email: string; - role: string; code: string; }; From 1b4c23b68125f2575290091457d2a557dad6389c Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 11 Sep 2025 13:18:49 +0530 Subject: [PATCH 12/25] feat: add support for removing invitations --- .../src/components/icons/Trash.tsx | 10 +++++++ .../components/invitations/AddInvitation.tsx | 6 +++- .../components/invitations/InvitedUsers.tsx | 16 ++++++++-- .../invitations/RemoveInvitation.tsx | 17 +++++++++++ .../components/invitations/invitations.tsx | 20 +++++++++++-- .../tenant-management/tenant-management.tsx | 10 ------- packages/tenants-react/src/styles/global.css | 7 ----- .../ui/src/components/icon/icon.module.scss | 4 +++ shared/ui/src/components/icon/icon.tsx | 29 +++++++++++++++++++ shared/ui/src/components/icon/index.ts | 1 + shared/ui/src/components/index.ts | 1 + 11 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 packages/tenants-react/src/components/icons/Trash.tsx create mode 100644 packages/tenants-react/src/components/invitations/RemoveInvitation.tsx create mode 100644 shared/ui/src/components/icon/icon.module.scss create mode 100644 shared/ui/src/components/icon/icon.tsx create mode 100644 shared/ui/src/components/icon/index.ts diff --git a/packages/tenants-react/src/components/icons/Trash.tsx b/packages/tenants-react/src/components/icons/Trash.tsx new file mode 100644 index 0000000..baf8135 --- /dev/null +++ b/packages/tenants-react/src/components/icons/Trash.tsx @@ -0,0 +1,10 @@ +import { Icon } from "@shared/ui"; + +type TrashProps = { + label?: string; +}; + +export const Trash: React.FC = ({ label }) => { + // TODO: Update with the actual icon + return ; +}; diff --git a/packages/tenants-react/src/components/invitations/AddInvitation.tsx b/packages/tenants-react/src/components/invitations/AddInvitation.tsx index e6d3438..b9f7469 100644 --- a/packages/tenants-react/src/components/invitations/AddInvitation.tsx +++ b/packages/tenants-react/src/components/invitations/AddInvitation.tsx @@ -41,7 +41,11 @@ export const AddInvitation: React.FC = ({ onCreate, selected required disabled={isSubmitting} /> -
diff --git a/packages/tenants-react/src/components/invitations/InvitedUsers.tsx b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx index 279adc0..9766d71 100644 --- a/packages/tenants-react/src/components/invitations/InvitedUsers.tsx +++ b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx @@ -5,14 +5,24 @@ import { TenantUsersTable } from "../table/TenantTable"; import { NoUsers } from "../users/NoUsers"; import { UserDetails } from "../users/UserDetails"; +import { RemoveInvitation } from "./RemoveInvitation"; + export type InvitedUsersProps = { - selectedTenantId: string; + onRemove: (email: string) => Promise; invitations: InviteeDetails[]; }; -export const InvitedUsers: React.FC = ({ selectedTenantId, invitations }) => { +export const InvitedUsers: React.FC = ({ onRemove, invitations }) => { const { t } = usePluginContext(); + const getExtraComponent = (invitation: InviteeDetails) => { + return ( +
+ onRemove(invitation.email)} /> +
+ ); + }; + return (
{invitations.length > 0 ? ( @@ -20,7 +30,7 @@ export const InvitedUsers: React.FC = ({ selectedTenantId, in ({ emailComponent: , - // extraComponent: getExtraComponent(user), + extraComponent: getExtraComponent(user), }))} />
diff --git a/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx b/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx new file mode 100644 index 0000000..c9619c0 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx @@ -0,0 +1,17 @@ +import { Button } from "@shared/ui"; + +import { Trash } from "../icons/Trash"; + +type RemoveInvitationProps = { + onRemove: () => Promise; +}; + +export const RemoveInvitation: React.FC = ({ onRemove }) => { + return ( +
+ +
+ ); +}; diff --git a/packages/tenants-react/src/components/invitations/invitations.tsx b/packages/tenants-react/src/components/invitations/invitations.tsx index 415dcc1..fdfbb40 100644 --- a/packages/tenants-react/src/components/invitations/invitations.tsx +++ b/packages/tenants-react/src/components/invitations/invitations.tsx @@ -1,4 +1,5 @@ import { InviteeDetails } from "@shared/tenants"; +import { usePrettyAction } from "@shared/ui"; import { useCallback, useEffect, useState } from "react"; import { usePluginContext } from "../../plugin"; @@ -14,7 +15,7 @@ type InvitationsProps = { export const Invitations: React.FC = ({ selectedTenantId, onFetch }) => { const { api } = usePluginContext(); - const { addInvitation } = api; + const { addInvitation, removeInvitation } = api; const [invitations, setInvitations] = useState([]); @@ -52,11 +53,26 @@ export const Invitations: React.FC = ({ selectedTenantId, onFe [addInvitation], ); + const onRemoveInvite = usePrettyAction( + async (email: string) => { + const response = await removeInvitation(email); + if (response.status === "ERROR") { + throw new Error(response.message); + } + + // If it was successful, remove the invitation from the + // list. + setInvitations((currentInvitations) => currentInvitations.filter((invitation) => invitation.email !== email)); + }, + [removeInvitation], + { errorMessage: "Failed to remove invitation, please try again" } + ); + return ( }> - + ); }; diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index 4ed465f..5df4461 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -66,16 +66,6 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any return { invitations: response.invitees }; }, [getInvitations]); - const onRemoveInvite = useCallback( - async (email: string) => { - const response = await removeInvitation(email); - if (response.status === "ERROR") { - throw new Error(response.message); - } - }, - [removeInvitation], - ); - const handleTenantSwitch = useCallback( async (tenantId: string) => { const response = await switchTenant(tenantId); diff --git a/packages/tenants-react/src/styles/global.css b/packages/tenants-react/src/styles/global.css index 5192b42..30651a1 100644 --- a/packages/tenants-react/src/styles/global.css +++ b/packages/tenants-react/src/styles/global.css @@ -28,10 +28,3 @@ .st-tab-panel::part(base) { padding: 0; } - -/* Style the button */ - -wa-button::part(base) { - background-color: var(--wa-color-brand-80); - color: var(--wa-color-neutral-00); -} diff --git a/shared/ui/src/components/icon/icon.module.scss b/shared/ui/src/components/icon/icon.module.scss new file mode 100644 index 0000000..48d2e17 --- /dev/null +++ b/shared/ui/src/components/icon/icon.module.scss @@ -0,0 +1,4 @@ +:global(.plugin-profile) { + .st-icon { + } +} diff --git a/shared/ui/src/components/icon/icon.tsx b/shared/ui/src/components/icon/icon.tsx new file mode 100644 index 0000000..40baed6 --- /dev/null +++ b/shared/ui/src/components/icon/icon.tsx @@ -0,0 +1,29 @@ +import classNames from "classnames/bind"; +import styles from "./icon.module.scss"; +import { useWebComponent } from "../utils"; +import { HTMLElementProps } from "../types"; + +const cx = classNames.bind(styles); + +export interface IconProps extends HTMLElementProps { + name?: string; + family?: string; + variant?: string; + fixedWidth?: false; + src?: string; + label?: string; + library?: string; +} + +export const Icon = (_props: IconProps) => { + const { isDefined, props } = useWebComponent({ + name: "wa-icon", + className: cx("st-icon"), + props: _props, + importCallback: () => import("@awesome.me/webawesome/dist/components/icon/icon.js"), + }); + + if (!isDefined) return null; + + return ; +}; diff --git a/shared/ui/src/components/icon/index.ts b/shared/ui/src/components/icon/index.ts new file mode 100644 index 0000000..74a3d6c --- /dev/null +++ b/shared/ui/src/components/icon/index.ts @@ -0,0 +1 @@ +export { Icon } from "./icon"; diff --git a/shared/ui/src/components/index.ts b/shared/ui/src/components/index.ts index d56ebfa..de0c5ab 100644 --- a/shared/ui/src/components/index.ts +++ b/shared/ui/src/components/index.ts @@ -8,3 +8,4 @@ export * from "./tag"; export * from "./theme-provider"; export * from "./callout"; export * from "./tab"; +export * from "./icon"; From 822f232607d1dfba54792558080c127e27bf4150 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 12 Sep 2025 12:51:29 +0530 Subject: [PATCH 13/25] feat: add support for viewing code and copying it for invitation --- .../src/components/icons/Copy.tsx | 10 +++ .../src/components/icons/Eye.tsx | 10 +++ .../src/components/invitations/Code.tsx | 66 +++++++++++++++++++ .../components/invitations/InvitedUsers.tsx | 11 +++- .../components/invitations/code.module.scss | 37 +++++++++++ .../components/invitations/invitations.tsx | 4 +- .../invitations/invited-users.module.scss | 5 ++ packages/tenants-react/src/translations.ts | 1 + 8 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 packages/tenants-react/src/components/icons/Copy.tsx create mode 100644 packages/tenants-react/src/components/icons/Eye.tsx create mode 100644 packages/tenants-react/src/components/invitations/Code.tsx create mode 100644 packages/tenants-react/src/components/invitations/code.module.scss create mode 100644 packages/tenants-react/src/components/invitations/invited-users.module.scss diff --git a/packages/tenants-react/src/components/icons/Copy.tsx b/packages/tenants-react/src/components/icons/Copy.tsx new file mode 100644 index 0000000..f24e473 --- /dev/null +++ b/packages/tenants-react/src/components/icons/Copy.tsx @@ -0,0 +1,10 @@ +import { Icon } from "@shared/ui"; + +type CopyProps = { + label?: string; +}; + +export const Copy: React.FC = ({ label }) => { + // TODO: Update with the actual icon + return ; +}; diff --git a/packages/tenants-react/src/components/icons/Eye.tsx b/packages/tenants-react/src/components/icons/Eye.tsx new file mode 100644 index 0000000..21e60f2 --- /dev/null +++ b/packages/tenants-react/src/components/icons/Eye.tsx @@ -0,0 +1,10 @@ +import { Icon } from "@shared/ui"; + +type EyeProps = { + label?: string; +}; + +export const Eye: React.FC = ({ label }) => { + // TODO: Update with the actual icon + return ; +}; diff --git a/packages/tenants-react/src/components/invitations/Code.tsx b/packages/tenants-react/src/components/invitations/Code.tsx new file mode 100644 index 0000000..06dc7aa --- /dev/null +++ b/packages/tenants-react/src/components/invitations/Code.tsx @@ -0,0 +1,66 @@ +import { Button, usePrettyAction, TextInput, Icon } from "@shared/ui"; +import classNames from "classnames/bind"; +import { useState } from "react"; + +import { usePluginContext } from "../../plugin"; +import { Copy } from "../icons/Copy"; +import { Eye } from "../icons/Eye"; + +import style from "./code.module.scss"; + +const cx = classNames.bind(style); + +type CodeProps = { + code: string; + tenantId: string; +}; + +export const Code: React.FC = ({ code, tenantId }) => { + const { t } = usePluginContext(); + const [showRawCode, setShowRawCode] = useState(false); + + const handleCodeCopyClick = usePrettyAction( + async () => { + const origin = window.location.origin; + const urlToCopy = `${origin}/user/invite/accept?tenantid=${encodeURIComponent(tenantId)}&code=${encodeURIComponent( + code, + )}`; + try { + if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") { + await navigator.clipboard.writeText(urlToCopy); + return; + } + } catch (_) { + throw new Error("Clipboard not supported by browser"); + } + }, + [code, tenantId], + { + errorMessage: "Failed to copy invitation link, please try again", + successMessage: "Invitation link copied!", + }, + ); + + return ( +
+ {showRawCode ? ( +
+
{code}
+ +
+ ) : ( + + )} + +
+ ); +}; diff --git a/packages/tenants-react/src/components/invitations/InvitedUsers.tsx b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx index 9766d71..42d634e 100644 --- a/packages/tenants-react/src/components/invitations/InvitedUsers.tsx +++ b/packages/tenants-react/src/components/invitations/InvitedUsers.tsx @@ -1,23 +1,30 @@ import { InviteeDetails } from "@shared/tenants"; +import classNames from "classnames/bind"; import { usePluginContext } from "../../plugin"; import { TenantUsersTable } from "../table/TenantTable"; import { NoUsers } from "../users/NoUsers"; import { UserDetails } from "../users/UserDetails"; +import { Code } from "./Code"; +import style from "./invited-users.module.scss"; import { RemoveInvitation } from "./RemoveInvitation"; +const cx = classNames.bind(style); + export type InvitedUsersProps = { onRemove: (email: string) => Promise; invitations: InviteeDetails[]; + tenantId: string; }; -export const InvitedUsers: React.FC = ({ onRemove, invitations }) => { +export const InvitedUsers: React.FC = ({ onRemove, invitations, tenantId }) => { const { t } = usePluginContext(); const getExtraComponent = (invitation: InviteeDetails) => { return ( -
+
+ onRemove(invitation.email)} />
); diff --git a/packages/tenants-react/src/components/invitations/code.module.scss b/packages/tenants-react/src/components/invitations/code.module.scss new file mode 100644 index 0000000..f808c29 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/code.module.scss @@ -0,0 +1,37 @@ +.codeWrapper { + display: flex; + align-items: center; + gap: 10px; + + .rawCodeContainer { + border-radius: 4px; + padding: 4px; + background-color: var(--wa-color-neutral-10); + border: 1px solid var(--wa-color-neutral-40); + display: flex; + align-items: center; + justify-content: space-between; + + .textContainer { + font-weight: 400; + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + color: var(--wa-color-brand-90); + } + } + + .viewCodeButtonWrapper { + display: flex; + align-items: center; + gap: 4px; + + font-weight: 500; + font-style: Medium; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + vertical-align: middle; + } +} diff --git a/packages/tenants-react/src/components/invitations/invitations.tsx b/packages/tenants-react/src/components/invitations/invitations.tsx index fdfbb40..578229c 100644 --- a/packages/tenants-react/src/components/invitations/invitations.tsx +++ b/packages/tenants-react/src/components/invitations/invitations.tsx @@ -65,14 +65,14 @@ export const Invitations: React.FC = ({ selectedTenantId, onFe setInvitations((currentInvitations) => currentInvitations.filter((invitation) => invitation.email !== email)); }, [removeInvitation], - { errorMessage: "Failed to remove invitation, please try again" } + { errorMessage: "Failed to remove invitation, please try again" }, ); return ( }> - + ); }; diff --git a/packages/tenants-react/src/components/invitations/invited-users.module.scss b/packages/tenants-react/src/components/invitations/invited-users.module.scss new file mode 100644 index 0000000..4cba20d --- /dev/null +++ b/packages/tenants-react/src/components/invitations/invited-users.module.scss @@ -0,0 +1,5 @@ +.viewCodeWrapper { + display: flex; + align-items: center; + gap: 10px; +} diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts index 450d62f..001c216 100644 --- a/packages/tenants-react/src/translations.ts +++ b/packages/tenants-react/src/translations.ts @@ -6,5 +6,6 @@ export const defaultTranslationsTenants = { PL_TB_NO_USERS_FOUND_TEXT: "No users have been found linked to this tenant.", PL_TB_NO_INVITATIONS_FOUND_TEXT: "There are currently no pending invites.", PL_TB_ADD_INVITE_BUTTON_TEXT: "+ Invite", + PL_TB_VIEW_CODE_TEXT: "Code", }, } as const; From f09acfadd3d158a6e8791acd05ffddc205b8576b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 15 Sep 2025 14:36:09 +0530 Subject: [PATCH 14/25] feat: add BE support for removing user from tenant --- packages/tenants-nodejs/src/plugin.ts | 78 +++++++++++++++++-- .../src/recipeImplementation.ts | 4 +- packages/tenants-nodejs/src/types.ts | 2 +- packages/tenants-react/src/api.ts | 11 +++ 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts index 3d39e97..faca8a2 100644 --- a/packages/tenants-nodejs/src/plugin.ts +++ b/packages/tenants-nodejs/src/plugin.ts @@ -2,7 +2,7 @@ import { NormalisedAppinfo, SuperTokensPlugin, UserContext } from "supertokens-n import MultiTenancy from "supertokens-node/recipe/multitenancy"; import Session from "supertokens-node/recipe/session"; import { logDebugMessage } from "supertokens-node/lib/build/logger"; -import supertokens from "supertokens-node"; +import supertokens, { RecipeUserId } from "supertokens-node"; import UserRoles from "supertokens-node/recipe/userroles"; import { createPluginInitFunction } from "@shared/js"; @@ -420,6 +420,70 @@ export const init = createPluginInitFunction< return getUsersResponse; }), }, + { + path: `${HANDLE_BASE_PATH}/remove`, + method: "post", + verifySessionOptions: { + sessionRequired: true, + overrideGlobalClaimValidators: (globalValidators) => { + return [ + ...globalValidators, + UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN]), + ]; + }, + }, + handler: withRequestHandler(async (req, res, session, userContext) => { + if (!session) { + throw new Error("Session not found"); + } + + const tenantIdToUse = session.getTenantId(); + + const userDetails = await supertokens.getUser(session.getUserId()); + const userRoleInTenant = await UserRoles.getRolesForUser(tenantIdToUse, session.getUserId(), userContext); + + // Check if the current logged in user can remove the user + // from tenant. + if (!implementation.canRemoveUserFromTenant(userDetails!, userRoleInTenant.roles, session)) { + return { + status: "ERROR", + message: "Logged in user not allowed to remove user from tenant" + }; + } + + const payload: { userId: string } | undefined = await req.getJSONBody(); + + if (!payload?.userId) { + return { + status: "ERROR", + message: "userId is required to remove from tenant" + }; + } + + // Get details of the user being removed + const userToRemove = await supertokens.getUser(payload.userId, userContext); + if (!userToRemove) { + // Cannot remove invalid user from tenant. + return { + status: "ERROR", + message: "Cannot remove non-existent user from tenant" + }; + } + + // Remove all roles of the user from the tenant + const allUserRolesInTenant = await UserRoles.getRolesForUser(tenantIdToUse, userToRemove.id); + for (const role of allUserRolesInTenant.roles) { + await UserRoles.removeUserRole(tenantIdToUse, userToRemove.id, role, userContext); + } + + // Remove the user from the tenant + await MultiTenancy.disassociateUserFromTenant(tenantIdToUse, new RecipeUserId(userToRemove.id), userContext); + + return { + status: "OK" + }; + }) + }, // Invite related routes { path: `${HANDLE_BASE_PATH}/invite/add`, @@ -853,12 +917,12 @@ export const init = createPluginInitFunction< ...input.accessTokenPayload, ...(pluginConfig.requireNonPublicTenantAssociation ? await MultipleTenantsPresentClaim.build( - input.userId, - input.recipeUserId, - tenantId, - input.accessTokenPayload, - input.userContext, - ) + input.userId, + input.recipeUserId, + tenantId, + input.accessTokenPayload, + input.userContext, + ) : {}), }; diff --git a/packages/tenants-nodejs/src/recipeImplementation.ts b/packages/tenants-nodejs/src/recipeImplementation.ts index 15c4ea3..d2c3718 100644 --- a/packages/tenants-nodejs/src/recipeImplementation.ts +++ b/packages/tenants-nodejs/src/recipeImplementation.ts @@ -238,9 +238,9 @@ export const getOverrideableTenantFunctionImplementation = ( // By default, only owners can approve tenant creation requests. return role === ROLES.ADMIN; }, - canRemoveUserFromTenant: async (user: User, role: string, session: SessionContainerInterface) => { + canRemoveUserFromTenant: async (user: User, roles: string[], session: SessionContainerInterface) => { // By default, only owners can remove users from a tenant. - return role === ROLES.ADMIN; + return roles.includes(ROLES.ADMIN); }, associateAllLoginMethodsOfUserWithTenant: async ( tenantId: string, diff --git a/packages/tenants-nodejs/src/types.ts b/packages/tenants-nodejs/src/types.ts index c1f8cbb..32af067 100644 --- a/packages/tenants-nodejs/src/types.ts +++ b/packages/tenants-nodejs/src/types.ts @@ -90,7 +90,7 @@ export type OverrideableTenantFunctionImplementation = { canCreateInvitation: (user: User, role: string, session: SessionContainerInterface) => Promise; canApproveJoinRequest: (user: User, role: string, session: SessionContainerInterface) => Promise; canApproveTenantCreationRequest: (user: User, role: string, session: SessionContainerInterface) => Promise; - canRemoveUserFromTenant: (user: User, role: string, session: SessionContainerInterface) => Promise; + canRemoveUserFromTenant: (user: User, roles: string[], session: SessionContainerInterface) => Promise; createTenantAndAssignAdmin: ( tenantDetails: { name: string; diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts index cdf4600..349bfd2 100644 --- a/packages/tenants-react/src/api.ts +++ b/packages/tenants-react/src/api.ts @@ -118,6 +118,16 @@ export const getApi = (querier: ReturnType) => { return response; }; + const removeUserFromTenant = async (userId: string) => { + const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/remove", + { userId }, + { withSession: true } + ); + + return response; + }; + return { fetchTenants, joinTenant, @@ -129,5 +139,6 @@ export const getApi = (querier: ReturnType) => { acceptInvitation, switchTenant, changeRole, + removeUserFromTenant, }; }; From d5c83715fe7fa1fb9018429bed31654897909c72 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 16 Sep 2025 10:56:33 +0530 Subject: [PATCH 15/25] feat: add UI support for removing user from tenants --- packages/tenants-nodejs/src/plugin.ts | 42 +++++++++++-------- .../invitations/RemoveInvitation.tsx | 5 ++- .../src/components/users/TenantUsers.tsx | 32 ++++++++++++-- .../src/components/users/users.module.scss | 5 +++ .../tenant-management/tenant-management.tsx | 17 +++++++- 5 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 packages/tenants-react/src/components/users/users.module.scss diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts index faca8a2..8a1236c 100644 --- a/packages/tenants-nodejs/src/plugin.ts +++ b/packages/tenants-nodejs/src/plugin.ts @@ -426,10 +426,7 @@ export const init = createPluginInitFunction< verifySessionOptions: { sessionRequired: true, overrideGlobalClaimValidators: (globalValidators) => { - return [ - ...globalValidators, - UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN]), - ]; + return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.ADMIN])]; }, }, handler: withRequestHandler(async (req, res, session, userContext) => { @@ -440,14 +437,18 @@ export const init = createPluginInitFunction< const tenantIdToUse = session.getTenantId(); const userDetails = await supertokens.getUser(session.getUserId()); - const userRoleInTenant = await UserRoles.getRolesForUser(tenantIdToUse, session.getUserId(), userContext); + const userRoleInTenant = await UserRoles.getRolesForUser( + tenantIdToUse, + session.getUserId(), + userContext, + ); // Check if the current logged in user can remove the user // from tenant. if (!implementation.canRemoveUserFromTenant(userDetails!, userRoleInTenant.roles, session)) { return { status: "ERROR", - message: "Logged in user not allowed to remove user from tenant" + message: "Logged in user not allowed to remove user from tenant", }; } @@ -456,7 +457,7 @@ export const init = createPluginInitFunction< if (!payload?.userId) { return { status: "ERROR", - message: "userId is required to remove from tenant" + message: "userId is required to remove from tenant", }; } @@ -466,7 +467,7 @@ export const init = createPluginInitFunction< // Cannot remove invalid user from tenant. return { status: "ERROR", - message: "Cannot remove non-existent user from tenant" + message: "Cannot remove non-existent user from tenant", }; } @@ -477,12 +478,16 @@ export const init = createPluginInitFunction< } // Remove the user from the tenant - await MultiTenancy.disassociateUserFromTenant(tenantIdToUse, new RecipeUserId(userToRemove.id), userContext); + await MultiTenancy.disassociateUserFromTenant( + tenantIdToUse, + new RecipeUserId(userToRemove.id), + userContext, + ); return { - status: "OK" + status: "OK", }; - }) + }), }, // Invite related routes { @@ -608,6 +613,9 @@ export const init = createPluginInitFunction< }; } + // Associate the user with the tenant + await MultiTenancy.associateUserToTenant(tenantId, session.getRecipeUserId()); + await assignAdminToUserInTenant(tenantId, session.getUserId()); logDebugMessage(`Admin role assigned to user: ${session.getUserId()}`); @@ -917,12 +925,12 @@ export const init = createPluginInitFunction< ...input.accessTokenPayload, ...(pluginConfig.requireNonPublicTenantAssociation ? await MultipleTenantsPresentClaim.build( - input.userId, - input.recipeUserId, - tenantId, - input.accessTokenPayload, - input.userContext, - ) + input.userId, + input.recipeUserId, + tenantId, + input.accessTokenPayload, + input.userContext, + ) : {}), }; diff --git a/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx b/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx index c9619c0..ae2075a 100644 --- a/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx +++ b/packages/tenants-react/src/components/invitations/RemoveInvitation.tsx @@ -4,12 +4,13 @@ import { Trash } from "../icons/Trash"; type RemoveInvitationProps = { onRemove: () => Promise; + disabled?: boolean; }; -export const RemoveInvitation: React.FC = ({ onRemove }) => { +export const RemoveInvitation: React.FC = ({ onRemove, disabled = false }) => { return (
-
diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index dd9b62f..d79756b 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames/bind"; import { useCallback, useEffect, useMemo, useState } from "react"; import Session from "supertokens-auth-react/recipe/session"; import { User } from "supertokens-web-js/types"; @@ -7,10 +8,14 @@ import { SelectInput } from "../../../../../shared/ui/src/components"; import { usePrettyAction } from "../../../../../shared/ui/src/hooks"; import { logDebugMessage } from "../../logger"; import { usePluginContext } from "../../plugin"; +import { RemoveInvitation } from "../invitations/RemoveInvitation"; import { TenantUsersTable } from "../table/TenantTable"; import { NoUsers } from "./NoUsers"; import { UserDetails } from "./UserDetails"; +import style from "./users.module.scss"; + +const cx = classNames.bind(style); // import { BaseFormSection } from '@supertokens-plugin-profile/common-details-shared'; @@ -19,9 +24,10 @@ type UserWithRole = { roles: string[] } & User; type TenantUsersProps = { onFetch: () => Promise<{ users: UserWithRole[] }>; onRoleChange: (userId: string, role: string) => Promise; + onUserRemove: (userId: string) => Promise; }; -export const TenantUsers: React.FC = ({ onFetch, onRoleChange }) => { +export const TenantUsers: React.FC = ({ onFetch, onRoleChange, onUserRemove }) => { const { t } = usePluginContext(); const [users, setUsers] = useState([]); const [userId, setUserId] = useState(null); @@ -72,6 +78,20 @@ export const TenantUsers: React.FC = ({ onFetch, onRoleChange { errorMessage: "Failed to change role" }, ); + const handleUserRemove = usePrettyAction( + async (userId: string) => { + logDebugMessage(`Removing user from tenant: ${userId}`); + const wasRemoved = await onUserRemove(userId); + + // Update the list of users if it was successful. + if (wasRemoved) { + setUsers((currenUsers) => currenUsers.filter((user) => user.id !== userId)); + } + }, + [onUserRemove], + { errorMessage: "Failed to remove user" }, + ); + const getExtraComponent = useCallback( (user: { roles: string[] } & User) => { // It's safe to assume that they would have one role @@ -84,7 +104,7 @@ export const TenantUsers: React.FC = ({ onFetch, onRoleChange }; return ( -
+
= ({ onFetch, onRoleChange disabled={!isCurrentUserAdmin || isRoleChanging} />
+
+ handleUserRemove(user.id)} + disabled={currentUserDetails!.id === user.id} + /> +
); }, - [isCurrentUserAdmin, isRoleChanging, handleRoleChange], + [isCurrentUserAdmin, isRoleChanging, handleRoleChange, handleUserRemove, currentUserDetails], ); if (!userId) { diff --git a/packages/tenants-react/src/components/users/users.module.scss b/packages/tenants-react/src/components/users/users.module.scss new file mode 100644 index 0000000..6533525 --- /dev/null +++ b/packages/tenants-react/src/components/users/users.module.scss @@ -0,0 +1,5 @@ +.userExtraComponent { + display: flex; + align-items: center; + gap: 20px; +} diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index 5df4461..62a872f 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -16,7 +16,8 @@ const cx = classNames.bind(style); export const TenantManagementWithoutToastWrapper = ({ section }: { section: any }) => { const { api, t } = usePluginContext(); - const { getUsers, getInvitations, removeInvitation, fetchTenants, switchTenant, changeRole } = api; + const { getUsers, getInvitations, removeInvitation, removeUserFromTenant, fetchTenants, switchTenant, changeRole } = + api; const [tenants, setTenants] = useState([]); const [selectedTenantId, setSelectedTenantId] = useState("public"); @@ -57,6 +58,18 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any [changeRole], ); + const onUserRemove = useCallback( + async (userId: string): boolean => { + const response = await removeUserFromTenant(userId); + if (response.status === "ERROR") { + logDebugMessage(`Got error while removing user: ${response.message}`); + return false; + } + return true; + }, + [removeUserFromTenant], + ); + // Invitations tab functions const onFetchInvitations = useCallback(async () => { const response = await getInvitations(); @@ -114,7 +127,7 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any {/* Tab Content */} - + From ed991a88caf10bb321c08068b393406ba1d8f987 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 16 Sep 2025 12:51:42 +0530 Subject: [PATCH 16/25] feat: add init UI structure for tenant requests --- .../components/invitations/invitationsOld.tsx | 144 ------------------ .../components/requests/TenantRequests.tsx | 34 +++++ .../components/requests/requests.module.scss | 2 + .../src/components/tab/tenant-tab.module.scss | 2 +- .../tenant-management/tenant-management.tsx | 8 +- packages/tenants-react/src/styles/global.css | 18 ++- packages/tenants-react/src/translations.ts | 4 + 7 files changed, 63 insertions(+), 149 deletions(-) delete mode 100644 packages/tenants-react/src/components/invitations/invitationsOld.tsx create mode 100644 packages/tenants-react/src/components/requests/TenantRequests.tsx create mode 100644 packages/tenants-react/src/components/requests/requests.module.scss diff --git a/packages/tenants-react/src/components/invitations/invitationsOld.tsx b/packages/tenants-react/src/components/invitations/invitationsOld.tsx deleted file mode 100644 index 8daec9f..0000000 --- a/packages/tenants-react/src/components/invitations/invitationsOld.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { InviteeDetails } from "@shared/tenants"; -// import { BaseFormSection } from "@supertokens-plugin-profile/common-details-shared"; -import { useCallback, useEffect, useState } from "react"; - -export const InvitationsWrapper = ({ - section, - onFetch, - onRemove, - onCreate, - selectedTenantId, -}: { - section: any; - onFetch: (tenantId?: string) => Promise<{ invitations: InviteeDetails[] }>; - onRemove: (email: string, tenantId?: string) => Promise; - onCreate?: (email: string, tenantId: string) => Promise; - selectedTenantId: string; -}) => { - const [invitations, setInvitations] = useState([]); - const [showInviteForm, setShowInviteForm] = useState(false); - const [inviteEmail, setInviteEmail] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - const [showCode, setShowCode] = useState(null); - const loadDetails = useCallback( - async (tenantId?: string) => { - const details = await onFetch(tenantId || selectedTenantId); - setInvitations(details.invitations); - }, - [onFetch, selectedTenantId], - ); - - const handleShowCode = (code: string) => { - setShowCode(code); - }; - - useEffect(() => { - if (selectedTenantId) { - loadDetails(selectedTenantId); - } - }, [selectedTenantId, loadDetails]); - - const handleInviteSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!onCreate || !inviteEmail.trim()) { - return; - } - - setIsSubmitting(true); - try { - await onCreate(inviteEmail.trim(), selectedTenantId); - setInviteEmail(""); - setShowInviteForm(false); - // Reload the invitations list - await loadDetails(selectedTenantId); - } catch (error) { - console.error("Failed to create invitation:", error); - } finally { - setIsSubmitting(false); - } - }; - - const handleRemoveInvitation = async (email: string, tenantId: string) => { - await onRemove(email, tenantId); - await loadDetails(tenantId); - }; - - const handleCancelInvite = () => { - setShowInviteForm(false); - setInviteEmail(""); - }; - - return ( -
-
-

{section.label}

-

{section.description}

- {onCreate && ( - - )} -
- - {showInviteForm && onCreate && ( -
-
-
- setInviteEmail(e.currentTarget.value)} - className="" - required - disabled={isSubmitting} - /> -
- - -
-
-
-
- )} - -
- {invitations.length > 0 ? ( -
- {/* {invitations.map((invitation) => ( -
-
-
- - -
- ))} */} -
- ) : ( -
-

No invitations found

-
- )} -
- - {showCode && ( -
-

{showCode}

- -
- )} -
- ); -}; diff --git a/packages/tenants-react/src/components/requests/TenantRequests.tsx b/packages/tenants-react/src/components/requests/TenantRequests.tsx new file mode 100644 index 0000000..3c818a2 --- /dev/null +++ b/packages/tenants-react/src/components/requests/TenantRequests.tsx @@ -0,0 +1,34 @@ +import { TabGroup, Tab, TabPanel } from "@shared/ui"; +import classNames from "classnames/bind"; + +import { usePluginContext } from "../../plugin"; + +import style from "./requests.module.scss"; +import { TenantTab } from "../tab/TenantTab"; + +const cx = classNames.bind(style); + +export const TenantRequests = () => { + const { t } = usePluginContext(); + + return ( +
+ + {t("PL_TB_TENANT_REQUESTS_ONBOARDING_TAB_LABEL")} + {t("PL_TB_TENANT_REQUESTS_CREATION_TAB_LABEL")} + + {/* Tab Content */} + + +
+
+
+ + +
+
+
+
+
+ ); +}; diff --git a/packages/tenants-react/src/components/requests/requests.module.scss b/packages/tenants-react/src/components/requests/requests.module.scss new file mode 100644 index 0000000..8519ac0 --- /dev/null +++ b/packages/tenants-react/src/components/requests/requests.module.scss @@ -0,0 +1,2 @@ +:global(.tenantRequestsWrapper) { +} diff --git a/packages/tenants-react/src/components/tab/tenant-tab.module.scss b/packages/tenants-react/src/components/tab/tenant-tab.module.scss index 872ab6c..1d338a3 100644 --- a/packages/tenants-react/src/components/tab/tenant-tab.module.scss +++ b/packages/tenants-react/src/components/tab/tenant-tab.module.scss @@ -1,6 +1,6 @@ .tabDescription { background-color: #f9f9f8; - border-bottom: 1px solid #dddde3; + border-bottom: 1px solid var(--neutral-color-neutral-6); padding: 14px; display: flex; align-items: center; diff --git a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx index 62a872f..60bfd7d 100644 --- a/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx +++ b/packages/tenants-react/src/pages/tenant-management/tenant-management.tsx @@ -4,7 +4,8 @@ import { SelectInput, TabGroup, Tab, TabPanel, ToastProvider, ToastContainer } f import classNames from "classnames/bind"; import { useState, useEffect, useCallback } from "react"; -import { Invitations } from "../../components/invitations/invitations"; +import { Invitations } from "../../components/invitations/Invitations"; +import { TenantRequests } from "../../components/requests/TenantRequests"; import { TenantTab } from "../../components/tab/TenantTab"; import { TenantUsers } from "../../components/users/TenantUsers"; import { logDebugMessage } from "../../logger"; @@ -59,7 +60,7 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any ); const onUserRemove = useCallback( - async (userId: string): boolean => { + async (userId: string): Promise => { const response = await removeUserFromTenant(userId); if (response.status === "ERROR") { logDebugMessage(`Got error while removing user: ${response.message}`); @@ -133,6 +134,9 @@ export const TenantManagementWithoutToastWrapper = ({ section }: { section: any + + +
diff --git a/packages/tenants-react/src/styles/global.css b/packages/tenants-react/src/styles/global.css index 30651a1..2090416 100644 --- a/packages/tenants-react/src/styles/global.css +++ b/packages/tenants-react/src/styles/global.css @@ -5,7 +5,10 @@ border-radius: 12px; background-color: #ffffff; - --indicator-color: #3e63dd; + --indicator-color: var(--accent-color-accent-9); + + /* Make the track color same as background color to hide it */ + --track-color: var(--neutral-color-neutral-8); } .st-tab-group::part(tabs) { @@ -22,9 +25,20 @@ } .st-tab:not([active])::part(base) { - color: #1c2024; + color: var(--neutral-color-neutral-12); } .st-tab-panel::part(base) { padding: 0; } + +.tenantRequestsWrapper .st-tab-group::part(base) { + background-color: #f9f9f8; + border: none; + border-radius: 0; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + box-shadow: none; + + --track-color: var(--neutral-color-neutral-6); +} diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts index 001c216..bc9f418 100644 --- a/packages/tenants-react/src/translations.ts +++ b/packages/tenants-react/src/translations.ts @@ -7,5 +7,9 @@ export const defaultTranslationsTenants = { PL_TB_NO_INVITATIONS_FOUND_TEXT: "There are currently no pending invites.", PL_TB_ADD_INVITE_BUTTON_TEXT: "+ Invite", PL_TB_VIEW_CODE_TEXT: "Code", + PL_TB_TENANT_REQUESTS_ONBOARDING_TAB_LABEL: "Tenant Onboarding", + PL_TB_TENANT_REQUESTS_CREATION_TAB_LABEL: "Tenant Creation", + PL_TB_TENANT_REQUESTS_ONBOARDING_DESCRIPTION: "A list of user requests to join your tenant.", + PL_TB_TENANT_REQUESTS_CREATION_DESCRIPTION: "List of requests to create new tenant in your app.", }, } as const; From 17d2addf95a7d56dc110243252a7ce1a055d7077 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Wed, 17 Sep 2025 12:50:32 +0530 Subject: [PATCH 17/25] feat: add support for tenant requests - onboarding accept/decline --- packages/tenants-nodejs/src/plugin.ts | 1 + packages/tenants-react/src/api.ts | 30 ++++++- .../src/components/requests/Action.tsx | 28 ++++++ .../requests/OnboardingRequests.tsx | 90 +++++++++++++++++++ .../components/requests/TenantRequests.tsx | 5 +- .../components/requests/requests.module.scss | 6 ++ .../src/components/users/TenantUsers.tsx | 2 +- packages/tenants-react/src/translations.ts | 3 + 8 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 packages/tenants-react/src/components/requests/Action.tsx create mode 100644 packages/tenants-react/src/components/requests/OnboardingRequests.tsx diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts index 8a1236c..939cfeb 100644 --- a/packages/tenants-nodejs/src/plugin.ts +++ b/packages/tenants-nodejs/src/plugin.ts @@ -7,6 +7,7 @@ import UserRoles from "supertokens-node/recipe/userroles"; import { createPluginInitFunction } from "@shared/js"; import { pluginUserMetadata, withRequestHandler } from "@shared/nodejs"; +import { SessionClaimValidator } from "supertokens-node/recipe/session"; import { OverrideableTenantFunctionImplementation, diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts index 349bfd2..f640b45 100644 --- a/packages/tenants-react/src/api.ts +++ b/packages/tenants-react/src/api.ts @@ -122,12 +122,37 @@ export const getApi = (querier: ReturnType) => { const response = await querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( "/remove", { userId }, - { withSession: true } + { withSession: true }, ); return response; }; + const getOnboardingRequests = async () => { + const response = await querier.post<{ status: "OK"; users: User[] } | { status: "ERROR"; message: string }>( + "/request/list", + {}, + { withSession: true }, + ); + return response; + }; + + const acceptOnboardingAccept = async (userId: string) => { + return querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/request/accept", + { userId }, + { withSession: true }, + ); + }; + + const declineOnboardingAccept = async (userId: string) => { + return querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/request/reject", + { userId }, + { withSession: true }, + ); + }; + return { fetchTenants, joinTenant, @@ -140,5 +165,8 @@ export const getApi = (querier: ReturnType) => { switchTenant, changeRole, removeUserFromTenant, + getOnboardingRequests, + acceptOnboardingAccept, + declineOnboardingAccept, }; }; diff --git a/packages/tenants-react/src/components/requests/Action.tsx b/packages/tenants-react/src/components/requests/Action.tsx new file mode 100644 index 0000000..d5a86ab --- /dev/null +++ b/packages/tenants-react/src/components/requests/Action.tsx @@ -0,0 +1,28 @@ +import { Button } from "@shared/ui"; +import classNames from "classnames/bind"; + +import { usePluginContext } from "../../plugin"; + +import style from "./requests.module.scss"; + +const cx = classNames.bind(style); + +type ActionProps = { + onAccept: () => Promise; + onDecline: () => Promise; +}; + +export const Action: React.FC = ({ onAccept, onDecline }) => { + const { t } = usePluginContext(); + + return ( +
+ + +
+ ); +}; diff --git a/packages/tenants-react/src/components/requests/OnboardingRequests.tsx b/packages/tenants-react/src/components/requests/OnboardingRequests.tsx new file mode 100644 index 0000000..4e8b9c9 --- /dev/null +++ b/packages/tenants-react/src/components/requests/OnboardingRequests.tsx @@ -0,0 +1,90 @@ +import { useEffect, useState } from "react"; +import { User } from "supertokens-web-js/types"; + +import { usePrettyAction } from "../../../../../shared/ui/src/hooks"; +import { usePluginContext } from "../../plugin"; +import { TenantUsersTable } from "../table/TenantTable"; +import { NoUsers } from "../users/NoUsers"; +import { UserDetails } from "../users/UserDetails"; + +import { Action } from "./Action"; + +export const OnboardingRequests = () => { + const { t, api } = usePluginContext(); + const [requests, setRequests] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const { getOnboardingRequests, acceptOnboardingAccept, declineOnboardingAccept } = api; + + const getUsers = usePrettyAction( + async () => { + setIsLoading(true); + try { + const onboardingRequestsResponse = await getOnboardingRequests(); + if (onboardingRequestsResponse.status === "ERROR") { + throw new Error(onboardingRequestsResponse.message); + } + setRequests(onboardingRequestsResponse.users); + } finally { + setIsLoading(false); + } + }, + [getOnboardingRequests], + { errorMessage: "Failed to get requests for tenant" }, + ); + + const onAcceptRequest = usePrettyAction( + async (userId: string) => { + const acceptResponse = await acceptOnboardingAccept(userId); + if (acceptResponse.status === "ERROR") { + throw new Error(acceptResponse.message); + } + + // Remove the request from the list of requests. + setRequests((existingRequests) => existingRequests.filter((req) => req.id !== userId)); + }, + [acceptOnboardingAccept], + { errorMessage: "Failed to accept onboarding request, please try again" }, + ); + + const onDeclineRequest = usePrettyAction( + async (userId: string) => { + const declineResponse = await declineOnboardingAccept(userId); + if (declineResponse.status === "ERROR") { + throw new Error(declineResponse.message); + } + + // Remove the request from the list of requests. + setRequests((existingRequests) => existingRequests.filter((req) => req.id !== userId)); + }, + [declineOnboardingAccept], + { errorMessage: "Failed to decline onboarding request, please try again" }, + ); + + useEffect(() => { + getUsers(); + }, []); + + const getExtraComponent = (user: User) => { + return onAcceptRequest(user.id)} onDecline={() => onDeclineRequest(user.id)} />; + }; + + if (isLoading) { + return
{t("PL_TB_TENANTS_LOADING_MESSAGE")}
; + } + + return ( +
+ {requests.length > 0 ? ( + ({ + emailComponent: , + extraComponent: getExtraComponent(user), + }))} + /> + ) : ( + + )} +
+ ); +}; diff --git a/packages/tenants-react/src/components/requests/TenantRequests.tsx b/packages/tenants-react/src/components/requests/TenantRequests.tsx index 3c818a2..cbe5752 100644 --- a/packages/tenants-react/src/components/requests/TenantRequests.tsx +++ b/packages/tenants-react/src/components/requests/TenantRequests.tsx @@ -2,9 +2,10 @@ import { TabGroup, Tab, TabPanel } from "@shared/ui"; import classNames from "classnames/bind"; import { usePluginContext } from "../../plugin"; +import { TenantTab } from "../tab/TenantTab"; +import { OnboardingRequests } from "./OnboardingRequests"; import style from "./requests.module.scss"; -import { TenantTab } from "../tab/TenantTab"; const cx = classNames.bind(style); @@ -20,7 +21,7 @@ export const TenantRequests = () => { {/* Tab Content */} -
+
diff --git a/packages/tenants-react/src/components/requests/requests.module.scss b/packages/tenants-react/src/components/requests/requests.module.scss index 8519ac0..f7bf896 100644 --- a/packages/tenants-react/src/components/requests/requests.module.scss +++ b/packages/tenants-react/src/components/requests/requests.module.scss @@ -1,2 +1,8 @@ :global(.tenantRequestsWrapper) { } + +.actionWrapper { + display: flex; + align-items: center; + gap: 10px; +} diff --git a/packages/tenants-react/src/components/users/TenantUsers.tsx b/packages/tenants-react/src/components/users/TenantUsers.tsx index d79756b..7101b44 100644 --- a/packages/tenants-react/src/components/users/TenantUsers.tsx +++ b/packages/tenants-react/src/components/users/TenantUsers.tsx @@ -127,7 +127,7 @@ export const TenantUsers: React.FC = ({ onFetch, onRoleChange, ); if (!userId) { - return
Loading....
; + return
{t("PL_TB_TENANTS_LOADING_MESSAGE")}
; } return ( diff --git a/packages/tenants-react/src/translations.ts b/packages/tenants-react/src/translations.ts index bc9f418..19e7ffc 100644 --- a/packages/tenants-react/src/translations.ts +++ b/packages/tenants-react/src/translations.ts @@ -11,5 +11,8 @@ export const defaultTranslationsTenants = { PL_TB_TENANT_REQUESTS_CREATION_TAB_LABEL: "Tenant Creation", PL_TB_TENANT_REQUESTS_ONBOARDING_DESCRIPTION: "A list of user requests to join your tenant.", PL_TB_TENANT_REQUESTS_CREATION_DESCRIPTION: "List of requests to create new tenant in your app.", + PL_TB_TENANTS_LOADING_MESSAGE: "Loading...", + PL_TB_TENANTS_REQUESTS_ACCEPT_BUTTON_TEXT: "Accept", + PL_TB_TENANTS_REQUESTS_DECLINE_BUTTON_TEXT: "Decline", }, } as const; From 5ae244cd65eeff685efab737505b98f0dcedc7fc Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 18 Sep 2025 12:42:54 +0530 Subject: [PATCH 18/25] feat: add support for rendering tenant creation requests --- packages/tenants-nodejs/src/plugin.ts | 8 +- .../src/recipeImplementation.ts | 25 ++++- packages/tenants-nodejs/src/types.ts | 9 +- packages/tenants-react/src/api.ts | 35 +++++- .../components/requests/CreationRequests.tsx | 105 ++++++++++++++++++ .../requests/OnboardingRequests.tsx | 2 +- .../components/requests/TenantRequests.tsx | 3 +- .../components/requests/requests.module.scss | 20 ++++ .../src/components/table/TenantTable.tsx | 15 ++- .../src/components/table/table.module.scss | 8 +- shared/tenants/src/types.ts | 4 +- 11 files changed, 213 insertions(+), 21 deletions(-) create mode 100644 packages/tenants-react/src/components/requests/CreationRequests.tsx diff --git a/packages/tenants-nodejs/src/plugin.ts b/packages/tenants-nodejs/src/plugin.ts index 939cfeb..055f924 100644 --- a/packages/tenants-nodejs/src/plugin.ts +++ b/packages/tenants-nodejs/src/plugin.ts @@ -7,7 +7,6 @@ import UserRoles from "supertokens-node/recipe/userroles"; import { createPluginInitFunction } from "@shared/js"; import { pluginUserMetadata, withRequestHandler } from "@shared/nodejs"; -import { SessionClaimValidator } from "supertokens-node/recipe/session"; import { OverrideableTenantFunctionImplementation, @@ -201,12 +200,12 @@ export const init = createPluginInitFunction< return [...globalValidators, UserRoles.UserRoleClaim.validators.includesAny([ROLES.APP_ADMIN])]; }, }, - handler: withRequestHandler(async (req, res, session) => { + handler: withRequestHandler(async (req, res, session, userContext) => { if (!session) { throw new Error("Session not found"); } - return implementation.getTenantCreationRequests(tenantCreationRequestMetadata); + return implementation.getTenantCreationRequests(tenantCreationRequestMetadata, userContext); }), }, { @@ -620,6 +619,9 @@ export const init = createPluginInitFunction< await assignAdminToUserInTenant(tenantId, session.getUserId()); logDebugMessage(`Admin role assigned to user: ${session.getUserId()}`); + await assignRoleToUserInTenant(tenantId, session.getUserId(), ROLES.APP_ADMIN); + logDebugMessage(`App Admin role assigned to user: ${session.getUserId()}`); + const roles = await UserRoles.getUsersThatHaveRole(tenantId, ROLES.ADMIN); logDebugMessage(`roles: ${JSON.stringify(roles)}`); diff --git a/packages/tenants-nodejs/src/recipeImplementation.ts b/packages/tenants-nodejs/src/recipeImplementation.ts index d2c3718..7e76ae4 100644 --- a/packages/tenants-nodejs/src/recipeImplementation.ts +++ b/packages/tenants-nodejs/src/recipeImplementation.ts @@ -2,8 +2,8 @@ import supertokens from "supertokens-node"; import { SessionContainerInterface } from "supertokens-node/recipe/session/types"; import MultiTenancy from "supertokens-node/recipe/multitenancy"; -import { InviteeDetails, ROLES, TenantList } from "@shared/tenants"; -import { User } from "supertokens-node/types"; +import { InviteeDetails, ROLES, TenantCreationRequestWithUser, TenantList } from "@shared/tenants"; +import { User, UserContext } from "supertokens-node/types"; import { ErrorResponse, MetadataType, @@ -307,11 +307,28 @@ export const getOverrideableTenantFunctionImplementation = ( requestId, }; }, - getTenantCreationRequests: async (metadata: TenantCreationRequestMetadataType) => { + getTenantCreationRequests: async (metadata: TenantCreationRequestMetadataType, userContext: UserContext) => { const tenantCreateRequestMetadata = await metadata.get(TENANT_CREATE_METADATA_REQUESTS_KEY); + + // Fetch the user details for each user + const requestsWithUserId = tenantCreateRequestMetadata.requests; + const requestsWithUser: TenantCreationRequestWithUser[] = []; + + for (const request of tenantCreateRequestMetadata.requests) { + const userDetails = await supertokens.getUser(request.userId, userContext); + if (!userDetails) { + logDebugMessage( + `Couldn't find user details for tenant request ${request.requestId} and user: ${request.userId}`, + ); + continue; + } + + requestsWithUser.push({ ...request, user: userDetails }); + } + return { status: "OK", - requests: tenantCreateRequestMetadata?.requests ?? [], + requests: requestsWithUser, }; }, acceptTenantCreationRequest: async (requestId, session, metadata) => { diff --git a/packages/tenants-nodejs/src/types.ts b/packages/tenants-nodejs/src/types.ts index 32af067..32da422 100644 --- a/packages/tenants-nodejs/src/types.ts +++ b/packages/tenants-nodejs/src/types.ts @@ -2,8 +2,8 @@ import { SessionContainerInterface } from "supertokens-node/recipe/session/types import { pluginUserMetadata } from "@shared/nodejs"; import { InviteeDetails, - TenantCreationRequest, TenantCreationRequestMetadata, + TenantCreationRequestWithUser, TenantList, TenantMetadata, } from "@shared/tenants"; @@ -64,7 +64,7 @@ export type SuperTokensPluginTenantPluginNormalisedConfig = { originalImplementation: EmailDeliveryInterface, ) => EmailDeliveryInterface; }; -} +}; export type SendPluginEmail = (input: PluginEmailDeliveryInput, userContext: UserContext) => Promise; @@ -135,9 +135,8 @@ export type OverrideableTenantFunctionImplementation = { ) => Promise<{ status: "OK" } | NonOkResponse | ErrorResponse>; getTenantCreationRequests: ( metadata: TenantCreationRequestMetadataType, - ) => Promise< - ({ status: "OK" } & { requests: (TenantCreationRequest & { userId: string })[] }) | NonOkResponse | ErrorResponse - >; + userContext: UserContext, + ) => Promise<({ status: "OK" } & { requests: TenantCreationRequestWithUser[] }) | NonOkResponse | ErrorResponse>; acceptTenantCreationRequest: ( requestId: string, session: SessionContainerInterface, diff --git a/packages/tenants-react/src/api.ts b/packages/tenants-react/src/api.ts index f640b45..ce7d892 100644 --- a/packages/tenants-react/src/api.ts +++ b/packages/tenants-react/src/api.ts @@ -1,5 +1,11 @@ import { getQuerier } from "@shared/react"; -import { InviteeDetails, TenantCreateData, TenantJoinData, TenantList } from "@shared/tenants"; +import { + InviteeDetails, + TenantCreateData, + TenantCreationRequestWithUser, + TenantJoinData, + TenantList, +} from "@shared/tenants"; import Session from "supertokens-auth-react/recipe/session"; import { User } from "supertokens-web-js/types"; @@ -153,6 +159,30 @@ export const getApi = (querier: ReturnType) => { ); }; + /* Tenant Creation related endpoints */ + + const getCreationRequests = async () => { + return querier.post< + { status: "OK"; requests: TenantCreationRequestWithUser[] } | { status: "ERROR"; message: string } + >("/tenant-requests/list", {}, { withSession: true }); + }; + + const acceptCreationRequest = async (requestId: string) => { + return querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/tenant-requests/accept", + { requestId }, + { withSession: true }, + ); + }; + + const declineCreationRequest = async (requestId: string) => { + return querier.post<{ status: "OK" } | { status: "ERROR"; message: string }>( + "/tenant-requests/reject", + { requestId }, + { withSession: true }, + ); + }; + return { fetchTenants, joinTenant, @@ -168,5 +198,8 @@ export const getApi = (querier: ReturnType) => { getOnboardingRequests, acceptOnboardingAccept, declineOnboardingAccept, + getCreationRequests, + acceptCreationRequest, + declineCreationRequest, }; }; diff --git a/packages/tenants-react/src/components/requests/CreationRequests.tsx b/packages/tenants-react/src/components/requests/CreationRequests.tsx new file mode 100644 index 0000000..4650ce3 --- /dev/null +++ b/packages/tenants-react/src/components/requests/CreationRequests.tsx @@ -0,0 +1,105 @@ +import { TenantCreationRequestWithUser } from "@shared/tenants"; +import { usePrettyAction } from "@shared/ui"; +import classNames from "classnames/bind"; +import { useEffect, useState } from "react"; + +import { usePluginContext } from "../../plugin"; +import { TenantUsersTable } from "../table/TenantTable"; +import { NoUsers } from "../users/NoUsers"; +import { UserDetails } from "../users/UserDetails"; + +import { Action } from "./Action"; +import style from "./requests.module.scss"; + +const cx = classNames.bind(style); + +export const CreationRequests = () => { + const { t, api } = usePluginContext(); + const [requests, setRequests] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const { getCreationRequests, acceptCreationRequest, declineCreationRequest } = api; + + const getRequests = usePrettyAction( + async () => { + setIsLoading(true); + try { + const onboardingRequestsResponse = await getCreationRequests(); + if (onboardingRequestsResponse.status === "ERROR") { + throw new Error(onboardingRequestsResponse.message); + } + setRequests(onboardingRequestsResponse.requests); + } finally { + setIsLoading(false); + } + }, + [getCreationRequests], + { errorMessage: "Failed to get creation requests" }, + ); + + const onAcceptRequest = usePrettyAction( + async (requestId: string) => { + const acceptResponse = await acceptCreationRequest(requestId); + if (acceptResponse.status === "ERROR") { + throw new Error(acceptResponse.message); + } + + // Remove the request from the list of requests. + setRequests((existingRequests) => existingRequests.filter((req) => req.requestId !== requestId)); + }, + [acceptCreationRequest], + { errorMessage: "Failed to accept creation request, please try again" }, + ); + + const onDeclineRequest = usePrettyAction( + async (requestId: string) => { + const declineResponse = await declineCreationRequest(requestId); + if (declineResponse.status === "ERROR") { + throw new Error(declineResponse.message); + } + + // Remove the request from the list of requests. + setRequests((existingRequests) => existingRequests.filter((req) => req.requestId !== requestId)); + }, + [declineCreationRequest], + { errorMessage: "Failed to decline creation request, please try again" }, + ); + + useEffect(() => { + getRequests(); + }, []); + + const getExtraComponent = (request: TenantCreationRequestWithUser) => { + return ( +
+
{request.name}
+ onAcceptRequest(request.requestId)} + onDecline={() => onDeclineRequest(request.requestId)} + /> +
+ ); + }; + + if (isLoading) { + return
{t("PL_TB_TENANTS_LOADING_MESSAGE")}
; + } + + return ( +
+ {requests.length > 0 ? ( + ({ + emailComponent: , + extraComponent: getExtraComponent(request), + }))} + extraComponentTitle="Tenant ID" + extraWidth="65%" + emailWidth="35%" + /> + ) : ( + + )} +
+ ); +}; diff --git a/packages/tenants-react/src/components/requests/OnboardingRequests.tsx b/packages/tenants-react/src/components/requests/OnboardingRequests.tsx index 4e8b9c9..f3cd546 100644 --- a/packages/tenants-react/src/components/requests/OnboardingRequests.tsx +++ b/packages/tenants-react/src/components/requests/OnboardingRequests.tsx @@ -1,7 +1,7 @@ +import { usePrettyAction } from "@shared/ui"; import { useEffect, useState } from "react"; import { User } from "supertokens-web-js/types"; -import { usePrettyAction } from "../../../../../shared/ui/src/hooks"; import { usePluginContext } from "../../plugin"; import { TenantUsersTable } from "../table/TenantTable"; import { NoUsers } from "../users/NoUsers"; diff --git a/packages/tenants-react/src/components/requests/TenantRequests.tsx b/packages/tenants-react/src/components/requests/TenantRequests.tsx index cbe5752..de5c9ec 100644 --- a/packages/tenants-react/src/components/requests/TenantRequests.tsx +++ b/packages/tenants-react/src/components/requests/TenantRequests.tsx @@ -4,6 +4,7 @@ import classNames from "classnames/bind"; import { usePluginContext } from "../../plugin"; import { TenantTab } from "../tab/TenantTab"; +import { CreationRequests } from "./CreationRequests"; import { OnboardingRequests } from "./OnboardingRequests"; import style from "./requests.module.scss"; @@ -26,7 +27,7 @@ export const TenantRequests = () => {
-
+
diff --git a/packages/tenants-react/src/components/requests/requests.module.scss b/packages/tenants-react/src/components/requests/requests.module.scss index f7bf896..0ca82c8 100644 --- a/packages/tenants-react/src/components/requests/requests.module.scss +++ b/packages/tenants-react/src/components/requests/requests.module.scss @@ -6,3 +6,23 @@ align-items: center; gap: 10px; } + +.tenantCreateActionWrapper { + display: flex; + align-items: center; + justify-content: space-between; + + .tenantName { + background-color: var(--neutral-color-neutral-3); + border-radius: 3px; + padding: 2px 8px; + + font-weight: 500; + font-style: Medium; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + vertical-align: middle; + color: var(--neutral-color-neutral-11); + } +} diff --git a/packages/tenants-react/src/components/table/TenantTable.tsx b/packages/tenants-react/src/components/table/TenantTable.tsx index 5a71286..db58f03 100644 --- a/packages/tenants-react/src/components/table/TenantTable.tsx +++ b/packages/tenants-react/src/components/table/TenantTable.tsx @@ -7,6 +7,9 @@ const cx = classNames.bind(style); type TableProps = { emailComponentTitle?: string; extraComponentTitle?: string; + // Optional widths. Accept any valid CSS width (%, px, fr not supported here). + emailWidth?: string; + extraWidth?: string; columns: { emailComponent: React.ReactNode; extraComponent?: React.ReactNode; @@ -16,10 +19,20 @@ type TableProps = { export const TenantUsersTable: React.FC = ({ emailComponentTitle = "Email", extraComponentTitle = undefined, + emailWidth, + extraWidth, columns, }) => { return ( -
+
{emailComponentTitle}
{extraComponentTitle &&
{extraComponentTitle}
} diff --git a/packages/tenants-react/src/components/table/table.module.scss b/packages/tenants-react/src/components/table/table.module.scss index aed3d1d..1d7e3e0 100644 --- a/packages/tenants-react/src/components/table/table.module.scss +++ b/packages/tenants-react/src/components/table/table.module.scss @@ -17,11 +17,11 @@ color: #60646c; .emailHeader { - width: 65%; + width: var(--email-col-width, 65%); } .extraHeader { - width: 35%; + width: var(--extra-col-width, 35%); } } @@ -33,11 +33,11 @@ align-items: center; .emailComponentWrapper { - width: 65%; + width: var(--email-col-width, 65%); } .extraComponentWrapper { - width: 35%; + width: var(--extra-col-width, 35%); } } } diff --git a/shared/tenants/src/types.ts b/shared/tenants/src/types.ts index c50e341..76254ca 100644 --- a/shared/tenants/src/types.ts +++ b/shared/tenants/src/types.ts @@ -1,4 +1,4 @@ -import { TenantConfig } from "supertokens-node/lib/build/recipe/multitenancy/types"; +import { User } from "supertokens-node"; export type TenantJoinData = { tenantId: string; @@ -29,6 +29,8 @@ export type TenantCreationRequest = { requestId: string; }; +export type TenantCreationRequestWithUser = TenantCreationRequest & { user: User }; + export type TenantMetadata = { invitees: InviteeDetails[]; }; From f13a95fd5b876e61150a9538e0587ad5559b634d Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 19 Sep 2025 08:07:19 +0530 Subject: [PATCH 19/25] fix: various issues with tenant invite --- .../components/invitations/AddInvitation.tsx | 7 +- .../src/components/invitations/Code.tsx | 2 +- .../invitations/invitations.module.scss | 66 +++++++++++++++++++ .../tenant-card/tenant-card.module.scss | 54 +++++++++++++++ .../components/tenant-card/tenant-card.tsx | 19 +++--- packages/tenants-react/src/plugin.tsx | 20 +++--- 6 files changed, 147 insertions(+), 21 deletions(-) create mode 100644 packages/tenants-react/src/components/invitations/invitations.module.scss create mode 100644 packages/tenants-react/src/components/tenant-card/tenant-card.module.scss diff --git a/packages/tenants-react/src/components/invitations/AddInvitation.tsx b/packages/tenants-react/src/components/invitations/AddInvitation.tsx index b9f7469..14c7bc9 100644 --- a/packages/tenants-react/src/components/invitations/AddInvitation.tsx +++ b/packages/tenants-react/src/components/invitations/AddInvitation.tsx @@ -21,8 +21,11 @@ export const AddInvitation: React.FC = ({ onCreate, selected const handleAddInvite = usePrettyAction( async () => { setIsSubmitting(true); - await onCreate(inviteEmail, selectedTenantId); - setIsSubmitting(false); + try { + await onCreate(inviteEmail, selectedTenantId); + } finally { + setIsSubmitting(false); + } setInviteEmail(""); }, [onCreate, inviteEmail, selectedTenantId], diff --git a/packages/tenants-react/src/components/invitations/Code.tsx b/packages/tenants-react/src/components/invitations/Code.tsx index 06dc7aa..2cba95d 100644 --- a/packages/tenants-react/src/components/invitations/Code.tsx +++ b/packages/tenants-react/src/components/invitations/Code.tsx @@ -22,7 +22,7 @@ export const Code: React.FC = ({ code, tenantId }) => { const handleCodeCopyClick = usePrettyAction( async () => { const origin = window.location.origin; - const urlToCopy = `${origin}/user/invite/accept?tenantid=${encodeURIComponent(tenantId)}&code=${encodeURIComponent( + const urlToCopy = `${origin}/user/invite/accept?tenantId=${encodeURIComponent(tenantId)}&code=${encodeURIComponent( code, )}`; try { diff --git a/packages/tenants-react/src/components/invitations/invitations.module.scss b/packages/tenants-react/src/components/invitations/invitations.module.scss new file mode 100644 index 0000000..ad5a9d0 --- /dev/null +++ b/packages/tenants-react/src/components/invitations/invitations.module.scss @@ -0,0 +1,66 @@ +.invitationDetailsChild { + border-radius: 12px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + border: 1px solid #dddde3; + background-color: #f9f9f8; + + .invitationDetailsChildHeader { + padding: 12px; + color: #60646c; + font-weight: var(--wa-font-weight-normal); + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + + .tenantName { + font-weight: var(--wa-font-weight-semibold); + font-style: Medium; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + } + } + + .invitationDetailsCodeContainer { + display: flex; + align-items: center; + color: #60646c; + padding: 12px; + background-color: #f2f2f0; + border-top: 1px solid #dddde3; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + + .invitationCodeContainer { + margin-left: 10px; + padding: 2px 8px; + border-radius: 9999px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + background: #00003b0d; + color: #0007139f; + } + } +} + +.invitationDetailsFooter { + display: flex; + justify-content: end; +} + +.invitationAcceptHeader { + font-weight: var(--wa-font-weight-extrabold); + font-style: Bold; + font-size: 28px; + line-height: 36px; + letter-spacing: -0.12px; + color: #1c2024; +} + +.invitationDetailsChild::part(header) { + padding: 0 !important; +} + +.invitationDetailsChild::part(body) { + padding: 0 !important; +} diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss b/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss new file mode 100644 index 0000000..5322d01 --- /dev/null +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.module.scss @@ -0,0 +1,54 @@ +.createTenantInputContainer { + border-radius: 12px; + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + border: 1px solid #dddde3; + background-color: #f2f2f0; + + .createTenantInputCardText { + padding: 12px; + color: #60646c; + font-weight: var(--wa-font-weight-normal); + font-style: Regular; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + } + + .createTenantInputWrapper { + display: flex; + align-items: center; + color: #60646c; + padding: 12px; + background-color: #f9f9f8; + border-top: 1px solid #dddde3; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + + .createTenantInput { + width: 100%; + } + } +} + +.createTenantHeader { + font-weight: var(--wa-font-weight-extrabold); + font-style: Bold; + font-size: 28px; + line-height: 36px; + letter-spacing: -0.12px; + color: #1c2024; +} + +.createTenantFooter { + display: flex; + justify-content: end; + align-items: center; +} + +.createTenantInputContainer::part(header) { + padding: 0 !important; +} + +.createTenantInputContainer::part(body) { + padding: 0 !important; +} diff --git a/packages/tenants-react/src/components/tenant-card/tenant-card.tsx b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx index 22a37cf..e4e0c9f 100644 --- a/packages/tenants-react/src/components/tenant-card/tenant-card.tsx +++ b/packages/tenants-react/src/components/tenant-card/tenant-card.tsx @@ -3,6 +3,10 @@ import { Button, Card, TextInput, usePrettyAction } from "@shared/ui"; import classNames from "classnames/bind"; import { useState } from "react"; +import style from "./tenant-card.module.scss"; + +const cx = classNames.bind(style); + interface TenantCardProps { data: TenantList; onJoin: (data: TenantJoinData) => Promise<{ status: "OK" } | { status: "ERROR"; message: string }>; @@ -13,10 +17,6 @@ interface TenantCardProps { } export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProps) => { - if (isLoading) { - return ; - } - const [newTenantName, setNewTenantName] = useState(""); const onSuccess = () => { @@ -54,9 +54,13 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp }, ); + if (isLoading) { + return ; + } + return ( - {/*
+
Create Tenant
@@ -64,8 +68,7 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp onClick={() => handleCreateAndJoin()} disabled={newTenantName.trim() === ""} variant="brand" - appearance="accent" - > + appearance="accent"> Create and Join
@@ -88,7 +91,7 @@ export const TenantCard = ({ data, onJoin, onCreate, isLoading }: TenantCardProp />
-
*/} +
); }; diff --git a/packages/tenants-react/src/plugin.tsx b/packages/tenants-react/src/plugin.tsx index 7b7a528..9edb475 100644 --- a/packages/tenants-react/src/plugin.tsx +++ b/packages/tenants-react/src/plugin.tsx @@ -11,10 +11,10 @@ import { BooleanClaim } from "supertokens-auth-react/recipe/session"; import { getApi } from "./api"; import { API_PATH, PLUGIN_ID } from "./constants"; import "./styles/global.css"; -// import { InvitationAcceptWrapper } from "./invitation-accept-wrapper"; +import { InvitationAcceptWrapper } from "./invitation-accept-wrapper"; import { enableDebugLogs } from "./logger"; import { TenantManagement } from "./pages/tenant-management/tenant-management"; -// import { SelectTenantPage } from "./select-tenant-page"; +import { SelectTenantPage } from "./select-tenant-page"; import { defaultTranslationsTenants } from "./translations"; import { SuperTokensPluginTenantsPluginConfig, @@ -147,14 +147,14 @@ export const init = createPluginInitFunction< return { status: "OK", routeHandlers: [ - // { - // path: "/user/tenants/select", - // handler: () => SelectTenantPage.call(null), - // }, - // { - // path: "/user/invite/accept", - // handler: () => InvitationAcceptWrapper.call(null), - // }, + { + path: "/user/tenants/select", + handler: () => SelectTenantPage.call(null), + }, + { + path: "/user/invite/accept", + handler: () => InvitationAcceptWrapper.call(null), + }, ], }; }, From 4cc5333c924bf0118a1926eaa9287b9d5a581c5e Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 22 Sep 2025 15:44:15 +0530 Subject: [PATCH 20/25] feat: add support for icon components support with custom SVG's --- package-lock.json | 571 +++++++++++++++++- .../src/components/icons/Copy.tsx | 3 +- .../src/components/icons/Eye.tsx | 5 +- .../src/components/icons/Trash.tsx | 3 +- shared/ui/README.md | 76 +++ shared/ui/build-scripts/process-icons.ts | 119 ++++ shared/ui/package.json | 7 +- shared/ui/src/components/icon/icon-manager.ts | 56 ++ .../ui/src/components/icon/icon-registry.ts | 30 + .../ui/src/components/icon/icon.module.scss | 46 ++ shared/ui/src/components/icon/icon.tsx | 9 + shared/ui/src/components/icon/index.ts | 2 + shared/ui/src/icons/README.md | 25 + shared/ui/src/icons/copy.svg | 4 + shared/ui/src/icons/eye-open.svg | 4 + shared/ui/src/icons/trash.svg | 4 + 16 files changed, 953 insertions(+), 11 deletions(-) create mode 100644 shared/ui/build-scripts/process-icons.ts create mode 100644 shared/ui/src/components/icon/icon-manager.ts create mode 100644 shared/ui/src/components/icon/icon-registry.ts create mode 100644 shared/ui/src/icons/README.md create mode 100644 shared/ui/src/icons/copy.svg create mode 100644 shared/ui/src/icons/eye-open.svg create mode 100644 shared/ui/src/icons/trash.svg diff --git a/package-lock.json b/package-lock.json index de347b5..7ce6a73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8767,6 +8767,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", @@ -12336,6 +12349,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -14715,6 +14738,536 @@ "webidl-conversions": "^4.0.2" } }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, "node_modules/turbo": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.5.5.tgz", @@ -17408,7 +17961,7 @@ }, "packages/tenant-discovery-nodejs": { "name": "@supertokens-plugins/tenant-discovery-nodejs", - "version": "0.0.1-beta.1", + "version": "0.1.0", "devDependencies": { "@shared/eslint": "*", "@shared/nodejs": "*", @@ -17438,7 +17991,7 @@ }, "packages/tenant-discovery-react": { "name": "@supertokens-plugins/tenant-discovery-react", - "version": "0.0.1-beta.1", + "version": "0.1.0", "dependencies": { "supertokens-js-override": "^0.0.4" }, @@ -18906,12 +19459,24 @@ "devDependencies": { "@shared/eslint": "*", "@shared/react": "*", - "@shared/tsconfig": "*" + "@shared/tsconfig": "*", + "@types/node": "^20.0.0", + "tsx": "^4.0.0" }, "peerDependencies": { "react": ">=18.3.1", "react-dom": ">=18.3.1" } + }, + "shared/ui/node_modules/@types/node": { + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } } } } diff --git a/packages/tenants-react/src/components/icons/Copy.tsx b/packages/tenants-react/src/components/icons/Copy.tsx index f24e473..ea95236 100644 --- a/packages/tenants-react/src/components/icons/Copy.tsx +++ b/packages/tenants-react/src/components/icons/Copy.tsx @@ -5,6 +5,5 @@ type CopyProps = { }; export const Copy: React.FC = ({ label }) => { - // TODO: Update with the actual icon - return ; + return ; }; diff --git a/packages/tenants-react/src/components/icons/Eye.tsx b/packages/tenants-react/src/components/icons/Eye.tsx index 21e60f2..846b75c 100644 --- a/packages/tenants-react/src/components/icons/Eye.tsx +++ b/packages/tenants-react/src/components/icons/Eye.tsx @@ -1,10 +1,9 @@ -import { Icon } from "@shared/ui"; +import { Icon } from "@shared/ui"; type EyeProps = { label?: string; }; export const Eye: React.FC = ({ label }) => { - // TODO: Update with the actual icon - return ; + return ; }; diff --git a/packages/tenants-react/src/components/icons/Trash.tsx b/packages/tenants-react/src/components/icons/Trash.tsx index baf8135..f95dfcc 100644 --- a/packages/tenants-react/src/components/icons/Trash.tsx +++ b/packages/tenants-react/src/components/icons/Trash.tsx @@ -5,6 +5,5 @@ type TrashProps = { }; export const Trash: React.FC = ({ label }) => { - // TODO: Update with the actual icon - return ; + return ; }; diff --git a/shared/ui/README.md b/shared/ui/README.md index 541ab93..bfc3a31 100644 --- a/shared/ui/README.md +++ b/shared/ui/README.md @@ -1 +1,77 @@ # shared/ui + +Share UI library for SuperTokens Plugin System + +## Bundled Icons Usage Guide + +This implementation provides a fully automated bundled icon system that works with webawesome without any network dependencies. + +### How It Works + +1. **Add SVG files** to `src/icons/` directory +2. **Build process** automatically converts them to data URLs and creates a registry +3. **Icon component** automatically initializes the bundled library when used +4. **Use icons** in your components with zero setup + +#### 1. Add Icons + +Drop your SVG files into `src/icons/`: + +``` +src/icons/ +├── user.svg +├── settings.svg +├── dashboard.svg +└── menu.svg +``` + +#### 2. Build + +```bash +npm run process-icons # Generate registry +npm run build # Full build (includes process-icons) +``` + +#### 3. Use Icons + +```tsx +import { Icon } from '@shared/ui'; + +// Use bundled icons + + + + +// Library defaults to "bundled" if no src is provided + +``` + +### Available Scripts + +- `npm run process-icons` - Generate icon registry from SVG files + +### Type Safety + +The build process generates TypeScript types for available icons: + +```tsx +import type { AvailableIconName } from "@shared/ui"; + +// This will give you autocomplete for available icon names +const iconName: AvailableIconName = "user"; // ✅ autocomplete works +const invalid: AvailableIconName = "invalid"; // ❌ TypeScript error +``` + +### Icon Guidelines + +- Use descriptive filenames (becomes the icon name) +- Include proper `viewBox` attribute +- Use `currentColor` for fills/strokes to support theming +- Keep files optimized and small +- Avoid special characters in filenames + +### Development Workflow + +1. Add new `.svg` files to `src/icons/` +2. Run `npm run process-icons` to update registry +3. Icons are immediately available: `` diff --git a/shared/ui/build-scripts/process-icons.ts b/shared/ui/build-scripts/process-icons.ts new file mode 100644 index 0000000..11df190 --- /dev/null +++ b/shared/ui/build-scripts/process-icons.ts @@ -0,0 +1,119 @@ +import fs from "fs"; +import path from "path"; + +interface IconData { + name: string; + dataUrl: string; +} + +function optimizeSvg(svgContent: string): string { + return ( + svgContent + .replace(/\s+/g, " ") + .replace(/>\s+<") + .replace(//g, "") + .replace(/\s*xmlns:.*?=".*?"/g, "") + .replace(/\s*xml:.*?=".*?"/g, "") + // Fix background rectangles by setting fill-opacity to 0 + .replace(/fill-opacity="0\.01"/g, 'fill-opacity="0"') + .replace(/fill-opacity="0\.02"/g, 'fill-opacity="0"') + .replace(/fill-opacity="0\.03"/g, 'fill-opacity="0"') + .replace(/fill-opacity="0\.04"/g, 'fill-opacity="0"') + .replace(/fill-opacity="0\.05"/g, 'fill-opacity="0"') + // Also handle any other small opacity values that might cause borders + .replace(/fill-opacity="0\.[0-9]+"/g, 'fill-opacity="0"') + // Remove fill from transparent rectangles to be extra safe + .replace(/]*)fill-opacity="0"([^>]*)fill="[^"]*"([^>]*)>/g, '') + .replace(/]*)fill="[^"]*"([^>]*)fill-opacity="0"([^>]*)>/g, '') + // Replace hardcoded colors with currentColor for theming + .replace(/fill="#[0-9a-fA-F]{6}"/g, 'fill="currentColor"') + .replace(/fill="#[0-9a-fA-F]{3}"/g, 'fill="currentColor"') + .replace(/stroke="#[0-9a-fA-F]{6}"/g, 'stroke="currentColor"') + .replace(/stroke="#[0-9a-fA-F]{3}"/g, 'stroke="currentColor"') + // Replace common color names with currentColor + .replace(/fill="black"/g, 'fill="currentColor"') + .replace(/stroke="black"/g, 'stroke="currentColor"') + // Remove stroke from the root SVG element to prevent borders (keep on individual elements) + .replace(/(]*)\s+stroke="[^"]*"([^>]*>)/g, "$1$2") + .replace(/(]*)\s+stroke-width="[^"]*"([^>]*>)/g, "$1$2") + // Remove empty groups and definitions + .replace(/]*><\/g>/g, "") + .replace(/]*><\/defs>/g, "") + .trim() + ); +} + +function processIcons(iconsDir: string): IconData[] { + if (!fs.existsSync(iconsDir)) { + console.warn(`Icons directory not found: ${iconsDir}`); + return []; + } + + const iconFiles = fs.readdirSync(iconsDir).filter((file) => file.endsWith(".svg") && !file.startsWith(".")); + + if (iconFiles.length === 0) { + console.warn(`No SVG files found in: ${iconsDir}`); + return []; + } + + console.log(`Processing ${iconFiles.length} icons...`); + + return iconFiles.map((file) => { + const filePath = path.join(iconsDir, file); + const svgContent = fs.readFileSync(filePath, "utf-8"); + + // Optimize SVG content + const optimizedSvg = optimizeSvg(svgContent); + + // Create data URL + const dataUrl = `data:image/svg+xml,${encodeURIComponent(optimizedSvg)}`; + + const iconName = path.basename(file, ".svg"); + console.log(` ✓ Processed: ${iconName}`); + + return { + name: iconName, + dataUrl, + }; + }); +} + +function generateIconRegistry() { + const iconsDir = path.join(__dirname, "../src/icons"); + const outputDir = path.join(__dirname, "../src/components/icon"); + const outputFile = path.join(outputDir, "icon-registry.ts"); + + // Ensure output directory exists + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // Process icons + const icons = processIcons(iconsDir); + + // Generate TypeScript file + const registryContent = `// Auto-generated icon registry +// This file is generated by build-scripts/process-icons.ts +// Do not edit manually - add SVG files to src/icons/ instead + +export interface IconData { + name: string; + dataUrl: string; +} + +export const iconRegistry: IconData[] = ${JSON.stringify(icons, null, 2)}; + +export const availableIcons = [ +${icons.map((icon) => ` '${icon.name}'`).join(",\n")} +] as const; + +export type AvailableIconName = typeof availableIcons[number]; +`; + + fs.writeFileSync(outputFile, registryContent); + console.log(`Generated icon registry: ${outputFile}`); + console.log(`Total icons: ${icons.length}`); +} + +// Run the script +generateIconRegistry(); diff --git a/shared/ui/package.json b/shared/ui/package.json index 0b6b893..9b90bfb 100644 --- a/shared/ui/package.json +++ b/shared/ui/package.json @@ -5,6 +5,9 @@ "exports": { ".": "./src/index.ts" }, + "scripts": { + "process-icons": "tsx build-scripts/process-icons.ts" + }, "dependencies": { "@awesome.me/webawesome": "^3.0.0-beta.4", "classnames": "^2.5.1" @@ -16,6 +19,8 @@ "devDependencies": { "@shared/eslint": "*", "@shared/tsconfig": "*", - "@shared/react": "*" + "@shared/react": "*", + "tsx": "^4.0.0", + "@types/node": "^20.0.0" } } diff --git a/shared/ui/src/components/icon/icon-manager.ts b/shared/ui/src/components/icon/icon-manager.ts new file mode 100644 index 0000000..19c6ca7 --- /dev/null +++ b/shared/ui/src/components/icon/icon-manager.ts @@ -0,0 +1,56 @@ +import { registerIconLibrary } from "@awesome.me/webawesome"; +import { iconRegistry, availableIcons } from "./icon-registry"; + +export class IconManager { + private static initialized = false; + + static async initialize(): Promise { + if (this.initialized) return true; + + if (iconRegistry.length === 0) { + console.warn('No bundled icons available. Add SVG files to src/icons/ and run "npm run process-icons".'); + return false; + } + + // Create a Map for fast lookup + const iconMap = new Map(iconRegistry.map((icon) => [icon.name, icon.dataUrl])); + + // Register the bundled icon library with webawesome + registerIconLibrary("bundled", { + resolver: (name: string) => { + const dataUrl = iconMap.get(name); + if (!dataUrl) { + console.warn( + `Icon "${name}" not found in bundled library. Available icons: ${this.getAvailableIcons().join(", ")}`, + ); + return ""; + } + return dataUrl; + }, + mutator: (svg) => { + // Simple, clean approach - just ensure proper theming + svg.setAttribute("fill", "currentColor"); + }, + }); + + this.initialized = true; + console.log(`Bundled icon library initialized with ${iconRegistry.length} icons`); + return true; + } + + static getAvailableIcons(): string[] { + return [...availableIcons]; + } + + static isIconAvailable(name: string): boolean { + return availableIcons.includes(name); + } + + static getIconCount(): number { + return iconRegistry.length; + } + + static isInitialized(): boolean { + return this.initialized; + } +} diff --git a/shared/ui/src/components/icon/icon-registry.ts b/shared/ui/src/components/icon/icon-registry.ts new file mode 100644 index 0000000..afffbb9 --- /dev/null +++ b/shared/ui/src/components/icon/icon-registry.ts @@ -0,0 +1,30 @@ +// Auto-generated icon registry +// This file is generated by build-scripts/process-icons.ts +// Do not edit manually - add SVG files to src/icons/ instead + +export interface IconData { + name: string; + dataUrl: string; +} + +export const iconRegistry: IconData[] = [ + { + name: "copy", + dataUrl: + "data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20%20fill-opacity%3D%220%22%2F%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M1.06668%2010.1334C1.06668%2011.017%201.78302%2011.7334%202.66668%2011.7334H4.26668V10.6668H2.66668C2.37213%2010.6668%202.13335%2010.4279%202.13335%2010.1334V2.66671C2.13335%202.37216%202.37213%202.13338%202.66668%202.13338H10.1333C10.4279%202.13338%2010.6667%202.37216%2010.6667%202.66671V4.26667H5.86668C4.98303%204.26667%204.26668%204.98301%204.26668%205.86667V13.3333C4.26668%2014.2169%204.98303%2014.9333%205.86668%2014.9333H13.3333C14.217%2014.9333%2014.9333%2014.2169%2014.9333%2013.3333V5.86667C14.9333%204.98301%2014.217%204.26667%2013.3333%204.26667H11.7333V2.66671C11.7333%201.78305%2011.017%201.06671%2010.1333%201.06671H2.66668C1.78302%201.06671%201.06668%201.78305%201.06668%202.66671V10.1334ZM5.33335%205.86667C5.33335%205.57212%205.57213%205.33334%205.86668%205.33334H13.3333C13.6279%205.33334%2013.8667%205.57212%2013.8667%205.86667V13.3333C13.8667%2013.6279%2013.6279%2013.8666%2013.3333%2013.8666H5.86668C5.57213%2013.8666%205.33335%2013.6279%205.33335%2013.3333V5.86667Z%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E", + }, + { + name: "eye-open", + dataUrl: + "data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20%20fill-opacity%3D%220%22%2F%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M8%2011.7333C5.12304%2011.7333%202.69815%2010.2633%201.1693%208.00002C2.69815%205.73672%205.12304%204.26668%208%204.26668C10.8769%204.26668%2013.3019%205.73672%2014.8307%208.00002C13.3019%2010.2633%2010.8769%2011.7333%208%2011.7333ZM8%203.20001C4.59505%203.20001%201.76682%205.02015%200.0810669%207.71736C-0.0270227%207.89031%20-0.0270223%208.10975%200.0810682%208.28269C1.76682%2010.9799%204.59505%2012.8%208%2012.8C11.4049%2012.8%2014.2332%2010.9799%2015.9189%208.28269C16.027%208.10975%2016.027%207.89031%2015.9189%207.71736C14.2332%205.02015%2011.4049%203.20001%208%203.20001ZM8%2010.1333C9.17821%2010.1333%2010.1333%209.17822%2010.1333%208.00001C10.1333%206.8218%209.17821%205.86668%208%205.86668C6.82179%205.86668%205.86667%206.8218%205.86667%208.00001C5.86667%209.17822%206.82179%2010.1333%208%2010.1333Z%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E", + }, + { + name: "trash", + dataUrl: + "data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20%20fill-opacity%3D%220%22%2F%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5.86668%201.06668C5.57213%201.06668%205.33335%201.30546%205.33335%201.60001C5.33335%201.89456%205.57213%202.13335%205.86668%202.13335H10.1333C10.4279%202.13335%2010.6667%201.89456%2010.6667%201.60001C10.6667%201.30546%2010.4279%201.06668%2010.1333%201.06668H5.86668ZM3.20001%203.73335C3.20001%203.4388%203.4388%203.20001%203.73335%203.20001H5.33335H10.6667H12.2667C12.5612%203.20001%2012.8%203.4388%2012.8%203.73335C12.8%204.0279%2012.5612%204.26668%2012.2667%204.26668H11.7333V12.8C11.7333%2013.3891%2011.2558%2013.8667%2010.6667%2013.8667H5.33335C4.74425%2013.8667%204.26668%2013.3891%204.26668%2012.8V4.26668H3.73335C3.4388%204.26668%203.20001%204.0279%203.20001%203.73335ZM5.33335%204.26668H10.6667V12.8H5.33335V4.26668Z%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E", + }, +]; + +export const availableIcons = ["copy", "eye-open", "trash"] as const; + +export type AvailableIconName = (typeof availableIcons)[number]; diff --git a/shared/ui/src/components/icon/icon.module.scss b/shared/ui/src/components/icon/icon.module.scss index 48d2e17..5a47904 100644 --- a/shared/ui/src/components/icon/icon.module.scss +++ b/shared/ui/src/components/icon/icon.module.scss @@ -1,4 +1,50 @@ +// Very aggressive stroke removal - targets all possible selectors +:global(wa-icon), +:global(wa-icon) :global(svg), +:global(wa-icon) :global(svg) :global(*) { + stroke: none !important; + stroke-width: 0 !important; + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +// Ensure fill is used instead +:global(wa-icon) :global(svg) { + fill: currentColor !important; +} + :global(.plugin-profile) { .st-icon { + stroke: none !important; + stroke-width: 0 !important; + border: none !important; + outline: none !important; + box-shadow: none !important; + background: transparent !important; + + // Target the internal SVG element + :global(svg) { + stroke: none !important; + stroke-width: 0 !important; + border: none !important; + outline: none !important; + box-shadow: none !important; + background: transparent !important; + fill: currentColor !important; + } + + // Target all elements inside SVG + :global(svg) :global(*) { + stroke: none !important; + stroke-width: 0 !important; + } + + // Specifically target path elements + :global(svg) :global(path) { + stroke: none !important; + stroke-width: 0 !important; + fill: currentColor !important; + } } } diff --git a/shared/ui/src/components/icon/icon.tsx b/shared/ui/src/components/icon/icon.tsx index 40baed6..f8882db 100644 --- a/shared/ui/src/components/icon/icon.tsx +++ b/shared/ui/src/components/icon/icon.tsx @@ -1,7 +1,9 @@ import classNames from "classnames/bind"; +import { useEffect } from "react"; import styles from "./icon.module.scss"; import { useWebComponent } from "../utils"; import { HTMLElementProps } from "../types"; +import { IconManager } from "./icon-manager"; const cx = classNames.bind(styles); @@ -23,6 +25,13 @@ export const Icon = (_props: IconProps) => { importCallback: () => import("@awesome.me/webawesome/dist/components/icon/icon.js"), }); + // Initialize bundled icons when component mounts + useEffect(() => { + if (_props.library === "bundled") { + IconManager.initialize().catch(console.error); + } + }, [_props.library]); + if (!isDefined) return null; return ; diff --git a/shared/ui/src/components/icon/index.ts b/shared/ui/src/components/icon/index.ts index 74a3d6c..ef15dea 100644 --- a/shared/ui/src/components/icon/index.ts +++ b/shared/ui/src/components/icon/index.ts @@ -1 +1,3 @@ export { Icon } from "./icon"; +export { IconManager } from "./icon-manager"; +export type { IconProps } from "./icon"; diff --git a/shared/ui/src/icons/README.md b/shared/ui/src/icons/README.md new file mode 100644 index 0000000..9538805 --- /dev/null +++ b/shared/ui/src/icons/README.md @@ -0,0 +1,25 @@ +# Icons Directory + +Add your SVG icon files here. They will be automatically processed and bundled at build time. + +## Usage + +1. Add `.svg` files to this directory +2. Run `npm run build` or `npm run process-icons` +3. Use icons in components: + ```tsx + + ``` + +## Guidelines + +- Use descriptive filenames (they become the icon names) +- SVG files should have proper `viewBox` attributes +- Use `currentColor` for fill/stroke to enable theming +- Keep files small and optimized + +## Available Icons + +- `copy` - Copy/duplicate icon +- `eye-open` - Visibility/show icon +- `trash` - Delete/remove icon diff --git a/shared/ui/src/icons/copy.svg b/shared/ui/src/icons/copy.svg new file mode 100644 index 0000000..550e187 --- /dev/null +++ b/shared/ui/src/icons/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shared/ui/src/icons/eye-open.svg b/shared/ui/src/icons/eye-open.svg new file mode 100644 index 0000000..021d4be --- /dev/null +++ b/shared/ui/src/icons/eye-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shared/ui/src/icons/trash.svg b/shared/ui/src/icons/trash.svg new file mode 100644 index 0000000..1596cc7 --- /dev/null +++ b/shared/ui/src/icons/trash.svg @@ -0,0 +1,4 @@ + + + + From 65391feedc8e7a138793d1d5ffcab3f6a861c902 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Tue, 23 Sep 2025 13:02:57 +0530 Subject: [PATCH 21/25] feat: add init port for tenant enrollment plugin --- .../tenant-enrollment-nodejs/.eslintrc.js | 10 + .../tenant-enrollment-nodejs/.prettierrc.js | 4 + .../tenant-enrollment-nodejs/CHANGELOG.md | 5 + .../tenant-enrollment-nodejs/package.json | 42 +++ .../tenant-enrollment-nodejs/src/constants.ts | 6 + .../tenant-enrollment-nodejs/src/index.ts | 4 + .../tenant-enrollment-nodejs/src/logger.ts | 4 + .../tenant-enrollment-nodejs/src/plugin.ts | 342 ++++++++++++++++++ .../src/recipeImplementation.ts | 170 +++++++++ .../tenant-enrollment-nodejs/src/types.ts | 62 ++++ .../tenant-enrollment-nodejs/tsconfig.json | 13 + packages/tenant-enrollment-react/.eslintrc.js | 14 + .../tenant-enrollment-react/.prettierrc.js | 4 + packages/tenant-enrollment-react/CHANGELOG.md | 5 + packages/tenant-enrollment-react/package.json | 54 +++ packages/tenant-enrollment-react/src/api.ts | 5 + .../tenant-enrollment-react/src/constants.ts | 3 + packages/tenant-enrollment-react/src/index.ts | 4 + .../tenant-enrollment-react/src/logger.ts | 5 + .../tenant-enrollment-react/src/plugin.tsx | 97 +++++ .../src/translations.ts | 4 + packages/tenant-enrollment-react/src/types.ts | 5 + .../tenant-enrollment-react/tsconfig.json | 13 + .../tenant-enrollment-react/vite.config.ts | 38 ++ .../tenant-enrollment-react/vitest.config.ts | 9 + 25 files changed, 922 insertions(+) create mode 100644 packages/tenant-enrollment-nodejs/.eslintrc.js create mode 100644 packages/tenant-enrollment-nodejs/.prettierrc.js create mode 100644 packages/tenant-enrollment-nodejs/CHANGELOG.md create mode 100644 packages/tenant-enrollment-nodejs/package.json create mode 100644 packages/tenant-enrollment-nodejs/src/constants.ts create mode 100644 packages/tenant-enrollment-nodejs/src/index.ts create mode 100644 packages/tenant-enrollment-nodejs/src/logger.ts create mode 100644 packages/tenant-enrollment-nodejs/src/plugin.ts create mode 100644 packages/tenant-enrollment-nodejs/src/recipeImplementation.ts create mode 100644 packages/tenant-enrollment-nodejs/src/types.ts create mode 100644 packages/tenant-enrollment-nodejs/tsconfig.json create mode 100644 packages/tenant-enrollment-react/.eslintrc.js create mode 100644 packages/tenant-enrollment-react/.prettierrc.js create mode 100644 packages/tenant-enrollment-react/CHANGELOG.md create mode 100644 packages/tenant-enrollment-react/package.json create mode 100644 packages/tenant-enrollment-react/src/api.ts create mode 100644 packages/tenant-enrollment-react/src/constants.ts create mode 100644 packages/tenant-enrollment-react/src/index.ts create mode 100644 packages/tenant-enrollment-react/src/logger.ts create mode 100644 packages/tenant-enrollment-react/src/plugin.tsx create mode 100644 packages/tenant-enrollment-react/src/translations.ts create mode 100644 packages/tenant-enrollment-react/src/types.ts create mode 100644 packages/tenant-enrollment-react/tsconfig.json create mode 100644 packages/tenant-enrollment-react/vite.config.ts create mode 100644 packages/tenant-enrollment-react/vitest.config.ts diff --git a/packages/tenant-enrollment-nodejs/.eslintrc.js b/packages/tenant-enrollment-nodejs/.eslintrc.js new file mode 100644 index 0000000..26db86f --- /dev/null +++ b/packages/tenant-enrollment-nodejs/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: [require.resolve('@shared/eslint/node.js')], + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + ignorePatterns: ['**/*.test.ts', '**/*.spec.ts'], +}; diff --git a/packages/tenant-enrollment-nodejs/.prettierrc.js b/packages/tenant-enrollment-nodejs/.prettierrc.js new file mode 100644 index 0000000..8986fc5 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/.prettierrc.js @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + ...require("@shared/eslint/prettier"), +}; diff --git a/packages/tenant-enrollment-nodejs/CHANGELOG.md b/packages/tenant-enrollment-nodejs/CHANGELOG.md new file mode 100644 index 0000000..490e78b --- /dev/null +++ b/packages/tenant-enrollment-nodejs/CHANGELOG.md @@ -0,0 +1,5 @@ +# @supertokens-plugins/tenant-enrollment-nodejs + +## 0.1.0 + +- Add the initial node tenant enrollment plugin diff --git a/packages/tenant-enrollment-nodejs/package.json b/packages/tenant-enrollment-nodejs/package.json new file mode 100644 index 0000000..15274e4 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/package.json @@ -0,0 +1,42 @@ +{ + "name": "@supertokens-plugins/tenant-enrollment-nodejs", + "version": "0.1.0", + "description": "Tenant Enrollment Plugin for SuperTokens", + "homepage": "https://github.com/supertokens/supertokens-plugins/blob/main/packages/tenant-enrollment-nodejs/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/supertokens/supertokens-plugins.git", + "directory": "packages/tenant-enrollment-nodejs" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts", + "pretty": "npx pretty-quick .", + "pretty-check": "npx pretty-quick --check .", + "test": "TEST_MODE=testing vitest run --pool=forks --passWithNoTests" + }, + "keywords": [ + "tenant-enrollment", + "plugin", + "supertokens" + ], + "dependencies": {}, + "peerDependencies": { + "supertokens-node": ">=23.0.0" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/tsconfig": "*", + "@types/react": "^17.0.20", + "express": "^5.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4", + "@shared/nodejs": "*" + }, + "browser": { + "fs": false + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/packages/tenant-enrollment-nodejs/src/constants.ts b/packages/tenant-enrollment-nodejs/src/constants.ts new file mode 100644 index 0000000..059159c --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/constants.ts @@ -0,0 +1,6 @@ +export const PLUGIN_ID = "supertokens-plugin-tenant-enrollment"; +export const PLUGIN_VERSION = "0.0.1"; + +export const PLUGIN_SDK_VERSION = ["23.0.0", "23.0.1", ">=23.0.1"]; + +export const HANDLE_BASE_PATH = `/plugin/${PLUGIN_ID}`; diff --git a/packages/tenant-enrollment-nodejs/src/index.ts b/packages/tenant-enrollment-nodejs/src/index.ts new file mode 100644 index 0000000..4080b43 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/index.ts @@ -0,0 +1,4 @@ +import { init } from "./plugin"; +export { init }; +export { PLUGIN_ID } from "./constants"; +export default { init }; \ No newline at end of file diff --git a/packages/tenant-enrollment-nodejs/src/logger.ts b/packages/tenant-enrollment-nodejs/src/logger.ts new file mode 100644 index 0000000..5943019 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/logger.ts @@ -0,0 +1,4 @@ +import { buildLogger } from "@shared/nodejs"; +import { PLUGIN_ID, PLUGIN_VERSION } from "./constants"; + +export const { logDebugMessage, enableDebugLogs } = buildLogger(PLUGIN_ID, PLUGIN_VERSION); diff --git a/packages/tenant-enrollment-nodejs/src/plugin.ts b/packages/tenant-enrollment-nodejs/src/plugin.ts new file mode 100644 index 0000000..53d4d25 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/plugin.ts @@ -0,0 +1,342 @@ +import { SuperTokensPlugin } from 'supertokens-node'; +import { createPluginInitFunction } from '@shared/js'; +import { PLUGIN_ID, PLUGIN_SDK_VERSION } from './constants'; +import { + OverrideableTenantFunctionImplementation, + SuperTokensPluginTenantEnrollmentPluginConfig, + SuperTokensPluginTenantEnrollmentPluginNormalisedConfig, +} from './types'; +import { getOverrideableTenantFunctionImplementation } from './recipeImplementation'; +import { logDebugMessage } from 'supertokens-node/lib/build/logger'; +import { + AssociateAllLoginMethodsOfUserWithTenant, + PLUGIN_ID as TENANTS_PLUGIN_ID, + SendPluginEmail, + GetAppUrl, +} from '@supertokens-plugins/tenants-nodejs'; +import { listUsersByAccountInfo } from 'supertokens-node'; +import { NormalisedAppinfo } from 'supertokens-node/types'; +import { enableDebugLogs } from './logger'; + +export const init = createPluginInitFunction< + SuperTokensPlugin, + SuperTokensPluginTenantEnrollmentPluginConfig, + OverrideableTenantFunctionImplementation, + SuperTokensPluginTenantEnrollmentPluginNormalisedConfig +>( + (pluginConfig, implementation) => { + let associateLoginMethodDef: AssociateAllLoginMethodsOfUserWithTenant; + let sendEmail: SendPluginEmail; + let appInfo: NormalisedAppinfo; + let getAppUrlDef: GetAppUrl; + return { + id: PLUGIN_ID, + compatibleSDKVersions: PLUGIN_SDK_VERSION, + init: (appConfig, plugins) => { + if (appConfig.debug) { + enableDebugLogs(); + } + + const tenantsPlugin = plugins.find((plugin: any) => plugin.id === TENANTS_PLUGIN_ID); + if (!tenantsPlugin) { + throw new Error('Base Tenants plugin not initialized, cannot continue.'); + } + + if (!tenantsPlugin.exports) { + throw new Error('Base Tenants plugin does not export, cannot continue.'); + } + + const associateAllLoginMethodsOfUserWithTenant = + tenantsPlugin.exports?.associateAllLoginMethodsOfUserWithTenant; + if (!associateAllLoginMethodsOfUserWithTenant) { + throw new Error('Tenants plugin does not export associateAllLoginMethodsOfUserWithTenant, cannot continue.'); + } + + const sendPluginEmail = tenantsPlugin.exports?.sendEmail; + if (!sendPluginEmail) { + throw new Error('Tenants plugin does not export sendEmail, cannot continue.'); + } + + const getUserIdsInTenantWithRole = tenantsPlugin.exports?.getUserIdsInTenantWithRole; + if (!getUserIdsInTenantWithRole) { + throw new Error('Tenants plugin does not export getUserIdsInTenantWithRole, cannot continue.'); + } + + associateLoginMethodDef = associateAllLoginMethodsOfUserWithTenant; + sendEmail = sendPluginEmail; + implementation.getUserIdsInTenantWithRole = getUserIdsInTenantWithRole; + + const getAppUrl = tenantsPlugin.exports?.getAppUrl; + if (!getAppUrl) { + throw new Error('Tenants plugin does not export getAppUrl, cannot continue'); + } + + getAppUrlDef = getAppUrl; + appInfo = appConfig.appInfo; + }, + routeHandlers: () => { + return { + status: 'OK', + routeHandlers: [], + }; + }, + overrideMap: { + emailpassword: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + signUp: async (input) => { + const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { + type: 'email', + email: input.email, + }); + logDebugMessage('Reason: ' + reason); + if (!canJoin) { + return { + status: 'LINKING_TO_SESSION_USER_FAILED', + reason: 'EMAIL_VERIFICATION_REQUIRED', + }; + } + + const response = await originalImplementation.signUp(input); + if (response.status !== 'OK') { + return response; + } + + const { wasAddedToTenant, reason: tenantJoiningReason } = + await implementation.handleTenantJoiningApproval( + response.user, + input.tenantId, + associateLoginMethodDef, + sendEmail, + getAppUrlDef(appInfo, undefined, input.userContext), + input.userContext, + ); + return { + ...response, + wasAddedToTenant, + reason: tenantJoiningReason, + }; + }, + }; + }, + apis: (originalImplementation) => { + return { + ...originalImplementation, + signUpPOST: async (input) => { + const response = await originalImplementation.signUpPOST!(input); + if (response.status === 'SIGN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_013')) { + // There is a possibility that the user is not allowed + // to signup to the tenant so we will have to update the message + // accordingly. + return { + ...response, + reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", + }; + } + + return response; + }, + }; + }, + }, + thirdparty: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + signInUpPOST: async (input) => { + const response = await originalImplementation.signInUpPOST!(input); + if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_020')) { + return { + ...response, + reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", + }; + } + + return response; + }, + }; + }, + functions: (originalImplementation) => { + return { + ...originalImplementation, + signInUp: async (input) => { + // Check if the user is signing up (i.e doesn't exist already) + // and only then apply the checks. Otherwise, we can skip. + const accountInfoResponse = await listUsersByAccountInfo(input.tenantId, { + thirdParty: { + id: input.thirdPartyId, + userId: input.thirdPartyUserId, + }, + }); + const isSignUp = accountInfoResponse.length === 0; + + if (!isSignUp) { + return originalImplementation.signInUp(input); + } + + const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { + type: 'thirdParty', + thirdPartyId: input.thirdPartyId, + }); + logDebugMessage('Reason: ' + reason); + if (!canJoin) { + return { + status: 'LINKING_TO_SESSION_USER_FAILED', + reason: 'EMAIL_VERIFICATION_REQUIRED', + }; + } + + const response = await originalImplementation.signInUp(input); + if (response.status !== 'OK') { + return response; + } + + const { wasAddedToTenant, reason: tenantJoiningReason } = + await implementation.handleTenantJoiningApproval( + response.user, + input.tenantId, + associateLoginMethodDef, + sendEmail, + getAppUrlDef(appInfo, undefined, input.userContext), + input.userContext, + ); + return { + ...response, + wasAddedToTenant, + reason: tenantJoiningReason, + }; + }, + }; + }, + }, + passwordless: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + createCodePOST: async (input) => { + const response = await originalImplementation.createCodePOST!(input); + if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_002')) { + return { + ...response, + reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", + } as any; + } + + return response; + }, + consumeCodePOST: async (input) => { + const response = await originalImplementation.consumeCodePOST!(input); + if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_002')) { + return { + ...response, + reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", + } as any; + } + + return response; + }, + }; + }, + functions: (originalImplementation) => { + return { + ...originalImplementation, + createCode: async (input) => { + // If this is a signup, we need to check if the user + // can signup to the tenant. + const accountInfoResponse = await listUsersByAccountInfo(input.tenantId, { + email: 'email' in input ? input.email : undefined, + phoneNumber: 'phoneNumber' in input ? input.phoneNumber : undefined, + }); + const isSignUp = accountInfoResponse.length === 0; + + if (!isSignUp) { + return originalImplementation.createCode(input); + } + + // If this is a signup but its through phone number, we cannot + // restrict it so we will let it go through. + if ('phoneNumber' in input) { + return originalImplementation.createCode(input); + } + + const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { + type: 'email', + email: input.email, + }); + logDebugMessage('Reason: ' + reason); + + if (!canJoin) { + return { + status: 'SIGN_IN_UP_NOT_ALLOWED', + } as any; + } + + return originalImplementation.createCode(input); + }, + consumeCode: async (input) => { + // If this is a signup, we need to check if the user + // can signup to the tenant. + // We will need to fetch the details of the user from the + // deviceId. + const deviceInfo = await originalImplementation.listCodesByPreAuthSessionId({ + tenantId: input.tenantId, + preAuthSessionId: input.preAuthSessionId, + userContext: input.userContext, + }); + + if (!deviceInfo) { + // This is handled in the consumeCode but we can handle + // it here as well + return { + status: 'RESTART_FLOW_ERROR', + }; + } + + const accountInfoResponse = await listUsersByAccountInfo( + input.tenantId, + deviceInfo.phoneNumber !== undefined + ? { + phoneNumber: deviceInfo.phoneNumber!, + } + : { + email: deviceInfo.email!, + }, + ); + const isSignUp = accountInfoResponse.length === 0; + + // If this is a signup or its through phone number, we cannot + // restrict it so we will let it go through. + if (!isSignUp || deviceInfo.phoneNumber !== undefined) { + return originalImplementation.consumeCode(input); + } + + // Since this is a signup, we need to check if the user + // can signup to the tenant. + const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { + type: 'email', + email: deviceInfo.email!, + }); + logDebugMessage('Reason: ' + reason); + + if (!canJoin) { + return { + status: 'SIGN_IN_UP_NOT_ALLOWED', + } as any; + } + + return originalImplementation.consumeCode(input); + }, + }; + }, + }, + }, + }; + }, + getOverrideableTenantFunctionImplementation, + (config) => ({ + emailDomainToTenantIdMap: config.emailDomainToTenantIdMap, + inviteOnlyTenants: config.inviteOnlyTenants ?? [], + requiresApprovalTenants: config.requiresApprovalTenants ?? [], + }), +); diff --git a/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts new file mode 100644 index 0000000..cbe8bb6 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts @@ -0,0 +1,170 @@ +import { User } from 'supertokens-node'; +import { OverrideableTenantFunctionImplementation, SuperTokensPluginTenantEnrollmentPluginConfig } from './types'; +import { + assignRoleToUserInTenant, + AssociateAllLoginMethodsOfUserWithTenant, + SendPluginEmail, +} from '@supertokens-plugins/tenants-nodejs'; +import { ROLES } from '@shared/tenants'; +import SuperTokens from 'supertokens-node'; +import { UserContext } from 'supertokens-node/lib/build/types'; + +export const getOverrideableTenantFunctionImplementation = ( + config: SuperTokensPluginTenantEnrollmentPluginConfig, +): OverrideableTenantFunctionImplementation => { + const implementation: OverrideableTenantFunctionImplementation = { + canUserJoinTenant: async (tenantId, emailOrThirdPartyId) => { + /** + * Check if the user can join the tenant based on the email domain + * + * @param email - The email of the user + * @param tenantId - The id of the tenant + * @returns true if the user can join the tenant, false otherwise + */ + + // Skip this for the public tenant + if (tenantId === 'public') { + return { + canJoin: true, + reason: undefined, + }; + } + + // Check if the tenant is invite only in which case we + // can't allow the user to join + if (implementation.isTenantInviteOnly(tenantId)) { + return { + canJoin: false, + reason: 'INVITE_ONLY', + }; + } + + let canJoin = false; + let reason = undefined; + if (emailOrThirdPartyId.type === 'email') { + canJoin = implementation.isMatchingEmailDomain(tenantId, emailOrThirdPartyId.email); + if (!canJoin) { + reason = 'EMAIL_DOMAIN_NOT_ALLOWED'; + } + } else if (emailOrThirdPartyId.type === 'thirdParty') { + canJoin = implementation.isApprovedIdPProvider(tenantId, emailOrThirdPartyId.thirdPartyId); + if (!canJoin) { + reason = 'IDP_NOT_ALLOWED'; + } + } + + return { + canJoin, + reason, + }; + }, + handleTenantJoiningApproval: async ( + user: User, + tenantId: string, + associateLoginMethodDef: AssociateAllLoginMethodsOfUserWithTenant, + sendEmail: SendPluginEmail, + appUrl: string, + userContext: UserContext, + ) => { + /** + * Handle the tenant joining functionality for the user. + * + * If the tenant requires approval, we will add a request for the + * user. + * If the tenant doesn't require approval, we will add them as a member + * right away. + * + * @param user - The user to handle the tenant joining for + * @param tenantId - The id of the tenant to handle the tenant joining for + * @param associateLoginMethodDef - The function to associate the login methods of the user with the tenant + */ + // Skip this for the public tenant + if (tenantId === 'public') { + return { + wasAddedToTenant: true, + reason: undefined, + }; + } + + // If the tenant doesn't require approval, add the user as a member + // and return. + if (!implementation.doesTenantRequireApproval(tenantId)) { + await assignRoleToUserInTenant(tenantId, user.id, ROLES.MEMBER); + return { + wasAddedToTenant: true, + }; + } + + // If the tenant requires approval, add a request for the user + // and return. + await associateLoginMethodDef(tenantId, user.id); + + await implementation.sendTenantJoiningRequestEmail(tenantId, user, appUrl, sendEmail, userContext); + + return { + wasAddedToTenant: false, + reason: 'REQUIRES_APPROVAL', + }; + }, + isTenantInviteOnly: (tenantId) => { + return config.inviteOnlyTenants?.includes(tenantId) ?? false; + }, + doesTenantRequireApproval: (tenantId) => { + return config.requiresApprovalTenants?.includes(tenantId) ?? false; + }, + isApprovedIdPProvider: (thirdPartyId) => { + return thirdPartyId.startsWith('boxy-saml'); + }, + isMatchingEmailDomain: (tenantId, email) => { + const emailDomain = email.split('@'); + if (emailDomain.length !== 2) { + return false; + } + + const parsedTenantId = config.emailDomainToTenantIdMap[emailDomain[1]!.toLowerCase()]; + return parsedTenantId === tenantId; + }, + sendTenantJoiningRequestEmail: async (tenantId, user, appUrl, sendEmail, userContext) => { + /** + * Send an email to all the admins of the tenant + * + * @param tenantId - The id of the tenant to send the email to + * @param user - The user who is requesting to join the tenant + * @param sendEmail - The function to send the email + */ + const adminUsers = await implementation.getUserIdsInTenantWithRole(tenantId, ROLES.ADMIN); + + // For each of the users, we will need to find their email address. + const adminEmails = await Promise.all( + adminUsers.map(async (userId) => { + const userDetails = await SuperTokens.getUser(userId); + // TODO: Handle multiple emails? + return userDetails?.emails[0]; + }), + ); + + // Send emails to all tenant admins using Promise.all + await Promise.all( + adminEmails + .filter((email) => email !== undefined) + .map(async (email) => { + await sendEmail( + { + type: 'TENANT_REQUEST_APPROVAL', + email, + tenantId, + senderEmail: user.emails[0], + appUrl, + }, + userContext, + ); + }), + ); + }, + getUserIdsInTenantWithRole: async (tenantId, role) => { + throw new Error('Not implemented'); + }, + }; + + return implementation; +}; diff --git a/packages/tenant-enrollment-nodejs/src/types.ts b/packages/tenant-enrollment-nodejs/src/types.ts new file mode 100644 index 0000000..0c7fd73 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/src/types.ts @@ -0,0 +1,62 @@ +import { User } from 'supertokens-node'; +import { + AssociateAllLoginMethodsOfUserWithTenant, + GetUserIdsInTenantWithRole, + SendPluginEmail, +} from '@supertokens-plugins/tenants-nodejs'; +import { UserContext } from 'supertokens-node/lib/build/types'; + +export type SuperTokensPluginTenantEnrollmentPluginConfig = { + emailDomainToTenantIdMap: Record; + inviteOnlyTenants?: string[]; + requiresApprovalTenants?: string[]; +}; + +export type SuperTokensPluginTenantEnrollmentPluginNormalisedConfig = { + emailDomainToTenantIdMap: Record; + inviteOnlyTenants: string[]; + requiresApprovalTenants: string[]; +}; + +export type EmailOrThirdPartyId = + | { + type: 'email'; + email: string; + } + | { + type: 'thirdParty'; + thirdPartyId: string; + }; + +export type OverrideableTenantFunctionImplementation = { + canUserJoinTenant: ( + tenantId: string, + emailOrThirdPartyId: EmailOrThirdPartyId, + ) => Promise<{ + canJoin: boolean; + reason?: string; + }>; + handleTenantJoiningApproval: ( + user: User, + tenantId: string, + associateLoginMethodDef: AssociateAllLoginMethodsOfUserWithTenant, + sendEmail: SendPluginEmail, + appUrl: string, + userContext: UserContext, + ) => Promise<{ + wasAddedToTenant: boolean; + reason?: string; + }>; + isTenantInviteOnly: (tenantId: string) => boolean; + doesTenantRequireApproval: (tenantId: string) => boolean; + isApprovedIdPProvider: (tenantId: string, thirdPartyId: string) => boolean; + isMatchingEmailDomain: (tenantId: string, email: string) => boolean; + sendTenantJoiningRequestEmail: ( + tenantId: string, + user: User, + appUrl: string, + sendEmail: SendPluginEmail, + userContext: UserContext, + ) => Promise; + getUserIdsInTenantWithRole: GetUserIdsInTenantWithRole; +}; diff --git a/packages/tenant-enrollment-nodejs/tsconfig.json b/packages/tenant-enrollment-nodejs/tsconfig.json new file mode 100644 index 0000000..06f8f7b --- /dev/null +++ b/packages/tenant-enrollment-nodejs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@shared/tsconfig/node.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist", + "types": ["node"], + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/packages/tenant-enrollment-react/.eslintrc.js b/packages/tenant-enrollment-react/.eslintrc.js new file mode 100644 index 0000000..6a19d70 --- /dev/null +++ b/packages/tenant-enrollment-react/.eslintrc.js @@ -0,0 +1,14 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: [require.resolve('@shared/eslint/react.js')], + parserOptions: { + project: true, + }, + rules: { + // Temporarily disable this rule due to a bug with mapped types + '@typescript-eslint/no-unused-vars': 'off', + // Disable global type warnings for third-party types + 'no-undef': 'off', + }, + ignorePatterns: ['**/*.test.ts', '**/*.spec.ts', 'tests/**/*'], +}; diff --git a/packages/tenant-enrollment-react/.prettierrc.js b/packages/tenant-enrollment-react/.prettierrc.js new file mode 100644 index 0000000..8986fc5 --- /dev/null +++ b/packages/tenant-enrollment-react/.prettierrc.js @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + ...require("@shared/eslint/prettier"), +}; diff --git a/packages/tenant-enrollment-react/CHANGELOG.md b/packages/tenant-enrollment-react/CHANGELOG.md new file mode 100644 index 0000000..3a37a45 --- /dev/null +++ b/packages/tenant-enrollment-react/CHANGELOG.md @@ -0,0 +1,5 @@ +# @supertokens-plugins/tenant-enrollment-react + +## 0.1.0 + +- Add the initial tenant enrollment react plugin diff --git a/packages/tenant-enrollment-react/package.json b/packages/tenant-enrollment-react/package.json new file mode 100644 index 0000000..5cf82f0 --- /dev/null +++ b/packages/tenant-enrollment-react/package.json @@ -0,0 +1,54 @@ +{ + "name": "@supertokens-plugins/tenant-enrollment-react", + "version": "0.1.0", + "description": "Tenant Enrollment Plugin for SuperTokens", + "homepage": "https://github.com/supertokens/supertokens-plugins/blob/main/packages/tenant-enrollment-react/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/supertokens/supertokens-plugins.git", + "directory": "packages/tenant-enrollment-react" + }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest --watch", + "build": "vite build && npm run pretty", + "pretty": "npx pretty-quick .", + "pretty-check": "npx pretty-quick --check ." + }, + "keywords": [ + "tenant-enrollment", + "plugin", + "supertokens" + ], + "dependencies": { + "supertokens-js-override": "^0.0.4" + }, + "peerDependencies": { + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "supertokens-auth-react": ">=0.50.0", + "supertokens-web-js": ">=0.16.0" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/tsconfig": "*", + "@testing-library/jest-dom": "^6.1.0", + "@types/react": "^17.0.20", + "jsdom": "^26.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^1.3.1", + "@shared/js": "*", + "@shared/react": "*", + "vite": "^6.3.5", + "@vitejs/plugin-react": "^4.5.2", + "vite-plugin-dts": "^4.5.4", + "rollup-plugin-peer-deps-external": "^2.2.4" + }, + "browser": { + "fs": false + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/packages/tenant-enrollment-react/src/api.ts b/packages/tenant-enrollment-react/src/api.ts new file mode 100644 index 0000000..4568a66 --- /dev/null +++ b/packages/tenant-enrollment-react/src/api.ts @@ -0,0 +1,5 @@ +import { getQuerier } from "@shared/react"; + +export const getApi = (querier: ReturnType) => { + return {}; +}; diff --git a/packages/tenant-enrollment-react/src/constants.ts b/packages/tenant-enrollment-react/src/constants.ts new file mode 100644 index 0000000..dcfb201 --- /dev/null +++ b/packages/tenant-enrollment-react/src/constants.ts @@ -0,0 +1,3 @@ +export const PLUGIN_ID = "supertokens-plugin-tenant-enrollment"; +export const PLUGIN_VERSION = "0.0.1"; +export const API_PATH = `plugin/${PLUGIN_ID}`; diff --git a/packages/tenant-enrollment-react/src/index.ts b/packages/tenant-enrollment-react/src/index.ts new file mode 100644 index 0000000..6f85b0d --- /dev/null +++ b/packages/tenant-enrollment-react/src/index.ts @@ -0,0 +1,4 @@ +import { PLUGIN_ID } from "./constants"; +import { init, usePluginContext } from "./plugin"; +export { init, PLUGIN_ID, usePluginContext }; +export default { init }; diff --git a/packages/tenant-enrollment-react/src/logger.ts b/packages/tenant-enrollment-react/src/logger.ts new file mode 100644 index 0000000..37cdd77 --- /dev/null +++ b/packages/tenant-enrollment-react/src/logger.ts @@ -0,0 +1,5 @@ +import { buildLogger } from "@shared/react"; + +import { PLUGIN_ID, PLUGIN_VERSION } from "./constants"; + +export const { logDebugMessage, enableDebugLogs } = buildLogger(PLUGIN_ID, PLUGIN_VERSION); diff --git a/packages/tenant-enrollment-react/src/plugin.tsx b/packages/tenant-enrollment-react/src/plugin.tsx new file mode 100644 index 0000000..f62a87e --- /dev/null +++ b/packages/tenant-enrollment-react/src/plugin.tsx @@ -0,0 +1,97 @@ +import { createPluginInitFunction } from "@shared/js"; +import { buildContext, getQuerier } from "@shared/react"; +import { useState } from "react"; +import { + SuperTokensPlugin, + SuperTokensPublicConfig, + SuperTokensPublicPlugin, + getTranslationFunction, +} from "supertokens-auth-react"; + +import { getApi } from "./api"; +import { PLUGIN_ID, API_PATH } from "./constants"; +import { enableDebugLogs } from "./logger"; +import { defaultTranslationsTenantEnrollment } from "./translations"; +import { SuperTokensPluginTenantEnrollmentPluginConfig, TranslationKeys } from "./types"; + + +const { usePluginContext, setContext } = buildContext<{ + plugins: SuperTokensPublicPlugin[]; + sdkVersion: string; + appConfig: SuperTokensPublicConfig; + pluginConfig: SuperTokensPluginTenantEnrollmentPluginConfig; + querier: ReturnType; + api: ReturnType; + t: (key: TranslationKeys) => string; + functions: null; +}>(); +export { usePluginContext }; + + +export const init = createPluginInitFunction< + SuperTokensPlugin, + SuperTokensPluginTenantEnrollmentPluginConfig, + {}, + // NOTE: Update the following type if we update the type to accept any values + SuperTokensPluginTenantEnrollmentPluginConfig +>((pluginConfig) => { + return { + id: PLUGIN_ID, + init: (config, plugins, sdkVersion) => { + if (config.enableDebugLogs) { + enableDebugLogs(); + } + + const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); + const api = getApi(querier); + + // Set up the usePlugin hook + const apiBasePath = new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString(); + const translations = getTranslationFunction(defaultTranslationsTenantEnrollment); + + setContext({ + plugins, + sdkVersion, + appConfig: config, + pluginConfig, + querier, + api, + t: translations, + functions: null, + }); + }, + routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { + return { + status: "OK", + routeHandlers: [ + // Add route handlers here + // Example: + // { + // path: '/example-page', + // handler: () => ExamplePage.call(null), + // }, + ], + }; + }, + overrideMap: { + // Add recipe overrides here + // Example: + // emailpassword: { + // functions: (originalImplementation) => ({ + // ...originalImplementation, + // // Override functions here + // }), + // }, + }, + generalAuthRecipeComponentOverrides: { + // Add component overrides here + // Example: + // AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { + // return ; + // }, + }, + }; +}, +{}, +(pluginConfig) => pluginConfig +); diff --git a/packages/tenant-enrollment-react/src/translations.ts b/packages/tenant-enrollment-react/src/translations.ts new file mode 100644 index 0000000..cb29933 --- /dev/null +++ b/packages/tenant-enrollment-react/src/translations.ts @@ -0,0 +1,4 @@ +export const defaultTranslationsTenantEnrollment = { + en: { + }, +} as const; diff --git a/packages/tenant-enrollment-react/src/types.ts b/packages/tenant-enrollment-react/src/types.ts new file mode 100644 index 0000000..c976b18 --- /dev/null +++ b/packages/tenant-enrollment-react/src/types.ts @@ -0,0 +1,5 @@ +import { defaultTranslationsTenantEnrollment } from "./translations"; + +export type SuperTokensPluginTenantEnrollmentPluginConfig = {}; + +export type TranslationKeys = keyof (typeof defaultTranslationsTenantEnrollment)["en"]; \ No newline at end of file diff --git a/packages/tenant-enrollment-react/tsconfig.json b/packages/tenant-enrollment-react/tsconfig.json new file mode 100644 index 0000000..9ce578d --- /dev/null +++ b/packages/tenant-enrollment-react/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@shared/tsconfig/react.json", + "compilerOptions": { + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "outDir": "./dist", + "declaration": true, + "declarationDir": "./dist", + "noUnusedLocals": false, + "noImplicitAny": false + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.tsx", "src/**/*.spec.tsx", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/packages/tenant-enrollment-react/vite.config.ts b/packages/tenant-enrollment-react/vite.config.ts new file mode 100644 index 0000000..7d43469 --- /dev/null +++ b/packages/tenant-enrollment-react/vite.config.ts @@ -0,0 +1,38 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import dts from 'vite-plugin-dts'; +import peerDepsExternal from 'rollup-plugin-peer-deps-external'; +import * as path from 'path'; +import packageJson from './package.json'; + +export default defineConfig(() => { + return { + root: __dirname, + plugins: [ + react(), + dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.json') }), + peerDepsExternal(), + ], + + build: { + outDir: 'dist', + sourcemap: false, + emptyOutDir: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + fileName: 'index', + name: packageJson.name, + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es' as const, 'cjs' as const], + }, + rollupOptions: { + cache: false, + }, + }, + }; +}); diff --git a/packages/tenant-enrollment-react/vitest.config.ts b/packages/tenant-enrollment-react/vitest.config.ts new file mode 100644 index 0000000..b45b161 --- /dev/null +++ b/packages/tenant-enrollment-react/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "jsdom", + setupFiles: ["./tests/setup.ts"], + }, +}); \ No newline at end of file From 98c7d48e6cb0606f1c3ef26baa1a20376cfaae95 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Wed, 24 Sep 2025 11:48:27 +0530 Subject: [PATCH 22/25] fix: formatting of plugin --- .../tenant-enrollment-nodejs/src/plugin.ts | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/tenant-enrollment-nodejs/src/plugin.ts b/packages/tenant-enrollment-nodejs/src/plugin.ts index 53d4d25..15ef5da 100644 --- a/packages/tenant-enrollment-nodejs/src/plugin.ts +++ b/packages/tenant-enrollment-nodejs/src/plugin.ts @@ -1,22 +1,22 @@ -import { SuperTokensPlugin } from 'supertokens-node'; -import { createPluginInitFunction } from '@shared/js'; -import { PLUGIN_ID, PLUGIN_SDK_VERSION } from './constants'; +import { SuperTokensPlugin } from "supertokens-node"; +import { createPluginInitFunction } from "@shared/js"; +import { PLUGIN_ID, PLUGIN_SDK_VERSION } from "./constants"; import { OverrideableTenantFunctionImplementation, SuperTokensPluginTenantEnrollmentPluginConfig, SuperTokensPluginTenantEnrollmentPluginNormalisedConfig, -} from './types'; -import { getOverrideableTenantFunctionImplementation } from './recipeImplementation'; -import { logDebugMessage } from 'supertokens-node/lib/build/logger'; +} from "./types"; +import { getOverrideableTenantFunctionImplementation } from "./recipeImplementation"; +import { logDebugMessage } from "supertokens-node/lib/build/logger"; import { AssociateAllLoginMethodsOfUserWithTenant, PLUGIN_ID as TENANTS_PLUGIN_ID, SendPluginEmail, GetAppUrl, -} from '@supertokens-plugins/tenants-nodejs'; -import { listUsersByAccountInfo } from 'supertokens-node'; -import { NormalisedAppinfo } from 'supertokens-node/types'; -import { enableDebugLogs } from './logger'; +} from "@supertokens-plugins/tenants-nodejs"; +import { listUsersByAccountInfo } from "supertokens-node"; +import { NormalisedAppinfo } from "supertokens-node/types"; +import { enableDebugLogs } from "./logger"; export const init = createPluginInitFunction< SuperTokensPlugin, @@ -39,27 +39,27 @@ export const init = createPluginInitFunction< const tenantsPlugin = plugins.find((plugin: any) => plugin.id === TENANTS_PLUGIN_ID); if (!tenantsPlugin) { - throw new Error('Base Tenants plugin not initialized, cannot continue.'); + throw new Error("Base Tenants plugin not initialized, cannot continue."); } if (!tenantsPlugin.exports) { - throw new Error('Base Tenants plugin does not export, cannot continue.'); + throw new Error("Base Tenants plugin does not export, cannot continue."); } const associateAllLoginMethodsOfUserWithTenant = tenantsPlugin.exports?.associateAllLoginMethodsOfUserWithTenant; if (!associateAllLoginMethodsOfUserWithTenant) { - throw new Error('Tenants plugin does not export associateAllLoginMethodsOfUserWithTenant, cannot continue.'); + throw new Error("Tenants plugin does not export associateAllLoginMethodsOfUserWithTenant, cannot continue."); } const sendPluginEmail = tenantsPlugin.exports?.sendEmail; if (!sendPluginEmail) { - throw new Error('Tenants plugin does not export sendEmail, cannot continue.'); + throw new Error("Tenants plugin does not export sendEmail, cannot continue."); } const getUserIdsInTenantWithRole = tenantsPlugin.exports?.getUserIdsInTenantWithRole; if (!getUserIdsInTenantWithRole) { - throw new Error('Tenants plugin does not export getUserIdsInTenantWithRole, cannot continue.'); + throw new Error("Tenants plugin does not export getUserIdsInTenantWithRole, cannot continue."); } associateLoginMethodDef = associateAllLoginMethodsOfUserWithTenant; @@ -68,7 +68,7 @@ export const init = createPluginInitFunction< const getAppUrl = tenantsPlugin.exports?.getAppUrl; if (!getAppUrl) { - throw new Error('Tenants plugin does not export getAppUrl, cannot continue'); + throw new Error("Tenants plugin does not export getAppUrl, cannot continue"); } getAppUrlDef = getAppUrl; @@ -76,7 +76,7 @@ export const init = createPluginInitFunction< }, routeHandlers: () => { return { - status: 'OK', + status: "OK", routeHandlers: [], }; }, @@ -87,19 +87,19 @@ export const init = createPluginInitFunction< ...originalImplementation, signUp: async (input) => { const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { - type: 'email', + type: "email", email: input.email, }); - logDebugMessage('Reason: ' + reason); + logDebugMessage("Reason: " + reason); if (!canJoin) { return { - status: 'LINKING_TO_SESSION_USER_FAILED', - reason: 'EMAIL_VERIFICATION_REQUIRED', + status: "LINKING_TO_SESSION_USER_FAILED", + reason: "EMAIL_VERIFICATION_REQUIRED", }; } const response = await originalImplementation.signUp(input); - if (response.status !== 'OK') { + if (response.status !== "OK") { return response; } @@ -125,7 +125,7 @@ export const init = createPluginInitFunction< ...originalImplementation, signUpPOST: async (input) => { const response = await originalImplementation.signUpPOST!(input); - if (response.status === 'SIGN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_013')) { + if (response.status === "SIGN_UP_NOT_ALLOWED" && response.reason.includes("ERR_CODE_013")) { // There is a possibility that the user is not allowed // to signup to the tenant so we will have to update the message // accordingly. @@ -146,7 +146,7 @@ export const init = createPluginInitFunction< ...originalImplementation, signInUpPOST: async (input) => { const response = await originalImplementation.signInUpPOST!(input); - if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_020')) { + if (response.status === "SIGN_IN_UP_NOT_ALLOWED" && response.reason.includes("ERR_CODE_020")) { return { ...response, reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", @@ -176,19 +176,19 @@ export const init = createPluginInitFunction< } const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { - type: 'thirdParty', + type: "thirdParty", thirdPartyId: input.thirdPartyId, }); - logDebugMessage('Reason: ' + reason); + logDebugMessage("Reason: " + reason); if (!canJoin) { return { - status: 'LINKING_TO_SESSION_USER_FAILED', - reason: 'EMAIL_VERIFICATION_REQUIRED', + status: "LINKING_TO_SESSION_USER_FAILED", + reason: "EMAIL_VERIFICATION_REQUIRED", }; } const response = await originalImplementation.signInUp(input); - if (response.status !== 'OK') { + if (response.status !== "OK") { return response; } @@ -216,7 +216,7 @@ export const init = createPluginInitFunction< ...originalImplementation, createCodePOST: async (input) => { const response = await originalImplementation.createCodePOST!(input); - if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_002')) { + if (response.status === "SIGN_IN_UP_NOT_ALLOWED" && response.reason.includes("ERR_CODE_002")) { return { ...response, reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", @@ -227,7 +227,7 @@ export const init = createPluginInitFunction< }, consumeCodePOST: async (input) => { const response = await originalImplementation.consumeCodePOST!(input); - if (response.status === 'SIGN_IN_UP_NOT_ALLOWED' && response.reason.includes('ERR_CODE_002')) { + if (response.status === "SIGN_IN_UP_NOT_ALLOWED" && response.reason.includes("ERR_CODE_002")) { return { ...response, reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", @@ -245,8 +245,8 @@ export const init = createPluginInitFunction< // If this is a signup, we need to check if the user // can signup to the tenant. const accountInfoResponse = await listUsersByAccountInfo(input.tenantId, { - email: 'email' in input ? input.email : undefined, - phoneNumber: 'phoneNumber' in input ? input.phoneNumber : undefined, + email: "email" in input ? input.email : undefined, + phoneNumber: "phoneNumber" in input ? input.phoneNumber : undefined, }); const isSignUp = accountInfoResponse.length === 0; @@ -256,19 +256,19 @@ export const init = createPluginInitFunction< // If this is a signup but its through phone number, we cannot // restrict it so we will let it go through. - if ('phoneNumber' in input) { + if ("phoneNumber" in input) { return originalImplementation.createCode(input); } const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { - type: 'email', + type: "email", email: input.email, }); - logDebugMessage('Reason: ' + reason); + logDebugMessage("Reason: " + reason); if (!canJoin) { return { - status: 'SIGN_IN_UP_NOT_ALLOWED', + status: "SIGN_IN_UP_NOT_ALLOWED", } as any; } @@ -289,7 +289,7 @@ export const init = createPluginInitFunction< // This is handled in the consumeCode but we can handle // it here as well return { - status: 'RESTART_FLOW_ERROR', + status: "RESTART_FLOW_ERROR", }; } @@ -297,11 +297,11 @@ export const init = createPluginInitFunction< input.tenantId, deviceInfo.phoneNumber !== undefined ? { - phoneNumber: deviceInfo.phoneNumber!, - } + phoneNumber: deviceInfo.phoneNumber!, + } : { - email: deviceInfo.email!, - }, + email: deviceInfo.email!, + }, ); const isSignUp = accountInfoResponse.length === 0; @@ -314,14 +314,14 @@ export const init = createPluginInitFunction< // Since this is a signup, we need to check if the user // can signup to the tenant. const { canJoin, reason } = await implementation.canUserJoinTenant(input.tenantId, { - type: 'email', + type: "email", email: deviceInfo.email!, }); - logDebugMessage('Reason: ' + reason); + logDebugMessage("Reason: " + reason); if (!canJoin) { return { - status: 'SIGN_IN_UP_NOT_ALLOWED', + status: "SIGN_IN_UP_NOT_ALLOWED", } as any; } From 241de14d261e737fadc84b500010d932973e6689 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Thu, 25 Sep 2025 15:59:20 +0530 Subject: [PATCH 23/25] feat: add support for showing tenant join approval pending screen --- package-lock.json | 339 ++++++++++++++++++ package.json | 3 +- .../tenant-enrollment-nodejs/package.json | 6 +- .../tenant-enrollment-nodejs/src/plugin.ts | 53 +-- .../src/recipeImplementation.ts | 40 +-- .../tenant-enrollment-nodejs/vite.config.ts | 37 ++ packages/tenant-enrollment-react/src/css.d.ts | 29 ++ .../awaiting-approval.module.scss | 30 ++ .../awaiting-approval/awaiting-approval.tsx | 23 ++ .../src/pages/awaiting-approval/index.ts | 1 + .../tenant-enrollment-react/src/plugin.tsx | 121 ++++--- .../src/translations.ts | 4 + packages/tenants-nodejs/package.json | 16 +- packages/tenants-react/package.json | 16 +- 14 files changed, 589 insertions(+), 129 deletions(-) create mode 100644 packages/tenant-enrollment-nodejs/vite.config.ts create mode 100644 packages/tenant-enrollment-react/src/css.d.ts create mode 100644 packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss create mode 100644 packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx create mode 100644 packages/tenant-enrollment-react/src/pages/awaiting-approval/index.ts diff --git a/package-lock.json b/package-lock.json index 7ce6a73..7953bd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@shared/js": "*", "@shared/nodejs": "*", "@shared/react": "*", + "@supertokens-plugins/tenants-nodejs": "*", "tsup": "^8.5.0" }, "devDependencies": { @@ -4281,6 +4282,14 @@ "resolved": "packages/tenant-discovery-react", "link": true }, + "node_modules/@supertokens-plugins/tenant-enrollment-nodejs": { + "resolved": "packages/tenant-enrollment-nodejs", + "link": true + }, + "node_modules/@supertokens-plugins/tenant-enrollment-react": { + "resolved": "packages/tenant-enrollment-react", + "link": true + }, "node_modules/@supertokens-plugins/tenants-nodejs": { "resolved": "packages/tenants-nodejs", "link": true @@ -18288,6 +18297,336 @@ } } }, + "packages/tenant-enrollment-nodejs": { + "name": "@supertokens-plugins/tenant-enrollment-nodejs", + "version": "0.1.0", + "devDependencies": { + "@shared/eslint": "*", + "@shared/nodejs": "*", + "@shared/tsconfig": "*", + "@types/react": "^17.0.20", + "express": "^5.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "@supertokens-plugins/tenants-nodejs": "*", + "supertokens-node": ">=23.0.0" + } + }, + "packages/tenant-enrollment-nodejs/node_modules/@types/react": { + "version": "17.0.88", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", + "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "packages/tenant-enrollment-react": { + "name": "@supertokens-plugins/tenant-enrollment-react", + "version": "0.1.0", + "dependencies": { + "supertokens-js-override": "^0.0.4" + }, + "devDependencies": { + "@shared/eslint": "*", + "@shared/js": "*", + "@shared/react": "*", + "@shared/tsconfig": "*", + "@testing-library/jest-dom": "^6.1.0", + "@types/react": "^17.0.20", + "@vitejs/plugin-react": "^4.5.2", + "jsdom": "^26.1.0", + "prettier": "3.6.2", + "pretty-quick": "^4.2.2", + "rollup-plugin-peer-deps-external": "^2.2.4", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vite-plugin-dts": "^4.5.4", + "vitest": "^1.3.1" + }, + "peerDependencies": { + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "supertokens-auth-react": ">=0.50.0", + "supertokens-web-js": ">=0.16.0" + } + }, + "packages/tenant-enrollment-react/node_modules/@types/react": { + "version": "17.0.88", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.88.tgz", + "integrity": "sha512-HEOvpzcFWkEcHq4EsTChnpimRc3Lz1/qzYRDFtobFp4obVa6QVjCDMjWmkgxgaTYttNvyjnldY8MUflGp5YiUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "packages/tenant-enrollment-react/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "packages/tenant-enrollment-react/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "packages/tenant-enrollment-react/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/tenant-enrollment-react/node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "packages/tenant-enrollment-react/node_modules/vitest/node_modules/vite": { + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "packages/tenants-nodejs": { "name": "@supertokens-plugins/tenants-nodejs", "version": "0.0.1", diff --git a/package.json b/package.json index ad35b8d..14103c7 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@shared/js": "*", "@shared/nodejs": "*", "@shared/react": "*", - "tsup": "^8.5.0" + "tsup": "^8.5.0", + "@supertokens-plugins/tenants-nodejs": "*" } } diff --git a/packages/tenant-enrollment-nodejs/package.json b/packages/tenant-enrollment-nodejs/package.json index 15274e4..c8c9ccf 100644 --- a/packages/tenant-enrollment-nodejs/package.json +++ b/packages/tenant-enrollment-nodejs/package.json @@ -9,7 +9,7 @@ "directory": "packages/tenant-enrollment-nodejs" }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", + "build": "vite build && npm run pretty", "pretty": "npx pretty-quick .", "pretty-check": "npx pretty-quick --check .", "test": "TEST_MODE=testing vitest run --pool=forks --passWithNoTests" @@ -19,9 +19,9 @@ "plugin", "supertokens" ], - "dependencies": {}, "peerDependencies": { - "supertokens-node": ">=23.0.0" + "supertokens-node": ">=23.0.0", + "@supertokens-plugins/tenants-nodejs": "*" }, "devDependencies": { "@shared/eslint": "*", diff --git a/packages/tenant-enrollment-nodejs/src/plugin.ts b/packages/tenant-enrollment-nodejs/src/plugin.ts index 15ef5da..fdf481d 100644 --- a/packages/tenant-enrollment-nodejs/src/plugin.ts +++ b/packages/tenant-enrollment-nodejs/src/plugin.ts @@ -98,25 +98,7 @@ export const init = createPluginInitFunction< }; } - const response = await originalImplementation.signUp(input); - if (response.status !== "OK") { - return response; - } - - const { wasAddedToTenant, reason: tenantJoiningReason } = - await implementation.handleTenantJoiningApproval( - response.user, - input.tenantId, - associateLoginMethodDef, - sendEmail, - getAppUrlDef(appInfo, undefined, input.userContext), - input.userContext, - ); - return { - ...response, - wasAddedToTenant, - reason: tenantJoiningReason, - }; + return originalImplementation.signUp(input); }, }; }, @@ -135,7 +117,30 @@ export const init = createPluginInitFunction< }; } - return response; + logDebugMessage(`Got response status for signup: ${response.status}`); + if (response.status !== "OK") { + return response; + } + + logDebugMessage("Going ahead with checking tenant joining approval"); + const { wasAddedToTenant, reason: tenantJoiningReason } = + await implementation.handleTenantJoiningApproval( + response.user, + input.tenantId, + associateLoginMethodDef, + sendEmail, + getAppUrlDef(appInfo, undefined, input.userContext), + input.userContext, + ); + logDebugMessage(`wasAdded: ${wasAddedToTenant}`); + logDebugMessage(`reason: ${tenantJoiningReason}`); + return { + status: "PENDING_APPROVAL", + wasAddedToTenant, + reason: tenantJoiningReason, + }; + + // return response; }, }; }, @@ -297,11 +302,11 @@ export const init = createPluginInitFunction< input.tenantId, deviceInfo.phoneNumber !== undefined ? { - phoneNumber: deviceInfo.phoneNumber!, - } + phoneNumber: deviceInfo.phoneNumber!, + } : { - email: deviceInfo.email!, - }, + email: deviceInfo.email!, + }, ); const isSignUp = accountInfoResponse.length === 0; diff --git a/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts index cbe8bb6..3ca464b 100644 --- a/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts +++ b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts @@ -1,13 +1,13 @@ -import { User } from 'supertokens-node'; -import { OverrideableTenantFunctionImplementation, SuperTokensPluginTenantEnrollmentPluginConfig } from './types'; +import { User } from "supertokens-node"; +import { OverrideableTenantFunctionImplementation, SuperTokensPluginTenantEnrollmentPluginConfig } from "./types"; import { assignRoleToUserInTenant, AssociateAllLoginMethodsOfUserWithTenant, SendPluginEmail, -} from '@supertokens-plugins/tenants-nodejs'; -import { ROLES } from '@shared/tenants'; -import SuperTokens from 'supertokens-node'; -import { UserContext } from 'supertokens-node/lib/build/types'; +} from "@supertokens-plugins/tenants-nodejs"; +import { ROLES } from "@shared/tenants"; +import SuperTokens from "supertokens-node"; +import { UserContext } from "supertokens-node/lib/build/types"; export const getOverrideableTenantFunctionImplementation = ( config: SuperTokensPluginTenantEnrollmentPluginConfig, @@ -23,7 +23,7 @@ export const getOverrideableTenantFunctionImplementation = ( */ // Skip this for the public tenant - if (tenantId === 'public') { + if (tenantId === "public") { return { canJoin: true, reason: undefined, @@ -35,21 +35,21 @@ export const getOverrideableTenantFunctionImplementation = ( if (implementation.isTenantInviteOnly(tenantId)) { return { canJoin: false, - reason: 'INVITE_ONLY', + reason: "INVITE_ONLY", }; } let canJoin = false; let reason = undefined; - if (emailOrThirdPartyId.type === 'email') { + if (emailOrThirdPartyId.type === "email") { canJoin = implementation.isMatchingEmailDomain(tenantId, emailOrThirdPartyId.email); if (!canJoin) { - reason = 'EMAIL_DOMAIN_NOT_ALLOWED'; + reason = "EMAIL_DOMAIN_NOT_ALLOWED"; } - } else if (emailOrThirdPartyId.type === 'thirdParty') { + } else if (emailOrThirdPartyId.type === "thirdParty") { canJoin = implementation.isApprovedIdPProvider(tenantId, emailOrThirdPartyId.thirdPartyId); if (!canJoin) { - reason = 'IDP_NOT_ALLOWED'; + reason = "IDP_NOT_ALLOWED"; } } @@ -79,7 +79,7 @@ export const getOverrideableTenantFunctionImplementation = ( * @param associateLoginMethodDef - The function to associate the login methods of the user with the tenant */ // Skip this for the public tenant - if (tenantId === 'public') { + if (tenantId === "public") { return { wasAddedToTenant: true, reason: undefined, @@ -99,11 +99,11 @@ export const getOverrideableTenantFunctionImplementation = ( // and return. await associateLoginMethodDef(tenantId, user.id); - await implementation.sendTenantJoiningRequestEmail(tenantId, user, appUrl, sendEmail, userContext); + // await implementation.sendTenantJoiningRequestEmail(tenantId, user, appUrl, sendEmail, userContext); return { wasAddedToTenant: false, - reason: 'REQUIRES_APPROVAL', + reason: "REQUIRES_APPROVAL", }; }, isTenantInviteOnly: (tenantId) => { @@ -113,10 +113,10 @@ export const getOverrideableTenantFunctionImplementation = ( return config.requiresApprovalTenants?.includes(tenantId) ?? false; }, isApprovedIdPProvider: (thirdPartyId) => { - return thirdPartyId.startsWith('boxy-saml'); + return thirdPartyId.startsWith("boxy-saml"); }, isMatchingEmailDomain: (tenantId, email) => { - const emailDomain = email.split('@'); + const emailDomain = email.split("@"); if (emailDomain.length !== 2) { return false; } @@ -150,10 +150,10 @@ export const getOverrideableTenantFunctionImplementation = ( .map(async (email) => { await sendEmail( { - type: 'TENANT_REQUEST_APPROVAL', + type: "TENANT_REQUEST_APPROVAL", email, tenantId, - senderEmail: user.emails[0], + senderEmail: user.emails[0]!, appUrl, }, userContext, @@ -162,7 +162,7 @@ export const getOverrideableTenantFunctionImplementation = ( ); }, getUserIdsInTenantWithRole: async (tenantId, role) => { - throw new Error('Not implemented'); + throw new Error("Not implemented"); }, }; diff --git a/packages/tenant-enrollment-nodejs/vite.config.ts b/packages/tenant-enrollment-nodejs/vite.config.ts new file mode 100644 index 0000000..f4625d4 --- /dev/null +++ b/packages/tenant-enrollment-nodejs/vite.config.ts @@ -0,0 +1,37 @@ +/// +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; +import peerDepsExternal from "rollup-plugin-peer-deps-external"; +import * as path from "path"; +import packageJson from "../tenants-nodejs/package.json"; + +export default defineConfig(() => ({ + root: __dirname, + plugins: [ + dts({ + entryRoot: "src", + tsconfigPath: path.join(__dirname, "tsconfig.json"), + }), + peerDepsExternal(), + ], + build: { + outDir: "./dist", + emptyOutDir: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: "src/index.ts", + name: packageJson.name, + fileName: "index", + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ["es" as const, "cjs" as const], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [], + }, + }, +})); diff --git a/packages/tenant-enrollment-react/src/css.d.ts b/packages/tenant-enrollment-react/src/css.d.ts new file mode 100644 index 0000000..93c8235 --- /dev/null +++ b/packages/tenant-enrollment-react/src/css.d.ts @@ -0,0 +1,29 @@ +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.scss" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.sass" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.less" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.module.styl" { + const classes: { [key: string]: string }; + export default classes; +} + +declare module "*.css" { + const css: string; + export default css; +} diff --git a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss new file mode 100644 index 0000000..2c14a84 --- /dev/null +++ b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss @@ -0,0 +1,30 @@ +.awaitingApprovalMessageContainer { + .header { + font-weight: 700; + font-size: 28px; + line-height: 36px; + letter-spacing: -0.12px; + color: var(--neutral-color-neutral-12); + margin: 0 0 16px 0; + } + + .messageContainer { + box-shadow: 0px 1.5px 2px 0px rgba(0, 0, 0, 0.133) inset; + border: 1px solid var(--neutral-color-neutral-6); + background-color: #f9f9f8; + border-radius: 12px; + padding: 14px; + + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0px; + + b { + font-weight: 600; + font-size: 14px; + line-height: 20px; + color: var(--neutral-color-neutral-11); + } + } +} diff --git a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx new file mode 100644 index 0000000..14be0bf --- /dev/null +++ b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx @@ -0,0 +1,23 @@ +import { Card } from "@shared/ui"; +import classNames from "classnames/bind"; + +import { usePluginContext } from "../../plugin"; + +import style from "./awaiting-approval.module.scss"; +const cx = classNames.bind(style); + +export const AwaitingApproval = () => { + const { t } = usePluginContext(); + + return ( + +
{t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_HEADER")}
+
+
+ {t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE")}{" "} + {t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE_HIGHLIGHT")} +
+
+
+ ); +}; diff --git a/packages/tenant-enrollment-react/src/pages/awaiting-approval/index.ts b/packages/tenant-enrollment-react/src/pages/awaiting-approval/index.ts new file mode 100644 index 0000000..ef943b8 --- /dev/null +++ b/packages/tenant-enrollment-react/src/pages/awaiting-approval/index.ts @@ -0,0 +1 @@ +export { AwaitingApproval } from "./awaiting-approval"; diff --git a/packages/tenant-enrollment-react/src/plugin.tsx b/packages/tenant-enrollment-react/src/plugin.tsx index f62a87e..b08924c 100644 --- a/packages/tenant-enrollment-react/src/plugin.tsx +++ b/packages/tenant-enrollment-react/src/plugin.tsx @@ -10,11 +10,11 @@ import { import { getApi } from "./api"; import { PLUGIN_ID, API_PATH } from "./constants"; -import { enableDebugLogs } from "./logger"; +import { enableDebugLogs, logDebugMessage } from "./logger"; +import { AwaitingApproval } from "./pages/awaiting-approval"; import { defaultTranslationsTenantEnrollment } from "./translations"; import { SuperTokensPluginTenantEnrollmentPluginConfig, TranslationKeys } from "./types"; - const { usePluginContext, setContext } = buildContext<{ plugins: SuperTokensPublicPlugin[]; sdkVersion: string; @@ -27,29 +27,29 @@ const { usePluginContext, setContext } = buildContext<{ }>(); export { usePluginContext }; - export const init = createPluginInitFunction< SuperTokensPlugin, SuperTokensPluginTenantEnrollmentPluginConfig, {}, // NOTE: Update the following type if we update the type to accept any values SuperTokensPluginTenantEnrollmentPluginConfig ->((pluginConfig) => { - return { - id: PLUGIN_ID, - init: (config, plugins, sdkVersion) => { - if (config.enableDebugLogs) { - enableDebugLogs(); - } +>( + (pluginConfig) => { + return { + id: PLUGIN_ID, + init: (config, plugins, sdkVersion) => { + if (config.enableDebugLogs) { + enableDebugLogs(); + } - const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); - const api = getApi(querier); + const querier = getQuerier(new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString()); + const api = getApi(querier); - // Set up the usePlugin hook - const apiBasePath = new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString(); - const translations = getTranslationFunction(defaultTranslationsTenantEnrollment); + // Set up the usePlugin hook + const apiBasePath = new URL(API_PATH, config.appInfo.apiDomain.getAsStringDangerous()).toString(); + const translations = getTranslationFunction(defaultTranslationsTenantEnrollment); - setContext({ + setContext({ plugins, sdkVersion, appConfig: config, @@ -59,39 +59,58 @@ export const init = createPluginInitFunction< t: translations, functions: null, }); - }, - routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { - return { - status: "OK", - routeHandlers: [ - // Add route handlers here - // Example: - // { - // path: '/example-page', - // handler: () => ExamplePage.call(null), - // }, - ], - }; - }, - overrideMap: { - // Add recipe overrides here - // Example: - // emailpassword: { - // functions: (originalImplementation) => ({ - // ...originalImplementation, - // // Override functions here - // }), - // }, - }, - generalAuthRecipeComponentOverrides: { - // Add component overrides here - // Example: - // AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { - // return ; - // }, - }, - }; -}, -{}, -(pluginConfig) => pluginConfig + }, + routeHandlers: (appConfig: any, plugins: any, sdkVersion: any) => { + return { + status: "OK", + routeHandlers: [ + { + path: "/awaiting-approval", + handler: () => AwaitingApproval.call(null), + }, + ], + }; + }, + overrideMap: { + emailpassword: { + functions: (originalImplementation) => ({ + ...originalImplementation, + signUp: async (input) => { + const signUpResponse = await originalImplementation.signUp(input); + logDebugMessage(`response: ${signUpResponse}`); + if ((signUpResponse.status as any) !== "PENDING_APPROVAL") { + return signUpResponse; + } + + // If it was okay, check if they were added to tenant or not. + const { wasAddedToTenant, reason } = signUpResponse as any; + if (wasAddedToTenant === true) { + // We don't have to do anything + return signUpResponse; + } + + // Since the tenant was not added, if we got a reason, we will have + // to parse it. + if (reason === undefined) { + return signUpResponse; + } + + // Since reason is defined, parse it and handle accordingly. + if (reason === "REQUIRES_APPROVAL") { + if (typeof window !== "undefined") { + window.location.assign("/awaiting-approval"); + } + } + + // NOTE: Currently we don't have any possibility of reason being any other + // value. If that changes, we can update in the future. + return signUpResponse; + }, + }), + }, + }, + }; + }, + {}, + (pluginConfig) => pluginConfig, ); diff --git a/packages/tenant-enrollment-react/src/translations.ts b/packages/tenant-enrollment-react/src/translations.ts index cb29933..1c0ace6 100644 --- a/packages/tenant-enrollment-react/src/translations.ts +++ b/packages/tenant-enrollment-react/src/translations.ts @@ -1,4 +1,8 @@ export const defaultTranslationsTenantEnrollment = { en: { + PL_TE_JOIN_TENANT_AWAITING_APPROVAL_HEADER: "Awaiting tenant admin approval", + PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE: + "It is essential to obtain the tenant administrator's approval before proceeding with the", + PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE_HIGHLIGHT: "tenant joining process", }, } as const; diff --git a/packages/tenants-nodejs/package.json b/packages/tenants-nodejs/package.json index 9c4fdac..f9c1277 100644 --- a/packages/tenants-nodejs/package.json +++ b/packages/tenants-nodejs/package.json @@ -42,19 +42,5 @@ }, "main": "./dist/index.js", "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - }, - "./index": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - }, - "./index.js": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - } - } + "types": "./dist/index.d.ts" } diff --git a/packages/tenants-react/package.json b/packages/tenants-react/package.json index ecd9bed..a97b974 100644 --- a/packages/tenants-react/package.json +++ b/packages/tenants-react/package.json @@ -53,19 +53,5 @@ }, "main": "./dist/index.js", "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - }, - "./index": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - }, - "./index.js": { - "types": "dist/index.d.ts", - "default": "dist/index.js" - } - } + "types": "./dist/index.d.ts" } From abe7091ab47aabea98e8d0cd9ee5454371de10bf Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Fri, 10 Oct 2025 14:15:19 +0530 Subject: [PATCH 24/25] feat: add support for picking up not allowed to signup error in FE --- .../tenant-enrollment-nodejs/src/plugin.ts | 26 +++++++++-------- .../src/recipeImplementation.ts | 8 +++--- .../tenant-enrollment-react/src/plugin.tsx | 28 ++++++++++++++++++- shared/tenants/src/errors.ts | 5 ++++ shared/tenants/src/index.ts | 5 ++-- 5 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 shared/tenants/src/errors.ts diff --git a/packages/tenant-enrollment-nodejs/src/plugin.ts b/packages/tenant-enrollment-nodejs/src/plugin.ts index fdf481d..76eb73e 100644 --- a/packages/tenant-enrollment-nodejs/src/plugin.ts +++ b/packages/tenant-enrollment-nodejs/src/plugin.ts @@ -93,8 +93,10 @@ export const init = createPluginInitFunction< logDebugMessage("Reason: " + reason); if (!canJoin) { return { - status: "LINKING_TO_SESSION_USER_FAILED", - reason: "EMAIL_VERIFICATION_REQUIRED", + // Use the `EMAIL_ALREADY_EXISTS_ERROR` since that is returned + // directly without modification from the `signUpPOST` method. + status: "EMAIL_ALREADY_EXISTS_ERROR", + reason, }; } @@ -107,17 +109,19 @@ export const init = createPluginInitFunction< ...originalImplementation, signUpPOST: async (input) => { const response = await originalImplementation.signUpPOST!(input); - if (response.status === "SIGN_UP_NOT_ALLOWED" && response.reason.includes("ERR_CODE_013")) { - // There is a possibility that the user is not allowed - // to signup to the tenant so we will have to update the message - // accordingly. + + logDebugMessage(`Got response status for signup: ${response.status}`); + + // If the status is `EMAIL_ALREADY_EXISTS_ERROR`, we will have to pick that + // up and return a GENERAL_ERROR instead to make the error passed along to + // the FE + if (response.status === "EMAIL_ALREADY_EXISTS_ERROR") { return { - ...response, - reason: "Cannot sign in / sign up due to security reasons or tenant doesn't allow signup", + status: "GENERAL_ERROR", + message: (response as any).reason, }; } - logDebugMessage(`Got response status for signup: ${response.status}`); if (response.status !== "OK") { return response; } @@ -135,12 +139,10 @@ export const init = createPluginInitFunction< logDebugMessage(`wasAdded: ${wasAddedToTenant}`); logDebugMessage(`reason: ${tenantJoiningReason}`); return { - status: "PENDING_APPROVAL", + status: "PENDING_APPROVAL" as any, wasAddedToTenant, reason: tenantJoiningReason, }; - - // return response; }, }; }, diff --git a/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts index 3ca464b..8177471 100644 --- a/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts +++ b/packages/tenant-enrollment-nodejs/src/recipeImplementation.ts @@ -5,7 +5,7 @@ import { AssociateAllLoginMethodsOfUserWithTenant, SendPluginEmail, } from "@supertokens-plugins/tenants-nodejs"; -import { ROLES } from "@shared/tenants"; +import { NOT_ALLOWED_TO_SIGNUP_REASONS, ROLES } from "@shared/tenants"; import SuperTokens from "supertokens-node"; import { UserContext } from "supertokens-node/lib/build/types"; @@ -35,7 +35,7 @@ export const getOverrideableTenantFunctionImplementation = ( if (implementation.isTenantInviteOnly(tenantId)) { return { canJoin: false, - reason: "INVITE_ONLY", + reason: NOT_ALLOWED_TO_SIGNUP_REASONS.INVITE_ONLY, }; } @@ -44,12 +44,12 @@ export const getOverrideableTenantFunctionImplementation = ( if (emailOrThirdPartyId.type === "email") { canJoin = implementation.isMatchingEmailDomain(tenantId, emailOrThirdPartyId.email); if (!canJoin) { - reason = "EMAIL_DOMAIN_NOT_ALLOWED"; + reason = NOT_ALLOWED_TO_SIGNUP_REASONS.EMAIL_DOMAIN_NOT_ALLOWED; } } else if (emailOrThirdPartyId.type === "thirdParty") { canJoin = implementation.isApprovedIdPProvider(tenantId, emailOrThirdPartyId.thirdPartyId); if (!canJoin) { - reason = "IDP_NOT_ALLOWED"; + reason = NOT_ALLOWED_TO_SIGNUP_REASONS.IDP_NOT_ALLOWED; } } diff --git a/packages/tenant-enrollment-react/src/plugin.tsx b/packages/tenant-enrollment-react/src/plugin.tsx index b08924c..5f62257 100644 --- a/packages/tenant-enrollment-react/src/plugin.tsx +++ b/packages/tenant-enrollment-react/src/plugin.tsx @@ -8,6 +8,8 @@ import { getTranslationFunction, } from "supertokens-auth-react"; +import { NOT_ALLOWED_TO_SIGNUP_REASONS } from "../../../shared/tenants/src"; + import { getApi } from "./api"; import { PLUGIN_ID, API_PATH } from "./constants"; import { enableDebugLogs, logDebugMessage } from "./logger"; @@ -76,8 +78,32 @@ export const init = createPluginInitFunction< functions: (originalImplementation) => ({ ...originalImplementation, signUp: async (input) => { - const signUpResponse = await originalImplementation.signUp(input); + let signUpResponse; + + try { + signUpResponse = await originalImplementation.signUp(input); + } catch (error: any) { + // Check if the error is a STGeneralError + logDebugMessage(`Caught error: ${error}`); + if (error.isSuperTokensGeneralError === true) { + logDebugMessage(`Got general error with reason: ${error.message}`); + + // Check if the message is one of the not allowed defined errors. + if (Object.values(NOT_ALLOWED_TO_SIGNUP_REASONS).includes(error.message)) { + logDebugMessage("Found not-allowed to signup flow, redirecting"); + + // Update the message before re-throwing the error + error.message = "Not allowed to signup to tenant"; + + // TODO: Redirect the user to not allowed to signup view + } + } + + throw error; + } + logDebugMessage(`response: ${signUpResponse}`); + if ((signUpResponse.status as any) !== "PENDING_APPROVAL") { return signUpResponse; } diff --git a/shared/tenants/src/errors.ts b/shared/tenants/src/errors.ts new file mode 100644 index 0000000..695dac3 --- /dev/null +++ b/shared/tenants/src/errors.ts @@ -0,0 +1,5 @@ +export const NOT_ALLOWED_TO_SIGNUP_REASONS = { + INVITE_ONLY: "INVITE_ONLY", + EMAIL_DOMAIN_NOT_ALLOWED: "EMAIL_DOMAIN_NOT_ALLOWED", + IDP_NOT_ALLOWED: "IDP_NOT_ALLOWED", +}; diff --git a/shared/tenants/src/index.ts b/shared/tenants/src/index.ts index 2c6ce5b..91f5a75 100644 --- a/shared/tenants/src/index.ts +++ b/shared/tenants/src/index.ts @@ -1,2 +1,3 @@ -export * from './types'; -export * from './roles'; +export * from "./types"; +export * from "./roles"; +export * from "./errors"; From 3df6a1b93578e2bbd1008d5f1b57b466f00e6ca8 Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 13 Oct 2025 12:26:10 +0530 Subject: [PATCH 25/25] feat: add support for redirecting user to signup blocked if not allowed --- .../src/components/no-access/NoAccess.tsx | 20 +++++++++++++++++++ .../src/components/no-access/index.ts | 1 + .../no-access/no-access.module.scss} | 10 +++++++++- .../awaiting-approval/awaiting-approval.tsx | 17 ++++++---------- .../src/pages/blocked/index.ts | 1 + .../src/pages/blocked/signup-blocked.tsx | 18 +++++++++++++++++ .../tenant-enrollment-react/src/plugin.tsx | 8 +++++++- .../src/translations.ts | 4 ++++ 8 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 packages/tenant-enrollment-react/src/components/no-access/NoAccess.tsx create mode 100644 packages/tenant-enrollment-react/src/components/no-access/index.ts rename packages/tenant-enrollment-react/src/{pages/awaiting-approval/awaiting-approval.module.scss => components/no-access/no-access.module.scss} (78%) create mode 100644 packages/tenant-enrollment-react/src/pages/blocked/index.ts create mode 100644 packages/tenant-enrollment-react/src/pages/blocked/signup-blocked.tsx diff --git a/packages/tenant-enrollment-react/src/components/no-access/NoAccess.tsx b/packages/tenant-enrollment-react/src/components/no-access/NoAccess.tsx new file mode 100644 index 0000000..c1eb780 --- /dev/null +++ b/packages/tenant-enrollment-react/src/components/no-access/NoAccess.tsx @@ -0,0 +1,20 @@ +import { Card } from "@shared/ui"; +import classNames from "classnames/bind"; + +import style from "./no-access.module.scss"; +const cx = classNames.bind(style); + +type NoAccessProps = { + headerText: string; + descriptionComponent: React.ReactNode; + useDangerAccent?: boolean; +}; + +export const NoAccess: React.FC = ({ headerText, descriptionComponent, useDangerAccent = false }) => { + return ( + +
{headerText}
+
{descriptionComponent}
+
+ ); +}; diff --git a/packages/tenant-enrollment-react/src/components/no-access/index.ts b/packages/tenant-enrollment-react/src/components/no-access/index.ts new file mode 100644 index 0000000..7930e5a --- /dev/null +++ b/packages/tenant-enrollment-react/src/components/no-access/index.ts @@ -0,0 +1 @@ +export { NoAccess } from "./NoAccess"; diff --git a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss b/packages/tenant-enrollment-react/src/components/no-access/no-access.module.scss similarity index 78% rename from packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss rename to packages/tenant-enrollment-react/src/components/no-access/no-access.module.scss index 2c14a84..0ec9dcc 100644 --- a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.module.scss +++ b/packages/tenant-enrollment-react/src/components/no-access/no-access.module.scss @@ -1,4 +1,4 @@ -.awaitingApprovalMessageContainer { +.noAccessMessageContainer { .header { font-weight: 700; font-size: 28px; @@ -26,5 +26,13 @@ line-height: 20px; color: var(--neutral-color-neutral-11); } + + &.danger { + border-color: var(--semantic-colors-error-6); + + b { + color: var(--semantic-colors-error-9); + } + } } } diff --git a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx index 14be0bf..d75d322 100644 --- a/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx +++ b/packages/tenant-enrollment-react/src/pages/awaiting-approval/awaiting-approval.tsx @@ -1,23 +1,18 @@ -import { Card } from "@shared/ui"; -import classNames from "classnames/bind"; - +import { NoAccess } from "../../components/no-access/NoAccess"; import { usePluginContext } from "../../plugin"; -import style from "./awaiting-approval.module.scss"; -const cx = classNames.bind(style); - export const AwaitingApproval = () => { const { t } = usePluginContext(); return ( - -
{t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_HEADER")}
-
+ {t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE")}{" "} {t("PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE_HIGHLIGHT")}
-
- + } + /> ); }; diff --git a/packages/tenant-enrollment-react/src/pages/blocked/index.ts b/packages/tenant-enrollment-react/src/pages/blocked/index.ts new file mode 100644 index 0000000..086f330 --- /dev/null +++ b/packages/tenant-enrollment-react/src/pages/blocked/index.ts @@ -0,0 +1 @@ +export { SignUpBlocked } from "./signup-blocked"; diff --git a/packages/tenant-enrollment-react/src/pages/blocked/signup-blocked.tsx b/packages/tenant-enrollment-react/src/pages/blocked/signup-blocked.tsx new file mode 100644 index 0000000..8f492c7 --- /dev/null +++ b/packages/tenant-enrollment-react/src/pages/blocked/signup-blocked.tsx @@ -0,0 +1,18 @@ +import { NoAccess } from "../../components/no-access/NoAccess"; +import { usePluginContext } from "../../plugin"; + +export const SignUpBlocked = () => { + const { t } = usePluginContext(); + + return ( + + {t("PL_TE_SIGN_UP_BLOCKED_MESSAGE_HIGHLIGHT")} {t("PL_TE_SIGN_UP_BLOCKED_MESSAGE_SUFFIX")} +
+ } + useDangerAccent + /> + ); +}; diff --git a/packages/tenant-enrollment-react/src/plugin.tsx b/packages/tenant-enrollment-react/src/plugin.tsx index 5f62257..c786b7a 100644 --- a/packages/tenant-enrollment-react/src/plugin.tsx +++ b/packages/tenant-enrollment-react/src/plugin.tsx @@ -14,6 +14,7 @@ import { getApi } from "./api"; import { PLUGIN_ID, API_PATH } from "./constants"; import { enableDebugLogs, logDebugMessage } from "./logger"; import { AwaitingApproval } from "./pages/awaiting-approval"; +import { SignUpBlocked } from "./pages/blocked"; import { defaultTranslationsTenantEnrollment } from "./translations"; import { SuperTokensPluginTenantEnrollmentPluginConfig, TranslationKeys } from "./types"; @@ -70,6 +71,10 @@ export const init = createPluginInitFunction< path: "/awaiting-approval", handler: () => AwaitingApproval.call(null), }, + { + path: "/signup-blocked", + handler: () => SignUpBlocked.call(null), + }, ], }; }, @@ -95,7 +100,8 @@ export const init = createPluginInitFunction< // Update the message before re-throwing the error error.message = "Not allowed to signup to tenant"; - // TODO: Redirect the user to not allowed to signup view + // Redirect the user to not allowed to signup view + window.location.assign("/signup-blocked"); } } diff --git a/packages/tenant-enrollment-react/src/translations.ts b/packages/tenant-enrollment-react/src/translations.ts index 1c0ace6..dd32bd1 100644 --- a/packages/tenant-enrollment-react/src/translations.ts +++ b/packages/tenant-enrollment-react/src/translations.ts @@ -4,5 +4,9 @@ export const defaultTranslationsTenantEnrollment = { PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE: "It is essential to obtain the tenant administrator's approval before proceeding with the", PL_TE_JOIN_TENANT_AWAITING_APPROVAL_MESSAGE_HIGHLIGHT: "tenant joining process", + PL_TE_SIGN_UP_BLOCKED_HEADER: "Signing up to the tenant is disabled", + PL_TE_SIGN_UP_BLOCKED_MESSAGE_HIGHLIGHT: "Signing up to this tenant is currently blocked.", + PL_TE_SIGN_UP_BLOCKED_MESSAGE_SUFFIX: + "If you think this is a mistake, please reach out to tenant administrators or request an invitation to join the tenant.", }, } as const;