@burdenoff/microfe-auth 2026.718.4 → 2026.731.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.
Files changed (80) hide show
  1. package/dist/auth/AuthRoot.d.ts +29 -0
  2. package/dist/auth/AuthRoutes.d.ts +27 -0
  3. package/dist/auth/components/AuthIcons.d.ts +16 -0
  4. package/dist/auth/components/AuthLayout.d.ts +16 -0
  5. package/dist/auth/components/CaptchaV2.d.ts +25 -0
  6. package/dist/auth/components/ConfirmActionDialog.d.ts +15 -0
  7. package/dist/auth/components/index.d.ts +3 -0
  8. package/dist/auth/components/profile/UserProfileMenu.d.ts +41 -0
  9. package/dist/auth/components/workspace/WorkspaceSelector.d.ts +49 -0
  10. package/dist/auth/constants/permissions.d.ts +29 -0
  11. package/dist/auth/constants/sessionKeys.d.ts +17 -0
  12. package/dist/auth/hooks/index.d.ts +19 -0
  13. package/dist/auth/hooks/useAccountOperations.d.ts +18 -0
  14. package/dist/auth/hooks/useActiveContextBootstrapMirror.d.ts +91 -0
  15. package/dist/auth/hooks/useApiKeyOperations.d.ts +28 -0
  16. package/dist/auth/hooks/useAppOperations.d.ts +32 -0
  17. package/dist/auth/hooks/useAuthOperations.d.ts +59 -0
  18. package/dist/auth/hooks/useAuthPermissions.d.ts +19 -0
  19. package/dist/auth/hooks/useCaptchaV3.d.ts +12 -0
  20. package/dist/auth/hooks/useContextSwitcherData.d.ts +91 -0
  21. package/dist/auth/hooks/useDelegationOperations.d.ts +38 -0
  22. package/dist/auth/hooks/useMfaOperations.d.ts +17 -0
  23. package/dist/auth/hooks/useMyWorkspaces.d.ts +47 -0
  24. package/dist/auth/hooks/useOAuthOperations.d.ts +18 -0
  25. package/dist/auth/hooks/usePasswordOperations.d.ts +12 -0
  26. package/dist/auth/hooks/useSessionOperations.d.ts +57 -0
  27. package/dist/auth/hooks/useSignupCaptcha.d.ts +22 -0
  28. package/dist/auth/hooks/useSsoOperations.d.ts +67 -0
  29. package/dist/auth/hooks/useTokenRefresh.d.ts +55 -0
  30. package/dist/auth/hooks/useUserOperations.d.ts +32 -0
  31. package/dist/auth/hooks/useWorkspaceOperations.d.ts +34 -0
  32. package/dist/auth/hooks/useWorkspaceToken.d.ts +55 -0
  33. package/dist/auth/index.d.ts +63 -0
  34. package/dist/auth/pages/AuditLogPage.d.ts +11 -0
  35. package/dist/auth/pages/EmailVerifyPage.d.ts +4 -0
  36. package/dist/auth/pages/LoginPage.d.ts +5 -0
  37. package/dist/auth/pages/MagicLinkPage.d.ts +5 -0
  38. package/dist/auth/pages/MfaSetupPage.d.ts +4 -0
  39. package/dist/auth/pages/MfaVerifyPage.d.ts +6 -0
  40. package/dist/auth/pages/OAuthCallbackPage.d.ts +6 -0
  41. package/dist/auth/pages/PasswordResetPage.d.ts +5 -0
  42. package/dist/auth/pages/ReactivatePage.d.ts +5 -0
  43. package/dist/auth/pages/RegisterPage.d.ts +4 -0
  44. package/dist/auth/pages/SSOClearPage.d.ts +21 -0
  45. package/dist/auth/pages/SSODepositPage.d.ts +23 -0
  46. package/dist/auth/pages/SSORelayCallbackPage.d.ts +7 -0
  47. package/dist/auth/pages/SSORelayPage.d.ts +17 -0
  48. package/dist/auth/pages/SecurityPage.d.ts +6 -0
  49. package/dist/auth/pages/SessionBridgePage.d.ts +13 -0
  50. package/dist/auth/pages/SessionsPage.d.ts +7 -0
  51. package/dist/auth/pages/SsoCallbackPage.d.ts +7 -0
  52. package/dist/auth/pages/SsoConfigPage.d.ts +12 -0
  53. package/dist/auth/pages/SsoLoginPage.d.ts +6 -0
  54. package/dist/auth/pages/UserDetailsPage.d.ts +10 -0
  55. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  56. package/dist/auth/pages/UserProfilePage.d.ts +4 -0
  57. package/dist/auth/pages/UsersPage.d.ts +4 -0
  58. package/dist/auth/providers/AuthProvider.d.ts +17 -0
  59. package/dist/auth/providers/CaptchaProvider.d.ts +12 -0
  60. package/dist/auth/store/authStore.d.ts +110 -0
  61. package/dist/auth/types/index.d.ts +678 -0
  62. package/dist/auth/utils/captchaConfig.d.ts +3 -0
  63. package/dist/auth/utils/formatters.d.ts +36 -0
  64. package/dist/auth/utils/gatewayBaseUrls.d.ts +5 -0
  65. package/dist/auth/utils/index.d.ts +2 -0
  66. package/dist/auth/utils/ssoOrigins.d.ts +28 -0
  67. package/dist/auth/utils/validation.d.ts +29 -0
  68. package/dist/dev/main.d.ts +1 -0
  69. package/dist/generated/global-operations.d.ts +3095 -0
  70. package/dist/generated/global-types.d.ts +23528 -0
  71. package/dist/generated/wspace-operations.d.ts +123 -0
  72. package/dist/generated/wspace-types.d.ts +57850 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/microfe-auth.css +1 -1
  75. package/dist/shared/hooks/index.d.ts +6 -0
  76. package/dist/shared/index.d.ts +5 -0
  77. package/dist/shared/types/index.d.ts +108 -0
  78. package/dist/translations/en.d.ts +320 -0
  79. package/dist/utils/nativeBridge.d.ts +2 -0
  80. package/package.json +6 -4
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SSORelayPage — Cross-domain SSO token relay.
3
+ *
4
+ * This page is loaded on the ORIGIN where the user is logged in.
5
+ * It reads tokens from localStorage and redirects to the TARGET domain's
6
+ * /auth/sso-callback with the tokens in a URL fragment (not query param
7
+ * for security — fragments are not sent to servers).
8
+ *
9
+ * Flow:
10
+ * 1. User visits productA.com → not logged in → detects SSO cookie
11
+ * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard
12
+ * (productB is where the user IS logged in — identified by cookie)
13
+ * 3. This page reads tokens from productB's localStorage
14
+ * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>
15
+ * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard
16
+ */
17
+ export declare function SSORelayPage(): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Security Dashboard - Main hub for all authentication features
3
+ */
4
+ export declare function SecurityPage({ enableAdminRoutes, }: {
5
+ enableAdminRoutes?: boolean;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SessionBridgePage — Cross-Domain Auth Session Bridge (Profile-Scoped)
3
+ *
4
+ * Embedded as hidden iframe by other app shells. Manages profile-scoped
5
+ * auth tokens in localStorage and relays state via postMessage.
6
+ *
7
+ * Storage uses ProfileStorageManager keys:
8
+ * bf-profiles, bf-p-{id}-user, bf-p-{id}-tokens, bf-p-{id}-workspace-token
9
+ * bf-active-profile (sessionStorage, per-tab)
10
+ *
11
+ * URL: /auth/session-bridge
12
+ */
13
+ export declare function SessionBridgePage(): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Active sessions management page with professional UI
3
+ *
4
+ * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)
5
+ * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)
6
+ */
7
+ export declare function SessionsPage(): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SSO Callback Page
3
+ * Handles redirects from:
4
+ * - SAML ACS server (with token/refreshToken in query params)
5
+ * - OIDC Authorization Server (with code/state in query params)
6
+ */
7
+ export declare function SsoCallbackPage(): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ interface SsoConfigPageProps {
2
+ /** When true, skips the enterprise-plan gate so admins always see the full UI */
3
+ bypassEnterprisePlanGate?: boolean;
4
+ }
5
+ /**
6
+ * SSO Configuration page for managing SAML/OIDC enterprise SSO.
7
+ * Admin only - allows configuring Identity Provider settings.
8
+ * In admin-app context, bypassEnterprisePlanGate=true and tenant/org/workspace
9
+ * scoping is read from URL search params (?tenant=&org=&workspace=).
10
+ */
11
+ export declare function SsoConfigPage({ bypassEnterprisePlanGate, }: SsoConfigPageProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * SSO Login Page
3
+ * Allows users to enter their email/company domain to initiate SSO login
4
+ * Supports both SAML 2.0 and OIDC authentication methods
5
+ */
6
+ export declare function SsoLoginPage(): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * User details page (admin only).
3
+ *
4
+ * Route: /auth/users/:id
5
+ *
6
+ * Renders the full profile of a single user, with admin actions
7
+ * (edit, deactivate, hard-delete, export). Reuses the same getUser query
8
+ * that UsersPage relies on.
9
+ */
10
+ export declare function UserDetailsPage(): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\nconst getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\n/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.userDetails.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n />\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n description={tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n />\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n description={\n error ??\n tr(\n \"auth.userDetails.userNotFoundDesc\",\n \"We couldn't find this user. They may have been removed, or the link is out of date.\",\n )\n }\n />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.userDetails.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.userDetails.purpose\",\n \"The full record for this person — their role, status, verification, and activity. From here you can export their data, deactivate them, or (for super admins) permanently delete a removed account.\",\n )}\n </PagePurpose>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n {/* Identity header — the one emphasis zone on this page */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center gap-4\">\n <div\n className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors\"\n >\n {tr(\"auth.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-user-details\"\n steps={[\n {\n id: \"back-to-users\",\n label: tr(\"auth.userDetails.nextBack\", \"Return to the user list\"),\n description: tr(\n \"auth.userDetails.nextBackDesc\",\n \"Browse or search everyone in your organization.\",\n ),\n onClick: () => navigate(\"/auth/users\"),\n },\n {\n id: \"export-user\",\n label: tr(\n \"auth.userDetails.nextExport\",\n \"Export this user's data\",\n ),\n description: tr(\n \"auth.userDetails.nextExportDesc\",\n \"Download their profile, sessions, and audit history as CSV.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n\n <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.userDetails.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.userDetails.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.userDetails.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.userDetails.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserDetailsPage.js","names":[],"sources":["../../../src/auth/pages/UserDetailsPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useAuthPermissions } from \"../hooks/useAuthPermissions\";\nimport { useUserOperations } from \"../hooks/useUserOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\nimport type { User, UserRole, UserStatus } from \"../types\";\n\nconst getRoleColor = (role: UserRole) => {\n switch (role) {\n case \"SUPER_ADMIN\":\n return \"bg-accent-purple-subtle text-accent-purple\";\n case \"TENANT_ADMIN\":\n return \"bg-status-info-bg-subtle text-status-info-text\";\n case \"ORG_ADMIN\":\n return \"bg-accent-indigo-subtle text-accent-indigo\";\n case \"WORKSPACE_ADMIN\":\n return \"bg-accent-cyan-subtle text-accent-cyan\";\n case \"MEMBER\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"GUEST\":\n return \"bg-bg-sunken text-text-primary\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\nconst getStatusColor = (status: UserStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return \"bg-status-success-bg-subtle text-status-success-text\";\n case \"INACTIVE\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"SUSPENDED\":\n return \"bg-status-warning-bg-subtle text-status-warning-text\";\n case \"DELETED\":\n return \"bg-status-error-bg-subtle text-status-error-text\";\n default:\n return \"bg-bg-sunken text-text-primary\";\n }\n};\n\n/**\n * User details page (admin only).\n *\n * Route: /auth/users/:id\n *\n * Renders the full profile of a single user, with admin actions\n * (edit, deactivate, hard-delete, export). Reuses the same getUser query\n * that UsersPage relies on.\n */\nexport function UserDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const currentUser = useAuthStore((state) => state.user);\n const {\n canViewUsers,\n canDeleteUsers,\n isLoading: permissionsLoading,\n } = useAuthPermissions();\n const { getUser, deleteUser, hardDeleteUser } = useUserOperations();\n\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingAction, setPendingAction] = useState<\n \"deactivate\" | \"hard-delete\" | null\n >(null);\n\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation();\n const [exportError, setExportError] = useState<string | null>(null);\n const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\n\n useEffect(() => {\n let cancelled = false;\n if (!id) {\n setError(\"Missing user id\");\n setLoading(false);\n return;\n }\n if (!isAuthenticated) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n getUser(id)\n .then((fetched) => {\n if (cancelled) return;\n if (!fetched) {\n setError(tr(\"auth.userDetails.userNotFound\", \"User not found\"));\n } else {\n setUser(fetched);\n }\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setError(\n err instanceof Error ? err.message : \"Failed to fetch user details\",\n );\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n // tr is stable within the render, id/getUser/isAuthenticated drive the effect\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id, getUser, isAuthenticated]);\n\n const handleDeactivate = async () => {\n if (!user) return;\n try {\n const result = await deleteUser(user.id);\n if (result.success) {\n setSuccessMessage(\n tr(\n \"auth.userDetails.userDeactivated\",\n 'User \"{email}\" has been deactivated.',\n ).replace(\"{email}\", user.email),\n );\n setPendingAction(null);\n // Refresh\n const fresh = await getUser(user.id);\n if (fresh) setUser(fresh);\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to delete user\");\n }\n };\n\n const handleHardDelete = async () => {\n if (!user) return;\n try {\n const result = await hardDeleteUser(user.id);\n if (result.success) {\n setPendingAction(null);\n navigate(\"/auth/users\");\n } else {\n setError(result.message);\n }\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to hard delete user\",\n );\n }\n };\n\n const handleExport = async () => {\n if (!user) return;\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: `User Data Export - ${user.email}`,\n entityTypes: [\n \"user_profile\",\n \"user_sessions\",\n \"permission_audit_logs\",\n ],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.userDetails.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n // Auth + role guards\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.userDetails.signInToView\",\n \"Please sign in to view user details\",\n )}\n />\n </div>\n );\n }\n\n if (permissionsLoading || loading) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16\" aria-live=\"polite\" aria-busy=\"true\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-secondary animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-4 text-text-secondary\">\n {tr(\"auth.userDetails.loading\", \"Loading user...\")}\n </p>\n </div>\n </div>\n );\n }\n\n if (!canViewUsers) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.userDetails.adminRequired\", \"Admin Access Required\")}\n description={tr(\n \"auth.userDetails.adminRequiredDesc\",\n \"You need administrator privileges to view user details.\",\n )}\n />\n </div>\n );\n }\n\n if (error || !user) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"mb-6 flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr(\"auth.userDetails.userNotFound\", \"User not found\")}\n description={\n error ??\n tr(\n \"auth.userDetails.userNotFoundDesc\",\n \"We couldn't find this user. They may have been removed, or the link is out of date.\",\n )\n }\n />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingAction !== null}\n onOpenChange={(open) => {\n if (!open) setPendingAction(null);\n }}\n title={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteTitle\", \"Permanently delete user?\")\n : tr(\"auth.userDetails.deactivateTitle\", \"Deactivate user?\")\n }\n description={\n pendingAction === \"hard-delete\"\n ? tr(\n \"auth.userDetails.hardDeleteDesc\",\n 'This permanently deletes \"{email}\" and cannot be undone.',\n ).replace(\"{email}\", user.email)\n : tr(\n \"auth.userDetails.deactivateDesc\",\n '\"{email}\" will be prevented from signing in until reactivated.',\n ).replace(\"{email}\", user.email)\n }\n confirmText={\n pendingAction === \"hard-delete\"\n ? tr(\"auth.userDetails.hardDeleteConfirm\", \"Delete permanently\")\n : tr(\"auth.userDetails.deactivateConfirm\", \"Deactivate user\")\n }\n variant={pendingAction === \"hard-delete\" ? \"danger\" : \"warning\"}\n onConfirm={async () => {\n if (pendingAction === \"hard-delete\") await handleHardDelete();\n else if (pendingAction === \"deactivate\") await handleDeactivate();\n }}\n />\n\n {/* Header row */}\n <div className=\"flex items-center justify-between mb-6\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/users\")}\n className=\"flex items-center gap-2 text-text-link hover:underline font-medium\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\"auth.userDetails.backToUsers\", \"Back to Users\")}\n </button>\n\n {/* Export button */}\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\n \"auth.userDetails.exportAriaLabel\",\n \"Export user data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.userDetails.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.userDetails.exportButton\", \"Export User Data\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.userDetails.purpose\",\n \"The full record for this person — their role, status, verification, and activity. From here you can export their data, deactivate them, or (for super admins) permanently delete a removed account.\",\n )}\n </PagePurpose>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-status-success-bg-subtle border border-status-success-border\">\n <p className=\"text-status-success-text\">{successMessage}</p>\n <button\n type=\"button\"\n onClick={() => setSuccessMessage(null)}\n className=\"mt-2 text-sm text-status-success-text hover:underline\"\n >\n {tr(\"auth.userDetails.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n <div className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n {/* Identity header — the one emphasis zone on this page */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center gap-4\">\n <div\n className=\"auth-brand-badge flex size-20 items-center justify-center rounded-full text-3xl font-semibold\"\n aria-hidden=\"true\"\n >\n {(user.name || user.username || user.email)\n .charAt(0)\n .toUpperCase()}\n </div>\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {user.name || user.username}\n </h1>\n <p className=\"text-text-secondary\" data-testid=\"user-email\">\n {user.email}\n </p>\n <div className=\"flex items-center gap-2 mt-2\">\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getRoleColor(user.role)}`}\n >\n {user.role.replace(\"_\", \" \")}\n </span>\n <span\n className={`px-2.5 py-1 rounded-full text-xs font-medium ${getStatusColor(user.status)}`}\n >\n {user.status}\n </span>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n <dl className=\"grid grid-cols-2 gap-6\">\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.username\", \"Username\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.username}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.email\", \"Email\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">{user.email}</dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.emailVerified\", \"Email Verified\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.emailVerified ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-status-warning-text\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.mfaEnabled\", \"MFA Enabled\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.mfaEnabled ? (\n <span className=\"text-status-success-text\">\n {tr(\"auth.userDetails.yes\", \"Yes\")}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr(\"auth.userDetails.no\", \"No\")}\n </span>\n )}\n </dd>\n </div>\n {user.phoneNumber && (\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.phoneNumber\", \"Phone Number\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.phoneNumber}\n </dd>\n </div>\n )}\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastLogin\", \"Last Login\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {user.lastLoginAt\n ? new Date(user.lastLoginAt).toLocaleString()\n : tr(\"auth.userDetails.never\", \"Never\")}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.created\", \"Created\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.createdAt).toLocaleString()}\n </dd>\n </div>\n <div className=\"p-4 rounded-lg bg-bg-canvas/50\">\n <dt className=\"text-sm font-medium text-text-secondary mb-1\">\n {tr(\"auth.userDetails.lastUpdated\", \"Last Updated\")}\n </dt>\n <dd className=\"text-text-primary font-medium\">\n {new Date(user.updatedAt).toLocaleString()}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-8 pt-6 border-t border-border-subtle flex gap-3\">\n {user.status !== \"DELETED\" && canDeleteUsers && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"deactivate\")}\n className=\"px-4 py-2.5 rounded-xl border border-status-error-border text-status-error-text font-medium hover:bg-status-error-bg-subtle transition-colors\"\n >\n {tr(\"auth.userDetails.deactivateUser\", \"Deactivate User\")}\n </button>\n )}\n {canDeleteUsers &&\n user.status === \"DELETED\" &&\n currentUser?.role === \"SUPER_ADMIN\" && (\n <button\n type=\"button\"\n onClick={() => setPendingAction(\"hard-delete\")}\n className=\"px-4 py-2.5 rounded-xl bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover transition-colors\"\n >\n {tr(\"auth.userDetails.permanentlyDelete\", \"Permanently Delete\")}\n </button>\n )}\n </div>\n\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-user-details\"\n steps={[\n {\n id: \"back-to-users\",\n label: tr(\"auth.userDetails.nextBack\", \"Return to the user list\"),\n description: tr(\n \"auth.userDetails.nextBackDesc\",\n \"Browse or search everyone in your organization.\",\n ),\n onClick: () => navigate(\"/auth/users\"),\n },\n {\n id: \"export-user\",\n label: tr(\n \"auth.userDetails.nextExport\",\n \"Export this user's data\",\n ),\n description: tr(\n \"auth.userDetails.nextExportDesc\",\n \"Download their profile, sessions, and audit history as CSV.\",\n ),\n onClick: () => void handleExport(),\n },\n ]}\n />\n\n <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\n \"auth.userDetails.exportQuotaTitle\",\n \"Export Limit Reached\",\n )}\n description={tr(\n \"auth.userDetails.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\n \"auth.userDetails.exportQuotaViewBilling\",\n \"View Billing\",\n )}\n cancelText={tr(\"auth.userDetails.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * User Profile page for viewing and editing own profile
3
+ */
4
+ export declare function UserProfilePage(): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Users CRUDL page for managing users (admin only)
3
+ */
4
+ export declare function UsersPage(): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import { AuthRootProps } from '../types';
3
+
4
+ /**
5
+ * Hook to access Auth context
6
+ */
7
+ export declare const useAuth: () => AuthRootProps;
8
+ /**
9
+ * Provider component for Auth microfrontend
10
+ * Sets up routing and context.
11
+ *
12
+ * NOTE: Apollo client is provided by the app shell's MultiGatewayProvider +
13
+ * GatewayRoute. Auth routes are wrapped with <GatewayRoute gateway="global">
14
+ * so all useQuery/useMutation calls automatically use the global gateway client.
15
+ * This MFE does NOT create its own Apollo client.
16
+ */
17
+ export declare const AuthProvider: FC<PropsWithChildren<AuthRootProps>>;
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ interface CaptchaProviderProps {
4
+ children: ReactNode;
5
+ }
6
+ /**
7
+ * Provides reCAPTCHA v3 context for invisible captcha.
8
+ * Only loads the Google reCAPTCHA script when VITE_CAPTCHA_ENABLED is "true".
9
+ * Otherwise, renders children directly without loading any scripts.
10
+ */
11
+ export declare function CaptchaProvider({ children }: CaptchaProviderProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,110 @@
1
+ import { User, Session, MfaDevice, OAuthAccount, TrustedDevice, SecurityAudit, ApiKey, App, AppUserDelegation, Workspace, Organization, Project } from '../types';
2
+
3
+ interface AuthState {
4
+ user: User | null;
5
+ accessToken: string | null;
6
+ refreshToken: string | null;
7
+ isAuthenticated: boolean;
8
+ organizations: Organization[];
9
+ currentOrganizationId: string | null;
10
+ isLoadingOrganizations: boolean;
11
+ workspaces: Workspace[];
12
+ currentWorkspaceId: string | null;
13
+ workspaceToken: string | null;
14
+ isLoadingWorkspaces: boolean;
15
+ projects: Project[];
16
+ currentProjectId: string | null;
17
+ isLoadingProjects: boolean;
18
+ mfaRequired: boolean;
19
+ mfaChallengeId: string | null;
20
+ mfaDevices: MfaDevice[];
21
+ activeSessions: Session[];
22
+ currentSessionId: string | null;
23
+ linkedAccounts: OAuthAccount[];
24
+ trustedDevices: TrustedDevice[];
25
+ securityEvents: SecurityAudit[];
26
+ apiKeys: ApiKey[];
27
+ apps: App[];
28
+ currentApp: App | null;
29
+ delegations: AppUserDelegation[];
30
+ grantedDelegations: AppUserDelegation[];
31
+ isLoading: boolean;
32
+ error: string | null;
33
+ errorCode: string | null;
34
+ setUser: (user: User | null) => void;
35
+ setAccessToken: (token: string | null) => void;
36
+ setAuthTokens: (accessToken: string, refreshToken: string) => void;
37
+ setAuthenticated: (isAuthenticated: boolean) => void;
38
+ clearAuth: () => void;
39
+ setOrganizations: (organizations: Organization[]) => void;
40
+ setCurrentOrganizationId: (orgId: string | null) => void;
41
+ setLoadingOrganizations: (loading: boolean) => void;
42
+ setWorkspaces: (workspaces: Workspace[]) => void;
43
+ setCurrentWorkspaceId: (workspaceId: string | null) => void;
44
+ setWorkspaceToken: (token: string | null) => void;
45
+ setLoadingWorkspaces: (loading: boolean) => void;
46
+ clearWorkspace: () => void;
47
+ setProjects: (projects: Project[]) => void;
48
+ setCurrentProjectId: (projectId: string | null) => void;
49
+ setLoadingProjects: (loading: boolean) => void;
50
+ clearProject: () => void;
51
+ clearContext: () => void;
52
+ loadFromProfile: (profileId: string) => void;
53
+ setMfaRequired: (required: boolean, challengeId?: string) => void;
54
+ setMfaDevices: (devices: MfaDevice[]) => void;
55
+ removeMfaDevice: (deviceId: string) => void;
56
+ setActiveSessions: (sessions: Session[]) => void;
57
+ removeSession: (sessionId: string) => void;
58
+ setLinkedAccounts: (accounts: OAuthAccount[]) => void;
59
+ removeLinkedAccount: (accountId: string) => void;
60
+ setTrustedDevices: (devices: TrustedDevice[]) => void;
61
+ removeTrustedDevice: (deviceId: string) => void;
62
+ setSecurityEvents: (events: SecurityAudit[]) => void;
63
+ setApiKeys: (keys: ApiKey[]) => void;
64
+ removeApiKey: (keyId: string) => void;
65
+ setApps: (apps: App[]) => void;
66
+ setCurrentApp: (app: App | null) => void;
67
+ addApp: (app: App) => void;
68
+ updateApp: (app: App) => void;
69
+ removeApp: (appId: string) => void;
70
+ setDelegations: (delegations: AppUserDelegation[]) => void;
71
+ setGrantedDelegations: (delegations: AppUserDelegation[]) => void;
72
+ addDelegation: (delegation: AppUserDelegation) => void;
73
+ removeDelegation: (delegationId: string) => void;
74
+ setLoading: (loading: boolean) => void;
75
+ setError: (error: string | null, errorCode?: string | null) => void;
76
+ }
77
+ /**
78
+ * Auth Zustand store — browser-tab-local state.
79
+ *
80
+ * SAFE for in-memory (no Redis needed):
81
+ * - accessToken / refreshToken / user: per-tab, backed by profileScopedStorage
82
+ * (localStorage key bf-p-{profileId}-*). Tabs sharing the same profile ID
83
+ * stay in sync via the "bf-profile-storage-change" custom event + "storage"
84
+ * cross-tab event.
85
+ * - workspaceToken: same pattern as above.
86
+ * - mfaRequired / mfaChallengeId: transient login-flow state; not needed
87
+ * cross-tab (each login has its own challenge).
88
+ * - organizations / workspaces / projects lists: cached context, re-fetched
89
+ * after account switch; no cross-tab sharing needed.
90
+ * - UI state (isLoading, error): purely ephemeral.
91
+ *
92
+ * NOT safe for in-memory (needs Redis in future if required server-side):
93
+ * - Nothing currently — all server-side session tracking is done by the
94
+ * backend (global-auth-svc) using its own DB. The frontend only holds
95
+ * short-lived JWT access tokens that expire independently.
96
+ */
97
+ export declare const useAuthStore: import('zustand').UseBoundStore<Omit<import('zustand').StoreApi<AuthState>, "setState" | "persist"> & {
98
+ setState(partial: AuthState | Partial<AuthState> | ((state: AuthState) => AuthState | Partial<AuthState>), replace?: false | undefined): unknown;
99
+ setState(state: AuthState | ((state: AuthState) => AuthState), replace: true): unknown;
100
+ persist: {
101
+ setOptions: (options: Partial<import('zustand/middleware').PersistOptions<AuthState, unknown, unknown>>) => void;
102
+ clearStorage: () => void;
103
+ rehydrate: () => Promise<void> | void;
104
+ hasHydrated: () => boolean;
105
+ onHydrate: (fn: (state: AuthState) => void) => () => void;
106
+ onFinishHydration: (fn: (state: AuthState) => void) => () => void;
107
+ getOptions: () => Partial<import('zustand/middleware').PersistOptions<AuthState, unknown, unknown>>;
108
+ };
109
+ }>;
110
+ export {};