@burdenoff/microfe-auth 2026.925.1 → 2026.1009.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 (160) hide show
  1. package/README.md +14 -0
  2. package/dist/auth/AuthRoot.d.ts +0 -1
  3. package/dist/auth/AuthRoot.js.map +1 -1
  4. package/dist/auth/AuthRoutes.d.ts +1 -1
  5. package/dist/auth/AuthRoutes.js.map +1 -1
  6. package/dist/auth/components/AuthIcons.d.ts +12 -13
  7. package/dist/auth/components/AuthIcons.js.map +1 -1
  8. package/dist/auth/components/AuthLayout.d.ts +1 -2
  9. package/dist/auth/components/AuthLayout.js.map +1 -1
  10. package/dist/auth/components/CaptchaV2.d.ts +1 -1
  11. package/dist/auth/components/CaptchaV2.js +2 -2
  12. package/dist/auth/components/CaptchaV2.js.map +1 -1
  13. package/dist/auth/components/ConfirmActionDialog.d.ts +1 -1
  14. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  15. package/dist/auth/components/profile/UserProfileMenu.d.ts +1 -1
  16. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  17. package/dist/auth/components/workspace/WorkspaceSelector.d.ts +1 -2
  18. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  19. package/dist/auth/constants/permissions.js.map +1 -1
  20. package/dist/auth/hooks/useAccountOperations.d.ts +0 -1
  21. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  22. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  23. package/dist/auth/hooks/useApiKeyOperations.d.ts +1 -2
  24. package/dist/auth/hooks/useAppOperations.d.ts +1 -2
  25. package/dist/auth/hooks/useAuthOperations.d.ts +0 -1
  26. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  27. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  28. package/dist/auth/hooks/useCaptchaV3.js +1 -1
  29. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  30. package/dist/auth/hooks/useContextSwitcherData.d.ts +1 -1
  31. package/dist/auth/hooks/useContextSwitcherData.js +346 -259
  32. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  33. package/dist/auth/hooks/useDelegationOperations.d.ts +0 -1
  34. package/dist/auth/hooks/useMfaOperations.d.ts +0 -1
  35. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  36. package/dist/auth/hooks/useMyWorkspaces.d.ts +0 -1
  37. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  38. package/dist/auth/hooks/useOAuthOperations.d.ts +0 -1
  39. package/dist/auth/hooks/useOAuthOperations.js +2 -2
  40. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  41. package/dist/auth/hooks/usePasswordOperations.d.ts +0 -1
  42. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  43. package/dist/auth/hooks/useSessionOperations.d.ts +0 -1
  44. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  45. package/dist/auth/hooks/useSignupCaptcha.js +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  47. package/dist/auth/hooks/useSsoOperations.d.ts +0 -1
  48. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  49. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  50. package/dist/auth/hooks/useUserOperations.d.ts +0 -1
  51. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  52. package/dist/auth/hooks/useWorkspaceOperations.d.ts +0 -1
  53. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  55. package/dist/auth/pages/AuditLogPage.d.ts +1 -1
  56. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  57. package/dist/auth/pages/EmailVerifyPage.d.ts +1 -1
  58. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  59. package/dist/auth/pages/LoginPage.d.ts +1 -1
  60. package/dist/auth/pages/LoginPage.js.map +1 -1
  61. package/dist/auth/pages/MagicLinkPage.d.ts +1 -1
  62. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  63. package/dist/auth/pages/MfaSetupPage.d.ts +1 -1
  64. package/dist/auth/pages/MfaSetupPage.js +88 -88
  65. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  66. package/dist/auth/pages/MfaVerifyPage.d.ts +1 -1
  67. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  68. package/dist/auth/pages/OAuthCallbackPage.d.ts +1 -1
  69. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  70. package/dist/auth/pages/PasswordResetPage.d.ts +1 -1
  71. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  72. package/dist/auth/pages/ReactivatePage.d.ts +1 -1
  73. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  74. package/dist/auth/pages/RegisterPage.d.ts +1 -1
  75. package/dist/auth/pages/RegisterPage.js.map +1 -1
  76. package/dist/auth/pages/SSOClearPage.d.ts +1 -1
  77. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  78. package/dist/auth/pages/SSODepositPage.d.ts +1 -1
  79. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  80. package/dist/auth/pages/SSORelayCallbackPage.d.ts +1 -1
  81. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  82. package/dist/auth/pages/SSORelayPage.d.ts +1 -1
  83. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  84. package/dist/auth/pages/SecurityPage.d.ts +1 -1
  85. package/dist/auth/pages/SecurityPage.js.map +1 -1
  86. package/dist/auth/pages/SessionBridgePage.d.ts +1 -1
  87. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  88. package/dist/auth/pages/SessionsPage.d.ts +1 -1
  89. package/dist/auth/pages/SessionsPage.js.map +1 -1
  90. package/dist/auth/pages/SsoCallbackPage.d.ts +1 -1
  91. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  92. package/dist/auth/pages/SsoConfigPage.d.ts +1 -1
  93. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  94. package/dist/auth/pages/SsoLoginPage.d.ts +1 -1
  95. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  96. package/dist/auth/pages/UserDetailsPage.d.ts +1 -1
  97. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  98. package/dist/auth/pages/UserProfilePage.d.ts +1 -1
  99. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  100. package/dist/auth/pages/UsersPage.d.ts +1 -1
  101. package/dist/auth/pages/UsersPage.js.map +1 -1
  102. package/dist/auth/providers/AuthProvider.d.ts +0 -1
  103. package/dist/auth/providers/AuthProvider.js.map +1 -1
  104. package/dist/auth/providers/CaptchaProvider.d.ts +1 -2
  105. package/dist/auth/providers/CaptchaProvider.js +2 -2
  106. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  107. package/dist/auth/store/authStore.d.ts +0 -1
  108. package/dist/auth/store/authStore.js.map +1 -1
  109. package/dist/auth/types/index.d.ts +0 -1
  110. package/dist/auth/utils/captchaConfig.js.map +1 -1
  111. package/dist/auth/utils/formatters.js.map +1 -1
  112. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  113. package/dist/auth/utils/legalUrls.js.map +1 -1
  114. package/dist/auth/utils/redirect.js.map +1 -1
  115. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  116. package/dist/auth/utils/validation.d.ts +0 -1
  117. package/dist/auth/utils/validation.js.map +1 -1
  118. package/dist/dev/main.d.ts +0 -1
  119. package/dist/generated/global-operations.js.map +1 -1
  120. package/dist/generated/wspace-operations.js.map +1 -1
  121. package/dist/index.d.ts +0 -1
  122. package/dist/shared/hooks/useTr.js.map +1 -1
  123. package/dist/shared/types/index.d.ts +0 -1
  124. package/dist/translations/en.js.map +1 -1
  125. package/package.json +27 -20
  126. package/dist/_virtual/_rolldown/runtime.js +0 -13
  127. package/dist/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js +0 -70
  128. package/dist/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js.map +0 -1
  129. package/dist/node_modules/object-assign/index.js +0 -41
  130. package/dist/node_modules/object-assign/index.js.map +0 -1
  131. package/dist/node_modules/prop-types/checkPropTypes.js +0 -46
  132. package/dist/node_modules/prop-types/checkPropTypes.js.map +0 -1
  133. package/dist/node_modules/prop-types/factoryWithThrowingShims.js +0 -49
  134. package/dist/node_modules/prop-types/factoryWithThrowingShims.js.map +0 -1
  135. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js +0 -272
  136. package/dist/node_modules/prop-types/factoryWithTypeCheckers.js.map +0 -1
  137. package/dist/node_modules/prop-types/index.js +0 -16
  138. package/dist/node_modules/prop-types/index.js.map +0 -1
  139. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js +0 -10
  140. package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js.map +0 -1
  141. package/dist/node_modules/prop-types/lib/has.js +0 -10
  142. package/dist/node_modules/prop-types/lib/has.js.map +0 -1
  143. package/dist/node_modules/qrcode.react/lib/esm/index.js +0 -959
  144. package/dist/node_modules/qrcode.react/lib/esm/index.js.map +0 -1
  145. package/dist/node_modules/react-async-script/lib/esm/async-script-loader.js +0 -114
  146. package/dist/node_modules/react-async-script/lib/esm/async-script-loader.js.map +0 -1
  147. package/dist/node_modules/react-google-recaptcha/lib/esm/index.js +0 -8
  148. package/dist/node_modules/react-google-recaptcha/lib/esm/index.js.map +0 -1
  149. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha-wrapper.js +0 -20
  150. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha-wrapper.js.map +0 -1
  151. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha.js +0 -148
  152. package/dist/node_modules/react-google-recaptcha/lib/esm/recaptcha.js.map +0 -1
  153. package/dist/node_modules/react-google-recaptcha-v3/dist/react-google-recaptcha-v3.esm.js +0 -445
  154. package/dist/node_modules/react-google-recaptcha-v3/dist/react-google-recaptcha-v3.esm.js.map +0 -1
  155. package/dist/node_modules/react-is/cjs/react-is.development.js +0 -84
  156. package/dist/node_modules/react-is/cjs/react-is.development.js.map +0 -1
  157. package/dist/node_modules/react-is/cjs/react-is.production.min.js +0 -64
  158. package/dist/node_modules/react-is/cjs/react-is.production.min.js.map +0 -1
  159. package/dist/node_modules/react-is/index.js +0 -12
  160. package/dist/node_modules/react-is/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityPage.js","names":[],"sources":["../../../src/auth/pages/SecurityPage.tsx"],"sourcesContent":["import { useId, useRef, useEffect, useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { EmphasisPanel, PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { useNavigate } from \"react-router\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useAccountOperations } from \"../hooks/useAccountOperations\";\nimport { formatDate } from \"../utils/formatters\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n// Icons\nconst ShieldIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n);\n\nconst LockIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n);\n\n// Hidden — moved to Developer Portal\n// const KeyIcon = ({ className }: { className?: string }) => (\n// <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n// <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5}\n// d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\" />\n// </svg>\n// );\n\n// const DelegationIcon = ({ className }: { className?: string }) => (\n// <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n// <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5}\n// d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n// </svg>\n// );\n\nconst UsersIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z\"\n />\n </svg>\n);\n\nconst UserProfileIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\"\n />\n </svg>\n);\n\nconst LinkIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\n />\n </svg>\n);\n\nconst ChevronRightIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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=\"M9 5l7 7-7 7\"\n />\n </svg>\n);\n\nconst CheckCircleIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\nconst ExclamationIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\nconst SpinnerIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\ninterface NavItem {\n name: string;\n description: string;\n path: string;\n icon: React.FC<{ className?: string }>;\n color: string;\n bgColor: string;\n adminOnly?: boolean;\n}\n\nconst DELETE_ACCOUNT_CONFIRMATION = \"DELETE MY ACCOUNT\";\n\n/**\n * Security Dashboard - Main hub for all authentication features\n */\nexport function SecurityPage({\n // Admin-only nav cards (Users → `/auth/users`, SSO Configuration →\n // `/auth/sso`) point at platform-global super-admin surfaces whose routes\n // only exist when `enableAdminRoutes={true}` (admin app shell). In\n // user-facing product hosts those routes dead-end on `/auth/login`, so the\n // cards are hidden unless admin routes are enabled. Defaults to false.\n enableAdminRoutes = false,\n}: {\n enableAdminRoutes?: boolean;\n}) {\n const tr = useTr();\n\n const navigate = useNavigate();\n const user = useAuthStore((state) => state.user);\n const { signOut } = useAuthOperations({\n disableCurrentUserQuery: true,\n });\n const { deactivateAccount, deleteAccount, isDeactivating, isDeleting } =\n useAccountOperations();\n const [isLoggingOut, setIsLoggingOut] = useState(false);\n const [showLogoutDialog, setShowLogoutDialog] = useState(false);\n\n const navigationItems: NavItem[] = [\n {\n name: tr(\"auth.security.navProfile\", \"User Profile\"),\n description: tr(\n \"auth.security.navProfileDesc\",\n \"View and edit your profile information\",\n ),\n path: \"/auth/profile\",\n icon: UserProfileIcon,\n color: \"text-accent-teal\",\n bgColor: \"bg-accent-teal-subtle\",\n },\n {\n name: tr(\"auth.security.navMfa\", \"Two-Factor Authentication\"),\n description: tr(\n \"auth.security.navMfaDesc\",\n \"Secure your account with TOTP\",\n ),\n path: \"/auth/mfa\",\n icon: LockIcon,\n color: \"text-accent-purple\",\n bgColor: \"bg-accent-purple-subtle\",\n },\n // Hidden — moved to Developer Portal\n // {\n // name: tr(\"auth.security.navApiKeys\", \"API Keys\"),\n // description: tr(\"auth.security.navApiKeysDesc\", \"Manage programmatic access tokens\"),\n // path: \"/auth/api-keys\",\n // icon: KeyIcon,\n // color: \"text-status-warning-text\",\n // bgColor: \"bg-status-warning-bg-subtle\",\n // },\n // {\n // name: tr(\"auth.security.navDelegations\", \"Delegations\"),\n // description: tr(\"auth.security.navDelegationsDesc\", \"Manage apps acting on your behalf\"),\n // path: \"/auth/delegations\",\n // icon: DelegationIcon,\n // color: \"text-status-warning-text\",\n // bgColor: \"bg-status-warning-bg-subtle\",\n // },\n {\n name: tr(\"auth.security.navUsers\", \"Users\"),\n description: tr(\"auth.security.navUsersDesc\", \"Manage system users\"),\n path: \"/auth/users\",\n icon: UsersIcon,\n color: \"text-accent-indigo\",\n bgColor: \"bg-accent-indigo-subtle\",\n adminOnly: true,\n },\n ];\n\n const quickActions = [\n {\n name: tr(\"auth.security.navSso\", \"SSO Configuration\"),\n description: tr(\"auth.security.navSsoDesc\", \"Enterprise single sign-on\"),\n path: \"/auth/sso\",\n icon: LinkIcon,\n color: \"text-accent-cyan\",\n bgColor: \"bg-accent-cyan-subtle\",\n adminOnly: true,\n },\n ];\n\n // Account management modal states\n const [showDeactivateModal, setShowDeactivateModal] = useState(false);\n const [showDeleteModal, setShowDeleteModal] = useState(false);\n const [accountPassword, setAccountPassword] = useState(\"\");\n const [deleteConfirmPhrase, setDeleteConfirmPhrase] = useState(\"\");\n const [accountError, setAccountError] = useState<string | null>(null);\n const [accountSuccess, setAccountSuccess] = useState<string | null>(null);\n\n // Accessible IDs for modal labels and form fields\n const deactivateTitleId = useId();\n const deactivatePasswordId = useId();\n const deactivateErrorId = useId();\n const deleteTitleId = useId();\n const deletePasswordId = useId();\n const deleteConfirmId = useId();\n const deleteErrorId = useId();\n\n // Focus management: refs to restore focus and manage modal focus\n const deactivateTriggerRef = useRef<HTMLButtonElement>(null);\n const deleteTriggerRef = useRef<HTMLButtonElement>(null);\n const deactivateDialogRef = useRef<HTMLDivElement>(null);\n const deleteDialogRef = useRef<HTMLDivElement>(null);\n\n // Move focus into modal when it opens; restore to trigger on close\n useEffect(() => {\n if (showDeactivateModal) {\n deactivateDialogRef.current?.focus();\n } else {\n deactivateTriggerRef.current?.focus();\n }\n }, [showDeactivateModal]);\n\n useEffect(() => {\n if (showDeleteModal) {\n deleteDialogRef.current?.focus();\n } else {\n deleteTriggerRef.current?.focus();\n }\n }, [showDeleteModal]);\n\n const handleLogout = async () => {\n setIsLoggingOut(true);\n try {\n await signOut();\n navigate(\"/auth/login\");\n setShowLogoutDialog(false);\n } catch (error) {\n console.error(\"Logout failed:\", error);\n setIsLoggingOut(false);\n }\n };\n\n const handleDeactivateAccount = async () => {\n setAccountError(null);\n try {\n const result = await deactivateAccount(accountPassword);\n if (result.success) {\n // Immediately redirect to login page\n navigate(\"/auth/login\", { replace: true });\n } else {\n setAccountError(\n result.message ||\n tr(\n \"auth.security.deactivateFailed\",\n \"Failed to deactivate account\",\n ),\n );\n }\n } catch (error) {\n setAccountError(\n error instanceof Error\n ? error.message\n : tr(\n \"auth.security.deactivateFailed\",\n \"Failed to deactivate account\",\n ),\n );\n }\n };\n\n const handleDeleteAccount = async () => {\n setAccountError(null);\n if (deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION) {\n setAccountError(\n tr(\n \"auth.security.deleteConfirmMismatch\",\n 'Please type \"DELETE MY ACCOUNT\" to confirm',\n ),\n );\n return;\n }\n void nativeImpact(\"medium\");\n try {\n const result = await deleteAccount(accountPassword, deleteConfirmPhrase);\n if (result.success) {\n void nativeNotify(\"success\");\n // Immediately redirect to login page\n navigate(\"/auth/login\", { replace: true });\n } else {\n void nativeNotify(\"error\");\n setAccountError(\n result.message ||\n tr(\"auth.security.deleteFailed\", \"Failed to delete account\"),\n );\n }\n } catch (error) {\n void nativeNotify(\"error\");\n setAccountError(\n error instanceof Error\n ? error.message\n : tr(\"auth.security.deleteFailed\", \"Failed to delete account\"),\n );\n }\n };\n\n const closeModals = () => {\n setShowDeactivateModal(false);\n setShowDeleteModal(false);\n setAccountPassword(\"\");\n setDeleteConfirmPhrase(\"\");\n setAccountError(null);\n setAccountSuccess(null);\n };\n\n const isAdmin =\n user?.role === \"SUPER_ADMIN\" ||\n user?.role === \"TENANT_ADMIN\" ||\n user?.role === \"ORG_ADMIN\";\n const filteredNavItems = navigationItems.filter(\n (item) => !item.adminOnly || (enableAdminRoutes && isAdmin),\n );\n const filteredQuickActions = quickActions.filter(\n (item) => !item.adminOnly || (enableAdminRoutes && isAdmin),\n );\n\n return (\n <div className=\"bg-bg-canvas p-6\">\n <ConfirmActionDialog\n open={showLogoutDialog}\n onOpenChange={setShowLogoutDialog}\n title={tr(\"auth.security.signOutDialogTitle\", \"Sign out now?\")}\n description={tr(\n \"auth.security.signOutDialogDescription\",\n \"You will be signed out of this device and returned to the login screen.\",\n )}\n confirmText={tr(\"auth.security.signOutDialogConfirm\", \"Sign out\")}\n variant=\"warning\"\n isPending={isLoggingOut}\n onConfirm={handleLogout}\n />\n <main className=\"max-w-7xl mx-auto space-y-8\">\n <div\n className=\"flex items-center gap-3\"\n data-tour=\"auth-security-header\"\n >\n <div className=\"auth-brand-badge flex size-10 items-center justify-center rounded-xl\">\n <ShieldIcon className=\"size-6 text-white\" />\n </div>\n <div>\n <h1 className=\"text-lg font-bold text-text-primary\">\n {tr(\"auth.security.title\", \"Security Center\")}\n </h1>\n <p className=\"text-xs text-text-secondary \">\n {tr(\"auth.security.subtitle\", \"Account & Authentication\")}\n </p>\n </div>\n </div>\n\n <PagePurpose data-tour=\"auth-security-purpose\">\n {tr(\n \"auth.security.purpose\",\n \"Your one place to keep your account safe: turn on two-factor authentication, change your password, review who can act on your behalf, and manage or close your account.\",\n )}\n </PagePurpose>\n\n {/* Security Status Banner — the one emphasis zone on this page */}\n <EmphasisPanel className=\"p-6\" data-tour=\"auth-security-status-banner\">\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-14 rounded-2xl bg-accent-soft flex items-center justify-center\">\n <ShieldIcon className=\"size-8 text-[var(--color-action-primary-bg)]\" />\n </div>\n <div>\n <h2 className=\"text-xl font-bold text-text-primary\">\n {tr(\"auth.security.securityStatus\", \"Security Status\")}\n </h2>\n <p className=\"text-text-secondary\">\n {user?.mfaEnabled\n ? tr(\n \"auth.security.wellProtected\",\n \"Your account is well protected\",\n )\n : tr(\n \"auth.security.enableMfaSuggestion\",\n \"Enable MFA for better security\",\n )}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n {user?.mfaEnabled ? (\n <span className=\"inline-flex items-center gap-2 px-4 py-2 rounded-xl bg-status-success-bg-subtle text-status-success-text font-medium\">\n <CheckCircleIcon className=\"size-5 text-status-success-text\" />\n {tr(\"auth.security.mfaEnabled\", \"MFA Enabled\")}\n </span>\n ) : (\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n variant=\"default\"\n className=\"font-semibold\"\n >\n <ExclamationIcon className=\"size-5\" />\n {tr(\"auth.security.enableMfaNow\", \"Enable MFA Now\")}\n </Button>\n )}\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Account Information Card */}\n <div\n className=\"mb-8 p-6 rounded-2xl bg-bg-surface border border-border-subtle shadow-sm\"\n data-tour=\"auth-account-info-card\"\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.accountInfo\", \"Account Information\")}\n </h3>\n <dl className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n <div className=\"sm:col-span-2 lg:col-span-1\">\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.email\", \"Email\")}\n </dt>\n <dd className=\"mt-1 text-text-primary\">\n <span className=\"block truncate\" title={user?.email}>\n {user?.email}\n </span>\n {user?.emailVerified && (\n <span className=\"mt-1 inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\">\n <CheckCircleIcon className=\"size-3\" />\n {tr(\"auth.security.verified\", \"Verified\")}\n </span>\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.username\", \"Username\")}\n </dt>\n <dd\n className=\"mt-1 text-text-primary truncate\"\n title={user?.username ?? undefined}\n >\n {user?.username || \"-\"}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.role\", \"Role\")}\n </dt>\n <dd className=\"mt-1\">\n <span className=\"inline-flex px-2.5 py-0.5 rounded-full bg-accent-indigo-subtle text-accent-indigo text-xs font-medium\">\n {user?.role || \"USER\"}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.memberSince\", \"Member Since\")}\n </dt>\n <dd className=\"mt-1 text-text-primary\">\n {user?.createdAt ? formatDate(user.createdAt) : \"-\"}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Main Navigation Grid */}\n <div className=\"mb-8\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.securityAuth\", \"Security & Authentication\")}\n </h3>\n <div\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"auth-security-nav-grid\"\n >\n {filteredNavItems.map((item) => (\n <button\n key={item.path}\n type=\"button\"\n onClick={() => navigate(item.path)}\n aria-label={item.name}\n className=\"group relative p-5 rounded-2xl bg-bg-surface border border-border-subtle hover:border-border-default hover:shadow-lg transition-all duration-200 text-left\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className={`size-12 rounded-xl ${item.bgColor} flex items-center justify-center flex-shrink-0 transition-transform`}\n >\n <item.icon className={`size-6 ${item.color}`} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-base font-semibold text-text-primary group-hover:text-accent-indigo transition-colors\">\n {item.name}\n </h4>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {item.description}\n </p>\n </div>\n <ChevronRightIcon className=\"size-5 text-text-tertiary group-hover:text-indigo-500 transition-colors flex-shrink-0\" />\n </div>\n </button>\n ))}\n </div>\n </div>\n\n {/* Quick Actions */}\n <div className=\"mb-8\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.quickActions\", \"Quick Actions\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {filteredQuickActions.map((item) => (\n <button\n key={item.path}\n type=\"button\"\n onClick={() => navigate(item.path)}\n aria-label={item.name}\n className=\"group relative p-5 rounded-2xl bg-bg-surface border border-border-subtle hover:border-border-default hover:shadow-lg transition-all duration-200 text-left\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className={`size-12 rounded-xl ${item.bgColor} flex items-center justify-center flex-shrink-0 transition-transform`}\n >\n <item.icon className={`size-6 ${item.color}`} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-base font-semibold text-text-primary group-hover:text-accent-indigo transition-colors\">\n {item.name}\n </h4>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {item.description}\n </p>\n </div>\n <ChevronRightIcon className=\"size-5 text-text-tertiary group-hover:text-indigo-500 transition-colors flex-shrink-0\" />\n </div>\n </button>\n ))}\n </div>\n </div>\n\n {/* Password & Danger Zone */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Password Section */}\n <div\n className=\"p-6 rounded-2xl bg-bg-surface border border-border-subtle\"\n data-tour=\"auth-password-section\"\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"auth.security.password\", \"Password\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {tr(\n \"auth.security.updatePassword\",\n \"Update your password to keep your account secure\",\n )}\n </p>\n <p className=\"mt-2 text-xs text-text-tertiary \">\n {tr(\"auth.security.lastChanged\", \"Last changed: Never\")}\n </p>\n </div>\n </div>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n variant=\"default\"\n className=\"mt-4 w-full font-semibold sm:w-auto\"\n >\n {tr(\"auth.security.changePassword\", \"Change Password\")}\n </Button>\n </div>\n\n {/* Danger Zone */}\n <div\n className=\"p-6 rounded-2xl bg-status-error-bg-subtle border border-status-error-border\"\n data-tour=\"auth-danger-zone\"\n >\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n {tr(\"auth.security.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text/80\">\n {tr(\n \"auth.security.dangerWarning\",\n \"These actions affect your account. Proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n ref={deactivateTriggerRef}\n type=\"button\"\n onClick={() => setShowDeactivateModal(true)}\n className=\"px-5 py-2.5 rounded-xl border-2 border-status-warning-border text-status-warning-text font-semibold hover:bg-status-warning-bg-subtle transition-all duration-200\"\n >\n {tr(\"auth.security.deactivateAccount\", \"Deactivate Account\")}\n </button>\n <button\n ref={deleteTriggerRef}\n type=\"button\"\n onClick={() => setShowDeleteModal(true)}\n className=\"px-5 py-2.5 rounded-xl border-2 border-status-error-border text-status-error-text font-semibold hover:bg-status-error-bg-subtle transition-all duration-200\"\n >\n {tr(\"auth.security.deleteAccount\", \"Delete Account\")}\n </button>\n </div>\n </div>\n </div>\n\n {/* Deactivate Account Modal */}\n {showDeactivateModal && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) closeModals();\n }}\n >\n <div\n ref={deactivateDialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={deactivateTitleId}\n tabIndex={-1}\n className=\"w-full max-w-md bg-bg-surface rounded-2xl shadow-2xl p-6 outline-none\"\n >\n <h3\n id={deactivateTitleId}\n className=\"text-xl font-bold text-text-primary mb-2\"\n >\n {tr(\"auth.security.deactivateTitle\", \"Deactivate Account\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.security.deactivateDesc\",\n \"Your account will be temporarily disabled. You can reactivate it by signing in again with your credentials.\",\n )}\n </p>\n {accountError && (\n <div\n id={deactivateErrorId}\n role=\"alert\"\n className=\"mb-4 p-3 rounded-lg bg-status-error-bg-subtle text-status-error-text text-sm\"\n >\n {accountError}\n </div>\n )}\n {accountSuccess && (\n <output className=\"block mb-4 p-3 rounded-lg bg-status-success-bg-subtle text-status-success-text text-sm\">\n {accountSuccess}\n </output>\n )}\n <div className=\"mb-4\">\n <label\n htmlFor={deactivatePasswordId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\n \"auth.security.enterPasswordConfirm\",\n \"Enter your password to confirm\",\n )}\n </label>\n <input\n id={deactivatePasswordId}\n type=\"password\"\n value={accountPassword}\n onChange={(e) => setAccountPassword(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.enterPasswordConfirm\",\n \"Enter your password to confirm\",\n )}\n aria-describedby={\n accountError ? deactivateErrorId : undefined\n }\n aria-invalid={!!accountError}\n autoComplete=\"current-password\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-warning-bg focus:border-transparent\"\n placeholder={tr(\n \"auth.security.yourPassword\",\n \"Your password\",\n )}\n />\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={closeModals}\n className=\"flex-1 px-4 py-2 rounded-lg border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.security.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleDeactivateAccount()}\n disabled={!accountPassword || isDeactivating}\n className=\"flex-1 px-4 py-2 rounded-lg bg-status-warning-bg text-white font-medium hover:bg-status-warning-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2\"\n >\n {isDeactivating && (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n )}\n {tr(\"auth.security.deactivateBtn\", \"Deactivate\")}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Delete Account Modal */}\n {showDeleteModal && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) closeModals();\n }}\n >\n <div\n ref={deleteDialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={deleteTitleId}\n tabIndex={-1}\n className=\"w-full max-w-md bg-bg-surface rounded-2xl shadow-2xl p-6 outline-none\"\n >\n <h3\n id={deleteTitleId}\n className=\"text-xl font-bold text-status-error-text mb-2\"\n >\n {tr(\"auth.security.deleteTitle\", \"Delete Account Permanently\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.security.deleteDesc\",\n \"This action is irreversible. All your data will be permanently deleted and cannot be recovered.\",\n )}\n </p>\n {accountError && (\n <div\n id={deleteErrorId}\n role=\"alert\"\n className=\"mb-4 p-3 rounded-lg bg-status-error-bg-subtle text-status-error-text text-sm\"\n >\n {accountError}\n </div>\n )}\n {accountSuccess && (\n <output className=\"block mb-4 p-3 rounded-lg bg-status-success-bg-subtle text-status-success-text text-sm\">\n {accountSuccess}\n </output>\n )}\n <div className=\"mb-4\">\n <label\n htmlFor={deletePasswordId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\n \"auth.security.enterPasswordDelete\",\n \"Enter your password\",\n )}\n </label>\n <input\n id={deletePasswordId}\n type=\"password\"\n value={accountPassword}\n onChange={(e) => setAccountPassword(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.enterPasswordDelete\",\n \"Enter your password\",\n )}\n aria-describedby={accountError ? deleteErrorId : undefined}\n aria-invalid={!!accountError}\n autoComplete=\"current-password\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-error-bg focus:border-transparent\"\n placeholder={tr(\n \"auth.security.yourPassword\",\n \"Your password\",\n )}\n />\n </div>\n <div className=\"mb-4\">\n <label\n htmlFor={deleteConfirmId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\"auth.security.type\", \"Type\")}{\" \"}\n <code className=\"px-1 py-0.5 rounded bg-bg-sunken text-status-error-text font-mono text-sm\">\n {DELETE_ACCOUNT_CONFIRMATION}\n </code>{\" \"}\n {tr(\"auth.security.toConfirm\", \"to confirm\")}\n </label>\n <input\n id={deleteConfirmId}\n type=\"text\"\n value={deleteConfirmPhrase}\n onChange={(e) => setDeleteConfirmPhrase(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.deleteConfirmAriaLabel\",\n \"Type DELETE MY ACCOUNT to confirm\",\n )}\n aria-describedby={accountError ? deleteErrorId : undefined}\n aria-invalid={\n deleteConfirmPhrase.length > 0 &&\n deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION\n }\n autoComplete=\"off\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-error-bg focus:border-transparent font-mono\"\n placeholder={tr(\n \"auth.security.deleteConfirmPlaceholder\",\n \"DELETE MY ACCOUNT\",\n )}\n />\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={closeModals}\n className=\"flex-1 px-4 py-2 rounded-lg border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.security.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleDeleteAccount()}\n disabled={\n !accountPassword ||\n deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION ||\n isDeleting\n }\n className=\"flex-1 px-4 py-2 rounded-lg bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2\"\n >\n {isDeleting && (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n )}\n {tr(\"auth.security.deleteForever\", \"Delete Forever\")}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <NextSteps\n storageKey=\"auth-security\"\n steps={[\n ...(user?.mfaEnabled\n ? []\n : [\n {\n id: \"enable-mfa\",\n label: tr(\n \"auth.security.nextEnableMfa\",\n \"Turn on two-factor authentication\",\n ),\n description: tr(\n \"auth.security.nextEnableMfaDesc\",\n \"The single biggest step you can take to protect your account.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n },\n ]),\n {\n id: \"review-profile\",\n label: tr(\n \"auth.security.nextProfile\",\n \"Review your profile details\",\n ),\n description: tr(\n \"auth.security.nextProfileDesc\",\n \"Make sure your name, email, and contact info are current.\",\n ),\n onClick: () => navigate(\"/auth/profile\"),\n },\n {\n id: \"change-password\",\n label: tr(\"auth.security.nextPassword\", \"Update your password\"),\n description: tr(\n \"auth.security.nextPasswordDesc\",\n \"Set a strong, unique password if you haven't recently.\",\n ),\n onClick: () => navigate(\"/auth/reset-password\"),\n },\n ]}\n />\n\n {/* Footer */}\n <footer className=\"mt-12 pt-6 border-t border-border-subtle \">\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 text-sm text-text-secondary \"\n data-tour=\"auth-footer-actions\"\n >\n <p>\n {tr(\"auth.security.signedInAs\", \"Signed in as\")}{\" \"}\n <span className=\"font-medium text-text-primary \">\n {user?.email}\n </span>\n </p>\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"hover:text-text-primary transition-colors\"\n >\n {tr(\"auth.security.switchAccount\", \"Switch Account\")}\n </button>\n <span className=\"text-text-muted\" aria-hidden=\"true\">\n |\n </span>\n <button\n type=\"button\"\n onClick={() => setShowLogoutDialog(true)}\n className=\"hover:text-status-error-text transition-colors\"\n >\n {tr(\"auth.security.signOut\", \"Sign Out\")}\n </button>\n </div>\n </div>\n </footer>\n </main>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAM,KAAc,EAAE,mBACpB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAGF,KAAY,EAAE,mBAClB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAkBF,MAAa,EAAE,mBACnB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAGF,MAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAGF,MAAY,EAAE,mBAClB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAGF,KAAoB,EAAE,mBAC1B,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;EACF,CAAA;CACE,CAAA,EAGF,KAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,eAAY;WAEZ,kBAAC,QAAD;EACE,UAAS;EACT,GAAE;EACF,UAAS;EACT,CAAA;CACE,CAAA,EAGF,MAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,eAAY;WAEZ,kBAAC,QAAD;EACE,UAAS;EACT,GAAE;EACF,UAAS;EACT,CAAA;CACE,CAAA,EAGF,KAAe,EAAE,mBACrB,kBAAC,OAAD;CAAgB;CAAW,MAAK;CAAO,SAAQ;CAAY,eAAY;WAAvE,CACE,kBAAC,UAAD;EACE,WAAU;EACV,IAAG;EACH,IAAG;EACH,GAAE;EACF,QAAO;EACP,aAAY;EACZ,CAAA,EACF,kBAAC,QAAD;EACE,WAAU;EACV,MAAK;EACL,GAAE;EACF,CAAA,CACE;IAaF,IAA8B;AAKpC,SAAgB,EAAa,EAM3B,uBAAoB,MAGnB;CACD,IAAM,IAAK,GAAO,EAEZ,IAAW,IAAa,EACxB,IAAO,GAAc,MAAU,EAAM,KAAK,EAC1C,EAAE,eAAY,GAAkB,EACpC,yBAAyB,IAC1B,CAAC,EACI,EAAE,sBAAmB,kBAAe,mBAAgB,kBACxD,IAAsB,EAClB,CAAC,IAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,IAAkB,KAAuB,EAAS,GAAM,EAEzD,KAA6B;EACjC;GACE,MAAM,EAAG,4BAA4B,eAAe;GACpD,aAAa,EACX,gCACA,yCACD;GACD,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;GACV;EACD;GACE,MAAM,EAAG,wBAAwB,4BAA4B;GAC7D,aAAa,EACX,4BACA,gCACD;GACD,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;GACV;EAkBD;GACE,MAAM,EAAG,0BAA0B,QAAQ;GAC3C,aAAa,EAAG,8BAA8B,sBAAsB;GACpE,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;GACT,WAAW;GACZ;EACF,EAEK,KAAe,CACnB;EACE,MAAM,EAAG,wBAAwB,oBAAoB;EACrD,aAAa,EAAG,4BAA4B,4BAA4B;EACxE,MAAM;EACN,MAAM;EACN,OAAO;EACP,SAAS;EACT,WAAW;EACZ,CACF,EAGK,CAAC,GAAqB,KAA0B,EAAS,GAAM,EAC/D,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAqB,KAA0B,EAAS,GAAG,EAC5D,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAC/D,CAAC,GAAgB,MAAqB,EAAwB,KAAK,EAGnE,IAAoB,GAAO,EAC3B,IAAuB,GAAO,EAC9B,IAAoB,GAAO,EAC3B,IAAgB,GAAO,EACvB,IAAmB,GAAO,EAC1B,IAAkB,GAAO,EACzB,IAAgB,GAAO,EAGvB,IAAuB,EAA0B,KAAK,EACtD,IAAmB,EAA0B,KAAK,EAClD,IAAsB,EAAuB,KAAK,EAClD,IAAkB,EAAuB,KAAK;AAWpD,CARA,QAAgB;AACd,EAAI,IACF,EAAoB,SAAS,OAAO,GAEpC,EAAqB,SAAS,OAAO;IAEtC,CAAC,EAAoB,CAAC,EAEzB,QAAgB;AACd,EAAI,IACF,EAAgB,SAAS,OAAO,GAEhC,EAAiB,SAAS,OAAO;IAElC,CAAC,EAAgB,CAAC;CAErB,IAAM,KAAe,YAAY;AAC/B,IAAgB,GAAK;AACrB,MAAI;AAGF,GAFA,MAAM,GAAS,EACf,EAAS,cAAc,EACvB,EAAoB,GAAM;WACnB,GAAO;AAEd,GADA,QAAQ,MAAM,kBAAkB,EAAM,EACtC,EAAgB,GAAM;;IAIpB,KAA0B,YAAY;AAC1C,IAAgB,KAAK;AACrB,MAAI;GACF,IAAM,IAAS,MAAM,EAAkB,EAAgB;AACvD,GAAI,EAAO,UAET,EAAS,eAAe,EAAE,SAAS,IAAM,CAAC,GAE1C,EACE,EAAO,WACL,EACE,kCACA,+BACD,CACJ;WAEI,GAAO;AACd,KACE,aAAiB,QACb,EAAM,UACN,EACE,kCACA,+BACD,CACN;;IAIC,KAAsB,YAAY;AAEtC,MADA,EAAgB,KAAK,EACjB,MAAwB,GAA6B;AACvD,KACE,EACE,uCACA,+CACD,CACF;AACD;;AAEG,KAAa,SAAS;AAC3B,MAAI;GACF,IAAM,IAAS,MAAM,EAAc,GAAiB,EAAoB;AACxE,GAAI,EAAO,WACJ,EAAa,UAAU,EAE5B,EAAS,eAAe,EAAE,SAAS,IAAM,CAAC,KAErC,EAAa,QAAQ,EAC1B,EACE,EAAO,WACL,EAAG,8BAA8B,2BAA2B,CAC/D;WAEI,GAAO;AAEd,GADK,EAAa,QAAQ,EAC1B,EACE,aAAiB,QACb,EAAM,UACN,EAAG,8BAA8B,2BAA2B,CACjE;;IAIC,UAAoB;AAMxB,EALA,EAAuB,GAAM,EAC7B,EAAmB,GAAM,EACzB,EAAmB,GAAG,EACtB,EAAuB,GAAG,EAC1B,EAAgB,KAAK,EACrB,GAAkB,KAAK;IAGnB,IACJ,GAAM,SAAS,iBACf,GAAM,SAAS,kBACf,GAAM,SAAS,aACX,KAAmB,GAAgB,QACtC,MAAS,CAAC,EAAK,aAAc,KAAqB,EACpD,EACK,KAAuB,GAAa,QACvC,MAAS,CAAC,EAAK,aAAc,KAAqB,EACpD;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,IAAD;GACE,MAAM;GACN,cAAc;GACd,OAAO,EAAG,oCAAoC,gBAAgB;GAC9D,aAAa,EACX,0CACA,0EACD;GACD,aAAa,EAAG,sCAAsC,WAAW;GACjE,SAAQ;GACR,WAAW;GACX,WAAW;GACX,CAAA,EACF,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACE,kBAAC,OAAD;KACE,WAAU;KACV,aAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAY,WAAU,qBAAsB,CAAA;MACxC,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,uBAAuB,kBAAkB;MAC1C,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,0BAA0B,2BAA2B;MACvD,CAAA,CACA,EAAA,CAAA,CACF;;IAEN,kBAAC,GAAD;KAAa,aAAU;eACpB,EACC,yBACA,0KACD;KACW,CAAA;IAGd,kBAAC,GAAD;KAAe,WAAU;KAAM,aAAU;eACvC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,gDAAiD,CAAA;QACnE,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,gCAAgC,kBAAkB;QACnD,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAM,aACH,EACE,+BACA,iCACD,GACD,EACE,qCACA,iCACD;QACH,CAAA,CACA,EAAA,CAAA,CACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBACZ,GAAM,aACL,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAiB,WAAU,mCAAoC,CAAA,EAC9D,EAAG,4BAA4B,cAAc,CACzC;YAEP,kBAAC,GAAD;QACE,MAAK;QACL,eAAe,EAAS,YAAY;QACpC,SAAQ;QACR,WAAU;kBAJZ,CAME,kBAAC,IAAD,EAAiB,WAAU,UAAW,CAAA,EACrC,EAAG,8BAA8B,iBAAiB,CAC5C;;OAEP,CAAA,CACF;;KACQ,CAAA;IAGhB,kBAAC,OAAD;KACE,WAAU;KACV,aAAU;eAFZ,CAIE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,6BAA6B,sBAAsB;MACpD,CAAA,EACL,kBAAC,MAAD;MAAI,WAAU;gBAAd;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,uBAAuB,QAAQ;SAChC,CAAA,EACL,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,QAAD;UAAM,WAAU;UAAiB,OAAO,GAAM;oBAC3C,GAAM;UACF,CAAA,EACN,GAAM,iBACL,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD,EAAiB,WAAU,UAAW,CAAA,EACrC,EAAG,0BAA0B,WAAW,CACpC;YAEN;WACD;;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,0BAA0B,WAAW;QACtC,CAAA,EACL,kBAAC,MAAD;QACE,WAAU;QACV,OAAO,GAAM,YAAY,KAAA;kBAExB,GAAM,YAAY;QAChB,CAAA,CACD,EAAA,CAAA;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,sBAAsB,OAAO;QAC9B,CAAA,EACL,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,QAAD;SAAM,WAAU;mBACb,GAAM,QAAQ;SACV,CAAA;QACJ,CAAA,CACD,EAAA,CAAA;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,6BAA6B,eAAe;QAC7C,CAAA,EACL,kBAAC,MAAD;QAAI,WAAU;kBACX,GAAM,YAAY,GAAW,EAAK,UAAU,GAAG;QAC7C,CAAA,CACD,EAAA,CAAA;OACH;QACD;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8BAA8B,4BAA4B;MAC3D,CAAA,EACL,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAET,GAAiB,KAAK,MACrB,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,KAAK;OAClC,cAAY,EAAK;OACjB,WAAU;iBAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UACE,WAAW,sBAAsB,EAAK,QAAQ;oBAE9C,kBAAC,EAAK,MAAN,EAAW,WAAW,UAAU,EAAK,SAAW,CAAA;UAC5C,CAAA;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAK;WACH,CAAA,EACL,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAK;WACJ,CAAA,CACA;;SACN,kBAAC,GAAD,EAAkB,WAAU,yFAA0F,CAAA;SAClH;;OACC,EAtBF,EAAK,KAsBH,CACT;MACE,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8BAA8B,gBAAgB;MAC/C,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACZ,GAAqB,KAAK,MACzB,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,KAAK;OAClC,cAAY,EAAK;OACjB,WAAU;iBAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UACE,WAAW,sBAAsB,EAAK,QAAQ;oBAE9C,kBAAC,EAAK,MAAN,EAAW,WAAW,UAAU,EAAK,SAAW,CAAA;UAC5C,CAAA;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAK;WACH,CAAA,EACL,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAK;WACJ,CAAA,CACA;;SACN,kBAAC,GAAD,EAAkB,WAAU,yFAA0F,CAAA;SAClH;;OACC,EAtBF,EAAK,KAsBH,CACT;MACE,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,0BAA0B,WAAW;SACtC,CAAA;QACL,kBAAC,KAAD;SAAG,WAAU;mBACV,EACC,gCACA,mDACD;SACC,CAAA;QACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,6BAA6B,sBAAsB;SACrD,CAAA;QACA,EAAA,CAAA;OACF,CAAA,EACN,kBAAC,GAAD;OACE,MAAK;OACL,eAAe,EAAS,uBAAuB;OAC/C,SAAQ;OACR,WAAU;iBAET,EAAG,gCAAgC,kBAAkB;OAC/C,CAAA,CACL;SAGN,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ;OAIE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,4BAA4B,cAAc;QAC3C,CAAA;OACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,+BACA,2DACD;QACC,CAAA;OACJ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,KAAK;SACL,MAAK;SACL,eAAe,EAAuB,GAAK;SAC3C,WAAU;mBAET,EAAG,mCAAmC,qBAAqB;SACrD,CAAA,EACT,kBAAC,UAAD;SACE,KAAK;SACL,MAAK;SACL,eAAe,EAAmB,GAAK;SACvC,WAAU;mBAET,EAAG,+BAA+B,iBAAiB;SAC7C,CAAA,CACL;;OACF;QACF;;IAGL,KACC,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,UAAU,MAAM;AACd,MAAI,EAAE,WAAW,EAAE,iBAAe,GAAa;;eAGjD,kBAAC,OAAD;MACE,KAAK;MACL,MAAK;MACL,cAAW;MACX,mBAAiB;MACjB,UAAU;MACV,WAAU;gBANZ;OAQE,kBAAC,MAAD;QACE,IAAI;QACJ,WAAU;kBAET,EAAG,iCAAiC,qBAAqB;QACvD,CAAA;OACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,gCACA,8GACD;QACC,CAAA;OACH,KACC,kBAAC,OAAD;QACE,IAAI;QACJ,MAAK;QACL,WAAU;kBAET;QACG,CAAA;OAEP,KACC,kBAAC,UAAD;QAAQ,WAAU;kBACf;QACM,CAAA;OAEX,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAET,EACC,sCACA,iCACD;SACK,CAAA,EACR,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAmB,EAAE,OAAO,MAAM;SACnD,iBAAc;SACd,cAAY,EACV,sCACA,iCACD;SACD,oBACE,IAAe,IAAoB,KAAA;SAErC,gBAAc,CAAC,CAAC;SAChB,cAAa;SACb,WAAU;SACV,aAAa,EACX,8BACA,gBACD;SACD,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAET,EAAG,wBAAwB,SAAS;SAC9B,CAAA,EACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,KAAK,IAAyB;SAC7C,UAAU,CAAC,KAAmB;SAC9B,WAAU;mBAJZ,CAMG,KACC,kBAAC,GAAD,EAAa,WAAU,uBAAwB,CAAA,EAEhD,EAAG,+BAA+B,aAAa,CACzC;WACL;;OACF;;KACF,CAAA;IAIP,KACC,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,UAAU,MAAM;AACd,MAAI,EAAE,WAAW,EAAE,iBAAe,GAAa;;eAGjD,kBAAC,OAAD;MACE,KAAK;MACL,MAAK;MACL,cAAW;MACX,mBAAiB;MACjB,UAAU;MACV,WAAU;gBANZ;OAQE,kBAAC,MAAD;QACE,IAAI;QACJ,WAAU;kBAET,EAAG,6BAA6B,6BAA6B;QAC3D,CAAA;OACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,4BACA,kGACD;QACC,CAAA;OACH,KACC,kBAAC,OAAD;QACE,IAAI;QACJ,MAAK;QACL,WAAU;kBAET;QACG,CAAA;OAEP,KACC,kBAAC,UAAD;QAAQ,WAAU;kBACf;QACM,CAAA;OAEX,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAET,EACC,qCACA,sBACD;SACK,CAAA,EACR,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAmB,EAAE,OAAO,MAAM;SACnD,iBAAc;SACd,cAAY,EACV,qCACA,sBACD;SACD,oBAAkB,IAAe,IAAgB,KAAA;SACjD,gBAAc,CAAC,CAAC;SAChB,cAAa;SACb,WAAU;SACV,aAAa,EACX,8BACA,gBACD;SACD,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAFZ;UAIG,EAAG,sBAAsB,OAAO;UAAE;UACnC,kBAAC,QAAD;WAAM,WAAU;qBACb;WACI,CAAA;UAAC;UACP,EAAG,2BAA2B,aAAa;UACtC;YACR,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAuB,EAAE,OAAO,MAAM;SACvD,iBAAc;SACd,cAAY,EACV,wCACA,oCACD;SACD,oBAAkB,IAAe,IAAgB,KAAA;SACjD,gBACE,EAAoB,SAAS,KAC7B,MAAwB;SAE1B,cAAa;SACb,WAAU;SACV,aAAa,EACX,0CACA,oBACD;SACD,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAET,EAAG,wBAAwB,SAAS;SAC9B,CAAA,EACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,KAAK,IAAqB;SACzC,UACE,CAAC,KACD,MAAwB,KACxB;SAEF,WAAU;mBARZ,CAUG,KACC,kBAAC,GAAD,EAAa,WAAU,uBAAwB,CAAA,EAEhD,EAAG,+BAA+B,iBAAiB,CAC7C;WACL;;OACF;;KACF,CAAA;IAGR,kBAAC,GAAD;KACE,YAAW;KACX,OAAO;MACL,GAAI,GAAM,aACN,EAAE,GACF,CACE;OACE,IAAI;OACJ,OAAO,EACL,+BACA,oCACD;OACD,aAAa,EACX,mCACA,gEACD;OACD,eAAe,EAAS,YAAY;OACrC,CACF;MACL;OACE,IAAI;OACJ,OAAO,EACL,6BACA,8BACD;OACD,aAAa,EACX,iCACA,4DACD;OACD,eAAe,EAAS,gBAAgB;OACzC;MACD;OACE,IAAI;OACJ,OAAO,EAAG,8BAA8B,uBAAuB;OAC/D,aAAa,EACX,kCACA,yDACD;OACD,eAAe,EAAS,uBAAuB;OAChD;MACF;KACD,CAAA;IAGF,kBAAC,UAAD;KAAQ,WAAU;eAChB,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ,CAIE,kBAAC,KAAD,EAAA,UAAA;OACG,EAAG,4BAA4B,eAAe;OAAE;OACjD,kBAAC,QAAD;QAAM,WAAU;kBACb,GAAM;QACF,CAAA;OACL,EAAA,CAAA,EACJ,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAS,cAAc;SACtC,WAAU;mBAET,EAAG,+BAA+B,iBAAiB;SAC7C,CAAA;QACT,kBAAC,QAAD;SAAM,WAAU;SAAkB,eAAY;mBAAO;SAE9C,CAAA;QACP,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAoB,GAAK;SACxC,WAAU;mBAET,EAAG,yBAAyB,WAAW;SACjC,CAAA;QACL;SACF;;KACC,CAAA;IACJ;KACH"}
1
+ {"version":3,"file":"SecurityPage.js","names":[],"sources":["../../../src/auth/pages/SecurityPage.tsx"],"sourcesContent":["import { useId, useRef, useEffect, useState } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { EmphasisPanel, PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { useNavigate } from \"react-router\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useAccountOperations } from \"../hooks/useAccountOperations\";\nimport { formatDate } from \"../utils/formatters\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n// Icons\nconst ShieldIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n);\n\nconst LockIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n);\n\n// Hidden — moved to Developer Portal\n// const KeyIcon = ({ className }: { className?: string }) => (\n// <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n// <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5}\n// d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\" />\n// </svg>\n// );\n\n// const DelegationIcon = ({ className }: { className?: string }) => (\n// <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n// <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5}\n// d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n// </svg>\n// );\n\nconst UsersIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z\"\n />\n </svg>\n);\n\nconst UserProfileIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\"\n />\n </svg>\n);\n\nconst LinkIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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={1.5}\n d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\n />\n </svg>\n);\n\nconst ChevronRightIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\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=\"M9 5l7 7-7 7\"\n />\n </svg>\n);\n\nconst CheckCircleIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\nconst ExclamationIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n fill=\"currentColor\"\n viewBox=\"0 0 20 20\"\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\nconst SpinnerIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\ninterface NavItem {\n name: string;\n description: string;\n path: string;\n icon: React.FC<{ className?: string }>;\n color: string;\n bgColor: string;\n adminOnly?: boolean;\n}\n\nconst DELETE_ACCOUNT_CONFIRMATION = \"DELETE MY ACCOUNT\";\n\n/**\n * Security Dashboard - Main hub for all authentication features\n */\nexport function SecurityPage({\n // Admin-only nav cards (Users → `/auth/users`, SSO Configuration →\n // `/auth/sso`) point at platform-global super-admin surfaces whose routes\n // only exist when `enableAdminRoutes={true}` (admin app shell). In\n // user-facing product hosts those routes dead-end on `/auth/login`, so the\n // cards are hidden unless admin routes are enabled. Defaults to false.\n enableAdminRoutes = false,\n}: {\n enableAdminRoutes?: boolean;\n}) {\n const tr = useTr();\n\n const navigate = useNavigate();\n const user = useAuthStore((state) => state.user);\n const { signOut } = useAuthOperations({\n disableCurrentUserQuery: true,\n });\n const { deactivateAccount, deleteAccount, isDeactivating, isDeleting } =\n useAccountOperations();\n const [isLoggingOut, setIsLoggingOut] = useState(false);\n const [showLogoutDialog, setShowLogoutDialog] = useState(false);\n\n const navigationItems: NavItem[] = [\n {\n name: tr(\"auth.security.navProfile\", \"User Profile\"),\n description: tr(\n \"auth.security.navProfileDesc\",\n \"View and edit your profile information\",\n ),\n path: \"/auth/profile\",\n icon: UserProfileIcon,\n color: \"text-accent-teal\",\n bgColor: \"bg-accent-teal-subtle\",\n },\n {\n name: tr(\"auth.security.navMfa\", \"Two-Factor Authentication\"),\n description: tr(\n \"auth.security.navMfaDesc\",\n \"Secure your account with TOTP\",\n ),\n path: \"/auth/mfa\",\n icon: LockIcon,\n color: \"text-accent-purple\",\n bgColor: \"bg-accent-purple-subtle\",\n },\n // Hidden — moved to Developer Portal\n // {\n // name: tr(\"auth.security.navApiKeys\", \"API Keys\"),\n // description: tr(\"auth.security.navApiKeysDesc\", \"Manage programmatic access tokens\"),\n // path: \"/auth/api-keys\",\n // icon: KeyIcon,\n // color: \"text-status-warning-text\",\n // bgColor: \"bg-status-warning-bg-subtle\",\n // },\n // {\n // name: tr(\"auth.security.navDelegations\", \"Delegations\"),\n // description: tr(\"auth.security.navDelegationsDesc\", \"Manage apps acting on your behalf\"),\n // path: \"/auth/delegations\",\n // icon: DelegationIcon,\n // color: \"text-status-warning-text\",\n // bgColor: \"bg-status-warning-bg-subtle\",\n // },\n {\n name: tr(\"auth.security.navUsers\", \"Users\"),\n description: tr(\"auth.security.navUsersDesc\", \"Manage system users\"),\n path: \"/auth/users\",\n icon: UsersIcon,\n color: \"text-accent-indigo\",\n bgColor: \"bg-accent-indigo-subtle\",\n adminOnly: true,\n },\n ];\n\n const quickActions = [\n {\n name: tr(\"auth.security.navSso\", \"SSO Configuration\"),\n description: tr(\"auth.security.navSsoDesc\", \"Enterprise single sign-on\"),\n path: \"/auth/sso\",\n icon: LinkIcon,\n color: \"text-accent-cyan\",\n bgColor: \"bg-accent-cyan-subtle\",\n adminOnly: true,\n },\n ];\n\n // Account management modal states\n const [showDeactivateModal, setShowDeactivateModal] = useState(false);\n const [showDeleteModal, setShowDeleteModal] = useState(false);\n const [accountPassword, setAccountPassword] = useState(\"\");\n const [deleteConfirmPhrase, setDeleteConfirmPhrase] = useState(\"\");\n const [accountError, setAccountError] = useState<string | null>(null);\n const [accountSuccess, setAccountSuccess] = useState<string | null>(null);\n\n // Accessible IDs for modal labels and form fields\n const deactivateTitleId = useId();\n const deactivatePasswordId = useId();\n const deactivateErrorId = useId();\n const deleteTitleId = useId();\n const deletePasswordId = useId();\n const deleteConfirmId = useId();\n const deleteErrorId = useId();\n\n // Focus management: refs to restore focus and manage modal focus\n const deactivateTriggerRef = useRef<HTMLButtonElement>(null);\n const deleteTriggerRef = useRef<HTMLButtonElement>(null);\n const deactivateDialogRef = useRef<HTMLDivElement>(null);\n const deleteDialogRef = useRef<HTMLDivElement>(null);\n\n // Move focus into modal when it opens; restore to trigger on close\n useEffect(() => {\n if (showDeactivateModal) {\n deactivateDialogRef.current?.focus();\n } else {\n deactivateTriggerRef.current?.focus();\n }\n }, [showDeactivateModal]);\n\n useEffect(() => {\n if (showDeleteModal) {\n deleteDialogRef.current?.focus();\n } else {\n deleteTriggerRef.current?.focus();\n }\n }, [showDeleteModal]);\n\n const handleLogout = async () => {\n setIsLoggingOut(true);\n try {\n await signOut();\n navigate(\"/auth/login\");\n setShowLogoutDialog(false);\n } catch (error) {\n console.error(\"Logout failed:\", error);\n setIsLoggingOut(false);\n }\n };\n\n const handleDeactivateAccount = async () => {\n setAccountError(null);\n try {\n const result = await deactivateAccount(accountPassword);\n if (result.success) {\n // Immediately redirect to login page\n navigate(\"/auth/login\", { replace: true });\n } else {\n setAccountError(\n result.message ||\n tr(\n \"auth.security.deactivateFailed\",\n \"Failed to deactivate account\",\n ),\n );\n }\n } catch (error) {\n setAccountError(\n error instanceof Error\n ? error.message\n : tr(\n \"auth.security.deactivateFailed\",\n \"Failed to deactivate account\",\n ),\n );\n }\n };\n\n const handleDeleteAccount = async () => {\n setAccountError(null);\n if (deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION) {\n setAccountError(\n tr(\n \"auth.security.deleteConfirmMismatch\",\n 'Please type \"DELETE MY ACCOUNT\" to confirm',\n ),\n );\n return;\n }\n void nativeImpact(\"medium\");\n try {\n const result = await deleteAccount(accountPassword, deleteConfirmPhrase);\n if (result.success) {\n void nativeNotify(\"success\");\n // Immediately redirect to login page\n navigate(\"/auth/login\", { replace: true });\n } else {\n void nativeNotify(\"error\");\n setAccountError(\n result.message ||\n tr(\"auth.security.deleteFailed\", \"Failed to delete account\"),\n );\n }\n } catch (error) {\n void nativeNotify(\"error\");\n setAccountError(\n error instanceof Error\n ? error.message\n : tr(\"auth.security.deleteFailed\", \"Failed to delete account\"),\n );\n }\n };\n\n const closeModals = () => {\n setShowDeactivateModal(false);\n setShowDeleteModal(false);\n setAccountPassword(\"\");\n setDeleteConfirmPhrase(\"\");\n setAccountError(null);\n setAccountSuccess(null);\n };\n\n const isAdmin =\n user?.role === \"SUPER_ADMIN\" ||\n user?.role === \"TENANT_ADMIN\" ||\n user?.role === \"ORG_ADMIN\";\n const filteredNavItems = navigationItems.filter(\n (item) => !item.adminOnly || (enableAdminRoutes && isAdmin),\n );\n const filteredQuickActions = quickActions.filter(\n (item) => !item.adminOnly || (enableAdminRoutes && isAdmin),\n );\n\n return (\n <div className=\"bg-bg-canvas p-6\">\n <ConfirmActionDialog\n open={showLogoutDialog}\n onOpenChange={setShowLogoutDialog}\n title={tr(\"auth.security.signOutDialogTitle\", \"Sign out now?\")}\n description={tr(\n \"auth.security.signOutDialogDescription\",\n \"You will be signed out of this device and returned to the login screen.\",\n )}\n confirmText={tr(\"auth.security.signOutDialogConfirm\", \"Sign out\")}\n variant=\"warning\"\n isPending={isLoggingOut}\n onConfirm={handleLogout}\n />\n <main className=\"max-w-7xl mx-auto space-y-8\">\n <div\n className=\"flex items-center gap-3\"\n data-tour=\"auth-security-header\"\n >\n <div className=\"auth-brand-badge flex size-10 items-center justify-center rounded-xl\">\n <ShieldIcon className=\"size-6 text-white\" />\n </div>\n <div>\n <h1 className=\"text-lg font-bold text-text-primary\">\n {tr(\"auth.security.title\", \"Security Center\")}\n </h1>\n <p className=\"text-xs text-text-secondary \">\n {tr(\"auth.security.subtitle\", \"Account & Authentication\")}\n </p>\n </div>\n </div>\n\n <PagePurpose data-tour=\"auth-security-purpose\">\n {tr(\n \"auth.security.purpose\",\n \"Your one place to keep your account safe: turn on two-factor authentication, change your password, review who can act on your behalf, and manage or close your account.\",\n )}\n </PagePurpose>\n\n {/* Security Status Banner — the one emphasis zone on this page */}\n <EmphasisPanel className=\"p-6\" data-tour=\"auth-security-status-banner\">\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-14 rounded-2xl bg-accent-soft flex items-center justify-center\">\n <ShieldIcon className=\"size-8 text-[var(--color-action-primary-bg)]\" />\n </div>\n <div>\n <h2 className=\"text-xl font-bold text-text-primary\">\n {tr(\"auth.security.securityStatus\", \"Security Status\")}\n </h2>\n <p className=\"text-text-secondary\">\n {user?.mfaEnabled\n ? tr(\n \"auth.security.wellProtected\",\n \"Your account is well protected\",\n )\n : tr(\n \"auth.security.enableMfaSuggestion\",\n \"Enable MFA for better security\",\n )}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n {user?.mfaEnabled ? (\n <span className=\"inline-flex items-center gap-2 px-4 py-2 rounded-xl bg-status-success-bg-subtle text-status-success-text font-medium\">\n <CheckCircleIcon className=\"size-5 text-status-success-text\" />\n {tr(\"auth.security.mfaEnabled\", \"MFA Enabled\")}\n </span>\n ) : (\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa\")}\n variant=\"default\"\n className=\"font-semibold\"\n >\n <ExclamationIcon className=\"size-5\" />\n {tr(\"auth.security.enableMfaNow\", \"Enable MFA Now\")}\n </Button>\n )}\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Account Information Card */}\n <div\n className=\"mb-8 p-6 rounded-2xl bg-bg-surface border border-border-subtle shadow-sm\"\n data-tour=\"auth-account-info-card\"\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.accountInfo\", \"Account Information\")}\n </h3>\n <dl className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6\">\n <div className=\"sm:col-span-2 lg:col-span-1\">\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.email\", \"Email\")}\n </dt>\n <dd className=\"mt-1 text-text-primary\">\n <span className=\"block truncate\" title={user?.email}>\n {user?.email}\n </span>\n {user?.emailVerified && (\n <span className=\"mt-1 inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\">\n <CheckCircleIcon className=\"size-3\" />\n {tr(\"auth.security.verified\", \"Verified\")}\n </span>\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.username\", \"Username\")}\n </dt>\n <dd\n className=\"mt-1 text-text-primary truncate\"\n title={user?.username ?? undefined}\n >\n {user?.username || \"-\"}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.role\", \"Role\")}\n </dt>\n <dd className=\"mt-1\">\n <span className=\"inline-flex px-2.5 py-0.5 rounded-full bg-accent-indigo-subtle text-accent-indigo text-xs font-medium\">\n {user?.role || \"USER\"}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.security.memberSince\", \"Member Since\")}\n </dt>\n <dd className=\"mt-1 text-text-primary\">\n {user?.createdAt ? formatDate(user.createdAt) : \"-\"}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Main Navigation Grid */}\n <div className=\"mb-8\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.securityAuth\", \"Security & Authentication\")}\n </h3>\n <div\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"auth-security-nav-grid\"\n >\n {filteredNavItems.map((item) => (\n <button\n key={item.path}\n type=\"button\"\n onClick={() => navigate(item.path)}\n aria-label={item.name}\n className=\"group relative p-5 rounded-2xl bg-bg-surface border border-border-subtle hover:border-border-default hover:shadow-lg transition-all duration-200 text-left\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className={`size-12 rounded-xl ${item.bgColor} flex items-center justify-center flex-shrink-0 transition-transform`}\n >\n <item.icon className={`size-6 ${item.color}`} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-base font-semibold text-text-primary group-hover:text-accent-indigo transition-colors\">\n {item.name}\n </h4>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {item.description}\n </p>\n </div>\n <ChevronRightIcon className=\"size-5 text-text-tertiary group-hover:text-indigo-500 transition-colors flex-shrink-0\" />\n </div>\n </button>\n ))}\n </div>\n </div>\n\n {/* Quick Actions */}\n <div className=\"mb-8\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr(\"auth.security.quickActions\", \"Quick Actions\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n {filteredQuickActions.map((item) => (\n <button\n key={item.path}\n type=\"button\"\n onClick={() => navigate(item.path)}\n aria-label={item.name}\n className=\"group relative p-5 rounded-2xl bg-bg-surface border border-border-subtle hover:border-border-default hover:shadow-lg transition-all duration-200 text-left\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className={`size-12 rounded-xl ${item.bgColor} flex items-center justify-center flex-shrink-0 transition-transform`}\n >\n <item.icon className={`size-6 ${item.color}`} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-base font-semibold text-text-primary group-hover:text-accent-indigo transition-colors\">\n {item.name}\n </h4>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {item.description}\n </p>\n </div>\n <ChevronRightIcon className=\"size-5 text-text-tertiary group-hover:text-indigo-500 transition-colors flex-shrink-0\" />\n </div>\n </button>\n ))}\n </div>\n </div>\n\n {/* Password & Danger Zone */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Password Section */}\n <div\n className=\"p-6 rounded-2xl bg-bg-surface border border-border-subtle\"\n data-tour=\"auth-password-section\"\n >\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"auth.security.password\", \"Password\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary \">\n {tr(\n \"auth.security.updatePassword\",\n \"Update your password to keep your account secure\",\n )}\n </p>\n <p className=\"mt-2 text-xs text-text-tertiary \">\n {tr(\"auth.security.lastChanged\", \"Last changed: Never\")}\n </p>\n </div>\n </div>\n <Button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n variant=\"default\"\n className=\"mt-4 w-full font-semibold sm:w-auto\"\n >\n {tr(\"auth.security.changePassword\", \"Change Password\")}\n </Button>\n </div>\n\n {/* Danger Zone */}\n <div\n className=\"p-6 rounded-2xl bg-status-error-bg-subtle border border-status-error-border\"\n data-tour=\"auth-danger-zone\"\n >\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n {tr(\"auth.security.dangerZone\", \"Danger Zone\")}\n </h3>\n <p className=\"mt-1 text-sm text-status-error-text/80\">\n {tr(\n \"auth.security.dangerWarning\",\n \"These actions affect your account. Proceed with caution.\",\n )}\n </p>\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n ref={deactivateTriggerRef}\n type=\"button\"\n onClick={() => setShowDeactivateModal(true)}\n className=\"px-5 py-2.5 rounded-xl border-2 border-status-warning-border text-status-warning-text font-semibold hover:bg-status-warning-bg-subtle transition-all duration-200\"\n >\n {tr(\"auth.security.deactivateAccount\", \"Deactivate Account\")}\n </button>\n <button\n ref={deleteTriggerRef}\n type=\"button\"\n onClick={() => setShowDeleteModal(true)}\n className=\"px-5 py-2.5 rounded-xl border-2 border-status-error-border text-status-error-text font-semibold hover:bg-status-error-bg-subtle transition-all duration-200\"\n >\n {tr(\"auth.security.deleteAccount\", \"Delete Account\")}\n </button>\n </div>\n </div>\n </div>\n\n {/* Deactivate Account Modal */}\n {showDeactivateModal && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) closeModals();\n }}\n >\n <div\n ref={deactivateDialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={deactivateTitleId}\n tabIndex={-1}\n className=\"w-full max-w-md bg-bg-surface rounded-2xl shadow-2xl p-6 outline-none\"\n >\n <h3\n id={deactivateTitleId}\n className=\"text-xl font-bold text-text-primary mb-2\"\n >\n {tr(\"auth.security.deactivateTitle\", \"Deactivate Account\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.security.deactivateDesc\",\n \"Your account will be temporarily disabled. You can reactivate it by signing in again with your credentials.\",\n )}\n </p>\n {accountError && (\n <div\n id={deactivateErrorId}\n role=\"alert\"\n className=\"mb-4 p-3 rounded-lg bg-status-error-bg-subtle text-status-error-text text-sm\"\n >\n {accountError}\n </div>\n )}\n {accountSuccess && (\n <output className=\"block mb-4 p-3 rounded-lg bg-status-success-bg-subtle text-status-success-text text-sm\">\n {accountSuccess}\n </output>\n )}\n <div className=\"mb-4\">\n <label\n htmlFor={deactivatePasswordId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\n \"auth.security.enterPasswordConfirm\",\n \"Enter your password to confirm\",\n )}\n </label>\n <input\n id={deactivatePasswordId}\n type=\"password\"\n value={accountPassword}\n onChange={(e) => setAccountPassword(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.enterPasswordConfirm\",\n \"Enter your password to confirm\",\n )}\n aria-describedby={\n accountError ? deactivateErrorId : undefined\n }\n aria-invalid={!!accountError}\n autoComplete=\"current-password\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-warning-bg focus:border-transparent\"\n placeholder={tr(\n \"auth.security.yourPassword\",\n \"Your password\",\n )}\n />\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={closeModals}\n className=\"flex-1 px-4 py-2 rounded-lg border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.security.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleDeactivateAccount()}\n disabled={!accountPassword || isDeactivating}\n className=\"flex-1 px-4 py-2 rounded-lg bg-status-warning-bg text-white font-medium hover:bg-status-warning-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2\"\n >\n {isDeactivating && (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n )}\n {tr(\"auth.security.deactivateBtn\", \"Deactivate\")}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Delete Account Modal */}\n {showDeleteModal && (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4\"\n role=\"presentation\"\n onClick={(e) => {\n if (e.target === e.currentTarget) closeModals();\n }}\n >\n <div\n ref={deleteDialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={deleteTitleId}\n tabIndex={-1}\n className=\"w-full max-w-md bg-bg-surface rounded-2xl shadow-2xl p-6 outline-none\"\n >\n <h3\n id={deleteTitleId}\n className=\"text-xl font-bold text-status-error-text mb-2\"\n >\n {tr(\"auth.security.deleteTitle\", \"Delete Account Permanently\")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n \"auth.security.deleteDesc\",\n \"This action is irreversible. All your data will be permanently deleted and cannot be recovered.\",\n )}\n </p>\n {accountError && (\n <div\n id={deleteErrorId}\n role=\"alert\"\n className=\"mb-4 p-3 rounded-lg bg-status-error-bg-subtle text-status-error-text text-sm\"\n >\n {accountError}\n </div>\n )}\n {accountSuccess && (\n <output className=\"block mb-4 p-3 rounded-lg bg-status-success-bg-subtle text-status-success-text text-sm\">\n {accountSuccess}\n </output>\n )}\n <div className=\"mb-4\">\n <label\n htmlFor={deletePasswordId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\n \"auth.security.enterPasswordDelete\",\n \"Enter your password\",\n )}\n </label>\n <input\n id={deletePasswordId}\n type=\"password\"\n value={accountPassword}\n onChange={(e) => setAccountPassword(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.enterPasswordDelete\",\n \"Enter your password\",\n )}\n aria-describedby={accountError ? deleteErrorId : undefined}\n aria-invalid={!!accountError}\n autoComplete=\"current-password\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-error-bg focus:border-transparent\"\n placeholder={tr(\n \"auth.security.yourPassword\",\n \"Your password\",\n )}\n />\n </div>\n <div className=\"mb-4\">\n <label\n htmlFor={deleteConfirmId}\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr(\"auth.security.type\", \"Type\")}{\" \"}\n <code className=\"px-1 py-0.5 rounded bg-bg-sunken text-status-error-text font-mono text-sm\">\n {DELETE_ACCOUNT_CONFIRMATION}\n </code>{\" \"}\n {tr(\"auth.security.toConfirm\", \"to confirm\")}\n </label>\n <input\n id={deleteConfirmId}\n type=\"text\"\n value={deleteConfirmPhrase}\n onChange={(e) => setDeleteConfirmPhrase(e.target.value)}\n aria-required=\"true\"\n aria-label={tr(\n \"auth.security.deleteConfirmAriaLabel\",\n \"Type DELETE MY ACCOUNT to confirm\",\n )}\n aria-describedby={accountError ? deleteErrorId : undefined}\n aria-invalid={\n deleteConfirmPhrase.length > 0 &&\n deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION\n }\n autoComplete=\"off\"\n className=\"w-full px-4 py-2 rounded-lg border border-border-default bg-bg-surface text-text-primary focus:ring-2 focus:ring-status-error-bg focus:border-transparent font-mono\"\n placeholder={tr(\n \"auth.security.deleteConfirmPlaceholder\",\n \"DELETE MY ACCOUNT\",\n )}\n />\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={closeModals}\n className=\"flex-1 px-4 py-2 rounded-lg border border-border-default text-text-primary font-medium hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.security.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleDeleteAccount()}\n disabled={\n !accountPassword ||\n deleteConfirmPhrase !== DELETE_ACCOUNT_CONFIRMATION ||\n isDeleting\n }\n className=\"flex-1 px-4 py-2 rounded-lg bg-status-error-bg text-white font-medium hover:bg-status-error-bg-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2\"\n >\n {isDeleting && (\n <SpinnerIcon className=\"size-4 animate-spin\" />\n )}\n {tr(\"auth.security.deleteForever\", \"Delete Forever\")}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <NextSteps\n storageKey=\"auth-security\"\n steps={[\n ...(user?.mfaEnabled\n ? []\n : [\n {\n id: \"enable-mfa\",\n label: tr(\n \"auth.security.nextEnableMfa\",\n \"Turn on two-factor authentication\",\n ),\n description: tr(\n \"auth.security.nextEnableMfaDesc\",\n \"The single biggest step you can take to protect your account.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n },\n ]),\n {\n id: \"review-profile\",\n label: tr(\n \"auth.security.nextProfile\",\n \"Review your profile details\",\n ),\n description: tr(\n \"auth.security.nextProfileDesc\",\n \"Make sure your name, email, and contact info are current.\",\n ),\n onClick: () => navigate(\"/auth/profile\"),\n },\n {\n id: \"change-password\",\n label: tr(\"auth.security.nextPassword\", \"Update your password\"),\n description: tr(\n \"auth.security.nextPasswordDesc\",\n \"Set a strong, unique password if you haven't recently.\",\n ),\n onClick: () => navigate(\"/auth/reset-password\"),\n },\n ]}\n />\n\n {/* Footer */}\n <footer className=\"mt-12 pt-6 border-t border-border-subtle \">\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 text-sm text-text-secondary \"\n data-tour=\"auth-footer-actions\"\n >\n <p>\n {tr(\"auth.security.signedInAs\", \"Signed in as\")}{\" \"}\n <span className=\"font-medium text-text-primary \">\n {user?.email}\n </span>\n </p>\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"hover:text-text-primary transition-colors\"\n >\n {tr(\"auth.security.switchAccount\", \"Switch Account\")}\n </button>\n <span className=\"text-text-muted\" aria-hidden=\"true\">\n |\n </span>\n <button\n type=\"button\"\n onClick={() => setShowLogoutDialog(true)}\n className=\"hover:text-status-error-text transition-colors\"\n >\n {tr(\"auth.security.signOut\", \"Sign Out\")}\n </button>\n </div>\n </div>\n </footer>\n </main>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAM,KAAc,EAAE,mBACpB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAGD,KAAY,EAAE,mBAClB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAkBD,MAAa,EAAE,mBACnB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAGD,MAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAGD,MAAY,EAAE,mBAClB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAGD,KAAoB,EAAE,mBAC1B,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,QAAO;CACP,eAAY;WAEZ,kBAAC,QAAD;EACE,eAAc;EACd,gBAAe;EACf,aAAa;EACb,GAAE;CACH,CAAA;AACE,CAAA,GAGD,KAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,eAAY;WAEZ,kBAAC,QAAD;EACE,UAAS;EACT,GAAE;EACF,UAAS;CACV,CAAA;AACE,CAAA,GAGD,MAAmB,EAAE,mBACzB,kBAAC,OAAD;CACa;CACX,MAAK;CACL,SAAQ;CACR,eAAY;WAEZ,kBAAC,QAAD;EACE,UAAS;EACT,GAAE;EACF,UAAS;CACV,CAAA;AACE,CAAA,GAGD,KAAe,EAAE,mBACrB,kBAAC,OAAD;CAAgB;CAAW,MAAK;CAAO,SAAQ;CAAY,eAAY;WAAvE,CACE,kBAAC,UAAD;EACE,WAAU;EACV,IAAG;EACH,IAAG;EACH,GAAE;EACF,QAAO;EACP,aAAY;CACb,CAAA,GACD,kBAAC,QAAD;EACE,WAAU;EACV,MAAK;EACL,GAAE;CACH,CAAA,CACE;IAaD,IAA8B;AAKpC,SAAgB,EAAa,EAM3B,uBAAoB,MAGnB;CACD,IAAM,IAAK,EAAM,GAEX,IAAW,GAAY,GACvB,IAAO,GAAc,MAAU,EAAM,IAAI,GACzC,EAAE,eAAY,GAAkB,EACpC,yBAAyB,GAC3B,CAAC,GACK,EAAE,sBAAmB,kBAAe,mBAAgB,kBACxD,GAAqB,GACjB,CAAC,IAAc,KAAmB,EAAS,EAAK,GAChD,CAAC,IAAkB,KAAuB,EAAS,EAAK,GAExD,KAA6B;EACjC;GACE,MAAM,EAAG,4BAA4B,cAAc;GACnD,aAAa,EACX,gCACA,wCACF;GACA,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;EACX;EACA;GACE,MAAM,EAAG,wBAAwB,2BAA2B;GAC5D,aAAa,EACX,4BACA,+BACF;GACA,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;EACX;EAkBA;GACE,MAAM,EAAG,0BAA0B,OAAO;GAC1C,aAAa,EAAG,8BAA8B,qBAAqB;GACnE,MAAM;GACN,MAAM;GACN,OAAO;GACP,SAAS;GACT,WAAW;EACb;CACF,GAEM,KAAe,CACnB;EACE,MAAM,EAAG,wBAAwB,mBAAmB;EACpD,aAAa,EAAG,4BAA4B,2BAA2B;EACvE,MAAM;EACN,MAAM;EACN,OAAO;EACP,SAAS;EACT,WAAW;CACb,CACF,GAGM,CAAC,GAAqB,KAA0B,EAAS,EAAK,GAC9D,CAAC,GAAiB,KAAsB,EAAS,EAAK,GACtD,CAAC,GAAiB,KAAsB,EAAS,EAAE,GACnD,CAAC,GAAqB,KAA0B,EAAS,EAAE,GAC3D,CAAC,GAAc,KAAmB,EAAwB,IAAI,GAC9D,CAAC,GAAgB,MAAqB,EAAwB,IAAI,GAGlE,IAAoB,EAAM,GAC1B,IAAuB,EAAM,GAC7B,IAAoB,EAAM,GAC1B,IAAgB,EAAM,GACtB,IAAmB,EAAM,GACzB,IAAkB,EAAM,GACxB,IAAgB,EAAM,GAGtB,IAAuB,EAA0B,IAAI,GACrD,IAAmB,EAA0B,IAAI,GACjD,IAAsB,EAAuB,IAAI,GACjD,IAAkB,EAAuB,IAAI;CAWnD,AARA,QAAgB;EACd,AAAI,IACF,EAAoB,SAAS,MAAM,IAEnC,EAAqB,SAAS,MAAM;CAExC,GAAG,CAAC,CAAmB,CAAC,GAExB,QAAgB;EACd,AAAI,IACF,EAAgB,SAAS,MAAM,IAE/B,EAAiB,SAAS,MAAM;CAEpC,GAAG,CAAC,CAAe,CAAC;CAEpB,IAAM,KAAe,YAAY;EAC/B,EAAgB,EAAI;EACpB,IAAI;GAGF,AAFA,MAAM,EAAQ,GACd,EAAS,aAAa,GACtB,EAAoB,EAAK;EAC3B,SAAS,GAAO;GAEd,AADA,QAAQ,MAAM,kBAAkB,CAAK,GACrC,EAAgB,EAAK;EACvB;CACF,GAEM,KAA0B,YAAY;EAC1C,EAAgB,IAAI;EACpB,IAAI;GACF,IAAM,IAAS,MAAM,EAAkB,CAAe;GACtD,AAAI,EAAO,UAET,EAAS,eAAe,EAAE,SAAS,GAAK,CAAC,IAEzC,EACE,EAAO,WACL,EACE,kCACA,8BACF,CACJ;EAEJ,SAAS,GAAO;GACd,EACE,aAAiB,QACb,EAAM,UACN,EACE,kCACA,8BACF,CACN;EACF;CACF,GAEM,KAAsB,YAAY;EAEtC,IADA,EAAgB,IAAI,GAChB,MAAwB,GAA6B;GACvD,EACE,EACE,uCACA,8CACF,CACF;GACA;EACF;EACA,GAAkB,QAAQ;EAC1B,IAAI;GACF,IAAM,IAAS,MAAM,EAAc,GAAiB,CAAmB;GACvE,AAAI,EAAO,WACT,EAAkB,SAAS,GAE3B,EAAS,eAAe,EAAE,SAAS,GAAK,CAAC,MAEzC,EAAkB,OAAO,GACzB,EACE,EAAO,WACL,EAAG,8BAA8B,0BAA0B,CAC/D;EAEJ,SAAS,GAAO;GAEd,AADA,EAAkB,OAAO,GACzB,EACE,aAAiB,QACb,EAAM,UACN,EAAG,8BAA8B,0BAA0B,CACjE;EACF;CACF,GAEM,UAAoB;EAMxB,AALA,EAAuB,EAAK,GAC5B,EAAmB,EAAK,GACxB,EAAmB,EAAE,GACrB,EAAuB,EAAE,GACzB,EAAgB,IAAI,GACpB,GAAkB,IAAI;CACxB,GAEM,IACJ,GAAM,SAAS,iBACf,GAAM,SAAS,kBACf,GAAM,SAAS,aACX,KAAmB,GAAgB,QACtC,MAAS,CAAC,EAAK,aAAc,KAAqB,CACrD,GACM,KAAuB,GAAa,QACvC,MAAS,CAAC,EAAK,aAAc,KAAqB,CACrD;CAEA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,IAAD;GACE,MAAM;GACN,cAAc;GACd,OAAO,EAAG,oCAAoC,eAAe;GAC7D,aAAa,EACX,0CACA,yEACF;GACA,aAAa,EAAG,sCAAsC,UAAU;GAChE,SAAQ;GACR,WAAW;GACX,WAAW;EACZ,CAAA,GACD,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACE,kBAAC,OAAD;KACE,WAAU;KACV,aAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAY,WAAU,oBAAqB,CAAA;KACxC,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,uBAAuB,iBAAiB;KAC1C,CAAA,GACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,0BAA0B,0BAA0B;KACvD,CAAA,CACA,EAAA,CAAA,CACF;;IAEL,kBAAC,GAAD;KAAa,aAAU;eACpB,EACC,yBACA,yKACF;IACW,CAAA;IAGb,kBAAC,GAAD;KAAe,WAAU;KAAM,aAAU;eACvC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,+CAAgD,CAAA;OACnE,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,gCAAgC,iBAAiB;OACnD,CAAA,GACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAM,aACH,EACE,+BACA,gCACF,IACA,EACE,qCACA,gCACF;OACH,CAAA,CACA,EAAA,CAAA,CACF;UACL,kBAAC,OAAD;OAAK,WAAU;iBACZ,GAAM,aACL,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAiB,WAAU,kCAAmC,CAAA,GAC7D,EAAG,4BAA4B,aAAa,CACzC;YAEN,kBAAC,GAAD;QACE,MAAK;QACL,eAAe,EAAS,WAAW;QACnC,SAAQ;QACR,WAAU;kBAJZ,CAME,kBAAC,IAAD,EAAiB,WAAU,SAAU,CAAA,GACpC,EAAG,8BAA8B,gBAAgB,CAC5C;;MAEP,CAAA,CACF;;IACQ,CAAA;IAGf,kBAAC,OAAD;KACE,WAAU;KACV,aAAU;eAFZ,CAIE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,6BAA6B,qBAAqB;KACpD,CAAA,GACJ,kBAAC,MAAD;MAAI,WAAU;gBAAd;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,uBAAuB,OAAO;QAChC,CAAA,GACJ,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,QAAD;UAAM,WAAU;UAAiB,OAAO,GAAM;oBAC3C,GAAM;SACH,CAAA,GACL,GAAM,iBACL,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD,EAAiB,WAAU,SAAU,CAAA,GACpC,EAAG,0BAA0B,UAAU,CACpC;WAEN;UACD;;OACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,0BAA0B,UAAU;OACtC,CAAA,GACJ,kBAAC,MAAD;QACE,WAAU;QACV,OAAO,GAAM,YAAY,KAAA;kBAExB,GAAM,YAAY;OACjB,CAAA,CACD,EAAA,CAAA;OACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,sBAAsB,MAAM;OAC9B,CAAA,GACJ,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,QAAD;SAAM,WAAU;mBACb,GAAM,QAAQ;QACX,CAAA;OACJ,CAAA,CACD,EAAA,CAAA;OACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,6BAA6B,cAAc;OAC7C,CAAA,GACJ,kBAAC,MAAD;QAAI,WAAU;kBACX,GAAM,YAAY,GAAW,EAAK,SAAS,IAAI;OAC9C,CAAA,CACD,EAAA,CAAA;MACH;OACD;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8BAA8B,2BAA2B;KAC3D,CAAA,GACJ,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAET,GAAiB,KAAK,MACrB,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,IAAI;OACjC,cAAY,EAAK;OACjB,WAAU;iBAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UACE,WAAW,sBAAsB,EAAK,QAAQ;oBAE9C,kBAAC,EAAK,MAAN,EAAW,WAAW,UAAU,EAAK,QAAU,CAAA;SAC5C,CAAA;SACL,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAK;UACJ,CAAA,GACJ,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAK;UACL,CAAA,CACA;;SACL,kBAAC,GAAD,EAAkB,WAAU,wFAAyF,CAAA;QAClH;;MACC,GAtBD,EAAK,IAsBJ,CACT;KACE,CAAA,CACF;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8BAA8B,eAAe;KAC/C,CAAA,GACJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,GAAqB,KAAK,MACzB,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAS,EAAK,IAAI;OACjC,cAAY,EAAK;OACjB,WAAU;iBAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UACE,WAAW,sBAAsB,EAAK,QAAQ;oBAE9C,kBAAC,EAAK,MAAN,EAAW,WAAW,UAAU,EAAK,QAAU,CAAA;SAC5C,CAAA;SACL,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAK;UACJ,CAAA,GACJ,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAK;UACL,CAAA,CACA;;SACL,kBAAC,GAAD,EAAkB,WAAU,wFAAyF,CAAA;QAClH;;MACC,GAtBD,EAAK,IAsBJ,CACT;KACE,CAAA,CACF;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAG,0BAA0B,UAAU;QACtC,CAAA;QACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EACC,gCACA,kDACF;QACC,CAAA;QACH,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAG,6BAA6B,qBAAqB;QACrD,CAAA;OACA,EAAA,CAAA;MACF,CAAA,GACL,kBAAC,GAAD;OACE,MAAK;OACL,eAAe,EAAS,sBAAsB;OAC9C,SAAQ;OACR,WAAU;iBAET,EAAG,gCAAgC,iBAAiB;MAC/C,CAAA,CACL;SAGL,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ;OAIE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,4BAA4B,aAAa;OAC3C,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,+BACA,0DACF;OACC,CAAA;OACH,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,KAAK;SACL,MAAK;SACL,eAAe,EAAuB,EAAI;SAC1C,WAAU;mBAET,EAAG,mCAAmC,oBAAoB;QACrD,CAAA,GACR,kBAAC,UAAD;SACE,KAAK;SACL,MAAK;SACL,eAAe,EAAmB,EAAI;SACtC,WAAU;mBAET,EAAG,+BAA+B,gBAAgB;QAC7C,CAAA,CACL;;MACF;OACF;;IAGJ,KACC,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,UAAU,MAAM;MACd,AAAI,EAAE,WAAW,EAAE,iBAAe,EAAY;KAChD;eAEA,kBAAC,OAAD;MACE,KAAK;MACL,MAAK;MACL,cAAW;MACX,mBAAiB;MACjB,UAAU;MACV,WAAU;gBANZ;OAQE,kBAAC,MAAD;QACE,IAAI;QACJ,WAAU;kBAET,EAAG,iCAAiC,oBAAoB;OACvD,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,gCACA,6GACF;OACC,CAAA;OACF,KACC,kBAAC,OAAD;QACE,IAAI;QACJ,MAAK;QACL,WAAU;kBAET;OACE,CAAA;OAEN,KACC,kBAAC,UAAD;QAAQ,WAAU;kBACf;OACK,CAAA;OAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAET,EACC,sCACA,gCACF;QACK,CAAA,GACP,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAmB,EAAE,OAAO,KAAK;SAClD,iBAAc;SACd,cAAY,EACV,sCACA,gCACF;SACA,oBACE,IAAe,IAAoB,KAAA;SAErC,gBAAc,CAAC,CAAC;SAChB,cAAa;SACb,WAAU;SACV,aAAa,EACX,8BACA,eACF;QACD,CAAA,CACE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAET,EAAG,wBAAwB,QAAQ;QAC9B,CAAA,GACR,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,KAAK,GAAwB;SAC5C,UAAU,CAAC,KAAmB;SAC9B,WAAU;mBAJZ,CAMG,KACC,kBAAC,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAE/C,EAAG,+BAA+B,YAAY,CACzC;UACL;;MACF;;IACF,CAAA;IAIN,KACC,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,UAAU,MAAM;MACd,AAAI,EAAE,WAAW,EAAE,iBAAe,EAAY;KAChD;eAEA,kBAAC,OAAD;MACE,KAAK;MACL,MAAK;MACL,cAAW;MACX,mBAAiB;MACjB,UAAU;MACV,WAAU;gBANZ;OAQE,kBAAC,MAAD;QACE,IAAI;QACJ,WAAU;kBAET,EAAG,6BAA6B,4BAA4B;OAC3D,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,4BACA,iGACF;OACC,CAAA;OACF,KACC,kBAAC,OAAD;QACE,IAAI;QACJ,MAAK;QACL,WAAU;kBAET;OACE,CAAA;OAEN,KACC,kBAAC,UAAD;QAAQ,WAAU;kBACf;OACK,CAAA;OAEV,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAET,EACC,qCACA,qBACF;QACK,CAAA,GACP,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAmB,EAAE,OAAO,KAAK;SAClD,iBAAc;SACd,cAAY,EACV,qCACA,qBACF;SACA,oBAAkB,IAAe,IAAgB,KAAA;SACjD,gBAAc,CAAC,CAAC;SAChB,cAAa;SACb,WAAU;SACV,aAAa,EACX,8BACA,eACF;QACD,CAAA,CACE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,SAAS;SACT,WAAU;mBAFZ;UAIG,EAAG,sBAAsB,MAAM;UAAG;UACnC,kBAAC,QAAD;WAAM,WAAU;qBACb;UACG,CAAA;UAAE;UACP,EAAG,2BAA2B,YAAY;SACtC;YACP,kBAAC,SAAD;SACE,IAAI;SACJ,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAuB,EAAE,OAAO,KAAK;SACtD,iBAAc;SACd,cAAY,EACV,wCACA,mCACF;SACA,oBAAkB,IAAe,IAAgB,KAAA;SACjD,gBACE,EAAoB,SAAS,KAC7B,MAAwB;SAE1B,cAAa;SACb,WAAU;SACV,aAAa,EACX,0CACA,mBACF;QACD,CAAA,CACE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,WAAU;mBAET,EAAG,wBAAwB,QAAQ;QAC9B,CAAA,GACR,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,KAAK,GAAoB;SACxC,UACE,CAAC,KACD,MAAwB,KACxB;SAEF,WAAU;mBARZ,CAUG,KACC,kBAAC,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAE/C,EAAG,+BAA+B,gBAAgB,CAC7C;UACL;;MACF;;IACF,CAAA;IAGP,kBAAC,GAAD;KACE,YAAW;KACX,OAAO;MACL,GAAI,GAAM,aACN,CAAC,IACD,CACE;OACE,IAAI;OACJ,OAAO,EACL,+BACA,mCACF;OACA,aAAa,EACX,mCACA,+DACF;OACA,eAAe,EAAS,WAAW;MACrC,CACF;MACJ;OACE,IAAI;OACJ,OAAO,EACL,6BACA,6BACF;OACA,aAAa,EACX,iCACA,2DACF;OACA,eAAe,EAAS,eAAe;MACzC;MACA;OACE,IAAI;OACJ,OAAO,EAAG,8BAA8B,sBAAsB;OAC9D,aAAa,EACX,kCACA,wDACF;OACA,eAAe,EAAS,sBAAsB;MAChD;KACF;IACD,CAAA;IAGD,kBAAC,UAAD;KAAQ,WAAU;eAChB,kBAAC,OAAD;MACE,WAAU;MACV,aAAU;gBAFZ,CAIE,kBAAC,KAAD,EAAA,UAAA;OACG,EAAG,4BAA4B,cAAc;OAAG;OACjD,kBAAC,QAAD;QAAM,WAAU;kBACb,GAAM;OACH,CAAA;MACL,EAAA,CAAA,GACH,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAS,aAAa;SACrC,WAAU;mBAET,EAAG,+BAA+B,gBAAgB;QAC7C,CAAA;QACR,kBAAC,QAAD;SAAM,WAAU;SAAkB,eAAY;mBAAO;QAE/C,CAAA;QACN,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAoB,EAAI;SACvC,WAAU;mBAET,EAAG,yBAAyB,UAAU;QACjC,CAAA;OACL;QACF;;IACC,CAAA;GACJ;IACH;;AAET"}
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * URL: /auth/session-bridge
12
12
  */
13
- export declare function SessionBridgePage(): import("react").JSX.Element;
13
+ export declare function SessionBridgePage(): import('react').JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"SessionBridgePage.js","names":[],"sources":["../../../src/auth/pages/SessionBridgePage.tsx"],"sourcesContent":["/**\n * SessionBridgePage — Cross-Domain Auth Session Bridge (Profile-Scoped)\n *\n * Embedded as hidden iframe by other app shells. Manages profile-scoped\n * auth tokens in localStorage and relays state via postMessage.\n *\n * Storage uses ProfileStorageManager keys:\n * bf-profiles, bf-p-{id}-user, bf-p-{id}-tokens, bf-p-{id}-workspace-token\n * bf-active-profile (sessionStorage, per-tab)\n *\n * URL: /auth/session-bridge\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { isAllowedSsoOriginString } from \"../utils/ssoOrigins\";\n\nconst BRIDGE_SOURCE = \"burdenoff-auth-bridge\";\nconst PARENT_SOURCE = \"burdenoff-auth-client\";\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\n\ninterface BridgeMessage {\n type: string;\n payload?: Record<string, unknown>;\n nonce?: string;\n source?: string;\n}\n\ninterface ConnectedParent {\n source: Window;\n origin: string;\n nonce: string;\n}\n\nfunction isWindowMessageSource(\n source: MessageEventSource | null,\n): source is Window {\n return (\n source !== null &&\n typeof (source as { postMessage?: unknown }).postMessage === \"function\"\n );\n}\n\nfunction isBridgeMessage(value: unknown): value is BridgeMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\"\n );\n}\n\nfunction readAuthState(): Record<string, unknown> {\n try {\n const profiles = localStorage.getItem(BF_PROFILES);\n let activeId = sessionStorage.getItem(BF_ACTIVE);\n\n // Fall back: if sessionStorage is empty but localStorage has profiles,\n // pick the first profile (same logic as resolveActiveProfileId)\n if (!activeId && profiles) {\n try {\n const profileList = JSON.parse(profiles) as Array<{ id: string }>;\n if (profileList.length > 0) {\n activeId = profileList[0].id;\n sessionStorage.setItem(BF_ACTIVE, activeId);\n }\n } catch {\n // ignore\n }\n }\n\n if (!activeId) {\n return {\n user: null,\n tokens: null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: null,\n };\n }\n const user = localStorage.getItem(`${BF_PREFIX}-${activeId}-user`);\n const tokens = localStorage.getItem(`${BF_PREFIX}-${activeId}-tokens`);\n const wsToken = localStorage.getItem(\n `${BF_PREFIX}-${activeId}-workspace-token`,\n );\n\n return {\n user: user ? JSON.parse(user) : null,\n tokens: tokens\n ? { ...JSON.parse(tokens), workspaceToken: wsToken ?? undefined }\n : null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: activeId,\n };\n } catch {\n return { user: null, tokens: null, profiles: [], activeProfileId: null };\n }\n}\n\nfunction writeAuthState(payload: Record<string, unknown>): void {\n const profileId =\n (payload.activeProfileId as string) ??\n ((payload.user as Record<string, unknown>)?.id as string) ??\n sessionStorage.getItem(BF_ACTIVE);\n if (!profileId) return;\n\n if (payload.user) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-user`,\n JSON.stringify(payload.user),\n );\n }\n if (payload.tokens && typeof payload.tokens === \"object\") {\n const t = payload.tokens as Record<string, unknown>;\n const { workspaceToken, ...rest } = t;\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-tokens`,\n JSON.stringify(rest),\n );\n if (workspaceToken) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-workspace-token`,\n String(workspaceToken),\n );\n }\n }\n if (payload.profiles) {\n localStorage.setItem(BF_PROFILES, JSON.stringify(payload.profiles));\n }\n sessionStorage.setItem(BF_ACTIVE, profileId);\n}\n\nfunction clearAuthState(): void {\n const activeId = sessionStorage.getItem(BF_ACTIVE);\n if (activeId) {\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-auth-storage`);\n // Remove from profiles list\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles = raw ? JSON.parse(raw) : [];\n localStorage.setItem(\n BF_PROFILES,\n JSON.stringify(\n profiles.filter((p: { id: string }) => p.id !== activeId),\n ),\n );\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n }\n localStorage.removeItem(\"auth-storage\");\n}\n\nfunction clearAllAuthState(): void {\n // Get all profiles and remove their data\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles: Array<{ id: string }> = raw ? JSON.parse(raw) : [];\n for (const p of profiles) {\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-auth-storage`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-context`);\n }\n } catch {\n /* ignore */\n }\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(`${BF_PREFIX}-`) && key.endsWith(\"-auth-storage\")) {\n localStorage.removeItem(key);\n }\n }\n localStorage.removeItem(BF_PROFILES);\n localStorage.removeItem(\"auth-storage\");\n sessionStorage.removeItem(BF_ACTIVE);\n}\n\n// SSO cookie helpers (shared domain cookie for local dev fast-path)\nconst SSO_COOKIE_NAME = \"bf-sso-profile\";\nconst SHELL_SSO_COOKIE_NAME = \"bf-sso\";\nfunction setSSoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SSO_COOKIE_NAME}=${encodeURIComponent(cookieValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction setShellSsoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction clearSSoCookieBridge(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n }\n } catch {\n /* ignore */\n }\n}\n\nexport function SessionBridgePage() {\n const connectedParents = useRef<Map<Window, ConnectedParent>>(new Map());\n const readySentTo = useRef<Map<Window, string>>(new Map());\n const storageAccessGranted = useRef(false);\n\n // Request unpartitioned storage access (Storage Access API).\n // This is critical for cross-domain SSO in modern browsers that\n // partition third-party iframe localStorage.\n useEffect(() => {\n async function requestAccess() {\n try {\n // Check if Storage Access API is available\n if (!document.requestStorageAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Check if we already have access\n const hasAccess = await document.hasStorageAccess();\n if (hasAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Request unpartitioned storage access\n await document.requestStorageAccess();\n storageAccessGranted.current = true;\n } catch {\n // Storage Access API not available or denied — fall through\n // to cookie-based fallback. localStorage will be partitioned.\n storageAccessGranted.current = false;\n }\n }\n void requestAccess();\n }, []);\n\n useEffect(() => {\n function isOriginAllowed(origin: string): boolean {\n return isAllowedSsoOriginString(origin);\n }\n\n function broadcastToAll(\n message: BridgeMessage,\n excludeSource?: Window | null,\n ): void {\n connectedParents.current.forEach((connection, parent) => {\n if (parent !== excludeSource) {\n connection.source.postMessage(\n { ...message, source: BRIDGE_SOURCE, nonce: connection.nonce },\n connection.origin,\n );\n }\n });\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!isOriginAllowed(event.origin)) return;\n if (!isBridgeMessage(event.data) || event.data.source !== PARENT_SOURCE) {\n return;\n }\n const message = event.data;\n const parentSource = isWindowMessageSource(event.source)\n ? event.source\n : null;\n if (!parentSource) return;\n\n const incomingNonce =\n typeof message.nonce === \"string\" && message.nonce.length > 0\n ? message.nonce\n : null;\n const existingConnection = connectedParents.current.get(parentSource);\n\n if (message.type !== \"AUTH_INIT\") {\n if (!incomingNonce || existingConnection?.nonce !== incomingNonce) {\n return;\n }\n } else if (!incomingNonce) {\n return;\n }\n\n switch (message.type) {\n case \"AUTH_INIT\": {\n connectedParents.current.set(parentSource, {\n source: parentSource,\n origin: event.origin,\n nonce: incomingNonce,\n });\n\n // If the parent sends its current auth state in AUTH_INIT,\n // store it so other cross-domain shells can pick it up.\n if (message.payload && typeof message.payload === \"object\") {\n const initPayload = message.payload as Record<string, unknown>;\n if (initPayload.user && initPayload.tokens) {\n writeAuthState(initPayload);\n }\n }\n\n const state = readAuthState();\n const connection = connectedParents.current.get(parentSource);\n if (connection) {\n connection.source.postMessage(\n {\n type: \"AUTH_STATE\",\n payload: state,\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n if (readySentTo.current.get(parentSource) !== connection.nonce) {\n readySentTo.current.set(parentSource, connection.nonce);\n connection.source.postMessage(\n {\n type: \"AUTH_BRIDGE_READY\",\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n }\n }\n break;\n }\n case \"AUTH_LOGIN\": {\n if (message.payload) {\n writeAuthState(message.payload);\n // Set SSO cookie for cross-domain fast-path detection\n const loginPayload = message.payload as Record<string, unknown>;\n const loginProfileId =\n (loginPayload.activeProfileId as string) ??\n ((loginPayload.user as Record<string, unknown>)?.id as string);\n if (loginProfileId) {\n clearSSoCookieBridge();\n setSSoCookieBridge(loginProfileId, event.origin);\n setShellSsoCookieBridge(loginProfileId, event.origin);\n }\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"login\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_LOGOUT\": {\n clearAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_LOGOUT_ALL\": {\n clearAllAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout_all\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_TOKEN_REFRESH\": {\n if (message.payload?.tokens) {\n writeAuthState({ tokens: message.payload.tokens });\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"refresh\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_ACCOUNT_SWITCH\": {\n if (message.payload) {\n writeAuthState(message.payload);\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"account_switch\" },\n },\n parentSource,\n );\n }\n break;\n }\n }\n }\n\n function handleStorageChange(event: StorageEvent): void {\n if (event.key?.startsWith(BF_PREFIX) || event.key === BF_PROFILES) {\n broadcastToAll({\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"storage_sync\" },\n });\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n window.addEventListener(\"storage\", handleStorageChange);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n window.removeEventListener(\"storage\", handleStorageChange);\n // Don't clear connectedParents or readySentTo on cleanup to maintain state across StrictMode remounts\n };\n }, []);\n\n return (\n <div\n id=\"auth-session-bridge-root\"\n data-bridge=\"active\"\n style={{ display: \"none\" }}\n />\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionBridgePage.js","names":[],"sources":["../../../src/auth/pages/SessionBridgePage.tsx"],"sourcesContent":["/**\n * SessionBridgePage — Cross-Domain Auth Session Bridge (Profile-Scoped)\n *\n * Embedded as hidden iframe by other app shells. Manages profile-scoped\n * auth tokens in localStorage and relays state via postMessage.\n *\n * Storage uses ProfileStorageManager keys:\n * bf-profiles, bf-p-{id}-user, bf-p-{id}-tokens, bf-p-{id}-workspace-token\n * bf-active-profile (sessionStorage, per-tab)\n *\n * URL: /auth/session-bridge\n */\n\nimport { useEffect, useRef } from \"react\";\nimport { isAllowedSsoOriginString } from \"../utils/ssoOrigins\";\n\nconst BRIDGE_SOURCE = \"burdenoff-auth-bridge\";\nconst PARENT_SOURCE = \"burdenoff-auth-client\";\nconst BF_PREFIX = \"bf-p\";\nconst BF_PROFILES = \"bf-profiles\";\nconst BF_ACTIVE = \"bf-active-profile\";\n\ninterface BridgeMessage {\n type: string;\n payload?: Record<string, unknown>;\n nonce?: string;\n source?: string;\n}\n\ninterface ConnectedParent {\n source: Window;\n origin: string;\n nonce: string;\n}\n\nfunction isWindowMessageSource(\n source: MessageEventSource | null,\n): source is Window {\n return (\n source !== null &&\n typeof (source as { postMessage?: unknown }).postMessage === \"function\"\n );\n}\n\nfunction isBridgeMessage(value: unknown): value is BridgeMessage {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\"\n );\n}\n\nfunction readAuthState(): Record<string, unknown> {\n try {\n const profiles = localStorage.getItem(BF_PROFILES);\n let activeId = sessionStorage.getItem(BF_ACTIVE);\n\n // Fall back: if sessionStorage is empty but localStorage has profiles,\n // pick the first profile (same logic as resolveActiveProfileId)\n if (!activeId && profiles) {\n try {\n const profileList = JSON.parse(profiles) as Array<{ id: string }>;\n if (profileList.length > 0) {\n activeId = profileList[0].id;\n sessionStorage.setItem(BF_ACTIVE, activeId);\n }\n } catch {\n // ignore\n }\n }\n\n if (!activeId) {\n return {\n user: null,\n tokens: null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: null,\n };\n }\n const user = localStorage.getItem(`${BF_PREFIX}-${activeId}-user`);\n const tokens = localStorage.getItem(`${BF_PREFIX}-${activeId}-tokens`);\n const wsToken = localStorage.getItem(\n `${BF_PREFIX}-${activeId}-workspace-token`,\n );\n\n return {\n user: user ? JSON.parse(user) : null,\n tokens: tokens\n ? { ...JSON.parse(tokens), workspaceToken: wsToken ?? undefined }\n : null,\n profiles: profiles ? JSON.parse(profiles) : [],\n activeProfileId: activeId,\n };\n } catch {\n return { user: null, tokens: null, profiles: [], activeProfileId: null };\n }\n}\n\nfunction writeAuthState(payload: Record<string, unknown>): void {\n const profileId =\n (payload.activeProfileId as string) ??\n ((payload.user as Record<string, unknown>)?.id as string) ??\n sessionStorage.getItem(BF_ACTIVE);\n if (!profileId) return;\n\n if (payload.user) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-user`,\n JSON.stringify(payload.user),\n );\n }\n if (payload.tokens && typeof payload.tokens === \"object\") {\n const t = payload.tokens as Record<string, unknown>;\n const { workspaceToken, ...rest } = t;\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-tokens`,\n JSON.stringify(rest),\n );\n if (workspaceToken) {\n localStorage.setItem(\n `${BF_PREFIX}-${profileId}-workspace-token`,\n String(workspaceToken),\n );\n }\n }\n if (payload.profiles) {\n localStorage.setItem(BF_PROFILES, JSON.stringify(payload.profiles));\n }\n sessionStorage.setItem(BF_ACTIVE, profileId);\n}\n\nfunction clearAuthState(): void {\n const activeId = sessionStorage.getItem(BF_ACTIVE);\n if (activeId) {\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${activeId}-auth-storage`);\n // Remove from profiles list\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles = raw ? JSON.parse(raw) : [];\n localStorage.setItem(\n BF_PROFILES,\n JSON.stringify(\n profiles.filter((p: { id: string }) => p.id !== activeId),\n ),\n );\n } catch {\n /* ignore */\n }\n sessionStorage.removeItem(BF_ACTIVE);\n }\n localStorage.removeItem(\"auth-storage\");\n}\n\nfunction clearAllAuthState(): void {\n // Get all profiles and remove their data\n try {\n const raw = localStorage.getItem(BF_PROFILES);\n const profiles: Array<{ id: string }> = raw ? JSON.parse(raw) : [];\n for (const p of profiles) {\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-user`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-tokens`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-workspace-token`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-auth-storage`);\n localStorage.removeItem(`${BF_PREFIX}-${p.id}-context`);\n }\n } catch {\n /* ignore */\n }\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(`${BF_PREFIX}-`) && key.endsWith(\"-auth-storage\")) {\n localStorage.removeItem(key);\n }\n }\n localStorage.removeItem(BF_PROFILES);\n localStorage.removeItem(\"auth-storage\");\n sessionStorage.removeItem(BF_ACTIVE);\n}\n\n// SSO cookie helpers (shared domain cookie for local dev fast-path)\nconst SSO_COOKIE_NAME = \"bf-sso-profile\";\nconst SHELL_SSO_COOKIE_NAME = \"bf-sso\";\nfunction setSSoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const cookieValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SSO_COOKIE_NAME}=${encodeURIComponent(cookieValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction setShellSsoCookieBridge(profileId: string, origin: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({ id: profileId, origin });\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n /* ignore */\n }\n}\n\nfunction clearSSoCookieBridge(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && ![\"localhost\", \"127.0.0.1\"].includes(hostname)) {\n document.cookie = `${SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n document.cookie = `${SHELL_SSO_COOKIE_NAME}=; path=/; domain=${hostname}; max-age=0`;\n }\n } catch {\n /* ignore */\n }\n}\n\nexport function SessionBridgePage() {\n const connectedParents = useRef<Map<Window, ConnectedParent>>(new Map());\n const readySentTo = useRef<Map<Window, string>>(new Map());\n const storageAccessGranted = useRef(false);\n\n // Request unpartitioned storage access (Storage Access API).\n // This is critical for cross-domain SSO in modern browsers that\n // partition third-party iframe localStorage.\n useEffect(() => {\n async function requestAccess() {\n try {\n // Check if Storage Access API is available\n if (!document.requestStorageAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Check if we already have access\n const hasAccess = await document.hasStorageAccess();\n if (hasAccess) {\n storageAccessGranted.current = true;\n return;\n }\n // Request unpartitioned storage access\n await document.requestStorageAccess();\n storageAccessGranted.current = true;\n } catch {\n // Storage Access API not available or denied — fall through\n // to cookie-based fallback. localStorage will be partitioned.\n storageAccessGranted.current = false;\n }\n }\n void requestAccess();\n }, []);\n\n useEffect(() => {\n function isOriginAllowed(origin: string): boolean {\n return isAllowedSsoOriginString(origin);\n }\n\n function broadcastToAll(\n message: BridgeMessage,\n excludeSource?: Window | null,\n ): void {\n connectedParents.current.forEach((connection, parent) => {\n if (parent !== excludeSource) {\n connection.source.postMessage(\n { ...message, source: BRIDGE_SOURCE, nonce: connection.nonce },\n connection.origin,\n );\n }\n });\n }\n\n function handleMessage(event: MessageEvent): void {\n if (!isOriginAllowed(event.origin)) return;\n if (!isBridgeMessage(event.data) || event.data.source !== PARENT_SOURCE) {\n return;\n }\n const message = event.data;\n const parentSource = isWindowMessageSource(event.source)\n ? event.source\n : null;\n if (!parentSource) return;\n\n const incomingNonce =\n typeof message.nonce === \"string\" && message.nonce.length > 0\n ? message.nonce\n : null;\n const existingConnection = connectedParents.current.get(parentSource);\n\n if (message.type !== \"AUTH_INIT\") {\n if (!incomingNonce || existingConnection?.nonce !== incomingNonce) {\n return;\n }\n } else if (!incomingNonce) {\n return;\n }\n\n switch (message.type) {\n case \"AUTH_INIT\": {\n connectedParents.current.set(parentSource, {\n source: parentSource,\n origin: event.origin,\n nonce: incomingNonce,\n });\n\n // If the parent sends its current auth state in AUTH_INIT,\n // store it so other cross-domain shells can pick it up.\n if (message.payload && typeof message.payload === \"object\") {\n const initPayload = message.payload as Record<string, unknown>;\n if (initPayload.user && initPayload.tokens) {\n writeAuthState(initPayload);\n }\n }\n\n const state = readAuthState();\n const connection = connectedParents.current.get(parentSource);\n if (connection) {\n connection.source.postMessage(\n {\n type: \"AUTH_STATE\",\n payload: state,\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n if (readySentTo.current.get(parentSource) !== connection.nonce) {\n readySentTo.current.set(parentSource, connection.nonce);\n connection.source.postMessage(\n {\n type: \"AUTH_BRIDGE_READY\",\n source: BRIDGE_SOURCE,\n nonce: connection.nonce,\n },\n event.origin,\n );\n }\n }\n break;\n }\n case \"AUTH_LOGIN\": {\n if (message.payload) {\n writeAuthState(message.payload);\n // Set SSO cookie for cross-domain fast-path detection\n const loginPayload = message.payload as Record<string, unknown>;\n const loginProfileId =\n (loginPayload.activeProfileId as string) ??\n ((loginPayload.user as Record<string, unknown>)?.id as string);\n if (loginProfileId) {\n clearSSoCookieBridge();\n setSSoCookieBridge(loginProfileId, event.origin);\n setShellSsoCookieBridge(loginProfileId, event.origin);\n }\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"login\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_LOGOUT\": {\n clearAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_LOGOUT_ALL\": {\n clearAllAuthState();\n clearSSoCookieBridge();\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"logout_all\" },\n },\n parentSource,\n );\n break;\n }\n case \"AUTH_TOKEN_REFRESH\": {\n if (message.payload?.tokens) {\n writeAuthState({ tokens: message.payload.tokens });\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"refresh\" },\n },\n parentSource,\n );\n }\n break;\n }\n case \"AUTH_ACCOUNT_SWITCH\": {\n if (message.payload) {\n writeAuthState(message.payload);\n broadcastToAll(\n {\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"account_switch\" },\n },\n parentSource,\n );\n }\n break;\n }\n }\n }\n\n function handleStorageChange(event: StorageEvent): void {\n if (event.key?.startsWith(BF_PREFIX) || event.key === BF_PROFILES) {\n broadcastToAll({\n type: \"AUTH_STATE_CHANGED\",\n payload: { ...readAuthState(), trigger: \"storage_sync\" },\n });\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n window.addEventListener(\"storage\", handleStorageChange);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n window.removeEventListener(\"storage\", handleStorageChange);\n // Don't clear connectedParents or readySentTo on cleanup to maintain state across StrictMode remounts\n };\n }, []);\n\n return (\n <div\n id=\"auth-session-bridge-root\"\n data-bridge=\"active\"\n style={{ display: \"none\" }}\n />\n );\n}\n"],"mappings":"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"}
@@ -4,4 +4,4 @@
4
4
  * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)
5
5
  * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)
