From 302edb1f1423a6b4026ff8c933240edbe2c99f8e Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Wed, 28 Jan 2026 17:47:04 +0100 Subject: [PATCH 01/18] parents translate, components receive plain strings --- .npmrc | 7 ++----- src/components/actions/InputField.tsx | 15 +++++++-------- src/components/actions/SocialButton.tsx | 7 ++----- src/pages/auth/SignInPage.tsx | 4 ++-- src/pages/auth/SignUpPage.tsx | 4 ++-- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.npmrc b/.npmrc index e6af7eb..1223b5c 100644 --- a/.npmrc +++ b/.npmrc @@ -1,5 +1,2 @@ -registry=https://registry.npmjs.org/ - -# scoped registry only for @ciscode-apps and let npmjs be default -@ciscode-template-model:registry=https://pkgs.dev.azure.com/CISCODEAPPS/Templates/_packaging/packages-fe/npm/registry/ -always-auth=true \ No newline at end of file +fund=false +audit=false diff --git a/src/components/actions/InputField.tsx b/src/components/actions/InputField.tsx index d91d7dc..24afeb0 100644 --- a/src/components/actions/InputField.tsx +++ b/src/components/actions/InputField.tsx @@ -1,6 +1,5 @@ // src/components/actions/InputField.tsx import * as React from "react"; -import { useT } from "@ciscode/ui-translate-core"; import { InputFieldProps } from "../../models/Type"; /** @@ -10,29 +9,29 @@ import { InputFieldProps } from "../../models/Type"; * - Automatically flips text alignment in RTL */ export const InputField: React.FC = ({ - label, // translation key for the label + label, type = "text", - placeholder, // translation key for the placeholder + placeholder, color = "", value, onChange, }) => { - const t = useT("authLib"); // assumes your translations live under the "auth" namespace + const inputId = React.useId(); return (
{label && ( )} onChange?.(e.target.value)} className={` diff --git a/src/components/actions/SocialButton.tsx b/src/components/actions/SocialButton.tsx index 8e7c118..e230abb 100644 --- a/src/components/actions/SocialButton.tsx +++ b/src/components/actions/SocialButton.tsx @@ -1,22 +1,19 @@ // src/components/actions/SocialButton.tsx import * as React from "react"; -import { useT } from "@ciscode/ui-translate-core"; import { SocialButtonProps } from "../../models/Type"; export const SocialButton: React.FC = ({ icon, label }) => { - const t = useT("authLib"); // assuming "auth" namespace for these labels - return (
{label {label && (
- {t(label)} + {label}
)}
diff --git a/src/pages/auth/SignInPage.tsx b/src/pages/auth/SignInPage.tsx index c4ab7d7..460953a 100644 --- a/src/pages/auth/SignInPage.tsx +++ b/src/pages/auth/SignInPage.tsx @@ -40,8 +40,8 @@ export const SignInPage: React.FC = () => { const [error, setError] = useState(null); const allProvidersData = { - google: { icon: googleIcon, label: "social.google" }, - microsoft: { icon: microsoftIcon, label: "social.microsoft" }, + google: { icon: googleIcon, label: t("social.google") }, + microsoft: { icon: microsoftIcon, label: t("social.microsoft") }, } as const; const providerButtons = oauthProviders diff --git a/src/pages/auth/SignUpPage.tsx b/src/pages/auth/SignUpPage.tsx index 06be3ff..ee1d2f2 100644 --- a/src/pages/auth/SignUpPage.tsx +++ b/src/pages/auth/SignUpPage.tsx @@ -40,8 +40,8 @@ export const SignUpPage: React.FC = () => { const [error, setError] = useState(null); const allProvidersData = { - google: { icon: googleIcon, label: "social.google" }, - microsoft: { icon: microsoftIcon, label: "social.microsoft" }, + google: { icon: googleIcon, label: t("social.google") }, + microsoft: { icon: microsoftIcon, label: t("social.microsoft") }, } as const; const providerButtons = oauthProviders From 0258a6e88eb76a3aaaa2c7e6dc92ce07e25a04f9 Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Wed, 28 Jan 2026 17:50:46 +0100 Subject: [PATCH 02/18] 1.0.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b772c6d..a315185 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.2", + "version": "1.0.3", "license": "ISC", "devDependencies": { "@types/node": "^22.13.1", diff --git a/package.json b/package.json index 953ae87..000802d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.2", + "version": "1.0.3", "description": "", "main": "dist/index.umd.js", "module": "dist/index.mjs", From a62f9b786a03952851cd3eced66da6105b5694cb Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Thu, 29 Jan 2026 11:56:34 +0100 Subject: [PATCH 03/18] tested in local, bug fixed --- package-lock.json | 493 ++++++++++++++++++++++++---------------------- package.json | 7 +- vite.config.ts | 11 +- 3 files changed, 270 insertions(+), 241 deletions(-) diff --git a/package-lock.json b/package-lock.json index a315185..6053937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "postcss": "^8.5.2", + "react-router": "^7.13.0", + "react-router-dom": "^7.13.0", "typescript": "^5.2.2", "vite": "^4.5.9" }, @@ -26,31 +28,18 @@ "react": ">=18.0.0 <20.0.0 || ^19.0.0", "react-cookie": "^8.0.1", "react-dom": ">=18.0.0 <20.0.0 || ^19.0.0", - "react-router": "^7.3.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" + "react-router": "^7.0.0", + "react-router-dom": "^7.0.0" } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -59,9 +48,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -69,22 +58,22 @@ } }, "node_modules/@babel/core": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helpers": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -100,16 +89,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -117,13 +106,13 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -133,30 +122,40 @@ "node": ">=6.9.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -166,9 +165,9 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -186,9 +185,9 @@ } }, "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==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -206,27 +205,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -278,48 +277,48 @@ } }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -713,34 +712,31 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, - "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/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -748,16 +744,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -765,6 +761,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -801,30 +804,32 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", - "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", "license": "MIT", "peer": true, "dependencies": { - "@types/react": "*", "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" } }, "node_modules/@types/node": { - "version": "22.15.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.18.tgz", - "integrity": "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==", + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", "dev": true, "license": "MIT", "dependencies": { @@ -832,19 +837,19 @@ } }, "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.21.tgz", - "integrity": "sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw==", + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { @@ -858,15 +863,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", - "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.10", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -874,7 +880,7 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/asynckit": { @@ -885,9 +891,9 @@ "peer": true }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", "dev": true, "funding": [ { @@ -905,10 +911,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -923,21 +928,31 @@ } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "peer": true, "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -955,10 +970,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -982,9 +998,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -1023,25 +1039,28 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1082,9 +1101,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.154", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.154.tgz", - "integrity": "sha512-G4VCFAyKbp1QJ+sWdXYIRYsPGvlV5sDACfCmoMFog3rjm1syLhI41WXm/swZypwCIWIm4IFLWzHY14joWMQ5Fw==", + "version": "1.5.282", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.282.tgz", + "integrity": "sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==", "dev": true, "license": "ISC" }, @@ -1186,9 +1205,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -1207,15 +1226,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -1223,16 +1243,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -1310,16 +1330,6 @@ "node": ">= 0.4" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1560,22 +1570,12 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1584,9 +1584,9 @@ "license": "ISC" }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -1604,7 +1604,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1627,9 +1627,9 @@ "peer": true }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "peer": true, "engines": { @@ -1652,16 +1652,16 @@ } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "peer": true, "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^19.2.4" } }, "node_modules/react-i18next": { @@ -1709,11 +1709,11 @@ } }, "node_modules/react-router": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", - "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -1731,6 +1731,23 @@ } } }, + "node_modules/react-router-dom": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/rollup": { "version": "3.29.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", @@ -1749,9 +1766,9 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT", "peer": true }, @@ -1766,11 +1783,11 @@ } }, "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", - "peer": true + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" }, "node_modules/source-map-js": { "version": "1.2.1", @@ -1783,9 +1800,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -1814,9 +1831,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 000802d..e062ac4 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "react": ">=18.0.0 <20.0.0 || ^19.0.0", "react-cookie": "^8.0.1", "react-dom": ">=18.0.0 <20.0.0 || ^19.0.0", - "react-router": "^7.3.0" + "react-router": "^7.0.0", + "react-router-dom": "^7.0.0" }, "devDependencies": { "@types/node": "^22.13.1", @@ -42,6 +43,8 @@ "autoprefixer": "^10.4.20", "postcss": "^8.5.2", "typescript": "^5.2.2", - "vite": "^4.5.9" + "vite": "^4.5.9", + "react-router": "^7.13.0", + "react-router-dom": "^7.13.0" } } diff --git a/vite.config.ts b/vite.config.ts index c502285..646e156 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,7 +9,16 @@ export default defineConfig({ fileName: "index", }, rollupOptions: { - external: ["react", "react-dom", "react-router", "react-cookie", "axios", "jwt-decode", "@ciscode-template-model/translate-core"], + external: [ + "react", + "react-dom", + "react-router", + "react-router-dom", + "react-cookie", + "axios", + "jwt-decode", + "@ciscode/ui-translate-core" + ], }, }, }); \ No newline at end of file From 3bd91d4ca85400dfcb44fcac0c86002d8cae7f81 Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Thu, 29 Jan 2026 12:02:53 +0100 Subject: [PATCH 04/18] 1.0.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6053937..b37db5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.3", + "version": "1.0.4", "license": "ISC", "devDependencies": { "@types/node": "^22.13.1", diff --git a/package.json b/package.json index e062ac4..251377f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.3", + "version": "1.0.4", "description": "", "main": "dist/index.umd.js", "module": "dist/index.mjs", From 240c1aadb40294dd7d88ddd2ecce280b4ae6910b Mon Sep 17 00:00:00 2001 From: Zaiidmo Date: Thu, 29 Jan 2026 20:51:33 +0100 Subject: [PATCH 05/18] 1.0.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b37db5a..1b4b4a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.4", + "version": "1.0.5", "license": "ISC", "devDependencies": { "@types/node": "^22.13.1", diff --git a/package.json b/package.json index 251377f..50a4c6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.4", + "version": "1.0.5", "description": "", "main": "dist/index.umd.js", "module": "dist/index.mjs", From b13ea31b29ad01af488aae94d58c9cfca67be7ce Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Fri, 30 Jan 2026 16:56:58 +0100 Subject: [PATCH 06/18] forgot and reset password done --- src/components/RequirePermissions.tsx | 2 +- src/pages/auth/ForgotPasswordPage.tsx | 108 +++++++++++++++++++ src/pages/auth/ResetPasswordPage.tsx | 144 ++++++++++++++++++++++++++ src/pages/auth/SignInPage.tsx | 7 +- src/pages/auth/SignUpPage.tsx | 2 +- src/providers/AuthProvider.tsx | 8 +- vite.config.ts | 17 ++- 7 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 src/pages/auth/ForgotPasswordPage.tsx create mode 100644 src/pages/auth/ResetPasswordPage.tsx diff --git a/src/components/RequirePermissions.tsx b/src/components/RequirePermissions.tsx index 2bb7c43..7673b68 100644 --- a/src/components/RequirePermissions.tsx +++ b/src/components/RequirePermissions.tsx @@ -1,6 +1,6 @@ // src/components/auth/RequirePermissions.tsx import React from 'react'; -import { Navigate } from 'react-router'; // or useNavigate() +import { Navigate } from 'react-router-dom'; // or useNavigate() import { useCan, useHasRole } from '../hooks/useAbility'; // your hooks interface Props { diff --git a/src/pages/auth/ForgotPasswordPage.tsx b/src/pages/auth/ForgotPasswordPage.tsx new file mode 100644 index 0000000..32537c8 --- /dev/null +++ b/src/pages/auth/ForgotPasswordPage.tsx @@ -0,0 +1,108 @@ +import React, { useState } from "react"; +import { useT } from "@ciscode/ui-translate-core"; +import { useNavigate } from "react-router-dom"; +import { InputField } from "../../components/actions/InputField"; +import { InlineError } from "../../components/InlineError"; +import { useAuthConfig } from "../../context/AuthConfigContext"; +import { useAuthState } from "../../context/AuthStateContext"; +import { toTailwindColorClasses } from "../../utils/colorHelpers"; + +export const ForgotPasswordPage: React.FC = () => { + const t = useT("authLib"); + const navigate = useNavigate(); + const { colors, brandName = t("brandName", { defaultValue: "MyBrand" }), logoUrl } = useAuthConfig(); + const { api } = useAuthState(); + + const { bgClass, textClass, borderClass } = toTailwindColorClasses(colors); + const gradientClass = `${bgClass} bg-gradient-to-r from-white/10 via-white/0 to-white/0`; + + const [email, setEmail] = useState(""); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + const [sent, setSent] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (pending) return; + setError(null); + setPending(true); + try { + await api.post("/api/auth/forgot-password", { email }); + // Always show generic success regardless of user existence + setSent(true); + } catch (err) { + // Do not enumerate users; still show success message + setSent(true); + console.error("Forgot password request failed", err); + } finally { + setPending(false); + } + } + + return ( +
+
+
+
+ {logoUrl ? ( + Brand Logo + ) : ( +

{brandName}

+ )} + +
+ +

+ {t("ForgotPasswordPage.title", { defaultValue: "Forgot your password?" })} +

+

+ {t("ForgotPasswordPage.subtitle", { defaultValue: "Enter your email to receive a reset link." })} +

+ + {error && } + + {sent ? ( +
+ {t("ForgotPasswordPage.sent", { + defaultValue: "If the email exists, we’ve sent a reset link. Please check your inbox." + })} +
+ ) : ( +
+ + + + )} +
+
+
+ ); +}; diff --git a/src/pages/auth/ResetPasswordPage.tsx b/src/pages/auth/ResetPasswordPage.tsx new file mode 100644 index 0000000..cf0df5e --- /dev/null +++ b/src/pages/auth/ResetPasswordPage.tsx @@ -0,0 +1,144 @@ +import React, { useMemo, useState } from "react"; +import { useT } from "@ciscode/ui-translate-core"; +import { useLocation, useNavigate } from "react-router-dom"; +import { InputField } from "../../components/actions/InputField"; +import { InlineError } from "../../components/InlineError"; +import { useAuthConfig } from "../../context/AuthConfigContext"; +import { useAuthState } from "../../context/AuthStateContext"; +import { toTailwindColorClasses } from "../../utils/colorHelpers"; + +export const ResetPasswordPage: React.FC = () => { + const t = useT("authLib"); + const navigate = useNavigate(); + const location = useLocation(); + + const { colors, brandName = t("brandName", { defaultValue: "MyBrand" }), logoUrl } = useAuthConfig(); + const { api } = useAuthState(); + + const { bgClass, textClass, borderClass } = toTailwindColorClasses(colors); + const gradientClass = `${bgClass} bg-gradient-to-r from-white/10 via-white/0 to-white/0`; + + const token = useMemo(() => new URLSearchParams(location.search).get("token"), [location.search]); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + + const minLength = 6; + const valid = token && newPassword.length >= minLength && newPassword === confirmPassword; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (pending) return; + setError(null); + + if (!token) { + setError(t("ResetPasswordPage.invalidLink", { defaultValue: "Invalid reset link." })); + return; + } + if (newPassword.length < minLength) { + setError( + t("ResetPasswordPage.tooShort", { defaultValue: `Password must be at least ${minLength} characters.` }) + ); + return; + } + if (newPassword !== confirmPassword) { + setError(t("ResetPasswordPage.mismatch", { defaultValue: "Passwords do not match." })); + return; + } + + setPending(true); + try { + await api.post("/api/auth/reset-password", { token, newPassword }); + // On success, show brief confirmation then navigate to login + navigate("/login", { replace: true }); + } catch (err: any) { + const status = err?.response?.status; + if (status === 400 || status === 401 || status === 410) { + setError( + t("ResetPasswordPage.invalidOrExpired", { + defaultValue: "Reset link is invalid or has expired. Request a new one.", + }) + ); + } else { + setError(t("errors.generic", { defaultValue: "Something went wrong. Please try again." })); + } + } finally { + setPending(false); + } + } + + return ( +
+
+
+
+ {logoUrl ? ( + Brand Logo + ) : ( +

{brandName}

+ )} + +
+ +

+ {t("ResetPasswordPage.title", { defaultValue: "Reset your password" })} +

+

+ {t("ResetPasswordPage.subtitle", { defaultValue: "Choose a new password to access your account." })} +

+ + {error && } + + {!token && ( +
+ {t("ResetPasswordPage.invalidLink", { defaultValue: "Invalid reset link." })} +
+ )} + +
+ + + + + +
+
+
+ ); +}; diff --git a/src/pages/auth/SignInPage.tsx b/src/pages/auth/SignInPage.tsx index 460953a..1e0a03b 100644 --- a/src/pages/auth/SignInPage.tsx +++ b/src/pages/auth/SignInPage.tsx @@ -9,7 +9,8 @@ import { useAuthState } from "../../context/AuthStateContext"; import { InlineError } from "../../components/InlineError"; import { AuthConfigProps } from "../../models/AuthConfig"; import { useT } from "@ciscode/ui-translate-core"; -import { useNavigate, useLocation } from "react-router"; +import { useNavigate, useLocation } from "react-router-dom"; +import { Link } from "react-router-dom" export const SignInPage: React.FC = () => { const t = useT("authLib"); @@ -232,9 +233,7 @@ export const SignInPage: React.FC = () => { onChange={setPassword} />
- + {t("SignInPage.forgotPassword")}
+
+ +

+ {t("ForgotPasswordPage.title", { defaultValue: "Forgot your password?" })} +

+

+ {t("ForgotPasswordPage.subtitle", { defaultValue: "Enter your email to receive a reset link." })} +

+ + {error && } + + {sent ? ( +
+ {t("ForgotPasswordPage.sent", { + defaultValue: "If the email exists, we’ve sent a reset link. Please check your inbox." + })} +
+ ) : ( +
+ + + + )} + + + + ); +}; diff --git a/src/pages/auth/ResetPasswordPage.tsx b/src/pages/auth/ResetPasswordPage.tsx new file mode 100644 index 0000000..cf0df5e --- /dev/null +++ b/src/pages/auth/ResetPasswordPage.tsx @@ -0,0 +1,144 @@ +import React, { useMemo, useState } from "react"; +import { useT } from "@ciscode/ui-translate-core"; +import { useLocation, useNavigate } from "react-router-dom"; +import { InputField } from "../../components/actions/InputField"; +import { InlineError } from "../../components/InlineError"; +import { useAuthConfig } from "../../context/AuthConfigContext"; +import { useAuthState } from "../../context/AuthStateContext"; +import { toTailwindColorClasses } from "../../utils/colorHelpers"; + +export const ResetPasswordPage: React.FC = () => { + const t = useT("authLib"); + const navigate = useNavigate(); + const location = useLocation(); + + const { colors, brandName = t("brandName", { defaultValue: "MyBrand" }), logoUrl } = useAuthConfig(); + const { api } = useAuthState(); + + const { bgClass, textClass, borderClass } = toTailwindColorClasses(colors); + const gradientClass = `${bgClass} bg-gradient-to-r from-white/10 via-white/0 to-white/0`; + + const token = useMemo(() => new URLSearchParams(location.search).get("token"), [location.search]); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + + const minLength = 6; + const valid = token && newPassword.length >= minLength && newPassword === confirmPassword; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (pending) return; + setError(null); + + if (!token) { + setError(t("ResetPasswordPage.invalidLink", { defaultValue: "Invalid reset link." })); + return; + } + if (newPassword.length < minLength) { + setError( + t("ResetPasswordPage.tooShort", { defaultValue: `Password must be at least ${minLength} characters.` }) + ); + return; + } + if (newPassword !== confirmPassword) { + setError(t("ResetPasswordPage.mismatch", { defaultValue: "Passwords do not match." })); + return; + } + + setPending(true); + try { + await api.post("/api/auth/reset-password", { token, newPassword }); + // On success, show brief confirmation then navigate to login + navigate("/login", { replace: true }); + } catch (err: any) { + const status = err?.response?.status; + if (status === 400 || status === 401 || status === 410) { + setError( + t("ResetPasswordPage.invalidOrExpired", { + defaultValue: "Reset link is invalid or has expired. Request a new one.", + }) + ); + } else { + setError(t("errors.generic", { defaultValue: "Something went wrong. Please try again." })); + } + } finally { + setPending(false); + } + } + + return ( +
+
+
+
+ {logoUrl ? ( + Brand Logo + ) : ( +

{brandName}

+ )} + +
+ +

+ {t("ResetPasswordPage.title", { defaultValue: "Reset your password" })} +

+

+ {t("ResetPasswordPage.subtitle", { defaultValue: "Choose a new password to access your account." })} +

+ + {error && } + + {!token && ( +
+ {t("ResetPasswordPage.invalidLink", { defaultValue: "Invalid reset link." })} +
+ )} + +
+ + + + + +
+
+
+ ); +}; diff --git a/src/pages/auth/SignInPage.tsx b/src/pages/auth/SignInPage.tsx index 460953a..1e0a03b 100644 --- a/src/pages/auth/SignInPage.tsx +++ b/src/pages/auth/SignInPage.tsx @@ -9,7 +9,8 @@ import { useAuthState } from "../../context/AuthStateContext"; import { InlineError } from "../../components/InlineError"; import { AuthConfigProps } from "../../models/AuthConfig"; import { useT } from "@ciscode/ui-translate-core"; -import { useNavigate, useLocation } from "react-router"; +import { useNavigate, useLocation } from "react-router-dom"; +import { Link } from "react-router-dom" export const SignInPage: React.FC = () => { const t = useT("authLib"); @@ -232,9 +233,7 @@ export const SignInPage: React.FC = () => { onChange={setPassword} />
- + {t("SignInPage.forgotPassword")}
+ + ); +} +``` + +### 3. Protect routes with permissions + +```tsx +import { RequirePermissions } from '@ciscode/ui-authentication-kit'; + +function AdminPanel() { + return ( + +
Admin Content
+
+ ); +} +``` + +## 📚 Documentation + +- **[API Reference](docs/API.md)** - Complete API documentation +- **[Examples](docs/EXAMPLES.md)** - Integration examples +- **[Styling Guide](docs/STYLING.md)** - Customization guide +- **[Accessibility](docs/ACCESSIBILITY.md)** - A11y patterns +- **[Migration Guide](docs/MIGRATION.md)** - Upgrade guides +- **[Architecture](docs/ARCHITECTURE.md)** - Project structure +- **[Release Guide](docs/RELEASE.md)** - Release workflow + +## 🎯 Key Components + +| Component | Description | +| -------------------- | -------------------------------------------------- | +| `AuthProvider` | Root provider for authentication state and routing | +| `ProfilePage` | User profile management UI | +| `RequirePermissions` | Permission-based route guard | +| `RbacProvider` | Role-based access control context | + +## 🪝 Core Hooks + +| Hook | Description | +| ---------------------- | -------------------------------------------------------- | +| `useAuthState()` | Access auth state (user, isAuthenticated, login, logout) | +| `useHasRole(role)` | Check if user has a specific role | +| `useHasModule(module)` | Check if user has access to a module | +| `useCan(permission)` | Check if user has a permission | +| `useGrant()` | Access RBAC grant management | + +## 🔐 RBAC Example + +```tsx +import { RbacProvider, useHasRole, useCan } from '@ciscode/ui-authentication-kit'; + +function App() { + return ( + + + + ); +} + +function Dashboard() { + const isAdmin = useHasRole('admin'); + const canEditUsers = useCan('users.edit'); + + return ( +
+ {isAdmin && } + {canEditUsers && } +
+ ); +} +``` + +## 🌐 Internationalization + +The kit integrates with `@ciscode/ui-translate-core` for multi-language support: + +```tsx +import { TranslateProvider } from '@ciscode/ui-translate-core'; + + + + + +; +``` + +## 🛠️ Development + +```bash +# Install dependencies +npm install + +# Build the library +npm run build + +# Run tests +npm test + +# Type check +npm run typecheck + +# Lint +npm run lint + +# Format code +npm run format:write +``` + +## 🤝 Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. + +1. Fork the repository +2. Create a feature branch from `develop` +3. Make your changes with tests +4. Submit a PR to `develop` + +## 📄 License + +ISC © [CISCODE](https://github.com/CISCODE-MA) + +## 🔗 Links + +- [GitHub Repository](https://github.com/CISCODE-MA/AuthKit-UI) +- [NPM Package](https://www.npmjs.com/package/@ciscode/ui-authentication-kit) +- [Report Issues](https://github.com/CISCODE-MA/AuthKit-UI/issues) + +## 📊 Browser Support + +- Chrome (latest) +- Firefox (latest) +- Safari (latest) +- Edge (latest) + +## 🙏 Acknowledgments + +Built with modern React patterns and best practices. Designed for enterprise applications. diff --git a/docs/ACCESSIBILITY.md b/docs/ACCESSIBILITY.md new file mode 100644 index 0000000..2d48b6c --- /dev/null +++ b/docs/ACCESSIBILITY.md @@ -0,0 +1,644 @@ +# Accessibility Guide + +Accessibility (a11y) patterns and best practices for `@ciscode/ui-authentication-kit`. + +--- + +## Overview + +The Auth Kit is built with accessibility as a core principle. All components follow WCAG 2.1 Level AA guidelines and support: + +- ✅ **Keyboard navigation** +- ✅ **Screen readers** (NVDA, JAWS, VoiceOver) +- ✅ **ARIA labels and roles** +- ✅ **Focus management** +- ✅ **Color contrast** +- ✅ **Reduced motion** + +--- + +## Table of Contents + +- [Keyboard Navigation](#keyboard-navigation) +- [Screen Reader Support](#screen-reader-support) +- [Focus Management](#focus-management) +- [ARIA Patterns](#aria-patterns) +- [Color Contrast](#color-contrast) +- [Error Handling](#error-handling) +- [Forms Accessibility](#forms-accessibility) +- [Testing Accessibility](#testing-accessibility) + +--- + +## Keyboard Navigation + +### Required Key Bindings + +All interactive components must support these keyboard shortcuts: + +| Key | Action | +| ------------- | -------------------------------------- | +| `Tab` | Navigate to next focusable element | +| `Shift + Tab` | Navigate to previous focusable element | +| `Enter` | Activate button or submit form | +| `Space` | Toggle checkbox/radio, activate button | +| `Escape` | Close modal/dropdown | +| `Arrow Keys` | Navigate within select/dropdown | + +### Example: Accessible Login Form + +```tsx +import { useRef, FormEvent } from 'react'; + +export function AccessibleLoginForm() { + const emailRef = useRef(null); + const passwordRef = useRef(null); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + // Handle login + }; + + return ( +
+

