@burdenoff/microfe-auth 2026.904.1 → 2026.908.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,7 +100,7 @@ function B({ basePath: e = "", enableAdminRoutes: c = !1, bypassEnterprisePlanGa
100
100
  /* @__PURE__ */ h(f, {
101
101
  path: "signup",
102
102
  element: /* @__PURE__ */ h(d, {
103
- to: "register",
103
+ to: "/auth/register",
104
104
  replace: !0
105
105
  })
106
106
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"AuthRoutes.js","names":[],"sources":["../../src/auth/AuthRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, useState } from \"react\";\nimport {\n useFeatureFlagDetails,\n FeatureGate,\n} from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { Navigate, Route, Routes } from \"react-router\";\nimport { useAuthStore } from \"./store/authStore\";\n\n// Eager imports - these are the most commonly accessed routes\nimport { LoginPage } from \"./pages/LoginPage\";\nimport { SessionBridgePage } from \"./pages/SessionBridgePage\";\nimport { SSORelayPage } from \"./pages/SSORelayPage\";\nimport { SSORelayCallbackPage } from \"./pages/SSORelayCallbackPage\";\nimport { SSODepositPage } from \"./pages/SSODepositPage\";\nimport { SSOClearPage } from \"./pages/SSOClearPage\";\n\n// Lazy-loaded pages - loaded on demand when their routes are accessed\nconst RegisterPage = lazy(() =>\n import(\"./pages/RegisterPage\").then((m) => ({ default: m.RegisterPage })),\n);\nconst PasswordResetPage = lazy(() =>\n import(\"./pages/PasswordResetPage\").then((m) => ({\n default: m.PasswordResetPage,\n })),\n);\nconst EmailVerifyPage = lazy(() =>\n import(\"./pages/EmailVerifyPage\").then((m) => ({\n default: m.EmailVerifyPage,\n })),\n);\nconst MfaVerifyPage = lazy(() =>\n import(\"./pages/MfaVerifyPage\").then((m) => ({ default: m.MfaVerifyPage })),\n);\nconst MagicLinkPage = lazy(() =>\n import(\"./pages/MagicLinkPage\").then((m) => ({ default: m.MagicLinkPage })),\n);\nconst ReactivatePage = lazy(() =>\n import(\"./pages/ReactivatePage\").then((m) => ({ default: m.ReactivatePage })),\n);\nconst SecurityPage = lazy(() =>\n import(\"./pages/SecurityPage\").then((m) => ({ default: m.SecurityPage })),\n);\nconst SessionsPage = lazy(() =>\n import(\"./pages/SessionsPage\").then((m) => ({ default: m.SessionsPage })),\n);\nconst MfaSetupPage = lazy(() =>\n import(\"./pages/MfaSetupPage\").then((m) => ({ default: m.MfaSetupPage })),\n);\nconst AuditLogPage = lazy(() =>\n import(\"./pages/AuditLogPage\").then((m) => ({ default: m.AuditLogPage })),\n);\nconst SsoConfigPage = lazy(() =>\n import(\"./pages/SsoConfigPage\").then((m) => ({ default: m.SsoConfigPage })),\n);\nconst SsoLoginPage = lazy(() =>\n import(\"./pages/SsoLoginPage\").then((m) => ({ default: m.SsoLoginPage })),\n);\nconst SsoCallbackPage = lazy(() =>\n import(\"./pages/SsoCallbackPage\").then((m) => ({\n default: m.SsoCallbackPage,\n })),\n);\nconst OAuthCallbackPage = lazy(() =>\n import(\"./pages/OAuthCallbackPage\").then((m) => ({\n default: m.OAuthCallbackPage,\n })),\n);\nconst UsersPage = lazy(() =>\n import(\"./pages/UsersPage\").then((m) => ({ default: m.UsersPage })),\n);\nconst UserDetailsPage = lazy(() =>\n import(\"./pages/UserDetailsPage\").then((m) => ({\n default: m.UserDetailsPage,\n })),\n);\nconst UserProfilePage = lazy(() =>\n import(\"./pages/UserProfilePage\").then((m) => ({\n default: m.UserProfilePage,\n })),\n);\n/**\n * Simple loading fallback for lazy-loaded pages\n */\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\n </div>\n );\n}\n\nfunction decodeJwtExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(\n atob(token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\")),\n ) as { exp?: number };\n\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction getStoredSessionSnapshot(): {\n hasValidSession: boolean;\n expiresAt: number | null;\n} {\n try {\n const activeProfileId = sessionStorage.getItem(\"bf-active-profile\");\n if (!activeProfileId) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${activeProfileId}-tokens`);\n if (!tokensRaw) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokens = JSON.parse(tokensRaw) as {\n accessToken?: string;\n expiresAt?: number;\n };\n\n if (!tokens.accessToken) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const expiresAt =\n typeof tokens.expiresAt === \"number\"\n ? tokens.expiresAt\n : decodeJwtExpiry(tokens.accessToken);\n\n if (!expiresAt) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n return {\n hasValidSession: expiresAt > Date.now(),\n expiresAt,\n };\n } catch {\n return { hasValidSession: false, expiresAt: null };\n }\n}\n\n/**\n * Protected route wrapper\n */\nfunction ProtectedRoute({ children }: { children: React.ReactNode }) {\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const clearAuth = useAuthStore((state) => state.clearAuth);\n const [sessionCheckVersion, setSessionCheckVersion] = useState(0);\n const sessionSnapshot = getStoredSessionSnapshot();\n\n useEffect(() => {\n const handleSessionUpdate = () => {\n setSessionCheckVersion((value) => value + 1);\n };\n\n window.addEventListener(\"bf-profile-storage-change\", handleSessionUpdate);\n window.addEventListener(\"storage\", handleSessionUpdate);\n\n return () => {\n window.removeEventListener(\n \"bf-profile-storage-change\",\n handleSessionUpdate,\n );\n window.removeEventListener(\"storage\", handleSessionUpdate);\n };\n }, []);\n\n useEffect(() => {\n if (!sessionSnapshot.expiresAt) return;\n\n const timeoutMs = Math.max(sessionSnapshot.expiresAt - Date.now(), 0) + 50;\n const timer = window.setTimeout(() => {\n setSessionCheckVersion((value) => value + 1);\n }, timeoutMs);\n\n return () => window.clearTimeout(timer);\n }, [sessionSnapshot.expiresAt, sessionCheckVersion]);\n\n useEffect(() => {\n if (isAuthenticated && !sessionSnapshot.hasValidSession) {\n clearAuth();\n }\n }, [clearAuth, isAuthenticated, sessionSnapshot.hasValidSession]);\n\n if (!sessionSnapshot.hasValidSession) {\n return <Navigate to=\"/auth/login\" replace />;\n }\n\n return <>{children}</>;\n}\n\n/**\n * Internal router for the Auth microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * When used within a parent route (e.g., <Route path=\"/auth/*\">), all paths are relative:\n * - login → Login page (accessible at /auth/login)\n * - register → Registration page (accessible at /auth/register)\n * - reset-password → Password reset page (accessible at /auth/reset-password)\n * - verify-email → Email verification page (accessible at /auth/verify-email)\n * - mfa-verify → MFA verification page (accessible at /auth/mfa-verify)\n * - magic-link → Magic link authentication page (accessible at /auth/magic-link)\n * - sso/login → SSO login page (accessible at /auth/sso/login)\n * - sso/callback → SSO callback handler (accessible at /auth/sso/callback)\n * - oauth/callback → OAuth callback handler (accessible at /auth/oauth/callback)\n * - security → Security settings (protected, accessible at /auth/security)\n * - sessions → Active sessions (protected, accessible at /auth/sessions)\n * - mfa → MFA setup (protected, accessible at /auth/mfa)\n * - users → Users management (protected, admin only, accessible at /auth/users)\n * - profile → User profile page (protected, accessible at /auth/profile)\n * - audit-log → Security audit log (protected, accessible at /auth/audit-log)\n * - sso → SSO configuration (protected, admin only, accessible at /auth/sso)\n */\nexport function AuthRoutes({\n basePath: _basePath = \"\",\n // Admin routes (`/auth/users`, `/auth/users/:id`, `/auth/sso`,\n // `/auth/audit-log`) are opt-in: only the admin app shell should pass\n // `enableAdminRoutes={true}`. User-facing product hosts (vibecontrols,\n // workspaces, botlit, healthybowl, etc.) intentionally do not expose\n // these surfaces — the gateway's RBAC directives still gate the\n // underlying queries/mutations, but hiding the routes prevents\n // accidental UI discoverability and reduces the attack surface for\n // misconfigured permissions.\n enableAdminRoutes = false,\n bypassEnterprisePlanGate = false,\n}: {\n basePath?: string;\n enableAdminRoutes?: boolean;\n bypassEnterprisePlanGate?: boolean;\n}) {\n // Note: basePath is kept for backwards compatibility but not used in route paths\n // since React Router v7 uses relative paths when nested inside a parent <Route>\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n return (\n <Routes>\n {/* Public routes (no auth required) */}\n {/* LoginPage is eager-loaded as it's the most common entry point */}\n <Route path=\"login\" element={<LoginPage />} />\n <Route\n path=\"register\"\n element={\n <Suspense fallback={<PageLoader />}>\n <RegisterPage />\n </Suspense>\n }\n />\n {/* \"signup\" is the URL most people guess (it's the term this app's own\n copy uses — \"Create your account\" / sign up), but the route has\n always been \"register\" with nothing to catch the alias — it silently\n rendered a blank page. Redirect rather than rename register itself,\n since register is the established/linked path. */}\n <Route path=\"signup\" element={<Navigate to=\"register\" replace />} />\n <Route\n path=\"reset-password\"\n element={\n <Suspense fallback={<PageLoader />}>\n <PasswordResetPage />\n </Suspense>\n }\n />\n <Route\n path=\"verify-email\"\n element={\n <Suspense fallback={<PageLoader />}>\n <EmailVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"mfa-verify\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MfaVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"magic-link\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MagicLinkPage />\n </Suspense>\n }\n />\n\n {/* SSO routes (public) */}\n <Route\n path=\"sso/login\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoLoginPage />\n </Suspense>\n }\n />\n <Route\n path=\"sso/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoCallbackPage />\n </Suspense>\n }\n />\n\n {/* OAuth callback route (public) */}\n <Route\n path=\"oauth/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <OAuthCallbackPage />\n </Suspense>\n }\n />\n\n {/* Account reactivation (public) */}\n <Route\n path=\"reactivate\"\n element={\n <Suspense fallback={<PageLoader />}>\n <ReactivatePage />\n </Suspense>\n }\n />\n\n {/* Session bridge (public - embedded as hidden iframe by other app shells) */}\n <Route path=\"session-bridge\" element={<SessionBridgePage />} />\n\n {/* Cross-domain SSO relay (reads local tokens, redirects with them to target) */}\n <Route path=\"sso-relay\" element={<SSORelayPage />} />\n\n {/* Cross-domain SSO callback (receives tokens from relay, stores locally) */}\n <Route path=\"sso-callback\" element={<SSORelayCallbackPage />} />\n\n {/* Cross-domain SSO deposit (bridge host receives tokens on top-level\n redirect right after login on a product origin, writes first-party\n localStorage on the bridge origin so other products can discover\n the session). */}\n <Route path=\"sso-deposit\" element={<SSODepositPage />} />\n\n {/* Cross-domain SLO clear (bridge host clears its central session on\n top-level redirect from a product origin's logout). */}\n <Route path=\"sso-clear\" element={<SSOClearPage />} />\n\n {/* Protected routes (auth required) */}\n <Route\n path=\"security\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.security\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SecurityPage enableAdminRoutes={enableAdminRoutes} />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"sessions\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sessions\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SessionsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"mfa\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.mfa\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <MfaSetupPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n {enableAdminRoutes && showAuditLog && (\n <Route\n path=\"audit-log\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.audit-log\" defaultValue={false}>\n <Suspense fallback={<PageLoader />}>\n <AuditLogPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n )}\n {!enableAdminRoutes && (\n <Route\n path=\"audit-log\"\n element={<Navigate to=\"/auth/login\" replace />}\n />\n )}\n <Route\n path=\"sso\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sso\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SsoConfigPage\n bypassEnterprisePlanGate={bypassEnterprisePlanGate}\n />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UsersPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users/:id\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserDetailsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"profile\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.profile\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserProfilePage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n\n {/* Default redirect - when visiting /auth or /auth/ with no subpath */}\n <Route index element={<Navigate to=\"login\" replace />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuthRoutes.js","names":[],"sources":["../../src/auth/AuthRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, useState } from \"react\";\nimport {\n useFeatureFlagDetails,\n FeatureGate,\n} from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { Navigate, Route, Routes } from \"react-router\";\nimport { useAuthStore } from \"./store/authStore\";\n\n// Eager imports - these are the most commonly accessed routes\nimport { LoginPage } from \"./pages/LoginPage\";\nimport { SessionBridgePage } from \"./pages/SessionBridgePage\";\nimport { SSORelayPage } from \"./pages/SSORelayPage\";\nimport { SSORelayCallbackPage } from \"./pages/SSORelayCallbackPage\";\nimport { SSODepositPage } from \"./pages/SSODepositPage\";\nimport { SSOClearPage } from \"./pages/SSOClearPage\";\n\n// Lazy-loaded pages - loaded on demand when their routes are accessed\nconst RegisterPage = lazy(() =>\n import(\"./pages/RegisterPage\").then((m) => ({ default: m.RegisterPage })),\n);\nconst PasswordResetPage = lazy(() =>\n import(\"./pages/PasswordResetPage\").then((m) => ({\n default: m.PasswordResetPage,\n })),\n);\nconst EmailVerifyPage = lazy(() =>\n import(\"./pages/EmailVerifyPage\").then((m) => ({\n default: m.EmailVerifyPage,\n })),\n);\nconst MfaVerifyPage = lazy(() =>\n import(\"./pages/MfaVerifyPage\").then((m) => ({ default: m.MfaVerifyPage })),\n);\nconst MagicLinkPage = lazy(() =>\n import(\"./pages/MagicLinkPage\").then((m) => ({ default: m.MagicLinkPage })),\n);\nconst ReactivatePage = lazy(() =>\n import(\"./pages/ReactivatePage\").then((m) => ({ default: m.ReactivatePage })),\n);\nconst SecurityPage = lazy(() =>\n import(\"./pages/SecurityPage\").then((m) => ({ default: m.SecurityPage })),\n);\nconst SessionsPage = lazy(() =>\n import(\"./pages/SessionsPage\").then((m) => ({ default: m.SessionsPage })),\n);\nconst MfaSetupPage = lazy(() =>\n import(\"./pages/MfaSetupPage\").then((m) => ({ default: m.MfaSetupPage })),\n);\nconst AuditLogPage = lazy(() =>\n import(\"./pages/AuditLogPage\").then((m) => ({ default: m.AuditLogPage })),\n);\nconst SsoConfigPage = lazy(() =>\n import(\"./pages/SsoConfigPage\").then((m) => ({ default: m.SsoConfigPage })),\n);\nconst SsoLoginPage = lazy(() =>\n import(\"./pages/SsoLoginPage\").then((m) => ({ default: m.SsoLoginPage })),\n);\nconst SsoCallbackPage = lazy(() =>\n import(\"./pages/SsoCallbackPage\").then((m) => ({\n default: m.SsoCallbackPage,\n })),\n);\nconst OAuthCallbackPage = lazy(() =>\n import(\"./pages/OAuthCallbackPage\").then((m) => ({\n default: m.OAuthCallbackPage,\n })),\n);\nconst UsersPage = lazy(() =>\n import(\"./pages/UsersPage\").then((m) => ({ default: m.UsersPage })),\n);\nconst UserDetailsPage = lazy(() =>\n import(\"./pages/UserDetailsPage\").then((m) => ({\n default: m.UserDetailsPage,\n })),\n);\nconst UserProfilePage = lazy(() =>\n import(\"./pages/UserProfilePage\").then((m) => ({\n default: m.UserProfilePage,\n })),\n);\n/**\n * Simple loading fallback for lazy-loaded pages\n */\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-primary border-t-transparent\" />\n </div>\n );\n}\n\nfunction decodeJwtExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(\n atob(token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\")),\n ) as { exp?: number };\n\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction getStoredSessionSnapshot(): {\n hasValidSession: boolean;\n expiresAt: number | null;\n} {\n try {\n const activeProfileId = sessionStorage.getItem(\"bf-active-profile\");\n if (!activeProfileId) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${activeProfileId}-tokens`);\n if (!tokensRaw) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const tokens = JSON.parse(tokensRaw) as {\n accessToken?: string;\n expiresAt?: number;\n };\n\n if (!tokens.accessToken) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n const expiresAt =\n typeof tokens.expiresAt === \"number\"\n ? tokens.expiresAt\n : decodeJwtExpiry(tokens.accessToken);\n\n if (!expiresAt) {\n return { hasValidSession: false, expiresAt: null };\n }\n\n return {\n hasValidSession: expiresAt > Date.now(),\n expiresAt,\n };\n } catch {\n return { hasValidSession: false, expiresAt: null };\n }\n}\n\n/**\n * Protected route wrapper\n */\nfunction ProtectedRoute({ children }: { children: React.ReactNode }) {\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const clearAuth = useAuthStore((state) => state.clearAuth);\n const [sessionCheckVersion, setSessionCheckVersion] = useState(0);\n const sessionSnapshot = getStoredSessionSnapshot();\n\n useEffect(() => {\n const handleSessionUpdate = () => {\n setSessionCheckVersion((value) => value + 1);\n };\n\n window.addEventListener(\"bf-profile-storage-change\", handleSessionUpdate);\n window.addEventListener(\"storage\", handleSessionUpdate);\n\n return () => {\n window.removeEventListener(\n \"bf-profile-storage-change\",\n handleSessionUpdate,\n );\n window.removeEventListener(\"storage\", handleSessionUpdate);\n };\n }, []);\n\n useEffect(() => {\n if (!sessionSnapshot.expiresAt) return;\n\n const timeoutMs = Math.max(sessionSnapshot.expiresAt - Date.now(), 0) + 50;\n const timer = window.setTimeout(() => {\n setSessionCheckVersion((value) => value + 1);\n }, timeoutMs);\n\n return () => window.clearTimeout(timer);\n }, [sessionSnapshot.expiresAt, sessionCheckVersion]);\n\n useEffect(() => {\n if (isAuthenticated && !sessionSnapshot.hasValidSession) {\n clearAuth();\n }\n }, [clearAuth, isAuthenticated, sessionSnapshot.hasValidSession]);\n\n if (!sessionSnapshot.hasValidSession) {\n return <Navigate to=\"/auth/login\" replace />;\n }\n\n return <>{children}</>;\n}\n\n/**\n * Internal router for the Auth microfrontend.\n * This component handles routing within the microfrontend using React Router v7.\n *\n * When used within a parent route (e.g., <Route path=\"/auth/*\">), all paths are relative:\n * - login → Login page (accessible at /auth/login)\n * - register → Registration page (accessible at /auth/register)\n * - reset-password → Password reset page (accessible at /auth/reset-password)\n * - verify-email → Email verification page (accessible at /auth/verify-email)\n * - mfa-verify → MFA verification page (accessible at /auth/mfa-verify)\n * - magic-link → Magic link authentication page (accessible at /auth/magic-link)\n * - sso/login → SSO login page (accessible at /auth/sso/login)\n * - sso/callback → SSO callback handler (accessible at /auth/sso/callback)\n * - oauth/callback → OAuth callback handler (accessible at /auth/oauth/callback)\n * - security → Security settings (protected, accessible at /auth/security)\n * - sessions → Active sessions (protected, accessible at /auth/sessions)\n * - mfa → MFA setup (protected, accessible at /auth/mfa)\n * - users → Users management (protected, admin only, accessible at /auth/users)\n * - profile → User profile page (protected, accessible at /auth/profile)\n * - audit-log → Security audit log (protected, accessible at /auth/audit-log)\n * - sso → SSO configuration (protected, admin only, accessible at /auth/sso)\n */\nexport function AuthRoutes({\n basePath: _basePath = \"\",\n // Admin routes (`/auth/users`, `/auth/users/:id`, `/auth/sso`,\n // `/auth/audit-log`) are opt-in: only the admin app shell should pass\n // `enableAdminRoutes={true}`. User-facing product hosts (vibecontrols,\n // workspaces, botlit, healthybowl, etc.) intentionally do not expose\n // these surfaces — the gateway's RBAC directives still gate the\n // underlying queries/mutations, but hiding the routes prevents\n // accidental UI discoverability and reduces the attack surface for\n // misconfigured permissions.\n enableAdminRoutes = false,\n bypassEnterprisePlanGate = false,\n}: {\n basePath?: string;\n enableAdminRoutes?: boolean;\n bypassEnterprisePlanGate?: boolean;\n}) {\n // Note: basePath is kept for backwards compatibility but not used in route paths\n // since React Router v7 uses relative paths when nested inside a parent <Route>\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n return (\n <Routes>\n {/* Public routes (no auth required) */}\n {/* LoginPage is eager-loaded as it's the most common entry point */}\n <Route path=\"login\" element={<LoginPage />} />\n <Route\n path=\"register\"\n element={\n <Suspense fallback={<PageLoader />}>\n <RegisterPage />\n </Suspense>\n }\n />\n {/* \"signup\" is the URL most people guess (it's the term this app's own\n copy uses — \"Create your account\" / sign up), but the route has\n always been \"register\" with nothing to catch the alias — it silently\n rendered a blank page. Redirect rather than rename register itself,\n since register is the established/linked path. */}\n <Route path=\"signup\" element={<Navigate to=\"/auth/register\" replace />} />\n <Route\n path=\"reset-password\"\n element={\n <Suspense fallback={<PageLoader />}>\n <PasswordResetPage />\n </Suspense>\n }\n />\n <Route\n path=\"verify-email\"\n element={\n <Suspense fallback={<PageLoader />}>\n <EmailVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"mfa-verify\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MfaVerifyPage />\n </Suspense>\n }\n />\n <Route\n path=\"magic-link\"\n element={\n <Suspense fallback={<PageLoader />}>\n <MagicLinkPage />\n </Suspense>\n }\n />\n\n {/* SSO routes (public) */}\n <Route\n path=\"sso/login\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoLoginPage />\n </Suspense>\n }\n />\n <Route\n path=\"sso/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <SsoCallbackPage />\n </Suspense>\n }\n />\n\n {/* OAuth callback route (public) */}\n <Route\n path=\"oauth/callback\"\n element={\n <Suspense fallback={<PageLoader />}>\n <OAuthCallbackPage />\n </Suspense>\n }\n />\n\n {/* Account reactivation (public) */}\n <Route\n path=\"reactivate\"\n element={\n <Suspense fallback={<PageLoader />}>\n <ReactivatePage />\n </Suspense>\n }\n />\n\n {/* Session bridge (public - embedded as hidden iframe by other app shells) */}\n <Route path=\"session-bridge\" element={<SessionBridgePage />} />\n\n {/* Cross-domain SSO relay (reads local tokens, redirects with them to target) */}\n <Route path=\"sso-relay\" element={<SSORelayPage />} />\n\n {/* Cross-domain SSO callback (receives tokens from relay, stores locally) */}\n <Route path=\"sso-callback\" element={<SSORelayCallbackPage />} />\n\n {/* Cross-domain SSO deposit (bridge host receives tokens on top-level\n redirect right after login on a product origin, writes first-party\n localStorage on the bridge origin so other products can discover\n the session). */}\n <Route path=\"sso-deposit\" element={<SSODepositPage />} />\n\n {/* Cross-domain SLO clear (bridge host clears its central session on\n top-level redirect from a product origin's logout). */}\n <Route path=\"sso-clear\" element={<SSOClearPage />} />\n\n {/* Protected routes (auth required) */}\n <Route\n path=\"security\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.security\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SecurityPage enableAdminRoutes={enableAdminRoutes} />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"sessions\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sessions\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SessionsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n <Route\n path=\"mfa\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.mfa\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <MfaSetupPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n {enableAdminRoutes && showAuditLog && (\n <Route\n path=\"audit-log\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.audit-log\" defaultValue={false}>\n <Suspense fallback={<PageLoader />}>\n <AuditLogPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n )}\n {!enableAdminRoutes && (\n <Route\n path=\"audit-log\"\n element={<Navigate to=\"/auth/login\" replace />}\n />\n )}\n <Route\n path=\"sso\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.sso\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <SsoConfigPage\n bypassEnterprisePlanGate={bypassEnterprisePlanGate}\n />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UsersPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"users/:id\"\n element={\n enableAdminRoutes ? (\n <ProtectedRoute>\n <FeatureGate flag=\"auth.users\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserDetailsPage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n ) : (\n <Navigate to=\"/auth/login\" replace />\n )\n }\n />\n <Route\n path=\"profile\"\n element={\n <ProtectedRoute>\n <FeatureGate flag=\"auth.profile\" defaultValue={true}>\n <Suspense fallback={<PageLoader />}>\n <UserProfilePage />\n </Suspense>\n </FeatureGate>\n </ProtectedRoute>\n }\n />\n\n {/* Default redirect - when visiting /auth or /auth/ with no subpath */}\n <Route index element={<Navigate to=\"login\" replace />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
@@ -3,135 +3,136 @@ import { useTr as t } from "../../shared/hooks/useTr.js";
3
3
  import { useAuthOperations as n } from "../hooks/useAuthOperations.js";
4
4
  import { useOAuthOperations as ee } from "../hooks/useOAuthOperations.js";
5
5
  import { AuthLayout as r } from "../components/AuthLayout.js";
6
- import { AlertIcon as i, CheckIcon as te, EmailIcon as a, EyeIcon as ne, EyeOffIcon as o, GitHubIcon as re, GoogleIcon as ie, LockIcon as s, MicrosoftIcon as c, SpinnerIcon as l, UserIcon as u, XIcon as d } from "../components/AuthIcons.js";
7
- import { validateEmail as f, validatePassword as p } from "../utils/validation.js";
6
+ import { AlertIcon as i, CheckIcon as te, EmailIcon as a, EyeIcon as o, EyeOffIcon as s, GitHubIcon as ne, GoogleIcon as re, LockIcon as c, MicrosoftIcon as l, SpinnerIcon as u, UserIcon as d, XIcon as f } from "../components/AuthIcons.js";
7
+ import { validateEmail as p, validatePassword as ie } from "../utils/validation.js";
8
+ import { resolveLegalUrl as m } from "../utils/legalUrls.js";
8
9
  import { CaptchaV2 as ae } from "../components/CaptchaV2.js";
9
10
  import { useSignupCaptcha as oe } from "../hooks/useSignupCaptcha.js";
10
- import { useState as m } from "react";
11
- import { useNavigate as se } from "react-router";
12
- import { Fragment as ce, jsx as h, jsxs as g } from "react/jsx-runtime";
13
- import { Button as _ } from "@burdenoff/fe-libs/button";
14
- import { PagePurpose as le } from "@burdenoff/fe-libs/ui";
11
+ import { useState as h } from "react";
12
+ import { useNavigate as g } from "react-router";
13
+ import { Fragment as se, jsx as _, jsxs as v } from "react/jsx-runtime";
14
+ import { Button as y } from "@burdenoff/fe-libs/button";
15
+ import { PagePurpose as ce } from "@burdenoff/fe-libs/ui";
15
16
  //#region src/auth/pages/RegisterPage.tsx
16
- function v() {
17
- let v = t(), { productName: y } = e(), b = se(), { signUp: x, resendVerification: S, isLoading: C, errorCode: w } = n(), { initiateGoogleOAuth: ue, initiateMicrosoftOAuth: de, isProviderConfigured: T } = ee(), [E, fe] = m(!1), [D, O] = m({
17
+ function b() {
18
+ let b = t(), { productName: x } = e(), S = g(), { signUp: C, resendVerification: le, isLoading: w, errorCode: T } = n(), { initiateGoogleOAuth: ue, initiateMicrosoftOAuth: de, isProviderConfigured: E } = ee(), [D, fe] = h(!1), [O, k] = h({
18
19
  email: "",
19
20
  username: "",
20
21
  password: "",
21
22
  firstName: "",
22
23
  lastName: ""
23
- }), [k, A] = m([]), [j, M] = m(!1), [N, P] = m(null), [F, I] = m(null), [L, R] = m(null), [z, B] = m(0), { enabled: V, siteKey: pe } = oe(), [H, U] = m(!1), [W, G] = m(null), K = !!N && w === "ACCOUNT_DEACTIVATED", q = !!N && w === "ACCOUNT_REMOVED", J = !!N && w === "ACCOUNT_SUSPENDED", Y = (() => {
24
- let e = D.password;
24
+ }), [A, j] = h([]), [M, N] = h(!1), [P, F] = h(null), [I, L] = h(null), [R, z] = h(null), [B, V] = h(0), { enabled: H, siteKey: pe } = oe(), [U, W] = h(!1), [G, K] = h(null), q = !!P && T === "ACCOUNT_DEACTIVATED", J = !!P && T === "ACCOUNT_REMOVED", Y = !!P && T === "ACCOUNT_SUSPENDED", X = (() => {
25
+ let e = O.password;
25
26
  return [
26
27
  {
27
- label: v("auth.register.pwMin8", "At least 12 characters"),
28
+ label: b("auth.register.pwMin8", "At least 12 characters"),
28
29
  met: e.length >= 12
29
30
  },
30
31
  {
31
- label: v("auth.register.pwUppercase", "One uppercase letter"),
32
+ label: b("auth.register.pwUppercase", "One uppercase letter"),
32
33
  met: /[A-Z]/.test(e)
33
34
  },
34
35
  {
35
- label: v("auth.register.pwLowercase", "One lowercase letter"),
36
+ label: b("auth.register.pwLowercase", "One lowercase letter"),
36
37
  met: /[a-z]/.test(e)
37
38
  },
38
39
  {
39
- label: v("auth.register.pwNumber", "One number"),
40
+ label: b("auth.register.pwNumber", "One number"),
40
41
  met: /\d/.test(e)
41
42
  },
42
43
  {
43
- label: v("auth.register.pwSpecial", "One special character"),
44
+ label: b("auth.register.pwSpecial", "One special character"),
44
45
  met: /[!@#$%^&*(),.?":{}|<>]/.test(e)
45
46
  }
46
47
  ];
47
- })(), X = (() => {
48
- let e = Y.filter((e) => e.met).length;
48
+ })(), Z = (() => {
49
+ let e = X.filter((e) => e.met).length;
49
50
  return e <= 2 ? {
50
- level: v("auth.register.passwordStrengthWeak", "weak"),
51
+ level: b("auth.register.passwordStrengthWeak", "weak"),
51
52
  color: "bg-status-error-bg-subtle0",
52
53
  width: "w-1/4"
53
54
  } : e <= 3 ? {
54
- level: v("auth.register.passwordStrengthFair", "fair"),
55
+ level: b("auth.register.passwordStrengthFair", "fair"),
55
56
  color: "bg-status-warning-text",
56
57
  width: "w-2/4"
57
58
  } : e <= 4 ? {
58
- level: v("auth.register.passwordStrengthGood", "good"),
59
+ level: b("auth.register.passwordStrengthGood", "good"),
59
60
  color: "bg-status-warning-bg-subtle0",
60
61
  width: "w-3/4"
61
62
  } : {
62
- level: v("auth.register.passwordStrengthStrong", "strong"),
63
+ level: b("auth.register.passwordStrengthStrong", "strong"),
63
64
  color: "bg-status-success-text",
64
65
  width: "w-full"
65
66
  };
66
- })(), Z = async (e) => {
67
+ })(), me = async (e) => {
67
68
  e.preventDefault();
68
69
  let t = [];
69
- f(D.email) || t.push(v("auth.register.invalidEmail", "Please enter a valid email address")), (!D.username || D.username.length < 3) && t.push(v("auth.register.usernameMin3", "Username must be at least 3 characters")), /^[a-zA-Z0-9_-]+$/.test(D.username) || t.push(v("auth.register.usernameChars", "Username can only contain letters, numbers, underscores, and hyphens"));
70
- let n = p(D.password);
71
- if (n.valid || t.push(...n.errors), V && !L && t.push(v("auth.register.completeCaptcha", "Please complete the reCAPTCHA verification")), t.length > 0) {
72
- A(t);
70
+ p(O.email) || t.push(b("auth.register.invalidEmail", "Please enter a valid email address")), (!O.username || O.username.length < 3) && t.push(b("auth.register.usernameMin3", "Username must be at least 3 characters")), /^[a-zA-Z0-9_-]+$/.test(O.username) || t.push(b("auth.register.usernameChars", "Username can only contain letters, numbers, underscores, and hyphens"));
71
+ let n = ie(O.password);
72
+ if (n.valid || t.push(...n.errors), H && !R && t.push(b("auth.register.completeCaptcha", "Please complete the reCAPTCHA verification")), t.length > 0) {
73
+ j(t);
73
74
  return;
74
75
  }
75
- A([]), P(null);
76
+ j([]), F(null);
76
77
  try {
77
- await x({
78
- ...D,
79
- ...L ? { recaptchaToken: L } : {}
80
- }), M(!0);
78
+ await C({
79
+ ...O,
80
+ ...R ? { recaptchaToken: R } : {}
81
+ }), N(!0);
81
82
  } catch (e) {
82
- P(e instanceof Error ? e.message : v("auth.register.signUpFailed", "Sign up failed")), console.error("Sign up failed:", e), V && (R(null), B((e) => e + 1));
83
+ F(e instanceof Error ? e.message : b("auth.register.signUpFailed", "Sign up failed")), console.error("Sign up failed:", e), H && (z(null), V((e) => e + 1));
83
84
  }
84
- }, me = () => {
85
- I(null);
85
+ }, he = () => {
86
+ L(null);
86
87
  try {
87
88
  ue();
88
89
  } catch (e) {
89
- I(e instanceof Error ? e.message : v("auth.register.googleSignUpFailed", "Failed to initiate Google sign up"));
90
+ L(e instanceof Error ? e.message : b("auth.register.googleSignUpFailed", "Failed to initiate Google sign up"));
90
91
  }
91
- }, he = () => {
92
- I(null);
92
+ }, ge = () => {
93
+ L(null);
93
94
  try {
94
95
  de();
95
96
  } catch (e) {
96
- I(e instanceof Error ? e.message : v("auth.register.microsoftSignUpFailed", "Failed to initiate Microsoft sign up"));
97
+ L(e instanceof Error ? e.message : b("auth.register.microsoftSignUpFailed", "Failed to initiate Microsoft sign up"));
97
98
  }
98
- }, Q = T("google"), $ = T("microsoft"), ge = [
99
+ }, Q = E("google"), $ = E("microsoft"), _e = [
99
100
  {
100
101
  name: "Google",
101
- icon: ie,
102
- onClick: me,
102
+ icon: re,
103
+ onClick: he,
103
104
  disabled: !Q,
104
- tooltip: Q ? void 0 : v("auth.register.googleNotConfigured", "Google sign up is not configured")
105
+ tooltip: Q ? void 0 : b("auth.register.googleNotConfigured", "Google sign up is not configured")
105
106
  },
106
107
  {
107
108
  name: "GitHub",
108
- icon: re,
109
+ icon: ne,
109
110
  onClick: () => {},
110
111
  disabled: !0,
111
- tooltip: v("auth.register.githubNotConfigured", "GitHub sign up is not configured")
112
+ tooltip: b("auth.register.githubNotConfigured", "GitHub sign up is not configured")
112
113
  },
113
114
  {
114
115
  name: "Microsoft",
115
- icon: c,
116
- onClick: he,
116
+ icon: l,
117
+ onClick: ge,
117
118
  disabled: !$,
118
- tooltip: $ ? void 0 : v("auth.register.microsoftNotConfigured", "Microsoft sign up is not configured")
119
+ tooltip: $ ? void 0 : b("auth.register.microsoftNotConfigured", "Microsoft sign up is not configured")
119
120
  }
120
- ], [_e, ve = ""] = v("auth.register.verificationEmailSentTo", "We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.").split("{{email}}");
121
- return j ? /* @__PURE__ */ h(r, {
122
- title: v("auth.register.checkEmail", "Check your email"),
123
- subtitle: v("auth.register.verificationLinkSent", "We've sent you a verification link"),
124
- children: /* @__PURE__ */ g("div", {
121
+ ], [ve, ye = ""] = b("auth.register.verificationEmailSentTo", "We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.").split("{{email}}");
122
+ return M ? /* @__PURE__ */ _(r, {
123
+ title: b("auth.register.checkEmail", "Check your email"),
124
+ subtitle: b("auth.register.verificationLinkSent", "We've sent you a verification link"),
125
+ children: /* @__PURE__ */ v("div", {
125
126
  className: "flex flex-col items-center justify-center py-6",
126
127
  children: [
127
- /* @__PURE__ */ h("div", {
128
+ /* @__PURE__ */ _("div", {
128
129
  className: "size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6",
129
- children: /* @__PURE__ */ h("svg", {
130
+ children: /* @__PURE__ */ _("svg", {
130
131
  className: "size-10 text-status-success-text",
131
132
  fill: "none",
132
133
  viewBox: "0 0 24 24",
133
134
  stroke: "currentColor",
134
- children: /* @__PURE__ */ h("path", {
135
+ children: /* @__PURE__ */ _("path", {
135
136
  strokeLinecap: "round",
136
137
  strokeLinejoin: "round",
137
138
  strokeWidth: 2,
@@ -139,56 +140,56 @@ function v() {
139
140
  })
140
141
  })
141
142
  }),
142
- /* @__PURE__ */ h("h3", {
143
+ /* @__PURE__ */ _("h3", {
143
144
  className: "text-lg font-semibold text-[var(--color-text-primary)] mb-2",
144
- children: v("auth.register.accountCreated", "Account created successfully!")
145
+ children: b("auth.register.accountCreated", "Account created successfully!")
145
146
  }),
146
- /* @__PURE__ */ g("p", {
147
+ /* @__PURE__ */ v("p", {
147
148
  className: "text-center text-[var(--color-text-secondary)] mb-8 max-w-sm",
148
149
  children: [
149
- _e,
150
- /* @__PURE__ */ h("span", {
150
+ ve,
151
+ /* @__PURE__ */ _("span", {
151
152
  className: "font-medium text-[var(--color-text-primary)]",
152
- children: D.email
153
+ children: O.email
153
154
  }),
154
- ve
155
+ ye
155
156
  ]
156
157
  }),
157
- /* @__PURE__ */ g("div", {
158
+ /* @__PURE__ */ v("div", {
158
159
  className: "space-y-3 w-full",
159
160
  children: [
160
- /* @__PURE__ */ h(_, {
161
+ /* @__PURE__ */ _(y, {
161
162
  type: "button",
162
- onClick: () => b("/auth/login"),
163
+ onClick: () => S("/auth/login"),
163
164
  variant: "default",
164
165
  size: "lg",
165
166
  className: "w-full font-semibold",
166
- children: v("auth.register.continueToSignIn", "Continue to sign in")
167
+ children: b("auth.register.continueToSignIn", "Continue to sign in")
167
168
  }),
168
- W && /* @__PURE__ */ h("p", {
169
+ G && /* @__PURE__ */ _("p", {
169
170
  className: "text-center text-sm text-status-error-text",
170
- children: W
171
+ children: G
171
172
  }),
172
- /* @__PURE__ */ g("p", {
173
+ /* @__PURE__ */ v("p", {
173
174
  className: "text-center text-sm text-[var(--color-text-tertiary)]",
174
175
  children: [
175
- v("auth.register.didntReceiveEmail", "Didn't receive the email? Check your spam folder or"),
176
+ b("auth.register.didntReceiveEmail", "Didn't receive the email? Check your spam folder or"),
176
177
  " ",
177
- /* @__PURE__ */ h("button", {
178
+ /* @__PURE__ */ _("button", {
178
179
  type: "button",
179
- disabled: H,
180
+ disabled: U,
180
181
  onClick: async () => {
181
- G(null), U(!0);
182
+ K(null), W(!0);
182
183
  try {
183
- await S(D.email);
184
+ await le(O.email);
184
185
  } catch (e) {
185
- G(e instanceof Error ? e.message : v("auth.register.resendFailed", "Failed to resend verification email"));
186
+ K(e instanceof Error ? e.message : b("auth.register.resendFailed", "Failed to resend verification email"));
186
187
  } finally {
187
- U(!1);
188
+ W(!1);
188
189
  }
189
190
  },
190
191
  className: "text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed",
191
- children: H ? v("auth.register.resending", "resending...") : v("auth.register.tryAgain", "try again")
192
+ children: U ? b("auth.register.resending", "resending...") : b("auth.register.tryAgain", "try again")
192
193
  })
193
194
  ]
194
195
  })
@@ -196,223 +197,223 @@ function v() {
196
197
  })
197
198
  ]
198
199
  })
199
- }) : /* @__PURE__ */ g(r, {
200
- title: v("auth.register.createAccount", "Create your account"),
201
- subtitle: v("auth.register.startJourney", "Start your journey with {{productName}}").replace("{{productName}}", y || "Burdenoff Workspaces"),
202
- productName: y,
200
+ }) : /* @__PURE__ */ v(r, {
201
+ title: b("auth.register.createAccount", "Create your account"),
202
+ subtitle: b("auth.register.startJourney", "Start your journey with {{productName}}").replace("{{productName}}", x || "Burdenoff Workspaces"),
203
+ productName: x,
203
204
  children: [
204
- /* @__PURE__ */ h(le, {
205
+ /* @__PURE__ */ _(ce, {
205
206
  className: "mb-5",
206
- children: v("auth.register.purpose", "Create your account to get a personal workspace and start collaborating. You'll verify your email, then you can sign in and invite your team.")
207
+ children: b("auth.register.purpose", "Create your account to get a personal workspace and start collaborating. You'll verify your email, then you can sign in and invite your team.")
207
208
  }),
208
- /* @__PURE__ */ g("form", {
209
- onSubmit: Z,
209
+ /* @__PURE__ */ v("form", {
210
+ onSubmit: me,
210
211
  className: "space-y-5",
211
212
  children: [
212
- K && /* @__PURE__ */ g("div", {
213
+ q && /* @__PURE__ */ v("div", {
213
214
  className: "flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border",
214
- children: [/* @__PURE__ */ h(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ g("div", {
215
+ children: [/* @__PURE__ */ _(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ v("div", {
215
216
  className: "space-y-2",
216
217
  children: [
217
- /* @__PURE__ */ h("p", {
218
+ /* @__PURE__ */ _("p", {
218
219
  className: "text-sm font-medium text-status-warning-text",
219
- children: v("auth.register.accountDeactivated", "This email belongs to a deactivated account.")
220
+ children: b("auth.register.accountDeactivated", "This email belongs to a deactivated account.")
220
221
  }),
221
- /* @__PURE__ */ h("p", {
222
+ /* @__PURE__ */ _("p", {
222
223
  className: "text-xs text-status-warning-text/80",
223
- children: v("auth.register.accountDeactivatedHelp", "If this is your account, you can reactivate it instead of creating a new one.")
224
+ children: b("auth.register.accountDeactivatedHelp", "If this is your account, you can reactivate it instead of creating a new one.")
224
225
  }),
225
- /* @__PURE__ */ h("button", {
226
+ /* @__PURE__ */ _("button", {
226
227
  type: "button",
227
- onClick: () => b("/auth/reactivate", { state: { email: D.email } }),
228
+ onClick: () => S("/auth/reactivate", { state: { email: O.email } }),
228
229
  className: "text-sm font-semibold text-status-warning-text underline underline-offset-2",
229
- children: v("auth.register.reactivateCta", "Reactivate my account")
230
+ children: b("auth.register.reactivateCta", "Reactivate my account")
230
231
  })
231
232
  ]
232
233
  })]
233
234
  }),
234
- (q || J) && /* @__PURE__ */ g("div", {
235
+ (J || Y) && /* @__PURE__ */ v("div", {
235
236
  className: "flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border",
236
- children: [/* @__PURE__ */ h(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ h("p", {
237
+ children: [/* @__PURE__ */ _(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ _("p", {
237
238
  className: "text-sm text-status-warning-text",
238
- children: N
239
+ children: P
239
240
  })]
240
241
  }),
241
- (N && !K && !q && !J || k.length > 0) && /* @__PURE__ */ g("div", {
242
+ (P && !q && !J && !Y || A.length > 0) && /* @__PURE__ */ v("div", {
242
243
  className: "flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
243
- children: [/* @__PURE__ */ h(i, { className: "size-5 text-status-error-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ g("div", {
244
+ children: [/* @__PURE__ */ _(i, { className: "size-5 text-status-error-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ v("div", {
244
245
  className: "space-y-1",
245
- children: [N && !K && !q && !J && /* @__PURE__ */ h("p", {
246
+ children: [P && !q && !J && !Y && /* @__PURE__ */ _("p", {
246
247
  className: "text-sm text-status-error-text",
247
- children: N
248
- }), k.map((e) => /* @__PURE__ */ h("p", {
248
+ children: P
249
+ }), A.map((e) => /* @__PURE__ */ _("p", {
249
250
  className: "text-sm text-status-error-text",
250
251
  children: e
251
252
  }, e))]
252
253
  })]
253
254
  }),
254
- /* @__PURE__ */ g("div", {
255
+ /* @__PURE__ */ v("div", {
255
256
  className: "grid grid-cols-2 gap-4",
256
- children: [/* @__PURE__ */ g("div", {
257
+ children: [/* @__PURE__ */ v("div", {
257
258
  className: "space-y-2",
258
- children: [/* @__PURE__ */ h("label", {
259
+ children: [/* @__PURE__ */ _("label", {
259
260
  htmlFor: "firstName",
260
261
  className: "block text-sm font-medium text-[var(--color-text-primary)]",
261
- children: v("auth.register.firstName", "First Name")
262
- }), /* @__PURE__ */ h("input", {
262
+ children: b("auth.register.firstName", "First Name")
263
+ }), /* @__PURE__ */ _("input", {
263
264
  id: "firstName",
264
265
  type: "text",
265
266
  required: !0,
266
267
  "aria-required": "true",
267
- "aria-label": v("auth.register.firstName", "First Name"),
268
- value: D.firstName,
269
- onChange: (e) => O({
270
- ...D,
268
+ "aria-label": b("auth.register.firstName", "First Name"),
269
+ value: O.firstName,
270
+ onChange: (e) => k({
271
+ ...O,
271
272
  firstName: e.target.value
272
273
  }),
273
274
  className: "block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
274
- placeholder: v("auth.register.firstNamePlaceholder", "Enter first name")
275
+ placeholder: b("auth.register.firstNamePlaceholder", "Enter first name")
275
276
  })]
276
- }), /* @__PURE__ */ g("div", {
277
+ }), /* @__PURE__ */ v("div", {
277
278
  className: "space-y-2",
278
- children: [/* @__PURE__ */ h("label", {
279
+ children: [/* @__PURE__ */ _("label", {
279
280
  htmlFor: "lastName",
280
281
  className: "block text-sm font-medium text-[var(--color-text-primary)]",
281
- children: v("auth.register.lastName", "Last Name")
282
- }), /* @__PURE__ */ h("input", {
282
+ children: b("auth.register.lastName", "Last Name")
283
+ }), /* @__PURE__ */ _("input", {
283
284
  id: "lastName",
284
285
  type: "text",
285
286
  required: !0,
286
287
  "aria-required": "true",
287
- "aria-label": v("auth.register.lastName", "Last Name"),
288
- value: D.lastName,
289
- onChange: (e) => O({
290
- ...D,
288
+ "aria-label": b("auth.register.lastName", "Last Name"),
289
+ value: O.lastName,
290
+ onChange: (e) => k({
291
+ ...O,
291
292
  lastName: e.target.value
292
293
  }),
293
294
  className: "block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
294
- placeholder: v("auth.register.lastNamePlaceholder", "Enter last name")
295
+ placeholder: b("auth.register.lastNamePlaceholder", "Enter last name")
295
296
  })]
296
297
  })]
297
298
  }),
298
- /* @__PURE__ */ g("div", {
299
+ /* @__PURE__ */ v("div", {
299
300
  className: "space-y-2",
300
- children: [/* @__PURE__ */ h("label", {
301
+ children: [/* @__PURE__ */ _("label", {
301
302
  htmlFor: "username",
302
303
  className: "block text-sm font-medium text-[var(--color-text-primary)]",
303
- children: v("auth.register.username", "Username")
304
- }), /* @__PURE__ */ g("div", {
304
+ children: b("auth.register.username", "Username")
305
+ }), /* @__PURE__ */ v("div", {
305
306
  className: "relative",
306
- children: [/* @__PURE__ */ h("div", {
307
+ children: [/* @__PURE__ */ _("div", {
307
308
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
308
- children: /* @__PURE__ */ h(u, { className: "size-5 text-[var(--color-text-tertiary)]" })
309
- }), /* @__PURE__ */ h("input", {
309
+ children: /* @__PURE__ */ _(d, { className: "size-5 text-[var(--color-text-tertiary)]" })
310
+ }), /* @__PURE__ */ _("input", {
310
311
  id: "username",
311
312
  type: "text",
312
313
  required: !0,
313
314
  "aria-required": "true",
314
- "aria-label": v("auth.register.username", "Username"),
315
+ "aria-label": b("auth.register.username", "Username"),
315
316
  autoComplete: "username",
316
- value: D.username,
317
- onChange: (e) => O({
318
- ...D,
317
+ value: O.username,
318
+ onChange: (e) => k({
319
+ ...O,
319
320
  username: e.target.value
320
321
  }),
321
322
  className: "block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
322
- placeholder: v("auth.register.usernamePlaceholder", "Choose a username")
323
+ placeholder: b("auth.register.usernamePlaceholder", "Choose a username")
323
324
  })]
324
325
  })]
325
326
  }),
326
- /* @__PURE__ */ g("div", {
327
+ /* @__PURE__ */ v("div", {
327
328
  className: "space-y-2",
328
- children: [/* @__PURE__ */ h("label", {
329
+ children: [/* @__PURE__ */ _("label", {
329
330
  htmlFor: "email",
330
331
  className: "block text-sm font-medium text-[var(--color-text-primary)]",
331
- children: v("auth.register.email", "Email")
332
- }), /* @__PURE__ */ g("div", {
332
+ children: b("auth.register.email", "Email")
333
+ }), /* @__PURE__ */ v("div", {
333
334
  className: "relative",
334
- children: [/* @__PURE__ */ h("div", {
335
+ children: [/* @__PURE__ */ _("div", {
335
336
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
336
- children: /* @__PURE__ */ h(a, { className: "size-5 text-[var(--color-text-tertiary)]" })
337
- }), /* @__PURE__ */ h("input", {
337
+ children: /* @__PURE__ */ _(a, { className: "size-5 text-[var(--color-text-tertiary)]" })
338
+ }), /* @__PURE__ */ _("input", {
338
339
  id: "email",
339
340
  type: "email",
340
341
  required: !0,
341
342
  "aria-required": "true",
342
- "aria-label": v("auth.register.email", "Email"),
343
+ "aria-label": b("auth.register.email", "Email"),
343
344
  autoComplete: "email",
344
- value: D.email,
345
- onChange: (e) => O({
346
- ...D,
345
+ value: O.email,
346
+ onChange: (e) => k({
347
+ ...O,
347
348
  email: e.target.value
348
349
  }),
349
350
  className: "block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
350
- placeholder: v("auth.register.emailPlaceholder", "you@example.com")
351
+ placeholder: b("auth.register.emailPlaceholder", "you@example.com")
351
352
  })]
352
353
  })]
353
354
  }),
354
- /* @__PURE__ */ g("div", {
355
+ /* @__PURE__ */ v("div", {
355
356
  className: "space-y-2",
356
357
  children: [
357
- /* @__PURE__ */ h("label", {
358
+ /* @__PURE__ */ _("label", {
358
359
  htmlFor: "password",
359
360
  className: "block text-sm font-medium text-[var(--color-text-primary)]",
360
- children: v("auth.register.password", "Password")
361
+ children: b("auth.register.password", "Password")
361
362
  }),
362
- /* @__PURE__ */ g("div", {
363
+ /* @__PURE__ */ v("div", {
363
364
  className: "relative",
364
365
  children: [
365
- /* @__PURE__ */ h("div", {
366
+ /* @__PURE__ */ _("div", {
366
367
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
367
- children: /* @__PURE__ */ h(s, { className: "size-5 text-[var(--color-text-tertiary)]" })
368
+ children: /* @__PURE__ */ _(c, { className: "size-5 text-[var(--color-text-tertiary)]" })
368
369
  }),
369
- /* @__PURE__ */ h("input", {
370
+ /* @__PURE__ */ _("input", {
370
371
  id: "password",
371
- type: E ? "text" : "password",
372
+ type: D ? "text" : "password",
372
373
  required: !0,
373
374
  "aria-required": "true",
374
- "aria-label": v("auth.register.password", "Password"),
375
+ "aria-label": b("auth.register.password", "Password"),
375
376
  autoComplete: "new-password",
376
- value: D.password,
377
- onChange: (e) => O({
378
- ...D,
377
+ value: O.password,
378
+ onChange: (e) => k({
379
+ ...O,
379
380
  password: e.target.value
380
381
  }),
381
382
  className: "block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
382
- placeholder: v("auth.register.passwordPlaceholder", "Create a strong password")
383
+ placeholder: b("auth.register.passwordPlaceholder", "Create a strong password")
383
384
  }),
384
- /* @__PURE__ */ h("button", {
385
+ /* @__PURE__ */ _("button", {
385
386
  type: "button",
386
- onClick: () => fe(!E),
387
- "aria-label": E ? v("auth.register.hidePassword", "Hide password") : v("auth.register.showPassword", "Show password"),
387
+ onClick: () => fe(!D),
388
+ "aria-label": D ? b("auth.register.hidePassword", "Hide password") : b("auth.register.showPassword", "Show password"),
388
389
  className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
389
- children: h(E ? o : ne, { className: "size-5" })
390
+ children: _(D ? s : o, { className: "size-5" })
390
391
  })
391
392
  ]
392
393
  }),
393
- D.password && /* @__PURE__ */ g("div", {
394
+ O.password && /* @__PURE__ */ v("div", {
394
395
  className: "space-y-3 pt-2",
395
- children: [/* @__PURE__ */ g("div", {
396
+ children: [/* @__PURE__ */ v("div", {
396
397
  className: "space-y-1",
397
- children: [/* @__PURE__ */ h("div", {
398
+ children: [/* @__PURE__ */ _("div", {
398
399
  className: "h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden",
399
- children: /* @__PURE__ */ h("div", { className: `h-full ${X.color} ${X.width} transition-all duration-300` })
400
- }), /* @__PURE__ */ g("p", {
400
+ children: /* @__PURE__ */ _("div", { className: `h-full ${Z.color} ${Z.width} transition-all duration-300` })
401
+ }), /* @__PURE__ */ v("p", {
401
402
  className: "text-xs text-[var(--color-text-tertiary)] capitalize",
402
403
  children: [
403
- v("auth.register.passwordStrength", "Password strength:"),
404
+ b("auth.register.passwordStrength", "Password strength:"),
404
405
  " ",
405
- /* @__PURE__ */ h("span", {
406
+ /* @__PURE__ */ _("span", {
406
407
  className: "font-medium",
407
- children: X.level
408
+ children: Z.level
408
409
  })
409
410
  ]
410
411
  })]
411
- }), /* @__PURE__ */ h("div", {
412
+ }), /* @__PURE__ */ _("div", {
412
413
  className: "grid grid-cols-2 gap-2",
413
- children: Y.map((e) => /* @__PURE__ */ g("div", {
414
+ children: X.map((e) => /* @__PURE__ */ v("div", {
414
415
  className: "flex items-center gap-2",
415
- children: [e.met ? /* @__PURE__ */ h(te, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ h(d, { className: "size-4 text-[var(--color-text-tertiary)]" }), /* @__PURE__ */ h("span", {
416
+ children: [e.met ? /* @__PURE__ */ _(te, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ _(f, { className: "size-4 text-[var(--color-text-tertiary)]" }), /* @__PURE__ */ _("span", {
416
417
  className: `text-xs ${e.met ? "text-status-success-text" : "text-[var(--color-text-tertiary)]"}`,
417
418
  children: e.label
418
419
  })]
@@ -421,87 +422,91 @@ function v() {
421
422
  })
422
423
  ]
423
424
  }),
424
- /* @__PURE__ */ g("p", {
425
+ /* @__PURE__ */ v("p", {
425
426
  className: "text-xs text-[var(--color-text-tertiary)] leading-relaxed",
426
427
  children: [
427
- v("auth.register.agreeToTerms", "By creating an account, you agree to our"),
428
+ b("auth.register.agreeToTerms", "By creating an account, you agree to our"),
428
429
  " ",
429
- /* @__PURE__ */ h("a", {
430
- href: "/legal/terms",
430
+ /* @__PURE__ */ _("a", {
431
+ href: m("/terms"),
432
+ target: "_blank",
433
+ rel: "noopener noreferrer",
431
434
  className: "text-text-link underline underline-offset-2",
432
- children: v("auth.register.termsOfService", "Terms of Service")
435
+ children: b("auth.register.termsOfService", "Terms of Service")
433
436
  }),
434
437
  " ",
435
- v("auth.register.and", "and"),
438
+ b("auth.register.and", "and"),
436
439
  " ",
437
- /* @__PURE__ */ h("a", {
438
- href: "/legal/privacy",
440
+ /* @__PURE__ */ _("a", {
441
+ href: m("/privacy"),
442
+ target: "_blank",
443
+ rel: "noopener noreferrer",
439
444
  className: "text-text-link underline underline-offset-2",
440
- children: v("auth.register.privacyPolicy", "Privacy Policy")
445
+ children: b("auth.register.privacyPolicy", "Privacy Policy")
441
446
  }),
442
447
  "."
443
448
  ]
444
449
  }),
445
- /* @__PURE__ */ h(ae, {
446
- enabled: V,
450
+ /* @__PURE__ */ _(ae, {
451
+ enabled: H,
447
452
  siteKey: pe,
448
- onVerify: R,
449
- onExpired: () => R(null)
450
- }, z),
451
- /* @__PURE__ */ h(_, {
453
+ onVerify: z,
454
+ onExpired: () => z(null)
455
+ }, B),
456
+ /* @__PURE__ */ _(y, {
452
457
  type: "submit",
453
- disabled: C,
458
+ disabled: w,
454
459
  variant: "default",
455
460
  size: "lg",
456
461
  className: "w-full font-semibold",
457
- children: C ? /* @__PURE__ */ g(ce, { children: [/* @__PURE__ */ h(l, { className: "size-5 animate-spin" }), /* @__PURE__ */ h("span", { children: v("auth.register.creatingAccount", "Creating account...") })] }) : v("auth.register.createAccountBtn", "Create account")
462
+ children: w ? /* @__PURE__ */ v(se, { children: [/* @__PURE__ */ _(u, { className: "size-5 animate-spin" }), /* @__PURE__ */ _("span", { children: b("auth.register.creatingAccount", "Creating account...") })] }) : b("auth.register.createAccountBtn", "Create account")
458
463
  })
459
464
  ]
460
465
  }),
461
- /* @__PURE__ */ g("div", {
466
+ /* @__PURE__ */ v("div", {
462
467
  className: "relative my-8",
463
- children: [/* @__PURE__ */ h("div", {
468
+ children: [/* @__PURE__ */ _("div", {
464
469
  className: "absolute inset-0 flex items-center",
465
- children: /* @__PURE__ */ h("div", { className: "w-full border-t border-[var(--color-border-default)]" })
466
- }), /* @__PURE__ */ h("div", {
470
+ children: /* @__PURE__ */ _("div", { className: "w-full border-t border-[var(--color-border-default)]" })
471
+ }), /* @__PURE__ */ _("div", {
467
472
  className: "relative flex justify-center",
468
- children: /* @__PURE__ */ h("span", {
473
+ children: /* @__PURE__ */ _("span", {
469
474
  className: "px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]",
470
- children: v("auth.register.orSignUpWith", "or sign up with")
475
+ children: b("auth.register.orSignUpWith", "or sign up with")
471
476
  })
472
477
  })]
473
478
  }),
474
- F && /* @__PURE__ */ g("div", {
479
+ I && /* @__PURE__ */ v("div", {
475
480
  className: "flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border",
476
- children: [/* @__PURE__ */ h(i, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ h("p", {
481
+ children: [/* @__PURE__ */ _(i, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ _("p", {
477
482
  className: "text-sm text-status-error-text",
478
- children: F
483
+ children: I
479
484
  })]
480
485
  }),
481
- /* @__PURE__ */ h("div", {
486
+ /* @__PURE__ */ _("div", {
482
487
  className: "grid grid-cols-3 gap-3",
483
- children: ge.map((e) => /* @__PURE__ */ g("button", {
488
+ children: _e.map((e) => /* @__PURE__ */ v("button", {
484
489
  type: "button",
485
490
  onClick: e.onClick,
486
491
  disabled: e.disabled,
487
492
  title: e.tooltip,
488
493
  className: `flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${e.disabled ? "border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50" : "border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]"}`,
489
- children: [/* @__PURE__ */ h(e.icon, { className: `size-5 ${e.disabled ? "opacity-50" : ""}` }), /* @__PURE__ */ h("span", {
494
+ children: [/* @__PURE__ */ _(e.icon, { className: `size-5 ${e.disabled ? "opacity-50" : ""}` }), /* @__PURE__ */ _("span", {
490
495
  className: `sr-only sm:not-sr-only text-sm font-medium transition-colors ${e.disabled ? "text-[var(--color-text-disabled)]" : "text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]"}`,
491
496
  children: e.name
492
497
  })]
493
498
  }, e.name))
494
499
  }),
495
- /* @__PURE__ */ g("p", {
500
+ /* @__PURE__ */ v("p", {
496
501
  className: "mt-8 text-center text-sm text-[var(--color-text-secondary)]",
497
502
  children: [
498
- v("auth.register.alreadyHaveAccount", "Already have an account?"),
503
+ b("auth.register.alreadyHaveAccount", "Already have an account?"),
499
504
  " ",
500
- /* @__PURE__ */ h("button", {
505
+ /* @__PURE__ */ _("button", {
501
506
  type: "button",
502
- onClick: () => b("/auth/login"),
507
+ onClick: () => S("/auth/login"),
503
508
  className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
504
- children: v("auth.register.signIn", "Sign in")
509
+ children: b("auth.register.signIn", "Sign in")
505
510
  })
506
511
  ]
507
512
  })
@@ -509,6 +514,6 @@ function v() {
509
514
  });
510
515
  }
511
516
  //#endregion
512
- export { v as RegisterPage };
517
+ export { b as RegisterPage };
513
518
 
514
519
  //# sourceMappingURL=RegisterPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RegisterPage.js","names":[],"sources":["../../../src/auth/pages/RegisterPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { validateEmail, validatePassword } from \"../utils/validation\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport {\n EmailIcon,\n LockIcon,\n UserIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n GoogleIcon,\n GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { CaptchaV2 } from \"../components/CaptchaV2\";\nimport { useSignupCaptcha } from \"../hooks/useSignupCaptcha\";\nimport type { SignUpInput } from \"../types\";\n\n/**\n * Registration page with professional design\n */\nexport function RegisterPage() {\n const tr = useTr();\n const { productName } = useAuth();\n const navigate = useNavigate();\n\n const { signUp, resendVerification, isLoading, errorCode } =\n useAuthOperations();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const [showPassword, setShowPassword] = useState(false);\n const [formData, setFormData] = useState<SignUpInput>({\n email: \"\",\n username: \"\",\n password: \"\",\n firstName: \"\",\n lastName: \"\",\n });\n const [validationErrors, setValidationErrors] = useState<string[]>([]);\n const [signupSuccess, setSignupSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [captchaToken, setCaptchaToken] = useState<string | null>(null);\n // reCAPTCHA v2 tokens are single-use. Bumping this key remounts the widget so\n // a fresh token is issued after a failed submit (the prior token is consumed\n // server-side; reusing it returns timeout-or-duplicate → CAPTCHA_FAILED loop).\n const [captchaResetKey, setCaptchaResetKey] = useState(0);\n const { enabled: captchaEnabled, siteKey: captchaSiteKey } =\n useSignupCaptcha();\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendError, setResendError] = useState<string | null>(null);\n\n // Signing up with an email that already belongs to a non-active account:\n // route the user to the right recovery path instead of dead-ending.\n const isAccountDeactivated =\n Boolean(error) && errorCode === \"ACCOUNT_DEACTIVATED\";\n const isAccountRemoved = Boolean(error) && errorCode === \"ACCOUNT_REMOVED\";\n const isAccountSuspended =\n Boolean(error) && errorCode === \"ACCOUNT_SUSPENDED\";\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = formData.password;\n return [\n {\n label: tr(\"auth.register.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.register.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.register.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.register.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.register.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.register.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.register.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-text\",\n width: \"w-full\",\n };\n })();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n const errors: string[] = [];\n if (!validateEmail(formData.email)) {\n errors.push(\n tr(\"auth.register.invalidEmail\", \"Please enter a valid email address\"),\n );\n }\n if (!formData.username || formData.username.length < 3) {\n errors.push(\n tr(\n \"auth.register.usernameMin3\",\n \"Username must be at least 3 characters\",\n ),\n );\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(formData.username)) {\n errors.push(\n tr(\n \"auth.register.usernameChars\",\n \"Username can only contain letters, numbers, underscores, and hyphens\",\n ),\n );\n }\n const passwordValidation = validatePassword(formData.password);\n if (!passwordValidation.valid) {\n errors.push(...passwordValidation.errors);\n }\n\n if (captchaEnabled && !captchaToken) {\n errors.push(\n tr(\n \"auth.register.completeCaptcha\",\n \"Please complete the reCAPTCHA verification\",\n ),\n );\n }\n\n if (errors.length > 0) {\n setValidationErrors(errors);\n return;\n }\n\n setValidationErrors([]);\n setError(null);\n try {\n await signUp({\n ...formData,\n ...(captchaToken ? { recaptchaToken: captchaToken } : {}),\n });\n // Sign up successful - check if email verification is required\n // (Backend returns empty tokens when email verification is needed)\n setSignupSuccess(true);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\"auth.register.signUpFailed\", \"Sign up failed\");\n setError(message);\n console.error(\"Sign up failed:\", err);\n // The captcha token (if any) was consumed during this attempt — discard it\n // and remount the widget so the user gets a fresh, solvable challenge on\n // retry instead of being trapped in a CAPTCHA_FAILED loop.\n if (captchaEnabled) {\n setCaptchaToken(null);\n setCaptchaResetKey((k) => k + 1);\n }\n }\n };\n\n /**\n * Handle Google OAuth signup\n */\n const handleGoogleSignup = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.googleSignUpFailed\",\n \"Failed to initiate Google sign up\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth signup\n */\n const handleMicrosoftSignup = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.microsoftSignUpFailed\",\n \"Failed to initiate Microsoft sign up\",\n ),\n );\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleSignup,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.register.googleNotConfigured\",\n \"Google sign up is not configured\",\n ),\n },\n {\n name: \"GitHub\",\n icon: GitHubIcon,\n onClick: () => {},\n disabled: true,\n tooltip: tr(\n \"auth.register.githubNotConfigured\",\n \"GitHub sign up is not configured\",\n ),\n },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftSignup,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.register.microsoftNotConfigured\",\n \"Microsoft sign up is not configured\",\n ),\n },\n ];\n\n const verificationEmailMessage = tr(\n \"auth.register.verificationEmailSentTo\",\n \"We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.\",\n );\n const [verificationEmailPrefix, verificationEmailSuffix = \"\"] =\n verificationEmailMessage.split(\"{{email}}\");\n\n // Show success state after signup\n if (signupSuccess) {\n return (\n <AuthLayout\n title={tr(\"auth.register.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.register.verificationLinkSent\",\n \"We've sent you a verification link\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Success Icon */}\n <div className=\"size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <h3 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.register.accountCreated\",\n \"Account created successfully!\",\n )}\n </h3>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8 max-w-sm\">\n {verificationEmailPrefix}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {formData.email}\n </span>\n {verificationEmailSuffix}\n </p>\n\n <div className=\"space-y-3 w-full\">\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.register.continueToSignIn\", \"Continue to sign in\")}\n </Button>\n\n {resendError && (\n <p className=\"text-center text-sm text-status-error-text\">\n {resendError}\n </p>\n )}\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.register.didntReceiveEmail\",\n \"Didn't receive the email? Check your spam folder or\",\n )}{\" \"}\n <button\n type=\"button\"\n disabled={isResendingVerification}\n onClick={async () => {\n setResendError(null);\n setIsResendingVerification(true);\n try {\n await resendVerification(formData.email);\n } catch (err) {\n setResendError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.resendFailed\",\n \"Failed to resend verification email\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n }}\n className=\"text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification\n ? tr(\"auth.register.resending\", \"resending...\")\n : tr(\"auth.register.tryAgain\", \"try again\")}\n </button>\n </p>\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.register.createAccount\", \"Create your account\")}\n subtitle={tr(\n \"auth.register.startJourney\",\n \"Start your journey with {{productName}}\",\n ).replace(\"{{productName}}\", productName || \"Burdenoff Workspaces\")}\n productName={productName}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.register.purpose\",\n \"Create your account to get a personal workspace and start collaborating. You'll verify your email, then you can sign in and invite your team.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Deactivated account → offer self-service reactivation */}\n {isAccountDeactivated && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.register.accountDeactivated\",\n \"This email belongs to a deactivated account.\",\n )}\n </p>\n <p className=\"text-xs text-status-warning-text/80\">\n {tr(\n \"auth.register.accountDeactivatedHelp\",\n \"If this is your account, you can reactivate it instead of creating a new one.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"text-sm font-semibold text-status-warning-text underline underline-offset-2\"\n >\n {tr(\"auth.register.reactivateCta\", \"Reactivate my account\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Admin-removed / suspended → point to support (no self-service path) */}\n {(isAccountRemoved || isAccountSuspended) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-status-warning-text\">{error}</p>\n </div>\n )}\n\n {/* Generic error / validation alert */}\n {((error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended) ||\n validationErrors.length > 0) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-1\">\n {error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended && (\n <p className=\"text-sm text-status-error-text\">{error}</p>\n )}\n {validationErrors.map((err) => (\n <p key={err} className=\"text-sm text-status-error-text\">\n {err}\n </p>\n ))}\n </div>\n </div>\n )}\n\n {/* Name Fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label\n htmlFor=\"firstName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.firstName\", \"First Name\")}\n </label>\n <input\n id=\"firstName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n <div className=\"space-y-2\">\n <label\n htmlFor=\"lastName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.lastName\", \"Last Name\")}\n </label>\n <input\n id=\"lastName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n </div>\n\n {/* Username Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"username\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"username\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.username\", \"Username\")}\n autoComplete=\"username\"\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.usernamePlaceholder\",\n \"Choose a username\",\n )}\n />\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.email\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.email\", \"Email\")}\n autoComplete=\"email\"\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.password\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.password\", \"Password\")}\n autoComplete=\"new-password\"\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.passwordPlaceholder\",\n \"Create a strong password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={\n showPassword\n ? tr(\"auth.register.hidePassword\", \"Hide password\")\n : tr(\"auth.register.showPassword\", \"Show password\")\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {formData.password && (\n <div className=\"space-y-3 pt-2\">\n {/* Strength Bar */}\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\"auth.register.passwordStrength\", \"Password strength:\")}{\" \"}\n <span className=\"font-medium\">{passwordStrength.level}</span>\n </p>\n </div>\n\n {/* Requirements Checklist */}\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-status-success-text\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Terms Agreement */}\n <p className=\"text-xs text-[var(--color-text-tertiary)] leading-relaxed\">\n {tr(\n \"auth.register.agreeToTerms\",\n \"By creating an account, you agree to our\",\n )}{\" \"}\n <a\n href=\"/legal/terms\"\n className=\"text-text-link underline underline-offset-2\"\n >\n {tr(\"auth.register.termsOfService\", \"Terms of Service\")}\n </a>{\" \"}\n {tr(\"auth.register.and\", \"and\")}{\" \"}\n <a\n href=\"/legal/privacy\"\n className=\"text-text-link underline underline-offset-2\"\n >\n {tr(\"auth.register.privacyPolicy\", \"Privacy Policy\")}\n </a>\n .\n </p>\n\n {/* reCAPTCHA v2 Checkbox — enabled per-product via the platform catalog.\n `key` remounts it after a failed submit so a fresh token is issued. */}\n <CaptchaV2\n key={captchaResetKey}\n enabled={captchaEnabled}\n siteKey={captchaSiteKey}\n onVerify={setCaptchaToken}\n onExpired={() => setCaptchaToken(null)}\n />\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.register.creatingAccount\", \"Creating account...\")}\n </span>\n </>\n ) : (\n tr(\"auth.register.createAccountBtn\", \"Create account\")\n )}\n </Button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.register.orSignUpWith\", \"or sign up with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Sign In Link */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.register.alreadyHaveAccount\", \"Already have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.register.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RegisterPage.js","names":[],"sources":["../../../src/auth/pages/RegisterPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { validateEmail, validatePassword } from \"../utils/validation\";\nimport { resolveLegalUrl } from \"../utils/legalUrls\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport {\n EmailIcon,\n LockIcon,\n UserIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n GoogleIcon,\n GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { CaptchaV2 } from \"../components/CaptchaV2\";\nimport { useSignupCaptcha } from \"../hooks/useSignupCaptcha\";\nimport type { SignUpInput } from \"../types\";\n\n/**\n * Registration page with professional design\n */\nexport function RegisterPage() {\n const tr = useTr();\n const { productName } = useAuth();\n const navigate = useNavigate();\n\n const { signUp, resendVerification, isLoading, errorCode } =\n useAuthOperations();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const [showPassword, setShowPassword] = useState(false);\n const [formData, setFormData] = useState<SignUpInput>({\n email: \"\",\n username: \"\",\n password: \"\",\n firstName: \"\",\n lastName: \"\",\n });\n const [validationErrors, setValidationErrors] = useState<string[]>([]);\n const [signupSuccess, setSignupSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [captchaToken, setCaptchaToken] = useState<string | null>(null);\n // reCAPTCHA v2 tokens are single-use. Bumping this key remounts the widget so\n // a fresh token is issued after a failed submit (the prior token is consumed\n // server-side; reusing it returns timeout-or-duplicate → CAPTCHA_FAILED loop).\n const [captchaResetKey, setCaptchaResetKey] = useState(0);\n const { enabled: captchaEnabled, siteKey: captchaSiteKey } =\n useSignupCaptcha();\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendError, setResendError] = useState<string | null>(null);\n\n // Signing up with an email that already belongs to a non-active account:\n // route the user to the right recovery path instead of dead-ending.\n const isAccountDeactivated =\n Boolean(error) && errorCode === \"ACCOUNT_DEACTIVATED\";\n const isAccountRemoved = Boolean(error) && errorCode === \"ACCOUNT_REMOVED\";\n const isAccountSuspended =\n Boolean(error) && errorCode === \"ACCOUNT_SUSPENDED\";\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = formData.password;\n return [\n {\n label: tr(\"auth.register.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.register.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.register.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.register.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.register.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.register.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.register.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-text\",\n width: \"w-full\",\n };\n })();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n const errors: string[] = [];\n if (!validateEmail(formData.email)) {\n errors.push(\n tr(\"auth.register.invalidEmail\", \"Please enter a valid email address\"),\n );\n }\n if (!formData.username || formData.username.length < 3) {\n errors.push(\n tr(\n \"auth.register.usernameMin3\",\n \"Username must be at least 3 characters\",\n ),\n );\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(formData.username)) {\n errors.push(\n tr(\n \"auth.register.usernameChars\",\n \"Username can only contain letters, numbers, underscores, and hyphens\",\n ),\n );\n }\n const passwordValidation = validatePassword(formData.password);\n if (!passwordValidation.valid) {\n errors.push(...passwordValidation.errors);\n }\n\n if (captchaEnabled && !captchaToken) {\n errors.push(\n tr(\n \"auth.register.completeCaptcha\",\n \"Please complete the reCAPTCHA verification\",\n ),\n );\n }\n\n if (errors.length > 0) {\n setValidationErrors(errors);\n return;\n }\n\n setValidationErrors([]);\n setError(null);\n try {\n await signUp({\n ...formData,\n ...(captchaToken ? { recaptchaToken: captchaToken } : {}),\n });\n // Sign up successful - check if email verification is required\n // (Backend returns empty tokens when email verification is needed)\n setSignupSuccess(true);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\"auth.register.signUpFailed\", \"Sign up failed\");\n setError(message);\n console.error(\"Sign up failed:\", err);\n // The captcha token (if any) was consumed during this attempt — discard it\n // and remount the widget so the user gets a fresh, solvable challenge on\n // retry instead of being trapped in a CAPTCHA_FAILED loop.\n if (captchaEnabled) {\n setCaptchaToken(null);\n setCaptchaResetKey((k) => k + 1);\n }\n }\n };\n\n /**\n * Handle Google OAuth signup\n */\n const handleGoogleSignup = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.googleSignUpFailed\",\n \"Failed to initiate Google sign up\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth signup\n */\n const handleMicrosoftSignup = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.microsoftSignUpFailed\",\n \"Failed to initiate Microsoft sign up\",\n ),\n );\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleSignup,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.register.googleNotConfigured\",\n \"Google sign up is not configured\",\n ),\n },\n {\n name: \"GitHub\",\n icon: GitHubIcon,\n onClick: () => {},\n disabled: true,\n tooltip: tr(\n \"auth.register.githubNotConfigured\",\n \"GitHub sign up is not configured\",\n ),\n },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftSignup,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.register.microsoftNotConfigured\",\n \"Microsoft sign up is not configured\",\n ),\n },\n ];\n\n const verificationEmailMessage = tr(\n \"auth.register.verificationEmailSentTo\",\n \"We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.\",\n );\n const [verificationEmailPrefix, verificationEmailSuffix = \"\"] =\n verificationEmailMessage.split(\"{{email}}\");\n\n // Show success state after signup\n if (signupSuccess) {\n return (\n <AuthLayout\n title={tr(\"auth.register.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.register.verificationLinkSent\",\n \"We've sent you a verification link\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Success Icon */}\n <div className=\"size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <h3 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.register.accountCreated\",\n \"Account created successfully!\",\n )}\n </h3>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8 max-w-sm\">\n {verificationEmailPrefix}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {formData.email}\n </span>\n {verificationEmailSuffix}\n </p>\n\n <div className=\"space-y-3 w-full\">\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.register.continueToSignIn\", \"Continue to sign in\")}\n </Button>\n\n {resendError && (\n <p className=\"text-center text-sm text-status-error-text\">\n {resendError}\n </p>\n )}\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.register.didntReceiveEmail\",\n \"Didn't receive the email? Check your spam folder or\",\n )}{\" \"}\n <button\n type=\"button\"\n disabled={isResendingVerification}\n onClick={async () => {\n setResendError(null);\n setIsResendingVerification(true);\n try {\n await resendVerification(formData.email);\n } catch (err) {\n setResendError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.resendFailed\",\n \"Failed to resend verification email\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n }}\n className=\"text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification\n ? tr(\"auth.register.resending\", \"resending...\")\n : tr(\"auth.register.tryAgain\", \"try again\")}\n </button>\n </p>\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.register.createAccount\", \"Create your account\")}\n subtitle={tr(\n \"auth.register.startJourney\",\n \"Start your journey with {{productName}}\",\n ).replace(\"{{productName}}\", productName || \"Burdenoff Workspaces\")}\n productName={productName}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.register.purpose\",\n \"Create your account to get a personal workspace and start collaborating. You'll verify your email, then you can sign in and invite your team.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Deactivated account → offer self-service reactivation */}\n {isAccountDeactivated && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.register.accountDeactivated\",\n \"This email belongs to a deactivated account.\",\n )}\n </p>\n <p className=\"text-xs text-status-warning-text/80\">\n {tr(\n \"auth.register.accountDeactivatedHelp\",\n \"If this is your account, you can reactivate it instead of creating a new one.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"text-sm font-semibold text-status-warning-text underline underline-offset-2\"\n >\n {tr(\"auth.register.reactivateCta\", \"Reactivate my account\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Admin-removed / suspended → point to support (no self-service path) */}\n {(isAccountRemoved || isAccountSuspended) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-status-warning-text\">{error}</p>\n </div>\n )}\n\n {/* Generic error / validation alert */}\n {((error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended) ||\n validationErrors.length > 0) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-1\">\n {error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended && (\n <p className=\"text-sm text-status-error-text\">{error}</p>\n )}\n {validationErrors.map((err) => (\n <p key={err} className=\"text-sm text-status-error-text\">\n {err}\n </p>\n ))}\n </div>\n </div>\n )}\n\n {/* Name Fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label\n htmlFor=\"firstName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.firstName\", \"First Name\")}\n </label>\n <input\n id=\"firstName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n <div className=\"space-y-2\">\n <label\n htmlFor=\"lastName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.lastName\", \"Last Name\")}\n </label>\n <input\n id=\"lastName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n </div>\n\n {/* Username Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"username\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"username\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.username\", \"Username\")}\n autoComplete=\"username\"\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.usernamePlaceholder\",\n \"Choose a username\",\n )}\n />\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.email\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.email\", \"Email\")}\n autoComplete=\"email\"\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.password\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.password\", \"Password\")}\n autoComplete=\"new-password\"\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.passwordPlaceholder\",\n \"Create a strong password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={\n showPassword\n ? tr(\"auth.register.hidePassword\", \"Hide password\")\n : tr(\"auth.register.showPassword\", \"Show password\")\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {formData.password && (\n <div className=\"space-y-3 pt-2\">\n {/* Strength Bar */}\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\"auth.register.passwordStrength\", \"Password strength:\")}{\" \"}\n <span className=\"font-medium\">{passwordStrength.level}</span>\n </p>\n </div>\n\n {/* Requirements Checklist */}\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-status-success-text\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Terms Agreement */}\n <p className=\"text-xs text-[var(--color-text-tertiary)] leading-relaxed\">\n {tr(\n \"auth.register.agreeToTerms\",\n \"By creating an account, you agree to our\",\n )}{\" \"}\n {/* These link to the PUBLIC marketing site, not an app route: the\n reader is signed out, and the app's /legal/:slug route sits behind\n the shell's auth guard. Opened in a new tab so a half-completed\n signup form is not discarded to read the terms. */}\n <a\n href={resolveLegalUrl(\"/terms\")}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-text-link underline underline-offset-2\"\n >\n {tr(\"auth.register.termsOfService\", \"Terms of Service\")}\n </a>{\" \"}\n {tr(\"auth.register.and\", \"and\")}{\" \"}\n <a\n href={resolveLegalUrl(\"/privacy\")}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-text-link underline underline-offset-2\"\n >\n {tr(\"auth.register.privacyPolicy\", \"Privacy Policy\")}\n </a>\n .\n </p>\n\n {/* reCAPTCHA v2 Checkbox — enabled per-product via the platform catalog.\n `key` remounts it after a failed submit so a fresh token is issued. */}\n <CaptchaV2\n key={captchaResetKey}\n enabled={captchaEnabled}\n siteKey={captchaSiteKey}\n onVerify={setCaptchaToken}\n onExpired={() => setCaptchaToken(null)}\n />\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.register.creatingAccount\", \"Creating account...\")}\n </span>\n </>\n ) : (\n tr(\"auth.register.createAccountBtn\", \"Create account\")\n )}\n </Button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.register.orSignUpWith\", \"or sign up with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Sign In Link */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.register.alreadyHaveAccount\", \"Already have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.register.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Public legal-document URLs for the signup consent copy.
3
+ *
4
+ * The signup form is shown to SIGNED-OUT visitors, so its "Terms of Service" and
5
+ * "Privacy Policy" links must resolve to the public marketing site. They used to
6
+ * be written as the app-relative paths `/legal/terms` and `/legal/privacy`, which
7
+ * resolved against the app origin (e.g. `https://app.fluidgrids.ai/legal/terms`).
8
+ * That route lives *inside* the shell's authenticated block, so a visitor who
9
+ * clicked it was bounced to `/auth/login?returnTo=%2Flegal%2Fterms` and the shell
10
+ * footer fired two FORBIDDEN queries on the way out:
11
+ *
12
+ * [Burdenoff backend error] You do not have permission to do that.
13
+ * {code: FORBIDDEN, operationName: GetFooterContentPages}
14
+ * {code: FORBIDDEN, operationName: GetFooterProduct}
15
+ *
16
+ * i.e. the consent links were unreadable for exactly the people being asked to
17
+ * consent. The marketing site is also the authoritative home of the policy text
18
+ * (rendered from the dated drafts in `fluidgrids-website/src/content/legal/`),
19
+ * so linking there avoids maintaining a second copy that can drift.
20
+ *
21
+ * microfe-auth is mounted by every Burdenoff product shell, so the origin is
22
+ * DERIVED from the current host rather than hardcoded — hardcoding one product's
23
+ * domain would send every other product's users to the wrong company's terms.
24
+ * Every product serves its marketing site at `www.<product-domain>` using the
25
+ * same `/terms` and `/privacy` route names (verified against fluidgrids.ai,
26
+ * burdenoff.com, bigconsole.com, planmagnet.com, adaptercloud.com, botlit.ai and
27
+ * timecampus.com).
28
+ */
29
+ /**
30
+ * Resolve the public marketing origin for the product currently being served,
31
+ * or `null` when it cannot be determined (local development, or a host with no
32
+ * registrable domain). Exported for unit testing; prefer `resolveLegalUrl`.
33
+ */
34
+ export declare function resolveMarketingOrigin(hostname?: string): string | null;
35
+ /** Public legal pages, by the route name every product marketing site uses. */
36
+ export type LegalDocumentPath = "/terms" | "/privacy";
37
+ /**
38
+ * Absolute URL of a public legal document for the current product.
39
+ *
40
+ * Falls back to the bare path when the marketing origin cannot be resolved
41
+ * (local development), which keeps the link valid-looking in dev without
42
+ * inventing a production domain.
43
+ */
44
+ export declare function resolveLegalUrl(path: LegalDocumentPath): string;
@@ -0,0 +1,26 @@
1
+ //#region src/auth/utils/legalUrls.ts
2
+ var e = [
3
+ "app.",
4
+ "alphaapp.",
5
+ "admin.",
6
+ "alphaadmin.",
7
+ "www."
8
+ ];
9
+ function t(t) {
10
+ let n = (t ?? (typeof window < "u" ? window.location.hostname : "")).toLowerCase().trim();
11
+ if (!n || n === "localhost" || n === "127.0.0.1" || n === "0.0.0.0" || n.startsWith("[") || n.endsWith(".local.burdenoff.com") || /^\d+\.\d+\.\d+\.\d+$/.test(n)) return null;
12
+ let r = n;
13
+ for (let t of e) if (r.startsWith(t)) {
14
+ r = r.slice(t.length);
15
+ break;
16
+ }
17
+ return r.includes(".") ? `https://www.${r}` : null;
18
+ }
19
+ function n(e) {
20
+ let n = t();
21
+ return n ? `${n}${e}` : e;
22
+ }
23
+ //#endregion
24
+ export { n as resolveLegalUrl };
25
+
26
+ //# sourceMappingURL=legalUrls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legalUrls.js","names":[],"sources":["../../../src/auth/utils/legalUrls.ts"],"sourcesContent":["/**\n * Public legal-document URLs for the signup consent copy.\n *\n * The signup form is shown to SIGNED-OUT visitors, so its \"Terms of Service\" and\n * \"Privacy Policy\" links must resolve to the public marketing site. They used to\n * be written as the app-relative paths `/legal/terms` and `/legal/privacy`, which\n * resolved against the app origin (e.g. `https://app.fluidgrids.ai/legal/terms`).\n * That route lives *inside* the shell's authenticated block, so a visitor who\n * clicked it was bounced to `/auth/login?returnTo=%2Flegal%2Fterms` and the shell\n * footer fired two FORBIDDEN queries on the way out:\n *\n * [Burdenoff backend error] You do not have permission to do that.\n * {code: FORBIDDEN, operationName: GetFooterContentPages}\n * {code: FORBIDDEN, operationName: GetFooterProduct}\n *\n * i.e. the consent links were unreadable for exactly the people being asked to\n * consent. The marketing site is also the authoritative home of the policy text\n * (rendered from the dated drafts in `fluidgrids-website/src/content/legal/`),\n * so linking there avoids maintaining a second copy that can drift.\n *\n * microfe-auth is mounted by every Burdenoff product shell, so the origin is\n * DERIVED from the current host rather than hardcoded — hardcoding one product's\n * domain would send every other product's users to the wrong company's terms.\n * Every product serves its marketing site at `www.<product-domain>` using the\n * same `/terms` and `/privacy` route names (verified against fluidgrids.ai,\n * burdenoff.com, bigconsole.com, planmagnet.com, adaptercloud.com, botlit.ai and\n * timecampus.com).\n */\n\n/**\n * Shell subdomains that front a product's app surface. Stripping one of these\n * leaves the bare product domain whose `www.` host serves the marketing site.\n */\nconst APP_HOST_PREFIXES = [\n \"app.\",\n \"alphaapp.\",\n \"admin.\",\n \"alphaadmin.\",\n \"www.\",\n];\n\n/**\n * Resolve the public marketing origin for the product currently being served,\n * or `null` when it cannot be determined (local development, or a host with no\n * registrable domain). Exported for unit testing; prefer `resolveLegalUrl`.\n */\nexport function resolveMarketingOrigin(hostname?: string): string | null {\n const host = (\n hostname ?? (typeof window !== \"undefined\" ? window.location.hostname : \"\")\n )\n .toLowerCase()\n .trim();\n\n if (!host) return null;\n\n // Local development has no marketing site to link to. `0.0.0.0` and IPv6\n // literals (which `location.hostname` reports bracketed, e.g. \"[::1]\") are\n // included because a bare dotted-quad would otherwise survive the \"has a dot\"\n // check below and produce a nonsense origin like `https://www.0.0.0.0`.\n if (\n host === \"localhost\" ||\n host === \"127.0.0.1\" ||\n host === \"0.0.0.0\" ||\n host.startsWith(\"[\") ||\n host.endsWith(\".local.burdenoff.com\")\n ) {\n return null;\n }\n\n // Any other IPv4 literal is likewise not a registrable domain.\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host)) return null;\n\n let domain = host;\n for (const prefix of APP_HOST_PREFIXES) {\n if (domain.startsWith(prefix)) {\n domain = domain.slice(prefix.length);\n break;\n }\n }\n\n // A bare hostname with no dot is not a registrable domain we can build on.\n if (!domain.includes(\".\")) return null;\n\n return `https://www.${domain}`;\n}\n\n/** Public legal pages, by the route name every product marketing site uses. */\nexport type LegalDocumentPath = \"/terms\" | \"/privacy\";\n\n/**\n * Absolute URL of a public legal document for the current product.\n *\n * Falls back to the bare path when the marketing origin cannot be resolved\n * (local development), which keeps the link valid-looking in dev without\n * inventing a production domain.\n */\nexport function resolveLegalUrl(path: LegalDocumentPath): string {\n const origin = resolveMarketingOrigin();\n return origin ? `${origin}${path}` : path;\n}\n"],"mappings":";AAiCA,IAAM,IAAoB;CACxB;CACA;CACA;CACA;CACA;CACD;AAOD,SAAgB,EAAuB,GAAkC;CACvE,IAAM,KACJ,MAAa,OAAO,SAAW,MAAc,OAAO,SAAS,WAAW,KAEvE,aAAa,CACb,MAAM;AAmBT,KAjBI,CAAC,KAOH,MAAS,eACT,MAAS,eACT,MAAS,aACT,EAAK,WAAW,IAAI,IACpB,EAAK,SAAS,uBAAuB,IAMnC,uBAAuB,KAAK,EAAK,CAAE,QAAO;CAE9C,IAAI,IAAS;AACb,MAAK,IAAM,KAAU,EACnB,KAAI,EAAO,WAAW,EAAO,EAAE;AAC7B,MAAS,EAAO,MAAM,EAAO,OAAO;AACpC;;AAOJ,QAFK,EAAO,SAAS,IAAI,GAElB,eAAe,MAFY;;AAepC,SAAgB,EAAgB,GAAiC;CAC/D,IAAM,IAAS,GAAwB;AACvC,QAAO,IAAS,GAAG,IAAS,MAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-auth",
3
- "version": "2026.904.1",
3
+ "version": "2026.908.1",
4
4
  "description": "Auth microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "imports": {