6
6
  */
7
- export declare function SessionsPage(): import("react").JSX.Element;
7
+ export declare function SessionsPage(): import('react').JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../src/auth/pages/SessionsPage.tsx"],"sourcesContent":["import { useState, useMemo } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useSessionOperations } from \"../hooks/useSessionOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n formatRelativeTime,\n parseDeviceName,\n parseBrowserName,\n} from \"../utils/formatters\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\n/**\n * Active sessions management page with professional UI\n *\n * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)\n * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)\n */\nexport function SessionsPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const {\n activeSessions,\n revokeSession,\n revokeAllSessions,\n isLoading,\n page,\n totalPages,\n totalCount,\n hasMore,\n goToNextPage,\n goToPrevPage,\n } = useSessionOperations({ disableTokenInfoQuery: true, pageSize: 20 });\n\n // One entry per device: keep the most-recently-active session for each device name\n const dedupedSessions = useMemo(() => {\n const byDevice = new Map<string, (typeof activeSessions)[number]>();\n for (const s of activeSessions) {\n const device = parseDeviceName(s.userAgent ?? \"\");\n const existing = byDevice.get(device);\n if (\n !existing ||\n new Date(s.lastActivityAt) > new Date(existing.lastActivityAt)\n ) {\n byDevice.set(device, s);\n }\n }\n return [...byDevice.values()];\n }, [activeSessions]);\n\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingSessionId, setPendingSessionId] = useState<string | null>(null);\n const [showRevokeAllDialog, setShowRevokeAllDialog] = useState(false);\n\n const handleRevokeSession = async (sessionId: string) => {\n try {\n await revokeSession(sessionId);\n setSuccessMessage(\n tr(\"auth.sessions.revokeSuccess\", \"Session revoked successfully\"),\n );\n setPendingSessionId(null);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sessions.revokeFailed\", \"Failed to revoke session\"),\n );\n }\n };\n\n const handleRevokeAll = async () => {\n try {\n await revokeAllSessions();\n setSuccessMessage(\n tr(\n \"auth.sessions.revokeAllSuccess\",\n \"All other sessions have been revoked\",\n ),\n );\n setShowRevokeAllDialog(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.sessions.revokeAllFailed\",\n \"Failed to revoke all sessions\",\n ),\n );\n }\n };\n\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.sessions.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.sessions.signInToView\",\n \"Please sign in to view your active sessions\",\n )}\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingSessionId !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingSessionId(null);\n }\n }}\n title={tr(\"auth.sessions.revokeSessionTitle\", \"Revoke this session?\")}\n description={tr(\n \"auth.sessions.revokeSessionDesc\",\n \"The selected device will be signed out immediately and will need to authenticate again.\",\n )}\n confirmText={tr(\"auth.sessions.revokeSessionConfirm\", \"Revoke session\")}\n variant=\"danger\"\n isPending={isLoading}\n onConfirm={async () => {\n if (pendingSessionId) {\n await handleRevokeSession(pendingSessionId);\n }\n }}\n />\n <ConfirmActionDialog\n open={showRevokeAllDialog}\n onOpenChange={setShowRevokeAllDialog}\n title={tr(\n \"auth.sessions.revokeAllTitle\",\n \"Revoke every other session?\",\n )}\n description={tr(\n \"auth.sessions.revokeAllDesc\",\n \"All devices except your current one will be signed out immediately.\",\n )}\n confirmText={tr(\n \"auth.sessions.revokeAllConfirm\",\n \"Revoke all other sessions\",\n )}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleRevokeAll}\n />\n\n {/* Live regions for screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-4\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.sessions.title\", \"Active Sessions\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.sessions.sessionCount\", \"You have\")} {totalCount}{\" \"}\n {tr(\n totalCount !== 1\n ? \"auth.sessions.sessionsPlural\"\n : \"auth.sessions.sessionsSingular\",\n totalCount !== 1 ? \"active sessions\" : \"active session\",\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n {/* Export button — commented out until wspace-export-svc is wired for auth_sessions\n <button\n type=\"button\"\n onClick={handleExport}\n disabled={isExporting}\n aria-label={tr(\n \"auth.sessions.exportAriaLabel\",\n \"Export sessions data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"size-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.sessions.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.sessions.export\", \"Export\")}\n </>\n )}\n </button>\n */}\n </div>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.sessions.purpose\",\n \"This is every device currently signed in to your account. Recognise them all? If a device looks unfamiliar or you've lost it, revoke its session to sign it out immediately.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: account-access summary — the one highlighted zone */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <p className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.sessions.summaryLabel\", \"Devices signed in\")}\n </p>\n <p className=\"mt-1 text-3xl font-semibold text-text-primary\">\n {totalCount}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.sessions.summaryHint\",\n \"Keep this list trimmed to devices you trust.\",\n )}\n </p>\n </div>\n {totalCount > 1 && (\n <button\n type=\"button\"\n onClick={() => setShowRevokeAllDialog(true)}\n className=\"shrink-0 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 duration-200\"\n >\n {tr(\"auth.sessions.revokeAllOthers\", \"Revoke all other sessions\")}\n </button>\n )}\n </div>\n </EmphasisPanel>\n\n {/* Success Message */}\n {successMessage && (\n <output className=\"block 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 transition-colors duration-200\"\n >\n {tr(\"auth.sessions.dismiss\", \"Dismiss\")}\n </button>\n </output>\n )}\n\n {/* Error Message */}\n {error && (\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 <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.sessions.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"size-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sessions.loading\", \"Loading sessions...\")}\n </span>\n </div>\n ) : dedupedSessions.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-sessions\"\n title={tr(\"auth.sessions.noSessions\", \"No active sessions found\")}\n description={tr(\n \"auth.sessions.noSessionsDesc\",\n \"When you sign in on a device it will appear here so you can review and manage it.\",\n )}\n />\n </output>\n ) : (\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={isLoading}>\n {dedupedSessions.map((session) => (\n <div\n key={session.id}\n className=\"p-6 rounded-xl border border-border-default bg-bg-surface shadow-sm hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-start gap-4\">\n {/* Device Icon */}\n <div\n className=\"size-12 rounded-xl bg-bg-accent flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <svg\n className=\"size-6 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-1\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {session.isCurrent &&\n parseDeviceName(session.userAgent) === \"Unknown Device\"\n ? tr(\"auth.sessions.thisDevice\", \"This Device\")\n : parseDeviceName(session.userAgent)}\n </h3>\n {session.isCurrent && (\n <output className=\"px-2.5 py-1 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\">\n {tr(\n \"auth.sessions.currentSession\",\n \"Current Session\",\n )}\n </output>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mb-2\">\n {parseBrowserName(session.userAgent) ===\n \"Unknown Browser\" && session.isCurrent\n ? tr(\"auth.sessions.currentBrowser\", \"Current Browser\")\n : parseBrowserName(session.userAgent)}\n </p>\n <dl className=\"flex flex-wrap gap-4 text-sm text-text-secondary\">\n {session.ipAddress && session.ipAddress !== \"unknown\" && (\n <div className=\"flex items-center gap-1.5\">\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=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.ipAddress\", \"IP address\")}\n </dt>\n <dd>{session.ipAddress}</dd>\n </div>\n )}\n {session.location && (\n <div className=\"flex items-center gap-1.5\">\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=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.location\", \"Location\")}\n </dt>\n <dd>{session.location}</dd>\n </div>\n )}\n <div className=\"flex items-center gap-1.5\">\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=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.lastActive\", \"Last active\")}\n </dt>\n <dd>\n {tr(\"auth.sessions.lastActive\", \"Last active\")}{\" \"}\n {formatRelativeTime(session.lastActivityAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n\n {!session.isCurrent && (\n <button\n type=\"button\"\n onClick={() => setPendingSessionId(session.id)}\n aria-label={`${tr(\"auth.sessions.revoke\", \"Revoke\")} ${parseDeviceName(session.userAgent)} session`}\n className=\"ml-4 px-4 py-2 rounded-lg text-sm font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Pagination controls */}\n {!isLoading && totalPages > 1 && (\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={goToPrevPage}\n disabled={page === 0}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.prevPage\", \"Previous page\")}\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=\"M15 19l-7-7 7-7\"\n />\n </svg>\n {tr(\"auth.sessions.prev\", \"Previous\")}\n </button>\n\n <span className=\"text-sm text-text-secondary\">\n {tr(\"auth.sessions.pageOf\", \"Page\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{page + 1}</span>{\" \"}\n {tr(\"auth.sessions.of\", \"of\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{totalPages}</span>\n </span>\n\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!hasMore}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.nextPage\", \"Next page\")}\n >\n {tr(\"auth.sessions.next\", \"Next\")}\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=\"M9 5l7 7-7 7\"\n />\n </svg>\n </button>\n </div>\n )}\n\n {!isLoading && dedupedSessions.length > 0 && (\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-sessions\"\n steps={[\n {\n id: \"enable-mfa\",\n label: tr(\n \"auth.sessions.nextEnableMfa\",\n \"Turn on two-factor authentication\",\n ),\n description: tr(\n \"auth.sessions.nextEnableMfaDesc\",\n \"Add a second sign-in step so a stolen password isn't enough.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n },\n {\n id: \"security-center\",\n label: tr(\n \"auth.sessions.nextSecurityCenter\",\n \"Open the Security Center\",\n ),\n description: tr(\n \"auth.sessions.nextSecurityCenterDesc\",\n \"Review your password and overall account protection.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../src/auth/pages/SessionsPage.tsx"],"sourcesContent":["import { useState, useMemo } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useSessionOperations } from \"../hooks/useSessionOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport {\n formatRelativeTime,\n parseDeviceName,\n parseBrowserName,\n} from \"../utils/formatters\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\n\n/**\n * Active sessions management page with professional UI\n *\n * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)\n * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)\n */\nexport function SessionsPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const {\n activeSessions,\n revokeSession,\n revokeAllSessions,\n isLoading,\n page,\n totalPages,\n totalCount,\n hasMore,\n goToNextPage,\n goToPrevPage,\n } = useSessionOperations({ disableTokenInfoQuery: true, pageSize: 20 });\n\n // One entry per device: keep the most-recently-active session for each device name\n const dedupedSessions = useMemo(() => {\n const byDevice = new Map<string, (typeof activeSessions)[number]>();\n for (const s of activeSessions) {\n const device = parseDeviceName(s.userAgent ?? \"\");\n const existing = byDevice.get(device);\n if (\n !existing ||\n new Date(s.lastActivityAt) > new Date(existing.lastActivityAt)\n ) {\n byDevice.set(device, s);\n }\n }\n return [...byDevice.values()];\n }, [activeSessions]);\n\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [pendingSessionId, setPendingSessionId] = useState<string | null>(null);\n const [showRevokeAllDialog, setShowRevokeAllDialog] = useState(false);\n\n const handleRevokeSession = async (sessionId: string) => {\n try {\n await revokeSession(sessionId);\n setSuccessMessage(\n tr(\"auth.sessions.revokeSuccess\", \"Session revoked successfully\"),\n );\n setPendingSessionId(null);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\"auth.sessions.revokeFailed\", \"Failed to revoke session\"),\n );\n }\n };\n\n const handleRevokeAll = async () => {\n try {\n await revokeAllSessions();\n setSuccessMessage(\n tr(\n \"auth.sessions.revokeAllSuccess\",\n \"All other sessions have been revoked\",\n ),\n );\n setShowRevokeAllDialog(false);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.sessions.revokeAllFailed\",\n \"Failed to revoke all sessions\",\n ),\n );\n }\n };\n\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.sessions.authRequired\", \"Authentication Required\")}\n description={tr(\n \"auth.sessions.signInToView\",\n \"Please sign in to view your active sessions\",\n )}\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <ConfirmActionDialog\n open={pendingSessionId !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPendingSessionId(null);\n }\n }}\n title={tr(\"auth.sessions.revokeSessionTitle\", \"Revoke this session?\")}\n description={tr(\n \"auth.sessions.revokeSessionDesc\",\n \"The selected device will be signed out immediately and will need to authenticate again.\",\n )}\n confirmText={tr(\"auth.sessions.revokeSessionConfirm\", \"Revoke session\")}\n variant=\"danger\"\n isPending={isLoading}\n onConfirm={async () => {\n if (pendingSessionId) {\n await handleRevokeSession(pendingSessionId);\n }\n }}\n />\n <ConfirmActionDialog\n open={showRevokeAllDialog}\n onOpenChange={setShowRevokeAllDialog}\n title={tr(\n \"auth.sessions.revokeAllTitle\",\n \"Revoke every other session?\",\n )}\n description={tr(\n \"auth.sessions.revokeAllDesc\",\n \"All devices except your current one will be signed out immediately.\",\n )}\n confirmText={tr(\n \"auth.sessions.revokeAllConfirm\",\n \"Revoke all other sessions\",\n )}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleRevokeAll}\n />\n\n {/* Live regions for screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {successMessage}\n </div>\n <div\n role=\"alert\"\n aria-live=\"assertive\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n >\n {error}\n </div>\n\n {/* Header */}\n <div className=\"flex items-center justify-between mb-4\">\n <div>\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.sessions.title\", \"Active Sessions\")}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\"auth.sessions.sessionCount\", \"You have\")} {totalCount}{\" \"}\n {tr(\n totalCount !== 1\n ? \"auth.sessions.sessionsPlural\"\n : \"auth.sessions.sessionsSingular\",\n totalCount !== 1 ? \"active sessions\" : \"active session\",\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n {/* Export button — commented out until wspace-export-svc is wired for auth_sessions\n <button\n type=\"button\"\n onClick={handleExport}\n disabled={isExporting}\n aria-label={tr(\n \"auth.sessions.exportAriaLabel\",\n \"Export sessions data\",\n )}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary font-medium hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon\n className=\"size-4 animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"auth.sessions.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.sessions.export\", \"Export\")}\n </>\n )}\n </button>\n */}\n </div>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.sessions.purpose\",\n \"This is every device currently signed in to your account. Recognise them all? If a device looks unfamiliar or you've lost it, revoke its session to sign it out immediately.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: account-access summary — the one highlighted zone */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <p className=\"text-sm font-medium text-text-secondary\">\n {tr(\"auth.sessions.summaryLabel\", \"Devices signed in\")}\n </p>\n <p className=\"mt-1 text-3xl font-semibold text-text-primary\">\n {totalCount}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.sessions.summaryHint\",\n \"Keep this list trimmed to devices you trust.\",\n )}\n </p>\n </div>\n {totalCount > 1 && (\n <button\n type=\"button\"\n onClick={() => setShowRevokeAllDialog(true)}\n className=\"shrink-0 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 duration-200\"\n >\n {tr(\"auth.sessions.revokeAllOthers\", \"Revoke all other sessions\")}\n </button>\n )}\n </div>\n </EmphasisPanel>\n\n {/* Success Message */}\n {successMessage && (\n <output className=\"block 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 transition-colors duration-200\"\n >\n {tr(\"auth.sessions.dismiss\", \"Dismiss\")}\n </button>\n </output>\n )}\n\n {/* Error Message */}\n {error && (\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 <div className=\"flex-1\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-status-error-text hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.sessions.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {isLoading ? (\n <div\n aria-live=\"polite\"\n aria-busy=\"true\"\n className=\"flex items-center justify-center py-12\"\n >\n <SpinnerIcon\n className=\"size-8 text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <span className=\"ml-3 text-text-secondary\">\n {tr(\"auth.sessions.loading\", \"Loading sessions...\")}\n </span>\n </div>\n ) : dedupedSessions.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-sessions\"\n title={tr(\"auth.sessions.noSessions\", \"No active sessions found\")}\n description={tr(\n \"auth.sessions.noSessionsDesc\",\n \"When you sign in on a device it will appear here so you can review and manage it.\",\n )}\n />\n </output>\n ) : (\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={isLoading}>\n {dedupedSessions.map((session) => (\n <div\n key={session.id}\n className=\"p-6 rounded-xl border border-border-default bg-bg-surface shadow-sm hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-start gap-4\">\n {/* Device Icon */}\n <div\n className=\"size-12 rounded-xl bg-bg-accent flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n <svg\n className=\"size-6 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-1\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {session.isCurrent &&\n parseDeviceName(session.userAgent) === \"Unknown Device\"\n ? tr(\"auth.sessions.thisDevice\", \"This Device\")\n : parseDeviceName(session.userAgent)}\n </h3>\n {session.isCurrent && (\n <output className=\"px-2.5 py-1 rounded-full bg-status-success-bg-subtle text-status-success-text text-xs font-medium\">\n {tr(\n \"auth.sessions.currentSession\",\n \"Current Session\",\n )}\n </output>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mb-2\">\n {parseBrowserName(session.userAgent) ===\n \"Unknown Browser\" && session.isCurrent\n ? tr(\"auth.sessions.currentBrowser\", \"Current Browser\")\n : parseBrowserName(session.userAgent)}\n </p>\n <dl className=\"flex flex-wrap gap-4 text-sm text-text-secondary\">\n {session.ipAddress && session.ipAddress !== \"unknown\" && (\n <div className=\"flex items-center gap-1.5\">\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=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.ipAddress\", \"IP address\")}\n </dt>\n <dd>{session.ipAddress}</dd>\n </div>\n )}\n {session.location && (\n <div className=\"flex items-center gap-1.5\">\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=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.location\", \"Location\")}\n </dt>\n <dd>{session.location}</dd>\n </div>\n )}\n <div className=\"flex items-center gap-1.5\">\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=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <dt className=\"sr-only\">\n {tr(\"auth.sessions.lastActive\", \"Last active\")}\n </dt>\n <dd>\n {tr(\"auth.sessions.lastActive\", \"Last active\")}{\" \"}\n {formatRelativeTime(session.lastActivityAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n\n {!session.isCurrent && (\n <button\n type=\"button\"\n onClick={() => setPendingSessionId(session.id)}\n aria-label={`${tr(\"auth.sessions.revoke\", \"Revoke\")} ${parseDeviceName(session.userAgent)} session`}\n className=\"ml-4 px-4 py-2 rounded-lg text-sm font-medium border border-status-error-border text-status-error-text bg-bg-surface hover:bg-status-error-bg-subtle transition-colors duration-200\"\n >\n {tr(\"auth.sessions.revoke\", \"Revoke\")}\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Pagination controls */}\n {!isLoading && totalPages > 1 && (\n <div className=\"flex items-center justify-between mt-6 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={goToPrevPage}\n disabled={page === 0}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.prevPage\", \"Previous page\")}\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=\"M15 19l-7-7 7-7\"\n />\n </svg>\n {tr(\"auth.sessions.prev\", \"Previous\")}\n </button>\n\n <span className=\"text-sm text-text-secondary\">\n {tr(\"auth.sessions.pageOf\", \"Page\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{page + 1}</span>{\" \"}\n {tr(\"auth.sessions.of\", \"of\")}{\" \"}\n <span className=\"font-medium text-text-primary\">{totalPages}</span>\n </span>\n\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!hasMore}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors font-medium text-sm\"\n aria-label={tr(\"auth.sessions.nextPage\", \"Next page\")}\n >\n {tr(\"auth.sessions.next\", \"Next\")}\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=\"M9 5l7 7-7 7\"\n />\n </svg>\n </button>\n </div>\n )}\n\n {!isLoading && dedupedSessions.length > 0 && (\n <NextSteps\n className=\"mt-8\"\n storageKey=\"auth-sessions\"\n steps={[\n {\n id: \"enable-mfa\",\n label: tr(\n \"auth.sessions.nextEnableMfa\",\n \"Turn on two-factor authentication\",\n ),\n description: tr(\n \"auth.sessions.nextEnableMfaDesc\",\n \"Add a second sign-in step so a stolen password isn't enough.\",\n ),\n onClick: () => navigate(\"/auth/mfa\"),\n },\n {\n id: \"security-center\",\n label: tr(\n \"auth.sessions.nextSecurityCenter\",\n \"Open the Security Center\",\n ),\n description: tr(\n \"auth.sessions.nextSecurityCenterDesc\",\n \"Review your password and overall account protection.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n ]}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -4,4 +4,4 @@
4
4
  * - SAML ACS server (with token/refreshToken in query params)
5
5
  * - OIDC Authorization Server (with code/state in query params)
6
6
  */
7
- export declare function SsoCallbackPage(): import("react").JSX.Element;
7
+ export declare function SsoCallbackPage(): import('react').JSX.Element;