Login to Your Account

+ +
+ + + + We'll never share your email + +
+ +
+ + + + Minimum 8 characters + +
+ + +
+ ); +} +``` + +--- + +## Screen Reader Support + +### ARIA Live Regions + +Use `aria-live` to announce dynamic content changes: + +```tsx +export function LoginForm() { + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + return ( +
+ {/* Error announcement */} +
+ {error &&

{error}

} +
+ + {/* Loading announcement */} +
+ {loading &&

Logging in, please wait...

} +
+ + {/* Form fields */} +
+ ); +} +``` + +### Screen Reader Only Text + +Hide visual elements but keep them accessible: + +```css +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} +``` + +```tsx + +``` + +--- + +## Focus Management + +### Focus Trapping in Modals + +Keep focus within modal when open: + +```tsx +import { useEffect, useRef } from 'react'; + +export function SessionExpiredModal({ isOpen, onClose }: Props) { + const dialogRef = useRef(null); + const previousActiveElement = useRef(null); + + useEffect(() => { + if (!isOpen) return; + + // Store previously focused element + previousActiveElement.current = document.activeElement as HTMLElement; + + // Focus modal + dialogRef.current?.focus(); + + // Trap focus + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Tab') { + const focusableElements = dialogRef.current?.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ); + + if (!focusableElements || focusableElements.length === 0) return; + + const firstElement = focusableElements[0] as HTMLElement; + const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement; + + if (e.shiftKey && document.activeElement === firstElement) { + e.preventDefault(); + lastElement.focus(); + } else if (!e.shiftKey && document.activeElement === lastElement) { + e.preventDefault(); + firstElement.focus(); + } + } + + if (e.key === 'Escape') { + onClose(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + + // Restore focus + previousActiveElement.current?.focus(); + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+

Session Expired

+

Your session has expired. Please log in again.

+ +
+ ); +} +``` + +### Skip Links + +Allow users to skip navigation: + +```tsx +export function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + + Skip to main content + + + + +
+ {children} +
+ + ); +} +``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: #000; + color: white; + padding: 8px; + text-decoration: none; + z-index: 100; +} + +.skip-link:focus { + top: 0; +} +``` + +--- + +## ARIA Patterns + +### Form Validation + +```tsx +export function EmailInput({ error }: { error?: string }) { + const errorId = error ? 'email-error' : undefined; + + return ( +
+ + + {error && ( + + {error} + + )} +
+ ); +} +``` + +### Loading States + +```tsx +export function LoadingButton({ loading, children, ...props }: Props) { + return ( + + ); +} +``` + +### Permission-Based Content + +```tsx +import { RequirePermissions, useCan } from '@ciscode/ui-authentication-kit'; + +export function AdminPanel() { + const canEdit = useCan('admin.edit'); + + return ( + +
+

Admin Panel

+ + {/* Announce permission state to screen readers */} +
+ {canEdit ? 'You have edit permissions' : 'Read-only access'} +
+ +
+ +
+
+
+ ); +} +``` + +--- + +## Color Contrast + +### Minimum Requirements (WCAG AA) + +- **Normal text**: 4.5:1 contrast ratio +- **Large text** (18pt+ or 14pt+ bold): 3:1 contrast ratio +- **UI components**: 3:1 contrast ratio + +### Recommended Colors + +```css +:root { + /* WCAG AA compliant */ + --auth-primary: #0056b3; /* 4.6:1 on white */ + --auth-success: #28a745; /* 4.5:1 on white */ + --auth-danger: #c82333; /* 5.1:1 on white */ + --auth-text: #212529; /* 16.5:1 on white */ + --auth-text-muted: #6c757d; /* 4.5:1 on white */ +} +``` + +### Don't Rely on Color Alone + +```tsx +// ❌ BAD: Only color indicates error + + +// ✅ GOOD: Icon + text + color +
+ + + +
+``` + +--- + +## Error Handling + +### Accessible Error Messages + +```tsx +export function LoginForm() { + const [errors, setErrors] = useState>({}); + const errorSummaryRef = useRef(null); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const validationErrors = validate(); + + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + + // Focus error summary + errorSummaryRef.current?.focus(); + return; + } + + // Submit form + }; + + return ( +
+ {Object.keys(errors).length > 0 && ( +
+

There are {Object.keys(errors).length} errors:

+
    + {Object.entries(errors).map(([field, message]) => ( +
  • + {message} +
  • + ))} +
+
+ )} + +
+ + + {errors.email && ( + + {errors.email} + + )} +
+ + {/* More fields */} + + +
+ ); +} +``` + +--- + +## Forms Accessibility + +### Input Labels + +```tsx +// ✅ GOOD: Explicit label association + + + +// ✅ GOOD: Nested label + + +// ❌ BAD: No label + +``` + +### Required Fields + +```tsx + + +``` + +### Fieldsets and Legends + +```tsx +
+ Account Type + + +
+``` + +### Autocomplete Attributes + +```tsx +// Help password managers and autofill + + + + + +``` + +--- + +## Testing Accessibility + +### Automated Testing + +```bash +npm install -D @axe-core/react +``` + +```tsx +// src/main.tsx (development only) +if (import.meta.env.DEV) { + import('@axe-core/react').then((axe) => { + axe.default(React, ReactDOM, 1000); + }); +} +``` + +### Manual Testing Checklist + +- [ ] **Keyboard Navigation** + - [ ] Can you reach all interactive elements with Tab? + - [ ] Can you activate buttons with Enter/Space? + - [ ] Can you close modals with Escape? + - [ ] Is focus visible at all times? + +- [ ] **Screen Reader** + - [ ] Are all images/icons labeled? + - [ ] Are errors announced? + - [ ] Are loading states announced? + - [ ] Do forms have proper labels? + +- [ ] **Color Contrast** + - [ ] Does text meet 4.5:1 contrast ratio? + - [ ] Do buttons meet 3:1 contrast ratio? + - [ ] Is error state visible without color? + +- [ ] **Forms** + - [ ] Are all inputs labeled? + - [ ] Are required fields marked? + - [ ] Are errors clearly communicated? + - [ ] Do inputs have autocomplete attributes? + +- [ ] **Focus Management** + - [ ] Does focus move to modal when opened? + - [ ] Is focus restored when modal closes? + - [ ] Is focus trapped in modal? + +### Browser Testing + +Test with these screen readers: + +- **Windows**: NVDA (free) or JAWS +- **macOS**: VoiceOver (built-in) +- **Linux**: Orca (free) +- **Mobile**: TalkBack (Android) or VoiceOver (iOS) + +### Testing Tools + +- [axe DevTools](https://www.deque.com/axe/devtools/) - Browser extension +- [WAVE](https://wave.webaim.org/) - Web accessibility evaluator +- [Lighthouse](https://developers.google.com/web/tools/lighthouse) - Chrome DevTools +- [pa11y](https://pa11y.org/) - Automated testing + +--- + +## Best Practices Summary + +### DO + +✅ Provide text alternatives for non-text content +✅ Ensure all functionality is keyboard accessible +✅ Provide sufficient time for users to interact +✅ Use ARIA attributes correctly +✅ Maintain focus order +✅ Test with real assistive technologies +✅ Announce dynamic content changes + +### DON'T + +❌ Use `div` or `span` as buttons +❌ Remove focus outlines without replacement +❌ Rely on color alone to convey information +❌ Use `tabindex` greater than 0 +❌ Disable zoom/pinch on mobile +❌ Auto-play audio/video +❌ Use time-based UI changes without warning + +--- + +## Resources + +- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/) +- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) +- [WebAIM](https://webaim.org/) +- [A11y Project](https://www.a11yproject.com/) +- [MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility) + +--- + +_Last Updated: January 31, 2026_ +_Version: 1.0.8_ diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..ce7b0e4 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,651 @@ +# API Reference + +Complete API documentation for `@ciscode/ui-authentication-kit`. + +--- + +## Table of Contents + +- [Components](#components) + - [AuthProvider](#authprovider) + - [ProfilePage](#profilepage) + - [RequirePermissions](#requirepermissions) + - [RbacProvider](#rbacprovider) +- [Hooks](#hooks) + - [useAuthState](#useauthstate) + - [useHasRole](#usehasrole) + - [useHasModule](#usehasmodule) + - [useCan](#usecan) + - [useGrant](#usegrant) +- [Types](#types) + +--- + +## Components + +### AuthProvider + +Root authentication provider that manages auth state, session handling, and routing. + +#### Props + +```typescript +interface AuthProviderProps { + config: AuthConfigProps; + children: React.ReactNode; +} + +interface AuthConfigProps { + /** Base API URL */ + apiUrl: string; + + /** Login endpoint path */ + loginPath: string; + + /** Register endpoint path */ + registerPath: string; + + /** User profile endpoint path */ + profilePath: string; + + /** Logout endpoint path */ + logoutPath: string; + + /** Where to redirect after successful login */ + redirectAfterLogin: string; + + /** Where to redirect after logout */ + redirectAfterLogout: string; + + /** Optional: Google OAuth client ID */ + googleClientId?: string; + + /** Optional: Enable session expiration modal */ + showSessionExpiredModal?: boolean; + + /** Optional: Token refresh interval in ms */ + refreshInterval?: number; +} +``` + +#### Usage + +```tsx +import { AuthProvider } from '@ciscode/ui-authentication-kit'; +import { BrowserRouter } from 'react-router-dom'; + +function App() { + return ( + + + {/* Your app */} + + + ); +} +``` + +#### Features + +- ✅ Automatic token storage and retrieval +- ✅ Session expiration detection +- ✅ JWT decoding and validation +- ✅ Axios interceptor attachment +- ✅ Built-in auth routes (`/login`, `/register`, `/forgot-password`, etc.) +- ✅ Google OAuth support +- ✅ Protected route wrapper + +#### Built-in Routes + +The `AuthProvider` automatically provides these routes: + +- `/login` - Sign in page +- `/register` - Sign up page +- `/forgot-password` - Password reset request +- `/reset-password` - Password reset form +- `/auth/google/callback` - Google OAuth callback + +--- + +### ProfilePage + +Pre-built user profile management component. + +#### Props + +```typescript +interface ProfilePageProps { + /** Optional: Custom className for styling */ + className?: string; + + /** Optional: Custom onUpdate callback */ + onUpdate?: (user: UserProfile) => void; +} +``` + +#### Usage + +```tsx +import { ProfilePage } from '@ciscode/ui-authentication-kit'; + +function UserProfile() { + return ( + console.log('Profile updated:', user)} + /> + ); +} +``` + +#### Features + +- ✅ Display current user info +- ✅ Edit profile fields +- ✅ Avatar upload +- ✅ Password change +- ✅ Form validation +- ✅ Loading states +- ✅ Error handling + +--- + +### RequirePermissions + +Component for protecting routes based on permissions and roles. + +#### Props + +```typescript +interface RequirePermissionsProps { + /** All permissions in this array must be present */ + fallbackpermessions?: string[]; + + /** At least one permission from this array must be present */ + anyPermessions?: string[]; + + /** Roles that bypass all permission checks */ + fallbackRoles?: string[]; + + /** Where to redirect if access is denied */ + redirectTo?: string; + + /** Content to render if access is granted */ + children: React.ReactNode; +} +``` + +#### Default Values + +```typescript +{ + fallbackpermessions: [], + anyPermessions: [], + fallbackRoles: ['super-admin'], + redirectTo: '/dashboard', +} +``` + +#### Usage + +```tsx +import { RequirePermissions } from '@ciscode/ui-authentication-kit'; + +// Require ALL permissions +function AdminPanel() { + return ( + +
Admin Content
+
+ ); +} + +// Require ANY permission +function ModeratorPanel() { + return ( + +
Moderator Content
+
+ ); +} + +// Combine both +function ComplexAccess() { + return ( + +
Complex Access Content
+
+ ); +} +``` + +#### Access Logic + +``` +if (user has fallbackRoles) → GRANT ACCESS + +if (user has ALL fallbackpermessions AND ANY anyPermessions) → GRANT ACCESS + +else → REDIRECT +``` + +--- + +### RbacProvider + +Context provider for role-based access control. + +#### Props + +```typescript +interface RbacProviderProps { + children: React.ReactNode; +} +``` + +#### Usage + +```tsx +import { RbacProvider, useGrant } from '@ciscode/ui-authentication-kit'; + +function App() { + return ( + + + + ); +} + +function Dashboard() { + const { grant, updateGrant } = useGrant(); + + return ( +
+
{JSON.stringify(grant, null, 2)}
+
+ ); +} +``` + +--- + +## Hooks + +### useAuthState + +Access authentication state and methods. + +#### Signature + +```typescript +function useAuthState(): AuthState; + +interface AuthState { + /** Current authenticated user */ + user: UserProfile | null; + + /** Is user authenticated */ + isAuthenticated: boolean; + + /** Is initial auth check in progress */ + booting: boolean; + + /** Current access token */ + accessToken: string | null; + + /** Has session expired */ + expired: boolean; + + /** Login method */ + login: (token: string) => void; + + /** Logout method */ + logout: () => void; + + /** Update user profile */ + updateUser: (user: UserProfile) => void; +} +``` + +#### Usage + +```tsx +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +function Header() { + const { user, isAuthenticated, logout, booting } = useAuthState(); + + if (booting) return
Loading...
; + + return ( +
+ {isAuthenticated ? ( + <> + Welcome, {user?.name} + + + ) : ( + Login + )} +
+ ); +} +``` + +--- + +### useHasRole + +Check if the current user has a specific role. + +#### Signature + +```typescript +function useHasRole(role: string): boolean; +``` + +#### Usage + +```tsx +import { useHasRole } from '@ciscode/ui-authentication-kit'; + +function AdminButton() { + const isAdmin = useHasRole('admin'); + + if (!isAdmin) return null; + + return ; +} +``` + +--- + +### useHasModule + +Check if the current user has access to a module. + +#### Signature + +```typescript +function useHasModule(module: string): boolean; +``` + +#### Usage + +```tsx +import { useHasModule } from '@ciscode/ui-authentication-kit'; + +function ReportsSection() { + const hasReports = useHasModule('reports'); + + if (!hasReports) return null; + + return
Reports Dashboard
; +} +``` + +--- + +### useCan + +Check if the current user has a specific permission. + +#### Signature + +```typescript +function useCan(permission: string): boolean; +``` + +#### Usage + +```tsx +import { useCan } from '@ciscode/ui-authentication-kit'; + +function UsersList() { + const canEditUsers = useCan('users.edit'); + const canDeleteUsers = useCan('users.delete'); + + return ( +
+ {users.map((user) => ( +
+ {user.name} + {canEditUsers && } + {canDeleteUsers && } +
+ ))} +
+ ); +} +``` + +--- + +### useGrant + +Access RBAC grant context for managing permissions. + +#### Signature + +```typescript +function useGrant(): GrantContext; + +interface GrantContext { + /** Current grant object */ + grant: Grant | null; + + /** Update grant */ + updateGrant: (grant: Grant) => void; +} + +interface Grant { + roles: string[]; + modules: string[]; + permissions: string[]; +} +``` + +#### Usage + +```tsx +import { useGrant } from '@ciscode/ui-authentication-kit'; + +function PermissionsManager() { + const { grant, updateGrant } = useGrant(); + + const addPermission = (permission: string) => { + if (grant) { + updateGrant({ + ...grant, + permissions: [...grant.permissions, permission], + }); + } + }; + + return ( +
+

Current Permissions:

+
    + {grant?.permissions.map((p) => ( +
  • {p}
  • + ))} +
+ +
+ ); +} +``` + +--- + +## Types + +### UserProfile + +```typescript +interface UserProfile { + id: string; + email: string; + name?: string; + avatar?: string; + roles?: string[]; + modules?: string[]; + permissions?: string[]; + createdAt?: string; + updatedAt?: string; +} +``` + +### AuthConfig + +```typescript +interface AuthConfigProps { + apiUrl: string; + loginPath: string; + registerPath: string; + profilePath: string; + logoutPath: string; + redirectAfterLogin: string; + redirectAfterLogout: string; + googleClientId?: string; + showSessionExpiredModal?: boolean; + refreshInterval?: number; +} +``` + +### Grant + +```typescript +interface Grant { + roles: string[]; + modules: string[]; + permissions: string[]; +} +``` + +--- + +## Error Handling + +All API-related hooks and components handle errors gracefully: + +```tsx +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +function LoginForm() { + const { login } = useAuthState(); + const [error, setError] = useState(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await login(token); + } catch (err) { + setError(err.message); + } + }; + + return ( +
+ {error &&
{error}
} + {/* Form fields */} +
+ ); +} +``` + +--- + +## Best Practices + +### 1. Always wrap your app with AuthProvider inside BrowserRouter + +```tsx + + + + + +``` + +### 2. Use RequirePermissions for route protection + +```tsx +// ❌ Manual permission checks everywhere +function Dashboard() { + const { user } = useAuthState(); + if (!user?.permissions.includes('dashboard.view')) { + return ; + } + // ... +} + +// ✅ Use RequirePermissions wrapper +function Dashboard() { + return ( + + {/* Dashboard content */} + + ); +} +``` + +### 3. Combine RBAC hooks for complex logic + +```tsx +function ActionButton() { + const isAdmin = useHasRole('admin'); + const canEdit = useCan('content.edit'); + const hasModule = useHasModule('cms'); + + const canShowButton = isAdmin || (canEdit && hasModule); + + return canShowButton ? : null; +} +``` + +### 4. Handle loading states + +```tsx +function ProtectedContent() { + const { booting, isAuthenticated } = useAuthState(); + + if (booting) return ; + if (!isAuthenticated) return ; + + return
Protected Content
; +} +``` + +--- + +## Migration from v1.x to v2.x + +See [MIGRATION.md](MIGRATION.md) for detailed upgrade instructions. + +--- + +_Last Updated: January 31, 2026_ +_Version: 1.0.8_ diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md new file mode 100644 index 0000000..f61ad25 --- /dev/null +++ b/docs/EXAMPLES.md @@ -0,0 +1,699 @@ +# Usage Examples + +Real-world integration examples for `@ciscode/ui-authentication-kit`. + +--- + +## Table of Contents + +- [Basic Setup](#basic-setup) +- [Custom Login Flow](#custom-login-flow) +- [Protected Routes](#protected-routes) +- [Role-Based Dashboard](#role-based-dashboard) +- [Multi-Tenant Application](#multi-tenant-application) +- [Social Authentication](#social-authentication) +- [Custom Profile Page](#custom-profile-page) +- [Permission-Based UI](#permission-based-ui) +- [Session Management](#session-management) +- [Integration with React Query](#integration-with-react-query) + +--- + +## Basic Setup + +Minimal setup for a React app with routing: + +```tsx +// src/main.tsx +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { AuthProvider } from '@ciscode/ui-authentication-kit'; +import App from './App'; + +const config = { + apiUrl: import.meta.env.VITE_API_URL, + loginPath: '/auth/login', + registerPath: '/auth/register', + profilePath: '/auth/profile', + logoutPath: '/auth/logout', + redirectAfterLogin: '/dashboard', + redirectAfterLogout: '/', +}; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +); +``` + +```tsx +// src/App.tsx +import { Routes, Route, Navigate } from 'react-router-dom'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; +import Dashboard from './pages/Dashboard'; +import Landing from './pages/Landing'; + +function App() { + const { isAuthenticated, booting } = useAuthState(); + + if (booting) { + return
Loading...
; + } + + return ( + + } /> + : } + /> + + ); +} + +export default App; +``` + +--- + +## Custom Login Flow + +Building a custom login page that uses the auth provider: + +```tsx +// src/pages/CustomLogin.tsx +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; +import axios from 'axios'; + +export default function CustomLogin() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + const { login } = useAuthState(); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setLoading(true); + + try { + // Call your API + const response = await axios.post('https://api.example.com/auth/login', { + email, + password, + }); + + // Extract token from response + const { accessToken } = response.data; + + // Update auth state + login(accessToken); + + // Navigate to dashboard + navigate('/dashboard'); + } catch (err: any) { + setError(err.response?.data?.message || 'Login failed'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Login

