@burdenoff/microfe-auth 2026.1031.1 → 2026.1101.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 (135) hide show
  1. package/dist/auth/AuthRoot.js +14 -11
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/AuthRoutes.js +1 -2
  4. package/dist/auth/AuthRoutes.js.map +1 -1
  5. package/dist/auth/components/AuthIcons.js +1 -2
  6. package/dist/auth/components/AuthIcons.js.map +1 -1
  7. package/dist/auth/components/AuthLayout.js +1 -2
  8. package/dist/auth/components/AuthLayout.js.map +1 -1
  9. package/dist/auth/components/CaptchaV2.js +9 -10
  10. package/dist/auth/components/CaptchaV2.js.map +1 -1
  11. package/dist/auth/components/ConfirmActionDialog.js +1 -2
  12. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  13. package/dist/auth/components/profile/UserProfileMenu.js +7 -6
  14. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  15. package/dist/auth/components/workspace/WorkspaceSelector.js +1 -2
  16. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  17. package/dist/auth/constants/permissions.js +1 -2
  18. package/dist/auth/constants/permissions.js.map +1 -1
  19. package/dist/auth/constants/sessionKeys.js +1 -2
  20. package/dist/auth/constants/sessionKeys.js.map +1 -1
  21. package/dist/auth/hooks/contextDeepLink.js +1 -2
  22. package/dist/auth/hooks/contextDeepLink.js.map +1 -1
  23. package/dist/auth/hooks/index.js +12 -11
  24. package/dist/auth/hooks/useAccountOperations.js +1 -2
  25. package/dist/auth/hooks/useAccountOperations.js.map +1 -1
  26. package/dist/auth/hooks/useActiveContextBootstrapMirror.js +1 -2
  27. package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -1
  28. package/dist/auth/hooks/useAuthOperations.js +37 -37
  29. package/dist/auth/hooks/useAuthOperations.js.map +1 -1
  30. package/dist/auth/hooks/useAuthPermissions.js +1 -2
  31. package/dist/auth/hooks/useAuthPermissions.js.map +1 -1
  32. package/dist/auth/hooks/useCaptchaV3.js +1 -2
  33. package/dist/auth/hooks/useCaptchaV3.js.map +1 -1
  34. package/dist/auth/hooks/useContextSwitcherData.js +81 -78
  35. package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
  36. package/dist/auth/hooks/useMfaOperations.js +4 -5
  37. package/dist/auth/hooks/useMfaOperations.js.map +1 -1
  38. package/dist/auth/hooks/useMyWorkspaces.js +4 -5
  39. package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
  40. package/dist/auth/hooks/useOAuthOperations.js +11 -12
  41. package/dist/auth/hooks/useOAuthOperations.js.map +1 -1
  42. package/dist/auth/hooks/usePasswordOperations.js +1 -2
  43. package/dist/auth/hooks/usePasswordOperations.js.map +1 -1
  44. package/dist/auth/hooks/useSessionOperations.js +3 -4
  45. package/dist/auth/hooks/useSessionOperations.js.map +1 -1
  46. package/dist/auth/hooks/useSignupCaptcha.js +1 -2
  47. package/dist/auth/hooks/useSignupCaptcha.js.map +1 -1
  48. package/dist/auth/hooks/useSsoOperations.js +80 -81
  49. package/dist/auth/hooks/useSsoOperations.js.map +1 -1
  50. package/dist/auth/hooks/useTokenRefresh.js +8 -12
  51. package/dist/auth/hooks/useTokenRefresh.js.map +1 -1
  52. package/dist/auth/hooks/useUserOperations.js +1 -2
  53. package/dist/auth/hooks/useUserOperations.js.map +1 -1
  54. package/dist/auth/hooks/useWorkspaceOperations.js +1 -2
  55. package/dist/auth/hooks/useWorkspaceOperations.js.map +1 -1
  56. package/dist/auth/hooks/useWorkspaceToken.js +1 -2
  57. package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
  58. package/dist/auth/index.js +19 -18
  59. package/dist/auth/pages/AuditLogPage.js +21 -22
  60. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  61. package/dist/auth/pages/EmailVerifyPage.js +15 -17
  62. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  63. package/dist/auth/pages/LoginPage.js +89 -105
  64. package/dist/auth/pages/LoginPage.js.map +1 -1
  65. package/dist/auth/pages/MagicLinkPage.js +30 -31
  66. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  67. package/dist/auth/pages/MfaSetupPage.js +18 -20
  68. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  69. package/dist/auth/pages/MfaVerifyPage.js +31 -35
  70. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  71. package/dist/auth/pages/OAuthCallbackPage.js +4 -11
  72. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  73. package/dist/auth/pages/PasswordResetPage.js +20 -23
  74. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  75. package/dist/auth/pages/ReactivatePage.js +14 -18
  76. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  77. package/dist/auth/pages/RegisterPage.js +180 -181
  78. package/dist/auth/pages/RegisterPage.js.map +1 -1
  79. package/dist/auth/pages/SSOClearPage.js +9 -10
  80. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  81. package/dist/auth/pages/SSODepositPage.js +9 -13
  82. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  83. package/dist/auth/pages/SSORelayCallbackPage.js +3 -7
  84. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  85. package/dist/auth/pages/SSORelayPage.js +11 -10
  86. package/dist/auth/pages/SSORelayPage.js.map +1 -1
  87. package/dist/auth/pages/SecurityPage.js +19 -21
  88. package/dist/auth/pages/SecurityPage.js.map +1 -1
  89. package/dist/auth/pages/SessionBridgePage.js +9 -16
  90. package/dist/auth/pages/SessionBridgePage.js.map +1 -1
  91. package/dist/auth/pages/SessionsPage.js +17 -18
  92. package/dist/auth/pages/SessionsPage.js.map +1 -1
  93. package/dist/auth/pages/SsoCallbackPage.js +17 -22
  94. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  95. package/dist/auth/pages/SsoConfigPage.js +69 -69
  96. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  97. package/dist/auth/pages/SsoLoginPage.js +9 -10
  98. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  99. package/dist/auth/pages/UserDetailsPage.js +16 -23
  100. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  101. package/dist/auth/pages/UserProfilePage.js +140 -141
  102. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  103. package/dist/auth/pages/UsersPage.js +446 -453
  104. package/dist/auth/pages/UsersPage.js.map +1 -1
  105. package/dist/auth/providers/AuthProvider.js +1 -2
  106. package/dist/auth/providers/AuthProvider.js.map +1 -1
  107. package/dist/auth/providers/CaptchaProvider.js +1 -2
  108. package/dist/auth/providers/CaptchaProvider.js.map +1 -1
  109. package/dist/auth/store/authStore.js +5 -12
  110. package/dist/auth/store/authStore.js.map +1 -1
  111. package/dist/auth/utils/captchaConfig.js +5 -8
  112. package/dist/auth/utils/captchaConfig.js.map +1 -1
  113. package/dist/auth/utils/formatters.js +1 -2
  114. package/dist/auth/utils/formatters.js.map +1 -1
  115. package/dist/auth/utils/gatewayBaseUrls.js +1 -2
  116. package/dist/auth/utils/gatewayBaseUrls.js.map +1 -1
  117. package/dist/auth/utils/legalUrls.js +2 -3
  118. package/dist/auth/utils/legalUrls.js.map +1 -1
  119. package/dist/auth/utils/redirect.js +1 -2
  120. package/dist/auth/utils/redirect.js.map +1 -1
  121. package/dist/auth/utils/ssoOrigins.js +2 -3
  122. package/dist/auth/utils/ssoOrigins.js.map +1 -1
  123. package/dist/auth/utils/validation.js +34 -36
  124. package/dist/auth/utils/validation.js.map +1 -1
  125. package/dist/generated/global-operations.js +2 -3
  126. package/dist/generated/global-operations.js.map +1 -1
  127. package/dist/generated/wspace-operations.js +2 -3
  128. package/dist/generated/wspace-operations.js.map +1 -1
  129. package/dist/microfe-auth.css +1 -1
  130. package/dist/shared/hooks/useTr.js +1 -2
  131. package/dist/shared/hooks/useTr.js.map +1 -1
  132. package/dist/translations/en.js +1 -2
  133. package/dist/translations/en.js.map +1 -1
  134. package/dist/utils/nativeBridge.js +1 -2
  135. package/package.json +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"MagicLinkPage.js","names":[],"sources":["../../../src/auth/pages/MagicLinkPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { EmailIcon, CheckIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { MagicLinkSendResult, AuthResult, User } from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: 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({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nimport {\n SendMagicLinkDocument,\n AuthenticateMagicLinkDocument,\n} from \"../../generated/global-operations\";\n// Response types for GraphQL mutations\ninterface SendMagicLinkResponse {\n sendMagicLink: MagicLinkSendResult;\n}\n\ninterface AuthenticateMagicLinkResponse {\n authenticateMagicLink: AuthResult;\n}\n\n// GraphQL Operations\n\ntype PageStatus = \"form\" | \"sent\" | \"verifying\" | \"success\" | \"error\";\n\n/**\n * Magic Link authentication page\n * Supports both sending magic links and verifying them via token\n */\nexport function MagicLinkPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const context = useAuth();\n const eventBus = useEventBus();\n\n const [email, setEmail] = useState(\"\");\n const [status, setStatus] = useState<PageStatus>(\n token ? \"verifying\" : \"form\",\n );\n const [message, setMessage] = useState(\"\");\n const hasVerifiedRef = useRef(false);\n\n const [sendMagicLink, { loading: sendLoading }] =\n useMutation<SendMagicLinkResponse>(SendMagicLinkDocument);\n const [authenticateMagicLink] = useMutation<AuthenticateMagicLinkResponse>(\n AuthenticateMagicLinkDocument,\n );\n\n const handleVerifyToken = useCallback(\n async (tokenValue: string) => {\n const store = useAuthStore.getState();\n\n try {\n setStatus(\"verifying\");\n store.setLoading(true);\n\n const { data } = await authenticateMagicLink({\n variables: { token: tokenValue },\n });\n const result = data!.authenticateMagicLink;\n\n if (!result.success || !result.user) {\n throw new Error(result.error || \"Authentication failed\");\n }\n\n store.setUser(result.user);\n store.setAuthTokens(result.token ?? \"\", result.refreshToken ?? \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token ?? \"\",\n refreshToken: result.refreshToken ?? \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"MagicLinkPage\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n\n setStatus(\"success\");\n setMessage(\n tr(\n \"auth.magicLink.signedInMessage\",\n \"You have been signed in successfully!\",\n ),\n );\n\n // Redirect to dashboard after successful authentication\n setTimeout(() => {\n navigate(\"/\", { replace: true });\n }, 1500);\n } catch (err) {\n // If user is already authenticated (token was already used), redirect to dashboard\n if (store.isAuthenticated && store.user) {\n console.info(\n \"[MagicLink] Token already used but user is authenticated, redirecting...\",\n );\n navigate(\"/\", { replace: true });\n return;\n }\n\n setStatus(\"error\");\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\n \"auth.magicLink.invalidOrExpired\",\n \"Invalid or expired magic link\",\n );\n setMessage(errorMessage);\n } finally {\n store.setLoading(false);\n }\n },\n [authenticateMagicLink, context, eventBus, navigate, tr],\n );\n\n // Handle token verification on page load (only once)\n useEffect(() => {\n if (token && !hasVerifiedRef.current) {\n hasVerifiedRef.current = true;\n handleVerifyToken(token);\n }\n }, [token, handleVerifyToken]);\n\n const handleSendMagicLink = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!email) {\n setMessage(\n tr(\"auth.magicLink.enterEmail\", \"Please enter your email address\"),\n );\n return;\n }\n\n try {\n const { data } = await sendMagicLink({ variables: { email } });\n\n if (data!.sendMagicLink.success) {\n setStatus(\"sent\");\n setMessage(\n data!.sendMagicLink.message ||\n tr(\n \"auth.magicLink.sentSuccess\",\n \"Magic link sent! Check your email.\",\n ),\n );\n } else {\n setMessage(\n data!.sendMagicLink.message ||\n tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\"),\n );\n }\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\");\n setMessage(errorMessage);\n }\n };\n\n // Verifying state\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.signingIn\", \"Signing you in...\")}\n subtitle={tr(\n \"auth.magicLink.verifyingSubtitle\",\n \"Please wait while we verify your magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]\" />\n <p className=\"mt-4 text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.verifying\", \"Verifying magic link...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Success state\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.welcomeBack\", \"Welcome back!\")}\n subtitle={tr(\n \"auth.magicLink.signedInSuccess\",\n \"You have been signed in successfully\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <CheckIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-4\">\n {message}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.redirecting\", \"Redirecting to application...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (status === \"error\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.oops\", \"Oops!\")}\n subtitle={tr(\"auth.magicLink.somethingWrong\", \"Something went wrong\")}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4\">\n <AlertIcon className=\"size-8 text-[var(--color-status-error)]\" />\n </div>\n <p className=\"text-[var(--color-status-error)] text-center mb-4\">\n {message}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]\"\n >\n {tr(\"auth.magicLink.tryAgain\", \"Try again\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Sent state\n if (status === \"sent\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.magicLink.sentMagicLink\",\n \"We've sent you a magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <EmailIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-2\">\n {tr(\"auth.magicLink.sentTo\", \"We've sent a magic link to:\")}\n </p>\n <p className=\"text-[var(--color-accent-primary)] font-medium mb-4\">\n {email}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)] text-center mb-6\">\n {tr(\n \"auth.magicLink.linkExpires\",\n \"Click the link in the email to sign in. The link expires in 24 hours.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\"auth.magicLink.differentEmail\", \"Use a different email\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.title\", \"Sign in with Magic Link\")}\n subtitle={tr(\n \"auth.magicLink.subtitle\",\n \"No password needed - we'll email you a link\",\n )}\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.magicLink.purpose\",\n \"Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSendMagicLink} className=\"space-y-6\">\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder={tr(\n \"auth.magicLink.emailPlaceholder\",\n \"you@example.com\",\n )}\n aria-label={tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n className=\"block w-full pl-[3.25rem] pr-4 py-3 border border-[var(--color-border-primary)] rounded-lg bg-[var(--color-background-base)] text-[var(--color-text-primary)] placeholder-[var(--color-text-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:border-transparent\"\n required\n />\n </div>\n </div>\n\n {message && (\n <p className=\"text-sm text-[var(--color-status-error)]\">{message}</p>\n )}\n\n <Button\n type=\"submit\"\n disabled={sendLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {sendLoading\n ? tr(\"auth.magicLink.sending\", \"Sending...\")\n : tr(\"auth.magicLink.sendButton\", \"Send Magic Link\")}\n </Button>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\n \"auth.magicLink.passwordInstead\",\n \"Sign in with password instead\",\n )}\n </button>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MagicLinkPage.js","names":["toProfileUser","user","setShellSsoCookie","domain","domainPart","securePart","ssoValue","userId","MagicLinkPage","tr","useTr","navigate","useNavigate","searchParams","useSearchParams","token","context","useAuth","eventBus","useEventBus","email","setEmail","useState","status","setStatus","message","setMessage","hasVerifiedRef","useRef","sendMagicLink","sendLoading","useMutation","SendMagicLinkDocument","authenticateMagicLink","AuthenticateMagicLinkDocument","handleVerifyToken","useCallback","store","useAuthStore","tokenValue","result","data","expiresAt","loginProfile","err","errorMessage","useEffect","AuthLayout","CheckIcon","AlertIcon","EmailIcon","PagePurpose","Button"],"sources":["../../../src/auth/pages/MagicLinkPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { EmailIcon, CheckIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { MagicLinkSendResult, AuthResult, User } from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: 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({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nimport {\n SendMagicLinkDocument,\n AuthenticateMagicLinkDocument,\n} from \"../../generated/global-operations\";\n// Response types for GraphQL mutations\ninterface SendMagicLinkResponse {\n sendMagicLink: MagicLinkSendResult;\n}\n\ninterface AuthenticateMagicLinkResponse {\n authenticateMagicLink: AuthResult;\n}\n\n// GraphQL Operations\n\ntype PageStatus = \"form\" | \"sent\" | \"verifying\" | \"success\" | \"error\";\n\n/**\n * Magic Link authentication page\n * Supports both sending magic links and verifying them via token\n */\nexport function MagicLinkPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const context = useAuth();\n const eventBus = useEventBus();\n\n const [email, setEmail] = useState(\"\");\n const [status, setStatus] = useState<PageStatus>(\n token ? \"verifying\" : \"form\",\n );\n const [message, setMessage] = useState(\"\");\n const hasVerifiedRef = useRef(false);\n\n const [sendMagicLink, { loading: sendLoading }] =\n useMutation<SendMagicLinkResponse>(SendMagicLinkDocument);\n const [authenticateMagicLink] = useMutation<AuthenticateMagicLinkResponse>(\n AuthenticateMagicLinkDocument,\n );\n\n const handleVerifyToken = useCallback(\n async (tokenValue: string) => {\n const store = useAuthStore.getState();\n\n try {\n setStatus(\"verifying\");\n store.setLoading(true);\n\n const { data } = await authenticateMagicLink({\n variables: { token: tokenValue },\n });\n const result = data!.authenticateMagicLink;\n\n if (!result.success || !result.user) {\n throw new Error(result.error || \"Authentication failed\");\n }\n\n store.setUser(result.user);\n store.setAuthTokens(result.token ?? \"\", result.refreshToken ?? \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token ?? \"\",\n refreshToken: result.refreshToken ?? \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"MagicLinkPage\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n\n setStatus(\"success\");\n setMessage(\n tr(\n \"auth.magicLink.signedInMessage\",\n \"You have been signed in successfully!\",\n ),\n );\n\n // Redirect to dashboard after successful authentication\n setTimeout(() => {\n navigate(\"/\", { replace: true });\n }, 1500);\n } catch (err) {\n // If user is already authenticated (token was already used), redirect to dashboard\n if (store.isAuthenticated && store.user) {\n console.info(\n \"[MagicLink] Token already used but user is authenticated, redirecting...\",\n );\n navigate(\"/\", { replace: true });\n return;\n }\n\n setStatus(\"error\");\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\n \"auth.magicLink.invalidOrExpired\",\n \"Invalid or expired magic link\",\n );\n setMessage(errorMessage);\n } finally {\n store.setLoading(false);\n }\n },\n [authenticateMagicLink, context, eventBus, navigate, tr],\n );\n\n // Handle token verification on page load (only once)\n useEffect(() => {\n if (token && !hasVerifiedRef.current) {\n hasVerifiedRef.current = true;\n handleVerifyToken(token);\n }\n }, [token, handleVerifyToken]);\n\n const handleSendMagicLink = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!email) {\n setMessage(\n tr(\"auth.magicLink.enterEmail\", \"Please enter your email address\"),\n );\n return;\n }\n\n try {\n const { data } = await sendMagicLink({ variables: { email } });\n\n if (data!.sendMagicLink.success) {\n setStatus(\"sent\");\n setMessage(\n data!.sendMagicLink.message ||\n tr(\n \"auth.magicLink.sentSuccess\",\n \"Magic link sent! Check your email.\",\n ),\n );\n } else {\n setMessage(\n data!.sendMagicLink.message ||\n tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\"),\n );\n }\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\");\n setMessage(errorMessage);\n }\n };\n\n // Verifying state\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.signingIn\", \"Signing you in...\")}\n subtitle={tr(\n \"auth.magicLink.verifyingSubtitle\",\n \"Please wait while we verify your magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]\" />\n <p className=\"mt-4 text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.verifying\", \"Verifying magic link...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Success state\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.welcomeBack\", \"Welcome back!\")}\n subtitle={tr(\n \"auth.magicLink.signedInSuccess\",\n \"You have been signed in successfully\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <CheckIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-4\">\n {message}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.redirecting\", \"Redirecting to application...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (status === \"error\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.oops\", \"Oops!\")}\n subtitle={tr(\"auth.magicLink.somethingWrong\", \"Something went wrong\")}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4\">\n <AlertIcon className=\"size-8 text-[var(--color-status-error)]\" />\n </div>\n <p className=\"text-[var(--color-status-error)] text-center mb-4\">\n {message}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]\"\n >\n {tr(\"auth.magicLink.tryAgain\", \"Try again\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Sent state\n if (status === \"sent\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.magicLink.sentMagicLink\",\n \"We've sent you a magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <EmailIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-2\">\n {tr(\"auth.magicLink.sentTo\", \"We've sent a magic link to:\")}\n </p>\n <p className=\"text-[var(--color-accent-primary)] font-medium mb-4\">\n {email}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)] text-center mb-6\">\n {tr(\n \"auth.magicLink.linkExpires\",\n \"Click the link in the email to sign in. The link expires in 24 hours.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\"auth.magicLink.differentEmail\", \"Use a different email\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.title\", \"Sign in with Magic Link\")}\n subtitle={tr(\n \"auth.magicLink.subtitle\",\n \"No password needed - we'll email you a link\",\n )}\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.magicLink.purpose\",\n \"Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSendMagicLink} className=\"space-y-6\">\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder={tr(\n \"auth.magicLink.emailPlaceholder\",\n \"you@example.com\",\n )}\n aria-label={tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n className=\"block w-full pl-[3.25rem] pr-4 py-3 border border-[var(--color-border-primary)] rounded-lg bg-[var(--color-background-base)] text-[var(--color-text-primary)] placeholder-[var(--color-text-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:border-transparent\"\n required\n />\n </div>\n </div>\n\n {message && (\n <p className=\"text-sm text-[var(--color-status-error)]\">{message}</p>\n )}\n\n <Button\n type=\"submit\"\n disabled={sendLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {sendLoading\n ? tr(\"auth.magicLink.sending\", \"Sending...\")\n : tr(\"auth.magicLink.sendButton\", \"Send Magic Link\")}\n </Button>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\n \"auth.magicLink.passwordInstead\",\n \"Sign in with password instead\",\n )}\n </button>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- import { useAuthStore as e } from "../store/authStore.js";
2
- import { useTr as t } from "../../shared/hooks/useTr.js";
3
- import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
4
- import { useMfaOperations as i } from "../hooks/useMfaOperations.js";
5
- import { ConfirmActionDialog as a } from "../components/ConfirmActionDialog.js";
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { useMfaOperations as t } from "../hooks/useMfaOperations.js";
3
+ import { useAuthStore as n } from "../store/authStore.js";
4
+ import { ConfirmActionDialog as r } from "../components/ConfirmActionDialog.js";
5
+ import { AlertIcon as i, SpinnerIcon as a } from "../components/AuthIcons.js";
6
6
  import { nativeCopyText as o, nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
7
7
  import { useState as l } from "react";
8
8
  import { useNavigate as u } from "react-router";
@@ -10,11 +10,13 @@ import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
10
10
  import { Button as m } from "@burdenoff/fe-libs/button";
11
11
  import { NextSteps as h, PagePurpose as g } from "@burdenoff/fe-libs/ui";
12
12
  import { QRCodeSVG as _ } from "qrcode.react";
13
- //#region src/auth/pages/MfaSetupPage.tsx
13
+ // src/auth/pages/MfaSetupPage.tsx
14
14
  function v() {
15
- let v = t(), y = u(), { setupMFA: b, enableMFA: x, disableMFA: S, isLoading: C } = i(), w = e((e) => e.user), T = e((e) => e.isAuthenticated), [E, D] = l(null), [O, k] = l(""), [A, j] = l(null), [M, N] = l(null), [P, F] = l(!1), [I, L] = l(!1), R = async (e) => {
15
+ let v = e(), y = u(), { setupMFA: b, enableMFA: x, disableMFA: S, isLoading: C } = t(), w = n((e) => e.user), T = n((e) => e.isAuthenticated), [E, D] = l(null), [O, k] = l(""), [A, j] = l(null), [M, N] = l(null), [P, F] = l(!1), [I, L] = l(!1), R = async (e) => {
16
16
  try {
17
- j(null), N(null), D(await b(e));
17
+ j(null), N(null);
18
+ let t = await b(e);
19
+ D(t);
18
20
  } catch (e) {
19
21
  console.error("Failed to setup MFA:", e), j(e instanceof Error ? e.message : "Failed to setup MFA. Please try again.");
20
22
  }
@@ -22,7 +24,7 @@ function v() {
22
24
  return T ? /* @__PURE__ */ p("div", {
23
25
  className: "max-w-2xl mx-auto p-6",
24
26
  children: [
25
- /* @__PURE__ */ f(a, {
27
+ /* @__PURE__ */ f(r, {
26
28
  open: I,
27
29
  onOpenChange: L,
28
30
  title: v("auth.mfaSetup.disableTitle", "Disable two-factor authentication?"),
@@ -78,7 +80,7 @@ function v() {
78
80
  }),
79
81
  A && /* @__PURE__ */ p("div", {
80
82
  className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
81
- children: [/* @__PURE__ */ f(n, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ p("div", {
83
+ children: [/* @__PURE__ */ f(i, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ p("div", {
82
84
  className: "flex-1",
83
85
  children: [/* @__PURE__ */ f("p", {
84
86
  className: "text-sm text-[var(--color-status-error-text)]",
@@ -213,11 +215,8 @@ function v() {
213
215
  }), /* @__PURE__ */ f(m, {
214
216
  type: "button",
215
217
  onClick: async () => {
216
- if (O.length !== 6) {
217
- j(v("auth.mfaSetup.enter6Digit", "Please enter a 6-digit code"));
218
- return;
219
- }
220
- try {
218
+ if (O.length !== 6) j(v("auth.mfaSetup.enter6Digit", "Please enter a 6-digit code"));
219
+ else try {
221
220
  j(null), await x(O) && (D(null), k(""), N(v("auth.mfaSetup.enableSuccess", "Two-factor authentication has been enabled successfully!")));
222
221
  } catch (e) {
223
222
  console.error("Failed to verify MFA code:", e), j(e instanceof Error ? e.message : "Invalid verification code. Please try again.");
@@ -227,7 +226,7 @@ function v() {
227
226
  variant: "default",
228
227
  size: "lg",
229
228
  className: "flex-1 font-semibold",
230
- children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.verifying", "Verifying...") })] }) : v("auth.mfaSetup.enable2fa", "Enable 2FA")
229
+ children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(a, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.verifying", "Verifying...") })] }) : v("auth.mfaSetup.enable2fa", "Enable 2FA")
231
230
  })]
232
231
  })
233
232
  ]
@@ -271,7 +270,7 @@ function v() {
271
270
  onClick: () => L(!0),
272
271
  disabled: C,
273
272
  className: "w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2",
274
- children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.disabling", "Disabling...") })] }) : v("auth.mfaSetup.disableTwoFactor", "Disable Two-Factor Authentication")
273
+ children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(a, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.disabling", "Disabling...") })] }) : v("auth.mfaSetup.disableTwoFactor", "Disable Two-Factor Authentication")
275
274
  }),
276
275
  /* @__PURE__ */ f(h, {
277
276
  className: "mt-6",
@@ -343,7 +342,7 @@ function v() {
343
342
  variant: "default",
344
343
  size: "lg",
345
344
  className: "w-full font-semibold",
346
- children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.settingUp", "Setting up...") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
345
+ children: C ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(a, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: v("auth.mfa.settingUp", "Setting up...") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
347
346
  className: "size-5",
348
347
  fill: "none",
349
348
  viewBox: "0 0 24 24",
@@ -366,7 +365,7 @@ function v() {
366
365
  children: [
367
366
  /* @__PURE__ */ f("div", {
368
367
  className: "size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
369
- children: /* @__PURE__ */ f(n, { className: "size-8 text-status-warning-text" })
368
+ children: /* @__PURE__ */ f(i, { className: "size-8 text-status-warning-text" })
370
369
  }),
371
370
  /* @__PURE__ */ f("h2", {
372
371
  className: "text-xl font-semibold text-text-primary mb-2",
@@ -380,7 +379,6 @@ function v() {
380
379
  })
381
380
  });
382
381
  }
383
- //#endregion
384
382
  export { v as MfaSetupPage };
385
383
 
386
384
  //# sourceMappingURL=MfaSetupPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MfaSetupPage.js","names":[],"sources":["../../../src/auth/pages/MfaSetupPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { QRCodeSVG } from \"qrcode.react\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport type { MFAMethod, MFASetupResult } from \"../types\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\n/**\n * MFA setup and management page with professional UI\n */\nexport function MfaSetupPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const { setupMFA, enableMFA, disableMFA, isLoading } = useMfaOperations();\n const user = useAuthStore((state) => state.user);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const [setupData, setSetupData] = useState<MFASetupResult | null>(null);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [backupCodesCopied, setBackupCodesCopied] = useState(false);\n const [showDisableDialog, setShowDisableDialog] = useState(false);\n\n const handleSetupMfa = async (method: MFAMethod) => {\n try {\n setError(null);\n setSuccessMessage(null);\n const data = await setupMFA(method);\n setSetupData(data);\n } catch (err) {\n console.error(\"Failed to setup MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to setup MFA. Please try again.\",\n );\n }\n };\n\n const handleVerifyAndEnable = async () => {\n if (verificationCode.length !== 6) {\n setError(tr(\"auth.mfaSetup.enter6Digit\", \"Please enter a 6-digit code\"));\n return;\n }\n\n try {\n setError(null);\n const success = await enableMFA(verificationCode);\n if (success) {\n setSetupData(null);\n setVerificationCode(\"\");\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.enableSuccess\",\n \"Two-factor authentication has been enabled successfully!\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to verify MFA code:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Invalid verification code. Please try again.\",\n );\n }\n };\n\n const handleDisableMfa = async () => {\n try {\n setError(null);\n setSuccessMessage(null);\n await disableMFA();\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.disableSuccess\",\n \"Two-factor authentication has been disabled.\",\n ),\n );\n setShowDisableDialog(false);\n } catch (err) {\n console.error(\"Failed to disable MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to disable MFA. Please try again.\",\n );\n }\n };\n\n const copyBackupCodes = async () => {\n if (setupData?.backupCodes) {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(setupData.backupCodes.join(\"\\n\"));\n if (ok) {\n setBackupCodesCopied(true);\n void nativeNotify(\"success\");\n setTimeout(() => setBackupCodesCopied(false), 2000);\n } else {\n void nativeNotify(\"error\");\n }\n }\n };\n\n const cancelSetup = () => {\n setSetupData(null);\n setVerificationCode(\"\");\n setError(null);\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-default rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.mfaSetup.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.mfaSetup.signInToManage\",\n \"Please sign in to manage MFA settings\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDisableDialog}\n onOpenChange={setShowDisableDialog}\n title={tr(\n \"auth.mfaSetup.disableTitle\",\n \"Disable two-factor authentication?\",\n )}\n description={tr(\n \"auth.mfaSetup.disableDesc\",\n \"Your account will become less secure and future sign-ins will only require your password until MFA is re-enabled.\",\n )}\n confirmText={tr(\"auth.mfaSetup.disableConfirm\", \"Disable MFA\")}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleDisableMfa}\n />\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.title\", \"Two-Factor Authentication\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.mfaSetup.subtitle\",\n \"Add an extra layer of security to your account\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.mfaSetup.purpose\",\n \"Two-factor authentication (2FA) asks for a one-time code from an authenticator app at sign-in, so your account stays safe even if your password leaks. Set it up here and keep your backup codes somewhere safe.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <div className=\"flex items-center gap-3\">\n <svg\n className=\"size-5 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {setupData ? (\n /* Setup Flow */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"size-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-5 text-[var(--color-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={2}\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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.setupAuthApp\", \"Setup Authenticator App\")}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.scanQr\",\n \"Scan the QR code with your authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-6\">\n {/* QR Code */}\n {setupData.qrCode && (\n <div className=\"flex justify-center\">\n <div className=\"p-4 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <QRCodeSVG\n value={setupData.qrCode}\n size={192}\n level=\"M\"\n includeMargin={false}\n className=\"size-48\"\n />\n </div>\n </div>\n )}\n\n {/* Manual Secret */}\n {setupData.secret && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-2\">\n {tr(\n \"auth.mfaSetup.cantScan\",\n \"Can't scan the QR code? Enter this key manually:\",\n )}\n </p>\n <code className=\"block p-3 rounded-lg bg-[var(--color-bg-muted)] text-[var(--color-text-primary)] font-mono text-sm break-all select-all\">\n {setupData.secret}\n </code>\n </div>\n )}\n\n {/* Backup Codes */}\n {setupData.backupCodes && setupData.backupCodes.length > 0 && (\n <div className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\">\n <div className=\"flex items-start gap-3 mb-3\">\n <svg\n className=\"size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <h3 className=\"font-medium text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.saveBackupCodes\",\n \"Save your backup codes\",\n )}\n </h3>\n <p className=\"text-sm text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.storeCodesSafely\",\n \"Store these codes safely. You can use them to access your account if you lose your authenticator device.\",\n )}\n </p>\n </div>\n </div>\n <div className=\"p-3 rounded-lg bg-[var(--color-bg-surface)] grid grid-cols-2 gap-2 mb-3\">\n {setupData.backupCodes.map((code, i) => (\n <code\n key={code}\n className=\"text-sm text-[var(--color-text-primary)] font-mono\"\n >\n {i + 1}. {code}\n </code>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={copyBackupCodes}\n className=\"text-sm font-medium text-[var(--color-status-warning-text)] hover:underline transition-colors duration-200\"\n >\n {backupCodesCopied\n ? tr(\"auth.mfaSetup.copied\", \"Copied!\")\n : tr(\"auth.mfaSetup.copyBackupCodes\", \"Copy backup codes\")}\n </button>\n </div>\n )}\n\n {/* Verification Code Input */}\n <div>\n <label\n htmlFor=\"mfa-verification-code\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n </label>\n <input\n id=\"mfa-verification-code\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={6}\n value={verificationCode}\n onChange={(e) =>\n setVerificationCode(e.target.value.replace(/\\D/g, \"\"))\n }\n placeholder={tr(\n \"auth.mfa.verificationCodePlaceholder\",\n \"000000\",\n )}\n aria-label={tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n aria-required=\"true\"\n autoComplete=\"one-time-code\"\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] text-center text-2xl tracking-widest font-mono focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n />\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={cancelSetup}\n className=\"flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.cancel\", \"Cancel\")}\n </button>\n <Button\n type=\"button\"\n onClick={handleVerifyAndEnable}\n disabled={isLoading || verificationCode.length !== 6}\n variant=\"default\"\n size=\"lg\"\n className=\"flex-1 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.verifying\", \"Verifying...\")}</span>\n </>\n ) : (\n tr(\"auth.mfaSetup.enable2fa\", \"Enable 2FA\")\n )}\n </Button>\n </div>\n </div>\n </div>\n ) : (\n /* Status Display */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n {!user?.mfaEnabled ? (\n /* MFA Not Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-text-tertiary)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOff\",\n \"Two-Factor Authentication is Off\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.notProtected\",\n \"Your account is not protected with 2FA\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-bg-accent)] border border-[var(--color-border-default)]\">\n <h3 className=\"font-medium text-[var(--color-text-link)] mb-2\">\n {tr(\"auth.mfaSetup.whyEnable\", \"Why enable 2FA?\")}\n </h3>\n <ul className=\"text-sm text-[var(--color-text-link)] space-y-1\">\n <li>\n • Protects your account even if your password is compromised\n </li>\n <li>• Adds a second verification step during sign-in</li>\n <li>\n • Works with popular authenticator apps (Google, Authy,\n etc.)\n </li>\n </ul>\n </div>\n\n <Button\n type=\"button\"\n onClick={() => handleSetupMfa(\"TOTP\")}\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.settingUp\", \"Setting up...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 <span>\n {tr(\n \"auth.mfaSetup.enableTwoFactor\",\n \"Enable Two-Factor Authentication\",\n )}\n </span>\n </>\n )}\n </Button>\n </>\n ) : (\n /* MFA Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOn\",\n \"Two-Factor Authentication is On\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.protected\",\n \"Your account is protected with an authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.mfaSetup.verificationOnSignIn\",\n \"You'll be asked for a verification code from your authenticator app when signing in.\",\n )}\n </p>\n </div>\n\n <button\n type=\"button\"\n onClick={() => setShowDisableDialog(true)}\n disabled={isLoading}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.disabling\", \"Disabling...\")}</span>\n </>\n ) : (\n tr(\n \"auth.mfaSetup.disableTwoFactor\",\n \"Disable Two-Factor Authentication\",\n )\n )}\n </button>\n\n <NextSteps\n className=\"mt-6\"\n storageKey=\"auth-mfa-enabled\"\n steps={[\n {\n id: \"review-sessions\",\n label: tr(\n \"auth.mfaSetup.nextReviewSessions\",\n \"Review your active sessions\",\n ),\n description: tr(\n \"auth.mfaSetup.nextReviewSessionsDesc\",\n \"Sign out any device you no longer recognise.\",\n ),\n onClick: () => navigate(\"/auth/sessions\"),\n },\n {\n id: \"security-center\",\n label: tr(\n \"auth.mfaSetup.nextSecurityCenter\",\n \"Visit the Security Center\",\n ),\n description: tr(\n \"auth.mfaSetup.nextSecurityCenterDesc\",\n \"Check your password and account protection at a glance.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n {\n id: \"update-profile\",\n label: tr(\n \"auth.mfaSetup.nextProfile\",\n \"Keep your profile up to date\",\n ),\n description: tr(\n \"auth.mfaSetup.nextProfileDesc\",\n \"Confirm your recovery email and contact details.\",\n ),\n onClick: () => navigate(\"/auth/profile\"),\n },\n ]}\n />\n </>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MfaSetupPage.js","names":["_Fragment","MfaSetupPage","tr","useTr","navigate","useNavigate","useMfaOperations","user","useAuthStore","state","isAuthenticated","setupData","setSetupData","useState","verificationCode","setVerificationCode","error","setError","successMessage","setSuccessMessage","backupCodesCopied","setBackupCodesCopied","showDisableDialog","setShowDisableDialog","handleSetupMfa","data","setupMFA","method","err","ConfirmActionDialog","isLoading","disableMFA","PagePurpose","AlertIcon","QRCodeSVG","i","code","nativeImpact","nativeCopyText","nativeNotify","Button","enableMFA","Fragment","SpinnerIcon","NextSteps"],"sources":["../../../src/auth/pages/MfaSetupPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { QRCodeSVG } from \"qrcode.react\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\nimport { SpinnerIcon, AlertIcon } from \"../components/AuthIcons\";\nimport type { MFAMethod, MFASetupResult } from \"../types\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\n\n/**\n * MFA setup and management page with professional UI\n */\nexport function MfaSetupPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const { setupMFA, enableMFA, disableMFA, isLoading } = useMfaOperations();\n const user = useAuthStore((state) => state.user);\n const isAuthenticated = useAuthStore((state) => state.isAuthenticated);\n const [setupData, setSetupData] = useState<MFASetupResult | null>(null);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [backupCodesCopied, setBackupCodesCopied] = useState(false);\n const [showDisableDialog, setShowDisableDialog] = useState(false);\n\n const handleSetupMfa = async (method: MFAMethod) => {\n try {\n setError(null);\n setSuccessMessage(null);\n const data = await setupMFA(method);\n setSetupData(data);\n } catch (err) {\n console.error(\"Failed to setup MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to setup MFA. Please try again.\",\n );\n }\n };\n\n const handleVerifyAndEnable = async () => {\n if (verificationCode.length !== 6) {\n setError(tr(\"auth.mfaSetup.enter6Digit\", \"Please enter a 6-digit code\"));\n return;\n }\n\n try {\n setError(null);\n const success = await enableMFA(verificationCode);\n if (success) {\n setSetupData(null);\n setVerificationCode(\"\");\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.enableSuccess\",\n \"Two-factor authentication has been enabled successfully!\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to verify MFA code:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Invalid verification code. Please try again.\",\n );\n }\n };\n\n const handleDisableMfa = async () => {\n try {\n setError(null);\n setSuccessMessage(null);\n await disableMFA();\n setSuccessMessage(\n tr(\n \"auth.mfaSetup.disableSuccess\",\n \"Two-factor authentication has been disabled.\",\n ),\n );\n setShowDisableDialog(false);\n } catch (err) {\n console.error(\"Failed to disable MFA:\", err);\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to disable MFA. Please try again.\",\n );\n }\n };\n\n const copyBackupCodes = async () => {\n if (setupData?.backupCodes) {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(setupData.backupCodes.join(\"\\n\"));\n if (ok) {\n setBackupCodesCopied(true);\n void nativeNotify(\"success\");\n setTimeout(() => setBackupCodesCopied(false), 2000);\n } else {\n void nativeNotify(\"error\");\n }\n }\n };\n\n const cancelSetup = () => {\n setSetupData(null);\n setVerificationCode(\"\");\n setError(null);\n };\n\n // Authentication check\n if (!isAuthenticated) {\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-default rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.mfaSetup.authRequired\", \"Authentication Required\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.mfaSetup.signInToManage\",\n \"Please sign in to manage MFA settings\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-2xl mx-auto p-6\">\n <ConfirmActionDialog\n open={showDisableDialog}\n onOpenChange={setShowDisableDialog}\n title={tr(\n \"auth.mfaSetup.disableTitle\",\n \"Disable two-factor authentication?\",\n )}\n description={tr(\n \"auth.mfaSetup.disableDesc\",\n \"Your account will become less secure and future sign-ins will only require your password until MFA is re-enabled.\",\n )}\n confirmText={tr(\"auth.mfaSetup.disableConfirm\", \"Disable MFA\")}\n variant=\"warning\"\n isPending={isLoading}\n onConfirm={handleDisableMfa}\n />\n {/* Header */}\n <div className=\"mb-8\">\n <h1 className=\"text-3xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.title\", \"Two-Factor Authentication\")}\n </h1>\n <p className=\"text-[var(--color-text-secondary)] mt-2\">\n {tr(\n \"auth.mfaSetup.subtitle\",\n \"Add an extra layer of security to your account\",\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n \"auth.mfaSetup.purpose\",\n \"Two-factor authentication (2FA) asks for a one-time code from an authenticator app at sign-in, so your account stays safe even if your password leaks. Set it up here and keep your backup codes somewhere safe.\",\n )}\n </PagePurpose>\n </div>\n\n {/* Success Message */}\n {successMessage && (\n <div className=\"mb-6 p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <div className=\"flex items-center gap-3\">\n <svg\n className=\"size-5 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-[var(--color-status-success-text)]\">\n {successMessage}\n </p>\n </div>\n </div>\n )}\n\n {/* Error Message */}\n {error && (\n <div className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.dismiss\", \"Dismiss\")}\n </button>\n </div>\n </div>\n )}\n\n {setupData ? (\n /* Setup Flow */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"size-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-5 text-[var(--color-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={2}\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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\"auth.mfaSetup.setupAuthApp\", \"Setup Authenticator App\")}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.scanQr\",\n \"Scan the QR code with your authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-6\">\n {/* QR Code */}\n {setupData.qrCode && (\n <div className=\"flex justify-center\">\n <div className=\"p-4 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <QRCodeSVG\n value={setupData.qrCode}\n size={192}\n level=\"M\"\n includeMargin={false}\n className=\"size-48\"\n />\n </div>\n </div>\n )}\n\n {/* Manual Secret */}\n {setupData.secret && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-2\">\n {tr(\n \"auth.mfaSetup.cantScan\",\n \"Can't scan the QR code? Enter this key manually:\",\n )}\n </p>\n <code className=\"block p-3 rounded-lg bg-[var(--color-bg-muted)] text-[var(--color-text-primary)] font-mono text-sm break-all select-all\">\n {setupData.secret}\n </code>\n </div>\n )}\n\n {/* Backup Codes */}\n {setupData.backupCodes && setupData.backupCodes.length > 0 && (\n <div className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\">\n <div className=\"flex items-start gap-3 mb-3\">\n <svg\n className=\"size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <h3 className=\"font-medium text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.saveBackupCodes\",\n \"Save your backup codes\",\n )}\n </h3>\n <p className=\"text-sm text-[var(--color-status-warning-text)]\">\n {tr(\n \"auth.mfaSetup.storeCodesSafely\",\n \"Store these codes safely. You can use them to access your account if you lose your authenticator device.\",\n )}\n </p>\n </div>\n </div>\n <div className=\"p-3 rounded-lg bg-[var(--color-bg-surface)] grid grid-cols-2 gap-2 mb-3\">\n {setupData.backupCodes.map((code, i) => (\n <code\n key={code}\n className=\"text-sm text-[var(--color-text-primary)] font-mono\"\n >\n {i + 1}. {code}\n </code>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={copyBackupCodes}\n className=\"text-sm font-medium text-[var(--color-status-warning-text)] hover:underline transition-colors duration-200\"\n >\n {backupCodesCopied\n ? tr(\"auth.mfaSetup.copied\", \"Copied!\")\n : tr(\"auth.mfaSetup.copyBackupCodes\", \"Copy backup codes\")}\n </button>\n </div>\n )}\n\n {/* Verification Code Input */}\n <div>\n <label\n htmlFor=\"mfa-verification-code\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n </label>\n <input\n id=\"mfa-verification-code\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={6}\n value={verificationCode}\n onChange={(e) =>\n setVerificationCode(e.target.value.replace(/\\D/g, \"\"))\n }\n placeholder={tr(\n \"auth.mfa.verificationCodePlaceholder\",\n \"000000\",\n )}\n aria-label={tr(\n \"auth.mfaSetup.enterCode\",\n \"Enter the 6-digit code from your app\",\n )}\n aria-required=\"true\"\n autoComplete=\"one-time-code\"\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] text-center text-2xl tracking-widest font-mono focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all duration-300\"\n />\n </div>\n\n {/* Action Buttons */}\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={cancelSetup}\n className=\"flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200\"\n >\n {tr(\"auth.mfaSetup.cancel\", \"Cancel\")}\n </button>\n <Button\n type=\"button\"\n onClick={handleVerifyAndEnable}\n disabled={isLoading || verificationCode.length !== 6}\n variant=\"default\"\n size=\"lg\"\n className=\"flex-1 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.verifying\", \"Verifying...\")}</span>\n </>\n ) : (\n tr(\"auth.mfaSetup.enable2fa\", \"Enable 2FA\")\n )}\n </Button>\n </div>\n </div>\n </div>\n ) : (\n /* Status Display */\n <div className=\"p-6 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-sm\">\n {!user?.mfaEnabled ? (\n /* MFA Not Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-text-tertiary)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOff\",\n \"Two-Factor Authentication is Off\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.notProtected\",\n \"Your account is not protected with 2FA\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-bg-accent)] border border-[var(--color-border-default)]\">\n <h3 className=\"font-medium text-[var(--color-text-link)] mb-2\">\n {tr(\"auth.mfaSetup.whyEnable\", \"Why enable 2FA?\")}\n </h3>\n <ul className=\"text-sm text-[var(--color-text-link)] space-y-1\">\n <li>\n • Protects your account even if your password is compromised\n </li>\n <li>• Adds a second verification step during sign-in</li>\n <li>\n • Works with popular authenticator apps (Google, Authy,\n etc.)\n </li>\n </ul>\n </div>\n\n <Button\n type=\"button\"\n onClick={() => handleSetupMfa(\"TOTP\")}\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.settingUp\", \"Setting up...\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 <span>\n {tr(\n \"auth.mfaSetup.enableTwoFactor\",\n \"Enable Two-Factor Authentication\",\n )}\n </span>\n </>\n )}\n </Button>\n </>\n ) : (\n /* MFA Enabled */\n <>\n <div className=\"flex items-center gap-4 mb-6\">\n <div className=\"size-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"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 </div>\n <div>\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr(\n \"auth.mfaSetup.twoFactorOn\",\n \"Two-Factor Authentication is On\",\n )}\n </h2>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaSetup.protected\",\n \"Your account is protected with an authenticator app\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"mb-6 p-4 rounded-lg bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)]\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.mfaSetup.verificationOnSignIn\",\n \"You'll be asked for a verification code from your authenticator app when signing in.\",\n )}\n </p>\n </div>\n\n <button\n type=\"button\"\n onClick={() => setShowDisableDialog(true)}\n disabled={isLoading}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.mfa.disabling\", \"Disabling...\")}</span>\n </>\n ) : (\n tr(\n \"auth.mfaSetup.disableTwoFactor\",\n \"Disable Two-Factor Authentication\",\n )\n )}\n </button>\n\n <NextSteps\n className=\"mt-6\"\n storageKey=\"auth-mfa-enabled\"\n steps={[\n {\n id: \"review-sessions\",\n label: tr(\n \"auth.mfaSetup.nextReviewSessions\",\n \"Review your active sessions\",\n ),\n description: tr(\n \"auth.mfaSetup.nextReviewSessionsDesc\",\n \"Sign out any device you no longer recognise.\",\n ),\n onClick: () => navigate(\"/auth/sessions\"),\n },\n {\n id: \"security-center\",\n label: tr(\n \"auth.mfaSetup.nextSecurityCenter\",\n \"Visit the Security Center\",\n ),\n description: tr(\n \"auth.mfaSetup.nextSecurityCenterDesc\",\n \"Check your password and account protection at a glance.\",\n ),\n onClick: () => navigate(\"/auth/security\"),\n },\n {\n id: \"update-profile\",\n label: tr(\n \"auth.mfaSetup.nextProfile\",\n \"Keep your profile up to date\",\n ),\n description: tr(\n \"auth.mfaSetup.nextProfileDesc\",\n \"Confirm your recovery email and contact details.\",\n ),\n onClick: () => navigate(\"/auth/profile\"),\n },\n ]}\n />\n </>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,26 +1,26 @@
1
- import { useAuthStore as e } from "../store/authStore.js";
2
- import { useTr as t } from "../../shared/hooks/useTr.js";
3
- import { AuthLayout as n } from "../components/AuthLayout.js";
4
- import { AlertIcon as r, SpinnerIcon as i } from "../components/AuthIcons.js";
5
- import { MFA_CHALLENGE_ID as a, MFA_PENDING_REFRESH as o, MFA_PENDING_TOKEN as s, MFA_REDIRECT as c } from "../constants/sessionKeys.js";
6
- import { useMfaOperations as l } from "../hooks/useMfaOperations.js";
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { useMfaOperations as t } from "../hooks/useMfaOperations.js";
3
+ import { useAuthStore as n } from "../store/authStore.js";
4
+ import { AuthLayout as r } from "../components/AuthLayout.js";
5
+ import { AlertIcon as i, SpinnerIcon as a } from "../components/AuthIcons.js";
6
+ import { MFA_CHALLENGE_ID as o, MFA_PENDING_REFRESH as s, MFA_PENDING_TOKEN as c, MFA_REDIRECT as l } from "../constants/sessionKeys.js";
7
7
  import { useEffect as u, useRef as d, useState as f } from "react";
8
8
  import { useNavigate as p } from "react-router";
9
9
  import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
10
10
  import { Button as _ } from "@burdenoff/fe-libs/button";
11
11
  import { PagePurpose as v } from "@burdenoff/fe-libs/ui";
12
- //#region src/auth/pages/MfaVerifyPage.tsx
12
+ // src/auth/pages/MfaVerifyPage.tsx
13
13
  function y() {
14
- let y = t(), b = p(), { verifyMFA: x, mfaChallengeId: S, isLoading: C } = l(), [w, T] = f([
14
+ let y = e(), b = p(), { verifyMFA: x, mfaChallengeId: S, isLoading: C } = t(), [w, T] = f([
15
15
  "",
16
16
  "",
17
17
  "",
18
18
  "",
19
19
  "",
20
20
  ""
21
- ]), [E, D] = f(null), O = d([]), k = typeof window < "u" ? sessionStorage.getItem(a) : null, A = S || k;
21
+ ]), [E, D] = f(null), O = d([]), k = typeof window < "u" ? sessionStorage.getItem(o) : null, A = S || k;
22
22
  u(() => {
23
- O.current[0]?.focus(), sessionStorage.getItem(s), sessionStorage.getItem(c);
23
+ O.current[0]?.focus(), sessionStorage.getItem(c), sessionStorage.getItem(l);
24
24
  }, [k]);
25
25
  let j = (e, t) => {
26
26
  let n = t.replace(/\D/g, "").slice(-1), r = [...w];
@@ -38,32 +38,29 @@ function y() {
38
38
  }), T(n);
39
39
  let r = Math.min(t.length - 1, 5);
40
40
  O.current[r]?.focus(), t.length === 6 && P(t);
41
- }, P = async (t) => {
42
- let n = t || w.join("");
43
- if (n.length === 6) {
44
- if (D(null), !A) {
45
- D(y("auth.mfaVerify.sessionExpired", "MFA session expired. Please log in again."));
46
- return;
47
- }
48
- try {
49
- let t = await x({
41
+ }, P = async (e) => {
42
+ let t = e || w.join("");
43
+ if (t.length === 6) {
44
+ if (D(null), !A) D(y("auth.mfaVerify.sessionExpired", "MFA session expired. Please log in again."));
45
+ else try {
46
+ let e = await x({
50
47
  challengeId: A,
51
- code: n,
48
+ code: t,
52
49
  method: "TOTP"
53
50
  });
54
- if (t.success) {
55
- let e = sessionStorage.getItem(c);
56
- sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), b(e || "/", { replace: !0 });
51
+ if (e.success) {
52
+ let e = sessionStorage.getItem(l);
53
+ sessionStorage.removeItem(o), sessionStorage.removeItem(c), sessionStorage.removeItem(s), sessionStorage.removeItem(l), b(e || "/", { replace: !0 });
57
54
  } else {
58
- if (t.error === "mfa_challenge_expired") {
59
- sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), b("/auth/login", {
55
+ if (e.error === "mfa_challenge_expired") {
56
+ sessionStorage.removeItem(o), sessionStorage.removeItem(c), sessionStorage.removeItem(s), sessionStorage.removeItem(l), n.getState().setMfaRequired(!1), b("/auth/login", {
60
57
  replace: !0,
61
58
  state: { message: y("auth.mfaVerify.sessionExpiredMessage", "Your verification session expired. Please sign in again.") }
62
59
  });
63
60
  return;
64
61
  }
65
- let n = t.error ?? "", r = /cannot read propert|null|undefined|typeerror/i.test(n);
66
- D(!n || r ? y("auth.mfaVerify.invalidCode", "Invalid verification code. Please try again.") : n), T([
62
+ let t = e.error ?? "", r = /cannot read propert|null|undefined|typeerror/i.test(t);
63
+ D(!t || r ? y("auth.mfaVerify.invalidCode", "Invalid verification code. Please try again.") : t), T([
67
64
  "",
68
65
  "",
69
66
  "",
@@ -72,9 +69,9 @@ function y() {
72
69
  ""
73
70
  ]), O.current[0]?.focus();
74
71
  }
75
- } catch (t) {
76
- if (console.error("MFA verification failed:", t), (t instanceof Error ? t.message : String(t)).includes("mfa_challenge_expired")) {
77
- sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), b("/auth/login", {
72
+ } catch (e) {
73
+ if (console.error("MFA verification failed:", e), (e instanceof Error ? e.message : String(e)).includes("mfa_challenge_expired")) {
74
+ sessionStorage.removeItem(o), sessionStorage.removeItem(c), sessionStorage.removeItem(s), sessionStorage.removeItem(l), n.getState().setMfaRequired(!1), b("/auth/login", {
78
75
  replace: !0,
79
76
  state: { message: y("auth.mfaVerify.sessionExpiredMessage", "Your verification session expired. Please sign in again.") }
80
77
  });
@@ -91,7 +88,7 @@ function y() {
91
88
  }
92
89
  }
93
90
  };
94
- return /* @__PURE__ */ g(n, {
91
+ return /* @__PURE__ */ g(r, {
95
92
  title: y("auth.mfaVerify.title", "Two-factor authentication"),
96
93
  subtitle: y("auth.mfaVerify.subtitle", "Enter the 6-digit code from your authenticator app"),
97
94
  children: [
@@ -125,7 +122,7 @@ function y() {
125
122
  children: [
126
123
  E && /* @__PURE__ */ g("div", {
127
124
  className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
128
- children: [/* @__PURE__ */ h(r, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ h("p", {
125
+ children: [/* @__PURE__ */ h(i, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ h("p", {
129
126
  className: "text-sm text-[var(--color-status-error-text)]",
130
127
  children: E
131
128
  })]
@@ -160,7 +157,7 @@ function y() {
160
157
  variant: "default",
161
158
  size: "lg",
162
159
  className: "w-full font-semibold",
163
- children: C ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ h("span", { children: y("auth.mfaVerify.verifying", "Verifying…") })] }) : y("auth.mfaVerify.verify", "Verify")
160
+ children: C ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(a, { className: "size-5 animate-spin" }), /* @__PURE__ */ h("span", { children: y("auth.mfaVerify.verifying", "Verifying…") })] }) : y("auth.mfaVerify.verify", "Verify")
164
161
  }),
165
162
  /* @__PURE__ */ g("div", {
166
163
  className: "text-center space-y-3",
@@ -181,7 +178,7 @@ function y() {
181
178
  children: /* @__PURE__ */ h("button", {
182
179
  type: "button",
183
180
  onClick: () => {
184
- sessionStorage.removeItem(a), sessionStorage.removeItem(s), sessionStorage.removeItem(o), sessionStorage.removeItem(c), e.getState().setMfaRequired(!1), b("/auth/login");
181
+ sessionStorage.removeItem(o), sessionStorage.removeItem(c), sessionStorage.removeItem(s), sessionStorage.removeItem(l), n.getState().setMfaRequired(!1), b("/auth/login");
185
182
  },
186
183
  className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
187
184
  children: y("auth.mfaVerify.backToSignIn", "Back to sign in")
@@ -190,7 +187,6 @@ function y() {
190
187
  ]
191
188
  });
192
189
  }
193
- //#endregion
194
190
  export { y as MfaVerifyPage };
195
191
 
196
192
  //# sourceMappingURL=MfaVerifyPage.js.map