+
+ {error &&
{error}
} + + setEmail(e.target.value)} + required + /> + + setPassword(e.target.value)} + required + /> + + +
+
+ ); +} +``` + +--- + +## Protected Routes + +Using `RequirePermissions` for route protection: + +```tsx +// src/App.tsx +import { Routes, Route } from 'react-router-dom'; +import { RequirePermissions } from '@ciscode/ui-authentication-kit'; +import AdminPanel from './pages/AdminPanel'; +import ModeratorPanel from './pages/ModeratorPanel'; +import Dashboard from './pages/Dashboard'; +import Unauthorized from './pages/Unauthorized'; + +function App() { + return ( + + } /> + + {/* Admin-only route */} + + + + } + /> + + {/* Moderator route - needs ANY of these permissions */} + + + + } + /> + + } /> + + ); +} +``` + +--- + +## Role-Based Dashboard + +Different dashboard views based on user role: + +```tsx +// src/pages/Dashboard.tsx +import { useHasRole, useAuthState } from '@ciscode/ui-authentication-kit'; +import AdminDashboard from '../components/AdminDashboard'; +import ManagerDashboard from '../components/ManagerDashboard'; +import UserDashboard from '../components/UserDashboard'; + +export default function Dashboard() { + const { user } = useAuthState(); + const isAdmin = useHasRole('admin'); + const isManager = useHasRole('manager'); + + return ( +
+

Welcome, {user?.name}!

+ + {isAdmin && } + {isManager && !isAdmin && } + {!isAdmin && !isManager && } +
+ ); +} +``` + +--- + +## Multi-Tenant Application + +Handling multiple tenants with role-based access: + +```tsx +// src/App.tsx +import { AuthProvider, RbacProvider } from '@ciscode/ui-authentication-kit'; +import { BrowserRouter } from 'react-router-dom'; +import { TenantProvider } from './contexts/TenantContext'; + +function App() { + return ( + + + + + + + + + + ); +} +``` + +```tsx +// src/components/TenantSwitcher.tsx +import { useAuthState, useGrant } from '@ciscode/ui-authentication-kit'; +import { useTenant } from '../contexts/TenantContext'; + +export default function TenantSwitcher() { + const { user } = useAuthState(); + const { currentTenant, setTenant } = useTenant(); + const { updateGrant } = useGrant(); + + const handleTenantChange = async (tenantId: string) => { + // Fetch permissions for new tenant + const response = await fetch(`/api/tenants/${tenantId}/permissions`); + const { roles, modules, permissions } = await response.json(); + + // Update RBAC context + updateGrant({ roles, modules, permissions }); + + // Update tenant + setTenant(tenantId); + }; + + return ( + + ); +} +``` + +--- + +## Social Authentication + +Google OAuth integration: + +```tsx +// src/main.tsx +import { AuthProvider } from '@ciscode/ui-authentication-kit'; + +const config = { + apiUrl: 'https://api.example.com', + loginPath: '/auth/login', + registerPath: '/auth/register', + profilePath: '/auth/profile', + logoutPath: '/auth/logout', + redirectAfterLogin: '/dashboard', + redirectAfterLogout: '/', + googleClientId: 'YOUR_GOOGLE_CLIENT_ID', // Enable Google OAuth +}; + +// AuthProvider automatically handles /auth/google/callback route +``` + +```tsx +// src/pages/Login.tsx +export default function Login() { + const handleGoogleLogin = () => { + // AuthProvider handles this automatically + window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${googleClientId}&redirect_uri=${redirectUri}&response_type=code&scope=openid%20email%20profile`; + }; + + return ( +
+ +
+ ); +} +``` + +--- + +## Custom Profile Page + +Extending the built-in ProfilePage: + +```tsx +// src/pages/CustomProfile.tsx +import { ProfilePage, useAuthState } from '@ciscode/ui-authentication-kit'; +import { useState } from 'react'; + +export default function CustomProfile() { + const { user, updateUser } = useAuthState(); + const [customField, setCustomField] = useState(''); + + const handleProfileUpdate = async (updatedUser: any) => { + // Add custom logic + const response = await fetch('/api/user/custom-field', { + method: 'POST', + body: JSON.stringify({ customField }), + }); + + // Update user in auth state + updateUser(updatedUser); + }; + + return ( +
+ + + {/* Custom fields */} +
+

Custom Settings

+ setCustomField(e.target.value)} + placeholder="Custom Field" + /> +
+
+ ); +} +``` + +--- + +## Permission-Based UI + +Granular UI control based on permissions: + +```tsx +// src/components/UserTable.tsx +import { useCan } from '@ciscode/ui-authentication-kit'; + +interface User { + id: string; + name: string; + email: string; +} + +export default function UserTable({ users }: { users: User[] }) { + const canView = useCan('users.view'); + const canEdit = useCan('users.edit'); + const canDelete = useCan('users.delete'); + const canCreate = useCan('users.create'); + + if (!canView) { + return
You don't have permission to view users.
; + } + + return ( +
+ {canCreate && } + + + + + + + {(canEdit || canDelete) && } + + + + {users.map((user) => ( + + + + {(canEdit || canDelete) && ( + + )} + + ))} + +
NameEmailActions
{user.name}{user.email} + {canEdit && } + {canDelete && } +
+
+ ); +} +``` + +--- + +## Session Management + +Handling session expiration and refresh: + +```tsx +// src/App.tsx +import { useEffect } from 'react'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; +import { useNavigate } from 'react-router-dom'; + +function App() { + const { expired, logout } = useAuthState(); + const navigate = useNavigate(); + + useEffect(() => { + if (expired) { + // Show notification + alert('Your session has expired. Please log in again.'); + + // Logout and redirect + logout(); + navigate('/login'); + } + }, [expired, logout, navigate]); + + return
{/* App content */}
; +} +``` + +```tsx +// src/components/SessionMonitor.tsx +import { useEffect, useState } from 'react'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +export default function SessionMonitor() { + const { accessToken } = useAuthState(); + const [timeLeft, setTimeLeft] = useState(0); + + useEffect(() => { + if (!accessToken) return; + + // Decode JWT and calculate time left + const payload = JSON.parse(atob(accessToken.split('.')[1])); + const expiresAt = payload.exp * 1000; + + const interval = setInterval(() => { + const remaining = Math.floor((expiresAt - Date.now()) / 1000); + setTimeLeft(remaining > 0 ? remaining : 0); + }, 1000); + + return () => clearInterval(interval); + }, [accessToken]); + + if (timeLeft <= 0) return null; + + return ( +
+ Session expires in: {Math.floor(timeLeft / 60)}:{(timeLeft % 60).toString().padStart(2, '0')} +
+ ); +} +``` + +--- + +## Integration with React Query + +Combining auth state with React Query: + +```tsx +// src/hooks/useAuthenticatedQuery.ts +import { useQuery } from '@tanstack/react-query'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; +import axios from 'axios'; + +export function useAuthenticatedQuery(key: string[], url: string, options?: any) { + const { isAuthenticated, accessToken } = useAuthState(); + + return useQuery({ + queryKey: key, + queryFn: async () => { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + return response.data; + }, + enabled: isAuthenticated, + ...options, + }); +} +``` + +```tsx +// src/pages/Dashboard.tsx +import { useAuthenticatedQuery } from '../hooks/useAuthenticatedQuery'; + +export default function Dashboard() { + const { data, isLoading, error } = useAuthenticatedQuery( + ['dashboard'], + '/api/dashboard', + ); + + if (isLoading) return
Loading...
; + if (error) return
Error loading dashboard
; + + return ( +
+

Dashboard

+
{JSON.stringify(data, null, 2)}
+
+ ); +} +``` + +--- + +## Advanced: Custom Auth Interceptor + +Adding custom logic to API requests: + +```tsx +// src/utils/setupAxios.ts +import axios from 'axios'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +export function setupAxiosInterceptors() { + // Request interceptor + axios.interceptors.request.use( + (config) => { + const token = localStorage.getItem('authToken'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + // Add custom headers + config.headers['X-Client-Version'] = '1.0.0'; + + return config; + }, + (error) => Promise.reject(error), + ); + + // Response interceptor + axios.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // Handle 401 with token refresh + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const refreshToken = localStorage.getItem('refreshToken'); + const response = await axios.post('/auth/refresh', { refreshToken }); + const { accessToken } = response.data; + + localStorage.setItem('authToken', accessToken); + originalRequest.headers.Authorization = `Bearer ${accessToken}`; + + return axios(originalRequest); + } catch (refreshError) { + // Refresh failed, logout + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + }, + ); +} +``` + +```tsx +// src/main.tsx +import { setupAxiosInterceptors } from './utils/setupAxios'; + +setupAxiosInterceptors(); + +ReactDOM.createRoot(document.getElementById('root')!).render(); +``` + +--- + +## Best Practices + +### 1. Centralize Auth Configuration + +```tsx +// src/config/auth.ts +export const authConfig = { + apiUrl: import.meta.env.VITE_API_URL, + loginPath: '/auth/login', + registerPath: '/auth/register', + profilePath: '/auth/profile', + logoutPath: '/auth/logout', + redirectAfterLogin: '/dashboard', + redirectAfterLogout: '/', +}; +``` + +### 2. Create Reusable Permission Components + +```tsx +// src/components/Can.tsx +import { useCan } from '@ciscode/ui-authentication-kit'; + +interface CanProps { + permission: string; + children: React.ReactNode; + fallback?: React.ReactNode; +} + +export function Can({ permission, children, fallback = null }: CanProps) { + const hasPermission = useCan(permission); + return hasPermission ? <>{children} : <>{fallback}; +} + +// Usage + + +; +``` + +### 3. Handle Loading States Consistently + +```tsx +// src/components/AuthGuard.tsx +import { useAuthState } from '@ciscode/ui-authentication-kit'; +import { Navigate } from 'react-router-dom'; + +export function AuthGuard({ children }: { children: React.ReactNode }) { + const { isAuthenticated, booting } = useAuthState(); + + if (booting) { + return
Loading...
; + } + + return isAuthenticated ? <>{children} : ; +} +``` + +--- + +_Last Updated: January 31, 2026_ +_Version: 1.0.8_ diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md new file mode 100644 index 0000000..f3b1fff --- /dev/null +++ b/docs/MIGRATION.md @@ -0,0 +1,483 @@ +# Migration Guide + +Upgrade guides for `@ciscode/ui-authentication-kit`. + +--- + +## Table of Contents + +- [Version 1.x to 2.x](#version-1x-to-2x) +- [Version 0.x to 1.x](#version-0x-to-1x) +- [Breaking Changes Log](#breaking-changes-log) + +--- + +## Version 1.x to 2.x + +> **Note**: Version 2.x is not yet released. This section will be updated when available. + +### Expected Breaking Changes + +The following changes are planned for v2.0.0: + +#### 1. Renamed Props + +```tsx +// v1.x + + +// v2.x (planned) + +``` + +#### 2. AuthProvider Config Structure + +```tsx +// v1.x + + +// v2.x (planned) + +``` + +#### 3. Hook Return Values + +```tsx +// v1.x +const { user, isAuthenticated, booting, login, logout } = useAuthState(); + +// v2.x (planned) +const { user, isAuthenticated, isLoading, login, logout } = useAuthState(); +// ↑ `booting` renamed to `isLoading` for consistency +``` + +--- + +## Version 0.x to 1.x + +### Breaking Changes + +#### 1. Package Rename + +```bash +# v0.x +npm install @ciscode/auth-ui-kit + +# v1.x +npm install @ciscode/ui-authentication-kit +``` + +```tsx +// v0.x +import { AuthProvider } from '@ciscode/auth-ui-kit'; + +// v1.x +import { AuthProvider } from '@ciscode/ui-authentication-kit'; +``` + +#### 2. AuthProvider Structure + +**Before (v0.x):** + +```tsx + + + +``` + +**After (v1.x):** + +```tsx + + + +``` + +**Migration:** + +```tsx +// Create config object +const authConfig = { + apiUrl: process.env.REACT_APP_API_URL, + loginPath: '/auth/login', + registerPath: '/auth/register', + profilePath: '/auth/profile', + logoutPath: '/auth/logout', + redirectAfterLogin: '/dashboard', + redirectAfterLogout: '/', +}; + + + +; +``` + +#### 3. Hook Name Changes + +| v0.x | v1.x | +| ------------------ | ---------------- | +| `useAuth()` | `useAuthState()` | +| `usePermission(p)` | `useCan(p)` | +| `useRole(r)` | `useHasRole(r)` | + +**Before (v0.x):** + +```tsx +const { user, authenticated } = useAuth(); +const canEdit = usePermission('users.edit'); +const isAdmin = useRole('admin'); +``` + +**After (v1.x):** + +```tsx +const { user, isAuthenticated } = useAuthState(); +const canEdit = useCan('users.edit'); +const isAdmin = useHasRole('admin'); +``` + +#### 4. RequirePermissions Props + +**Before (v0.x):** + +```tsx + +``` + +**After (v1.x):** + +```tsx + +``` + +#### 5. RBAC Context + +**Before (v0.x):** + +```tsx +import { PermissionProvider } from '@ciscode/auth-ui-kit'; + + + +; +``` + +**After (v1.x):** + +```tsx +import { RbacProvider } from '@ciscode/ui-authentication-kit'; + + + +; +``` + +#### 6. ProfilePage Component + +**Before (v0.x):** + +```tsx +import { Profile } from '@ciscode/auth-ui-kit'; + +; +``` + +**After (v1.x):** + +```tsx +import { ProfilePage } from '@ciscode/ui-authentication-kit'; + +; +``` + +--- + +## Step-by-Step Migration (v0.x → v1.x) + +### Step 1: Update Package + +```bash +# Uninstall old package +npm uninstall @ciscode/auth-ui-kit + +# Install new package +npm install @ciscode/ui-authentication-kit +``` + +### Step 2: Update Imports + +Use find-and-replace in your editor: + +```tsx +// Find +@ciscode/auth-ui-kit + +// Replace with +@ciscode/ui-authentication-kit +``` + +### Step 3: Update AuthProvider + +```tsx +// Old (v0.x) + + +// New (v1.x) + +``` + +### Step 4: Update Hook Names + +```bash +# Find and replace in your codebase + +# useAuth → useAuthState +sed -i 's/useAuth(/useAuthState(/g' **/*.tsx + +# usePermission → useCan +sed -i 's/usePermission(/useCan(/g' **/*.tsx + +# useRole → useHasRole +sed -i 's/useRole(/useHasRole(/g' **/*.tsx +``` + +### Step 5: Update Hook Destructuring + +```tsx +// Old (v0.x) +const { user, authenticated } = useAuth(); + +// New (v1.x) +const { user, isAuthenticated } = useAuthState(); +``` + +### Step 6: Update RequirePermissions + +```tsx +// Old (v0.x) + + +// New (v1.x) + +``` + +### Step 7: Update Component Names + +```tsx +// Old (v0.x) +import { Profile, PermissionProvider } from '@ciscode/auth-ui-kit'; + +// New (v1.x) +import { ProfilePage, RbacProvider } from '@ciscode/ui-authentication-kit'; +``` + +### Step 8: Test Your Application + +Run your test suite and manually test: + +```bash +npm test +npm run dev +``` + +--- + +## Breaking Changes Log + +### v1.0.0 (January 2026) + +**Package:** + +- Renamed package from `@ciscode/auth-ui-kit` to `@ciscode/ui-authentication-kit` + +**Components:** + +- `Profile` → `ProfilePage` +- `PermissionProvider` → `RbacProvider` +- `AuthProvider` now requires `config` object prop + +**Hooks:** + +- `useAuth()` → `useAuthState()` +- `usePermission(p)` → `useCan(p)` +- `useRole(r)` → `useHasRole(r)` +- Return value `authenticated` → `isAuthenticated` + +**Props:** + +- `RequirePermissions.permissions` → `fallbackpermessions` +- `RequirePermissions.roles` → `fallbackRoles` + +**Types:** + +- `AuthConfig` interface restructured +- `User` → `UserProfile` + +### v0.9.0 (December 2025) + +**Added:** + +- Google OAuth support +- Session expiration modal +- Module-based permissions + +**Changed:** + +- Improved TypeScript types +- Better error handling + +### v0.8.0 (November 2025) + +**Initial release:** + +- Basic auth components +- RBAC support +- Profile management + +--- + +## Deprecation Warnings + +The following features are deprecated and will be removed in v2.0: + +### ⚠️ Deprecated in v1.0 + +```tsx +// DEPRECATED: Old hook names still work but log warnings +useAuth() // Use useAuthState() instead +usePermission(p) // Use useCan(p) instead +useRole(r) // Use useHasRole(r) instead + +// DEPRECATED: Old prop names + // Use fallbackpermessions + // Use fallbackRoles +``` + +To silence warnings, update to new APIs. + +--- + +## Migration Helpers + +### Codemod Script (Optional) + +Create a script to automate migration: + +```bash +#!/bin/bash +# migrate-to-v1.sh + +echo "Migrating to @ciscode/ui-authentication-kit v1.x..." + +# Update package name in imports +find src -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i 's/@ciscode\/auth-ui-kit/@ciscode\/ui-authentication-kit/g' + +# Update hook names +find src -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i 's/useAuth(/useAuthState(/g' +find src -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i 's/usePermission(/useCan(/g' +find src -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i 's/useRole(/useHasRole(/g' + +# Update component names +find src -type f -name "*.tsx" | xargs sed -i 's/ +

Login

+ + + + + ); +} +``` + +--- + +### 2. Tailwind CSS + +```tsx +// CustomLogin.tsx +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +export default function CustomLogin() { + const { login } = useAuthState(); + + return ( +
+

Login

+ + + +
+ ); +} +``` + +--- + +### 3. Styled Components + +```tsx +// CustomLogin.tsx +import styled from 'styled-components'; +import { useAuthState } from '@ciscode/ui-authentication-kit'; + +const Container = styled.div` + max-width: 400px; + margin: 0 auto; + padding: 2rem; + background: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +`; + +const Input = styled.input` + width: 100%; + padding: 0.75rem; + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 1rem; +`; + +const Button = styled.button` + width: 100%; + padding: 0.75rem; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background: #0056b3; + } +`; + +export default function CustomLogin() { + const { login } = useAuthState(); + + return ( + +

Login

+ + + +
+ ); +} +``` + +--- + +## Component-Specific Styling + +### ProfilePage + +The `ProfilePage` component accepts a `className` prop: + +```tsx +import { ProfilePage } from '@ciscode/ui-authentication-kit'; + +// With CSS Module +import styles from './Profile.module.css'; + + +// With Tailwind + + +// With styled-components +const StyledProfile = styled(ProfilePage)` + max-width: 800px; + margin: 0 auto; +`; + +``` + +### RequirePermissions + +Style the wrapper or content: + +```tsx +import { RequirePermissions } from '@ciscode/ui-authentication-kit'; + + +
+

Admin Panel

+ {/* Content */} +
+
; +``` + +--- + +## Theme System + +Create a centralized theme for your auth components: + +### CSS Variables Approach + +```css +/* styles/theme.css */ +:root { + /* Colors */ + --auth-primary: #007bff; + --auth-primary-hover: #0056b3; + --auth-secondary: #6c757d; + --auth-success: #28a745; + --auth-danger: #dc3545; + --auth-warning: #ffc107; + + /* Typography */ + --auth-font-family: 'Inter', system-ui, sans-serif; + --auth-font-size-base: 16px; + --auth-font-size-lg: 18px; + --auth-font-size-sm: 14px; + + /* Spacing */ + --auth-spacing-xs: 0.25rem; + --auth-spacing-sm: 0.5rem; + --auth-spacing-md: 1rem; + --auth-spacing-lg: 1.5rem; + --auth-spacing-xl: 2rem; + + /* Borders */ + --auth-border-radius: 4px; + --auth-border-radius-lg: 8px; + --auth-border-color: #ddd; + + /* Shadows */ + --auth-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --auth-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); + --auth-shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15); +} +``` + +```tsx +// src/main.tsx +import './styles/theme.css'; +``` + +```css +/* styles/auth-components.css */ +.auth-container { + max-width: 400px; + margin: 0 auto; + padding: var(--auth-spacing-xl); + background: white; + border-radius: var(--auth-border-radius-lg); + box-shadow: var(--auth-shadow-md); +} + +.auth-input { + width: 100%; + padding: var(--auth-spacing-md); + font-size: var(--auth-font-size-base); + border: 1px solid var(--auth-border-color); + border-radius: var(--auth-border-radius); + margin-bottom: var(--auth-spacing-md); +} + +.auth-button { + width: 100%; + padding: var(--auth-spacing-md); + background: var(--auth-primary); + color: white; + border: none; + border-radius: var(--auth-border-radius); + cursor: pointer; + font-size: var(--auth-font-size-base); + transition: background 0.2s; +} + +.auth-button:hover { + background: var(--auth-primary-hover); +} +``` + +### Theme Context (Advanced) + +```tsx +// src/contexts/ThemeContext.tsx +import React, { createContext, useContext, useState } from 'react'; + +interface Theme { + colors: { + primary: string; + secondary: string; + danger: string; + success: string; + }; + spacing: { + sm: string; + md: string; + lg: string; + }; +} + +const defaultTheme: Theme = { + colors: { + primary: '#007bff', + secondary: '#6c757d', + danger: '#dc3545', + success: '#28a745', + }, + spacing: { + sm: '0.5rem', + md: '1rem', + lg: '1.5rem', + }, +}; + +const ThemeContext = createContext(defaultTheme); + +export const useTheme = () => useContext(ThemeContext); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme] = useState(defaultTheme); + + return {children}; +}; +``` + +```tsx +// Usage +import { useTheme } from '../contexts/ThemeContext'; + +function StyledButton() { + const theme = useTheme(); + + return ( + + ); +} +``` + +--- + +## Tailwind Integration + +### Setup Tailwind with Auth Kit + +```bash +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p +``` + +```js +// tailwind.config.js +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + 'auth-primary': '#007bff', + 'auth-secondary': '#6c757d', + }, + }, + }, + plugins: [], +}; +``` + +### Reusable Tailwind Components + +```tsx +// src/components/AuthInput.tsx +interface AuthInputProps extends React.InputHTMLAttributes { + error?: string; +} + +export function AuthInput({ error, className = '', ...props }: AuthInputProps) { + return ( +
+ + {error &&

{error}

} +
+ ); +} +``` + +```tsx +// src/components/AuthButton.tsx +interface AuthButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'danger'; + loading?: boolean; +} + +export function AuthButton({ + variant = 'primary', + loading, + children, + className = '', + ...props +}: AuthButtonProps) { + const variants = { + primary: 'bg-blue-600 hover:bg-blue-700 text-white', + secondary: 'bg-gray-600 hover:bg-gray-700 text-white', + danger: 'bg-red-600 hover:bg-red-700 text-white', + }; + + return ( + + ); +} +``` + +--- + +## CSS Variables + +The Auth Kit components expose CSS variables for easy customization: + +```css +/* Override default variables */ +:root { + --auth-kit-primary: #ff6b6b; + --auth-kit-secondary: #4ecdc4; + --auth-kit-font-family: 'Poppins', sans-serif; + --auth-kit-border-radius: 12px; +} +``` + +### Available Variables + +| Variable | Default | Description | +| -------------------------- | ----------- | ------------------ | +| `--auth-kit-primary` | `#007bff` | Primary color | +| `--auth-kit-secondary` | `#6c757d` | Secondary color | +| `--auth-kit-danger` | `#dc3545` | Error/danger color | +| `--auth-kit-success` | `#28a745` | Success color | +| `--auth-kit-font-family` | `system-ui` | Font family | +| `--auth-kit-border-radius` | `4px` | Border radius | +| `--auth-kit-spacing` | `1rem` | Base spacing unit | + +--- + +## Dark Mode + +### CSS Variables Approach + +```css +/* styles/theme.css */ +:root { + --auth-bg: white; + --auth-text: #1a1a1a; + --auth-border: #ddd; +} + +[data-theme='dark'] { + --auth-bg: #1a1a1a; + --auth-text: #ffffff; + --auth-border: #333; +} + +.auth-container { + background: var(--auth-bg); + color: var(--auth-text); + border: 1px solid var(--auth-border); +} +``` + +```tsx +// src/components/ThemeToggle.tsx +import { useState, useEffect } from 'react'; + +export function ThemeToggle() { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + }, [theme]); + + return ( + + ); +} +``` + +### Tailwind Dark Mode + +```js +// tailwind.config.js +export default { + darkMode: 'class', // or 'media' + // ... +}; +``` + +```tsx +// Usage +
+ +
+``` + +--- + +## Responsive Design + +### Mobile-First Approach + +```css +/* Mobile default */ +.auth-container { + padding: 1rem; + max-width: 100%; +} + +/* Tablet */ +@media (min-width: 768px) { + .auth-container { + padding: 2rem; + max-width: 500px; + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .auth-container { + padding: 3rem; + max-width: 600px; + } +} +``` + +### Tailwind Responsive + +```tsx +
+ +
+``` + +--- + +## Best Practices + +### 1. Consistent Design System + +```tsx +// src/styles/tokens.ts +export const tokens = { + colors: { + primary: '#007bff', + secondary: '#6c757d', + success: '#28a745', + danger: '#dc3545', + warning: '#ffc107', + }, + spacing: { + xs: '0.25rem', + sm: '0.5rem', + md: '1rem', + lg: '1.5rem', + xl: '2rem', + }, + borderRadius: { + sm: '4px', + md: '8px', + lg: '12px', + }, +}; +``` + +### 2. Component Composition + +```tsx +// Build complex UIs from simple components + + + + + + Login + + + Forgot password? + + +``` + +### 3. Accessibility + +Always maintain proper contrast and focus states: + +```css +.auth-button:focus { + outline: 2px solid var(--auth-primary); + outline-offset: 2px; +} + +.auth-input:focus { + border-color: var(--auth-primary); + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); +} +``` + +--- + +_Last Updated: January 31, 2026_ +_Version: 1.0.8_ From 05d92c903b6dafd5aac7e665d616e4dd804a4423 Mon Sep 17 00:00:00 2001 From: Zaiidmo Date: Sat, 31 Jan 2026 19:30:47 +0100 Subject: [PATCH 14/18] 1.0.9 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0891426..d19ccd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.8", + "version": "1.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.8", + "version": "1.0.9", "license": "ISC", "devDependencies": { "@types/node": "^22.13.1", diff --git a/package.json b/package.json index e6d9887..f9db2f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.8", + "version": "1.0.9", "description": "", "main": "dist/index.umd.js", "module": "dist/index.mjs", From 10886973c606f33a9bfec0bffd599d875323897e Mon Sep 17 00:00:00 2001 From: Zaiidmo Date: Sat, 31 Jan 2026 19:31:47 +0100 Subject: [PATCH 15/18] ops: updated publishing trigger --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fb0fb2b..b553720 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,8 @@ on: push: tags: - "v*.*.*" + branches: + - master workflow_dispatch: permissions: From 3fe9bc87177863dca527be72f90976a75aab422f Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Mon, 2 Feb 2026 11:06:32 +0100 Subject: [PATCH 16/18] verify email page done --- src/pages/auth/SignUpPage.tsx | 14 ++- src/pages/auth/VerifyEmailPage.tsx | 135 +++++++++++++++++++++++++++++ src/providers/AuthProvider.tsx | 4 + 3 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 src/pages/auth/VerifyEmailPage.tsx diff --git a/src/pages/auth/SignUpPage.tsx b/src/pages/auth/SignUpPage.tsx index b820233..6637c07 100644 --- a/src/pages/auth/SignUpPage.tsx +++ b/src/pages/auth/SignUpPage.tsx @@ -31,7 +31,7 @@ export const SignUpPage: React.FC = () => { baseUrl, // IMPORTANT: used for OAuth redirect (same as SignIn) } = useAuthConfig(); - const { login, api } = useAuthState(); + const { api } = useAuthState(); const [fname, setFname] = useState(""); const [lname, setLname] = useState(""); @@ -67,15 +67,21 @@ export const SignUpPage: React.FC = () => { try { // 1) Register the user - await api.post("/api/auth/register", { + const { data } = await api.post("/api/auth/register", { fullname: { fname, lname }, username, email, password, }); - // 2) Auto-login after successful registration - await login({ email, password }); + // 2) Redirect to verify email page (no auto-login) + if (data?.emailSent) { + navigate(`/verify-email?email=${encodeURIComponent(email)}` , { replace: true }); + return; + } + // Fallback: still guide user to verify page + navigate(`/verify-email?email=${encodeURIComponent(email)}` , { replace: true }); + return; } catch (err: any) { const status = err?.response?.status; if (status === 400) { diff --git a/src/pages/auth/VerifyEmailPage.tsx b/src/pages/auth/VerifyEmailPage.tsx new file mode 100644 index 0000000..37c5b80 --- /dev/null +++ b/src/pages/auth/VerifyEmailPage.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import { useT } from "@ciscode/ui-translate-core"; +import { useAuthConfig } from "../../context/AuthConfigContext"; +import { toTailwindColorClasses } from "../../utils/colorHelpers"; +import { useSearchParams, useNavigate } from "react-router-dom"; + +export const VerifyEmailPage: React.FC = () => { + const t = useT("authLib"); + const navigate = useNavigate(); + const [params] = useSearchParams(); + const email = params.get("email") || ""; + + const { + brandName = t("brandName", { defaultValue: "MyBrand" }), + colors = { bg: "bg-sky-500", text: "text-white", border: "border-sky-500" }, + logoUrl, + illustrationUrl = t("community.illustrationUrl", { + defaultValue: + "https://cdn.builder.io/api/v1/image/assets/TEMP/35ba84b8335fda2819c3a14ea3d00321a0fd0e79e571caa31108468010868ca5?placeholderIfAbsent=true&apiKey=a460e9a46e514356ac1106eada03046c", + }), + communityContent = { + title: t("community.title"), + description: t("community.description"), + }, + } = useAuthConfig(); + + const { bgClass, textClass, borderClass } = toTailwindColorClasses(colors); + const gradientClass = `${bgClass} bg-gradient-to-r from-white/10 via-white/0 to-white/0`; + + return ( +
+
+ {/* Left Illustration Panel */} +
+
+ {logoUrl ? ( +
+ Brand Logo +

{brandName}

+
+ ) : ( +

{brandName}

+ )} +
+
+

{communityContent.title}

+

+ {communityContent.description} +

+
+
+
+ Verify email illustration +
+
+
+ + {/* Right Panel */} +
+ {/* Logo for small screens */} +
+ {logoUrl ? ( + Brand Logo + ) : ( +

{brandName}

+ )} +
+ + {/* Success banner */} +
+
+ + + +
+

+ {t("VerifyEmailPage.sentTitle", { defaultValue: "Verification email sent" })} +

+

+ {t("VerifyEmailPage.sentDesc", { + defaultValue: `We sent a verification link to ${email}. Please check your inbox and spam/junk folder.`, + })} +

+
+
+
+ + {/* Guidance + actions */} +
+

+ {t("VerifyEmailPage.helpText", { + defaultValue: + "Open the email and click the verification link to activate your account.", + })} +

+ + {/* Optional resend – pending backend endpoint; keep disabled for now */} +
+ + +
+
+
+
+
+ ); +}; diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 20c0a4e..ed46d37 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -12,6 +12,7 @@ import { attachAuthInterceptor, resetSessionFlag } from '../utils/attachAuthInte import { SessionExpiredModal } from '../components/SessionExpiredModal'; import { SignInPage } from '../pages/auth/SignInPage'; import { SignUpPage } from '../pages/auth/SignUpPage'; +import { VerifyEmailPage } from '../pages/auth/VerifyEmailPage'; import { Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom"; import { GoogleCallbackPage } from "../pages/auth/GoogleCallbackPage"; import { ForgotPasswordPage } from "../pages/auth/ForgotPasswordPage"; @@ -193,6 +194,9 @@ export const AuthProvider: React.FC = ({ config, children }) => { } /> + {/* public verify-email route */} + } /> + {/* public forgot/reset password routes */} } /> } /> From c8721383400f8dddbf2c0b753c4952309e4e6e39 Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Mon, 2 Feb 2026 11:18:49 +0100 Subject: [PATCH 17/18] merged --- src/providers/AuthProvider.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 245151a..ed46d37 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -194,12 +194,9 @@ export const AuthProvider: React.FC = ({ config, children }) => { } /> -<<<<<<< HEAD {/* public verify-email route */} } /> -======= ->>>>>>> 10886973c606f33a9bfec0bffd599d875323897e {/* public forgot/reset password routes */} } /> } /> From d67567289674731cc1dc27f9a44e031e90e4323b Mon Sep 17 00:00:00 2001 From: a-elkhiraooui-ciscode Date: Mon, 2 Feb 2026 11:34:20 +0100 Subject: [PATCH 18/18] 1.0.10 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d19ccd3..4256ac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.9", + "version": "1.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.9", + "version": "1.0.10", "license": "ISC", "devDependencies": { "@types/node": "^22.13.1", diff --git a/package.json b/package.json index f9db2f7..8dff03f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ciscode/ui-authentication-kit", - "version": "1.0.9", + "version": "1.0.10", "description": "", "main": "dist/index.umd.js", "module": "dist/index.mjs",