@burdenoff/microfe-auth 2026.1030.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 +70 -64
  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":"MfaVerifyPage.js","names":[],"sources":["../../../src/auth/pages/MfaVerifyPage.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate } from \"react-router\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n} from \"../constants/sessionKeys\";\n\n/**\n * MFA verification page with professional design\n * Shown when user needs to verify MFA code during login\n * Handles both regular login MFA and SSO MFA flows\n */\nexport function MfaVerifyPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const {\n verifyMFA,\n mfaChallengeId: storeChallengeId,\n isLoading,\n } = useMfaOperations();\n const [code, setCode] = useState([\"\", \"\", \"\", \"\", \"\", \"\"]);\n const [error, setError] = useState<string | null>(null);\n const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n\n // Get challenge ID from store or sessionStorage (SSO flow)\n const sessionChallengeId =\n typeof window !== \"undefined\"\n ? sessionStorage.getItem(MFA_CHALLENGE_ID)\n : null;\n const mfaChallengeId = storeChallengeId || sessionChallengeId;\n\n // Focus first input on mount and detect SSO flow\n useEffect(() => {\n inputRefs.current[0]?.focus();\n\n // Check if this is an SSO flow (has pending tokens in sessionStorage)\n const hasPendingToken = sessionStorage.getItem(MFA_PENDING_TOKEN);\n const hasRedirect = sessionStorage.getItem(MFA_REDIRECT);\n if (hasPendingToken || hasRedirect || sessionChallengeId) {\n }\n }, [sessionChallengeId]);\n\n const handleChange = (index: number, value: string) => {\n // Only allow digits\n const digit = value.replace(/\\D/g, \"\").slice(-1);\n\n const newCode = [...code];\n newCode[index] = digit;\n setCode(newCode);\n\n // Auto-focus next input\n if (digit && index < 5) {\n inputRefs.current[index + 1]?.focus();\n }\n\n // Auto-submit when all digits are entered\n if (digit && index === 5) {\n const fullCode = newCode.join(\"\");\n if (fullCode.length === 6) {\n handleSubmit(fullCode);\n }\n }\n };\n\n const handleKeyDown = (index: number, e: React.KeyboardEvent) => {\n if (e.key === \"Backspace\" && !code[index] && index > 0) {\n inputRefs.current[index - 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pastedData = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, 6);\n const newCode = [...code];\n pastedData.split(\"\").forEach((char, i) => {\n if (i < 6) newCode[i] = char;\n });\n setCode(newCode);\n\n // Focus last filled input or first empty\n const lastIndex = Math.min(pastedData.length - 1, 5);\n inputRefs.current[lastIndex]?.focus();\n\n // Auto-submit if full code pasted\n if (pastedData.length === 6) {\n handleSubmit(pastedData);\n }\n };\n\n const handleSubmit = async (fullCode?: string) => {\n const codeToSubmit = fullCode || code.join(\"\");\n if (codeToSubmit.length !== 6) return;\n\n setError(null);\n\n if (!mfaChallengeId) {\n setError(\n tr(\n \"auth.mfaVerify.sessionExpired\",\n \"MFA session expired. Please log in again.\",\n ),\n );\n return;\n }\n\n try {\n const result = await verifyMFA({\n challengeId: mfaChallengeId,\n code: codeToSubmit,\n method: \"TOTP\",\n });\n\n if (result.success) {\n // Clean up SSO session data\n const ssoRedirect = sessionStorage.getItem(MFA_REDIRECT);\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n\n // Redirect to SSO redirect URL or default to dashboard\n const redirectUrl = ssoRedirect || \"/\";\n navigate(redirectUrl, { replace: true });\n } else {\n // Handle expired challenge - redirect to login\n if (result.error === \"mfa_challenge_expired\") {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: tr(\n \"auth.mfaVerify.sessionExpiredMessage\",\n \"Your verification session expired. Please sign in again.\",\n ),\n },\n });\n return;\n }\n\n // Sanitize technical / null-deref-style messages bubbling up from\n // resolver internals — always show a user-friendly fallback.\n const rawErr = result.error ?? \"\";\n const isTechnical =\n /cannot read propert|null|undefined|typeerror/i.test(rawErr);\n setError(\n !rawErr || isTechnical\n ? tr(\n \"auth.mfaVerify.invalidCode\",\n \"Invalid verification code. Please try again.\",\n )\n : rawErr,\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n } catch (err) {\n console.error(\"MFA verification failed:\", err);\n\n // Check if the error is about expired challenge\n const errorMessage = err instanceof Error ? err.message : String(err);\n if (errorMessage.includes(\"mfa_challenge_expired\")) {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: tr(\n \"auth.mfaVerify.sessionExpiredMessage\",\n \"Your verification session expired. Please sign in again.\",\n ),\n },\n });\n return;\n }\n\n setError(\n tr(\n \"auth.mfaVerify.verificationFailed\",\n \"Verification failed. Please try again.\",\n ),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n };\n\n return (\n <AuthLayout\n title={tr(\"auth.mfaVerify.title\", \"Two-factor authentication\")}\n subtitle={tr(\n \"auth.mfaVerify.subtitle\",\n \"Enter the 6-digit code from your authenticator app\",\n )}\n >\n {/* Shield Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-8 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=\"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\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.mfaVerify.purpose\",\n \"This extra step confirms it's really you. Open your authenticator app and enter the current 6-digit code to finish signing in.\",\n )}\n </PagePurpose>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n handleSubmit();\n }}\n className=\"space-y-6\"\n >\n {/* Error Alert */}\n {error && (\n <div className=\"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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Code Input */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)] text-center\">\n {tr(\"auth.mfaVerify.verificationCode\", \"Verification Code\")}\n </label>\n <div className=\"flex justify-center gap-2 sm:gap-3\">\n {code.map((digit, index) => (\n <input\n key={index}\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n maxLength={1}\n aria-label={\n tr(\n \"auth.mfaVerify.digitAriaLabel\",\n \"Verification code digit\",\n ) + ` ${index + 1}`\n }\n value={digit}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n className=\"w-10 h-12 sm:w-12 sm:h-14 text-center text-xl sm:text-2xl font-semibold rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n ))}\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading || code.join(\"\").length !== 6}\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.mfaVerify.verifying\", \"Verifying…\")}</span>\n </>\n ) : (\n tr(\"auth.mfaVerify.verify\", \"Verify\")\n )}\n </Button>\n\n {/* Recovery Options */}\n <div className=\"text-center space-y-3\">\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaVerify.cantAccess\",\n \"Can't access your authenticator app?\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa-recovery\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.useRecoveryCode\", \"Use a recovery code\")}\n </button>\n </div>\n </form>\n\n {/* Back to Login */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n <button\n type=\"button\"\n onClick={() => {\n // Clear MFA state so LoginPage doesn't immediately redirect back here\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n navigate(\"/auth/login\");\n }}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MfaVerifyPage.js","names":["_Fragment","MfaVerifyPage","tr","useTr","navigate","useNavigate","storeChallengeId","useMfaOperations","code","setCode","useState","error","setError","inputRefs","useRef","sessionChallengeId","MFA_CHALLENGE_ID","mfaChallengeId","MFA_PENDING_TOKEN","MFA_REDIRECT","handleChange","digit","value","newCode","index","fullCode","handleSubmit","handleKeyDown","e","handlePaste","pastedData","i","char","lastIndex","codeToSubmit","result","verifyMFA","ssoRedirect","MFA_PENDING_REFRESH","useAuthStore","rawErr","isTechnical","err","AuthLayout","PagePurpose","AlertIcon","el","Button","isLoading","Fragment","SpinnerIcon"],"sources":["../../../src/auth/pages/MfaVerifyPage.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate } from \"react-router\";\nimport { useMfaOperations } from \"../hooks/useMfaOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n MFA_CHALLENGE_ID,\n MFA_PENDING_TOKEN,\n MFA_PENDING_REFRESH,\n MFA_REDIRECT,\n} from \"../constants/sessionKeys\";\n\n/**\n * MFA verification page with professional design\n * Shown when user needs to verify MFA code during login\n * Handles both regular login MFA and SSO MFA flows\n */\nexport function MfaVerifyPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const {\n verifyMFA,\n mfaChallengeId: storeChallengeId,\n isLoading,\n } = useMfaOperations();\n const [code, setCode] = useState([\"\", \"\", \"\", \"\", \"\", \"\"]);\n const [error, setError] = useState<string | null>(null);\n const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n\n // Get challenge ID from store or sessionStorage (SSO flow)\n const sessionChallengeId =\n typeof window !== \"undefined\"\n ? sessionStorage.getItem(MFA_CHALLENGE_ID)\n : null;\n const mfaChallengeId = storeChallengeId || sessionChallengeId;\n\n // Focus first input on mount and detect SSO flow\n useEffect(() => {\n inputRefs.current[0]?.focus();\n\n // Check if this is an SSO flow (has pending tokens in sessionStorage)\n const hasPendingToken = sessionStorage.getItem(MFA_PENDING_TOKEN);\n const hasRedirect = sessionStorage.getItem(MFA_REDIRECT);\n if (hasPendingToken || hasRedirect || sessionChallengeId) {\n }\n }, [sessionChallengeId]);\n\n const handleChange = (index: number, value: string) => {\n // Only allow digits\n const digit = value.replace(/\\D/g, \"\").slice(-1);\n\n const newCode = [...code];\n newCode[index] = digit;\n setCode(newCode);\n\n // Auto-focus next input\n if (digit && index < 5) {\n inputRefs.current[index + 1]?.focus();\n }\n\n // Auto-submit when all digits are entered\n if (digit && index === 5) {\n const fullCode = newCode.join(\"\");\n if (fullCode.length === 6) {\n handleSubmit(fullCode);\n }\n }\n };\n\n const handleKeyDown = (index: number, e: React.KeyboardEvent) => {\n if (e.key === \"Backspace\" && !code[index] && index > 0) {\n inputRefs.current[index - 1]?.focus();\n }\n };\n\n const handlePaste = (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pastedData = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\")\n .slice(0, 6);\n const newCode = [...code];\n pastedData.split(\"\").forEach((char, i) => {\n if (i < 6) newCode[i] = char;\n });\n setCode(newCode);\n\n // Focus last filled input or first empty\n const lastIndex = Math.min(pastedData.length - 1, 5);\n inputRefs.current[lastIndex]?.focus();\n\n // Auto-submit if full code pasted\n if (pastedData.length === 6) {\n handleSubmit(pastedData);\n }\n };\n\n const handleSubmit = async (fullCode?: string) => {\n const codeToSubmit = fullCode || code.join(\"\");\n if (codeToSubmit.length !== 6) return;\n\n setError(null);\n\n if (!mfaChallengeId) {\n setError(\n tr(\n \"auth.mfaVerify.sessionExpired\",\n \"MFA session expired. Please log in again.\",\n ),\n );\n return;\n }\n\n try {\n const result = await verifyMFA({\n challengeId: mfaChallengeId,\n code: codeToSubmit,\n method: \"TOTP\",\n });\n\n if (result.success) {\n // Clean up SSO session data\n const ssoRedirect = sessionStorage.getItem(MFA_REDIRECT);\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n\n // Redirect to SSO redirect URL or default to dashboard\n const redirectUrl = ssoRedirect || \"/\";\n navigate(redirectUrl, { replace: true });\n } else {\n // Handle expired challenge - redirect to login\n if (result.error === \"mfa_challenge_expired\") {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: tr(\n \"auth.mfaVerify.sessionExpiredMessage\",\n \"Your verification session expired. Please sign in again.\",\n ),\n },\n });\n return;\n }\n\n // Sanitize technical / null-deref-style messages bubbling up from\n // resolver internals — always show a user-friendly fallback.\n const rawErr = result.error ?? \"\";\n const isTechnical =\n /cannot read propert|null|undefined|typeerror/i.test(rawErr);\n setError(\n !rawErr || isTechnical\n ? tr(\n \"auth.mfaVerify.invalidCode\",\n \"Invalid verification code. Please try again.\",\n )\n : rawErr,\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n } catch (err) {\n console.error(\"MFA verification failed:\", err);\n\n // Check if the error is about expired challenge\n const errorMessage = err instanceof Error ? err.message : String(err);\n if (errorMessage.includes(\"mfa_challenge_expired\")) {\n // Clear all MFA state\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n\n // Redirect to login with message\n navigate(\"/auth/login\", {\n replace: true,\n state: {\n message: tr(\n \"auth.mfaVerify.sessionExpiredMessage\",\n \"Your verification session expired. Please sign in again.\",\n ),\n },\n });\n return;\n }\n\n setError(\n tr(\n \"auth.mfaVerify.verificationFailed\",\n \"Verification failed. Please try again.\",\n ),\n );\n setCode([\"\", \"\", \"\", \"\", \"\", \"\"]);\n inputRefs.current[0]?.focus();\n }\n };\n\n return (\n <AuthLayout\n title={tr(\"auth.mfaVerify.title\", \"Two-factor authentication\")}\n subtitle={tr(\n \"auth.mfaVerify.subtitle\",\n \"Enter the 6-digit code from your authenticator app\",\n )}\n >\n {/* Shield Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center\">\n <svg\n className=\"size-8 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=\"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\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.mfaVerify.purpose\",\n \"This extra step confirms it's really you. Open your authenticator app and enter the current 6-digit code to finish signing in.\",\n )}\n </PagePurpose>\n <form\n onSubmit={(e) => {\n e.preventDefault();\n handleSubmit();\n }}\n className=\"space-y-6\"\n >\n {/* Error Alert */}\n {error && (\n <div className=\"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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Code Input */}\n <div className=\"space-y-2\">\n <label className=\"block text-sm font-medium text-[var(--color-text-primary)] text-center\">\n {tr(\"auth.mfaVerify.verificationCode\", \"Verification Code\")}\n </label>\n <div className=\"flex justify-center gap-2 sm:gap-3\">\n {code.map((digit, index) => (\n <input\n key={index}\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n maxLength={1}\n aria-label={\n tr(\n \"auth.mfaVerify.digitAriaLabel\",\n \"Verification code digit\",\n ) + ` ${index + 1}`\n }\n value={digit}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n className=\"w-10 h-12 sm:w-12 sm:h-14 text-center text-xl sm:text-2xl font-semibold rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n />\n ))}\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading || code.join(\"\").length !== 6}\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.mfaVerify.verifying\", \"Verifying…\")}</span>\n </>\n ) : (\n tr(\"auth.mfaVerify.verify\", \"Verify\")\n )}\n </Button>\n\n {/* Recovery Options */}\n <div className=\"text-center space-y-3\">\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.mfaVerify.cantAccess\",\n \"Can't access your authenticator app?\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/mfa-recovery\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.useRecoveryCode\", \"Use a recovery code\")}\n </button>\n </div>\n </form>\n\n {/* Back to Login */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n <button\n type=\"button\"\n onClick={() => {\n // Clear MFA state so LoginPage doesn't immediately redirect back here\n sessionStorage.removeItem(MFA_CHALLENGE_ID);\n sessionStorage.removeItem(MFA_PENDING_TOKEN);\n sessionStorage.removeItem(MFA_PENDING_REFRESH);\n sessionStorage.removeItem(MFA_REDIRECT);\n useAuthStore.getState().setMfaRequired(false);\n navigate(\"/auth/login\");\n }}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.mfaVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { AlertIcon as r, CheckIcon as i, SpinnerIcon as a } from "../components/
5
5
  import { useEffect as o, useRef as s, useState as c } from "react";
6
6
  import { useNavigate as l, useSearchParams as u } from "react-router";
7
7
  import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
8
- //#region src/auth/pages/OAuthCallbackPage.tsx
8
+ // src/auth/pages/OAuthCallbackPage.tsx
9
9
  function m() {
10
10
  let m = e(), h = l(), [g] = u(), { completeOAuth: _ } = t(), [v, y] = c("loading"), [b, x] = c(""), S = s(!1);
11
11
  return o(() => {
@@ -16,15 +16,9 @@ function m() {
16
16
  }, 3e4);
17
17
  return (async () => {
18
18
  let e = g.get("code"), t = g.get("state"), n = g.get("error"), r = g.get("error_description");
19
- if (n) {
20
- y("error"), x(r || `OAuth error: ${n}`);
21
- return;
22
- }
23
- if (!e || !t) {
24
- y("error"), x("Missing authorization code or state parameter.");
25
- return;
26
- }
27
- try {
19
+ if (n) y("error"), x(r || `OAuth error: ${n}`);
20
+ else if (!e || !t) y("error"), x("Missing authorization code or state parameter.");
21
+ else try {
28
22
  let n = await _(e, t);
29
23
  n.success ? (y("success"), setTimeout(() => {
30
24
  h("/auth/security", { replace: !0 });
@@ -97,7 +91,6 @@ function m() {
97
91
  })
98
92
  });
99
93
  }
100
- //#endregion
101
94
  export { m as OAuthCallbackPage };
102
95
 
103
96
  //# sourceMappingURL=OAuthCallbackPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OAuthCallbackPage.js","names":[],"sources":["../../../src/auth/pages/OAuthCallbackPage.tsx"],"sourcesContent":["import { useEffect, useState, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { SpinnerIcon, AlertIcon, CheckIcon } from \"../components/AuthIcons\";\n\n/**\n * OAuth Callback Page\n * Handles the redirect from OAuth providers (Google, GitHub, Microsoft)\n * Extracts the authorization code and completes the authentication flow\n */\nexport function OAuthCallbackPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { completeOAuth } = useOAuthOperations();\n\n const [status, setStatus] = useState<\"loading\" | \"success\" | \"error\">(\n \"loading\",\n );\n const [errorMessage, setErrorMessage] = useState<string>(\"\");\n\n // Ref to prevent double execution in React Strict Mode\n const hasAttemptedRef = useRef(false);\n\n useEffect(() => {\n // Prevent double execution in React Strict Mode\n if (hasAttemptedRef.current) {\n return;\n }\n hasAttemptedRef.current = true;\n\n // Timeout after 30 seconds\n const timeoutId = setTimeout(() => {\n if (status === \"loading\") {\n setStatus(\"error\");\n setErrorMessage(\n \"Request timed out. Please check if the backend is running and try again.\",\n );\n }\n }, 30000);\n\n const handleCallback = async () => {\n // Extract parameters from URL\n const code = searchParams.get(\"code\");\n const state = searchParams.get(\"state\");\n const error = searchParams.get(\"error\");\n const errorDescription = searchParams.get(\"error_description\");\n\n // Check for OAuth error response\n if (error) {\n setStatus(\"error\");\n setErrorMessage(errorDescription || `OAuth error: ${error}`);\n return;\n }\n\n // Validate required parameters\n if (!code || !state) {\n setStatus(\"error\");\n setErrorMessage(\"Missing authorization code or state parameter.\");\n return;\n }\n\n try {\n // Complete OAuth flow\n const result = await completeOAuth(code, state);\n\n if (result.success) {\n setStatus(\"success\");\n // Redirect to security dashboard after short delay\n setTimeout(() => {\n navigate(\"/auth/security\", { replace: true });\n }, 1500);\n } else if (result.requiresMFA) {\n // Redirect to MFA verification\n navigate(\"/auth/mfa-verify\", { replace: true });\n } else {\n setStatus(\"error\");\n setErrorMessage(result.error || \"Authentication failed\");\n }\n } catch (err) {\n setStatus(\"error\");\n setErrorMessage(\n err instanceof Error\n ? err.message\n : \"Authentication failed. Please try again.\",\n );\n }\n };\n\n handleCallback().finally(() => {\n clearTimeout(timeoutId);\n });\n\n return () => {\n clearTimeout(timeoutId);\n };\n }, [searchParams, completeOAuth, navigate, status]);\n\n return (\n <AuthLayout\n title={\n status === \"loading\"\n ? tr(\"auth.oauthCallback.completing\", \"Completing Sign In\")\n : status === \"success\"\n ? tr(\"auth.oauthCallback.welcome\", \"Welcome!\")\n : tr(\"auth.oauthCallback.failed\", \"Authentication Failed\")\n }\n subtitle={\n status === \"loading\"\n ? tr(\n \"auth.oauthCallback.pleaseWait\",\n \"Please wait while we complete your authentication...\",\n )\n : status === \"success\"\n ? tr(\n \"auth.oauthCallback.successMsg\",\n \"You have been successfully signed in.\",\n )\n : tr(\n \"auth.oauthCallback.problemMsg\",\n \"There was a problem signing you in.\",\n )\n }\n >\n <div className=\"flex flex-col items-center justify-center py-8 space-y-6\">\n {status === \"loading\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-info-bg-subtle flex items-center justify-center\">\n <SpinnerIcon className=\"size-8 text-text-link animate-spin\" />\n </div>\n </div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.oauthCallback.verifying\",\n \"Verifying your credentials...\",\n )}\n </p>\n </>\n )}\n\n {status === \"success\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center\">\n <CheckIcon className=\"size-8 text-status-success-text\" />\n </div>\n </div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.oauthCallback.redirecting\",\n \"Redirecting to your dashboard...\",\n )}\n </p>\n </>\n )}\n\n {status === \"error\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-error-text\" />\n </div>\n </div>\n <div className=\"text-center space-y-2\">\n <p className=\"text-sm text-status-error-text font-medium\">\n {errorMessage}\n </p>\n <p className=\"text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.oauthCallback.tryAgainHint\",\n \"Please try again or use a different sign-in method.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\", { replace: true })}\n className=\"px-6 py-2.5 rounded-xl bg-[var(--color-action-primary-bg)] text-white font-medium hover:bg-[var(--color-action-primary-bgHover)] transition-colors\"\n >\n {tr(\"auth.oauthCallback.backToLogin\", \"Back to Login\")}\n </button>\n </>\n )}\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,IAAoB;CAClC,IAAM,IAAK,EAAM,GAEX,IAAW,EAAY,GACvB,CAAC,KAAgB,EAAgB,GACjC,EAAE,qBAAkB,EAAmB,GAEvC,CAAC,GAAQ,KAAa,EAC1B,SACF,GACM,CAAC,GAAc,KAAmB,EAAiB,EAAE,GAGrD,IAAkB,EAAO,EAAK;CA4EpC,OA1EA,QAAgB;EAEd,IAAI,EAAgB,SAClB;EAEF,EAAgB,UAAU;EAG1B,IAAM,IAAY,iBAAiB;GACjC,AAAI,MAAW,cACb,EAAU,OAAO,GACjB,EACE,0EACF;EAEJ,GAAG,GAAK;EAsDR,QAJA,YAhDmC;GAEjC,IAAM,IAAO,EAAa,IAAI,MAAM,GAC9B,IAAQ,EAAa,IAAI,OAAO,GAChC,IAAQ,EAAa,IAAI,OAAO,GAChC,IAAmB,EAAa,IAAI,mBAAmB;GAG7D,IAAI,GAAO;IAET,AADA,EAAU,OAAO,GACjB,EAAgB,KAAoB,gBAAgB,GAAO;IAC3D;GACF;GAGA,IAAI,CAAC,KAAQ,CAAC,GAAO;IAEnB,AADA,EAAU,OAAO,GACjB,EAAgB,gDAAgD;IAChE;GACF;GAEA,IAAI;IAEF,IAAM,IAAS,MAAM,EAAc,GAAM,CAAK;IAE9C,AAAI,EAAO,WACT,EAAU,SAAS,GAEnB,iBAAiB;KACf,EAAS,kBAAkB,EAAE,SAAS,GAAK,CAAC;IAC9C,GAAG,IAAI,KACE,EAAO,cAEhB,EAAS,oBAAoB,EAAE,SAAS,GAAK,CAAC,KAE9C,EAAU,OAAO,GACjB,EAAgB,EAAO,SAAS,uBAAuB;GAE3D,SAAS,GAAK;IAEZ,AADA,EAAU,OAAO,GACjB,EACE,aAAe,QACX,EAAI,UACJ,0CACN;GACF;EACF,GAEe,EAAE,cAAc;GAC7B,aAAa,CAAS;EACxB,CAAC,SAEY;GACX,aAAa,CAAS;EACxB;CACF,GAAG;EAAC;EAAc;EAAe;EAAU;CAAM,CAAC,GAGhD,kBAAC,GAAD;EACE,OACE,MAAW,YACP,EAAG,iCAAiC,oBAAoB,IACxD,MAAW,YACT,EAAG,8BAA8B,UAAU,IAC3C,EAAG,6BAA6B,uBAAuB;EAE/D,UACE,MAAW,YACP,EACE,iCACA,sDACF,IACA,MAAW,YACT,EACE,iCACA,uCACF,IACA,EACE,iCACA,qCACF;YAGR,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,MAAW,aACV,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAa,WAAU,qCAAsC,CAAA;KAC1D,CAAA;IACF,CAAA,GACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,gCACA,+BACF;IACC,CAAA,CACH,EAAA,CAAA;IAGH,MAAW,aACV,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAW,WAAU,kCAAmC,CAAA;KACrD,CAAA;IACF,CAAA,GACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,kCACA,kCACF;IACC,CAAA,CACH,EAAA,CAAA;IAGH,MAAW,WACV,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA;MACnD,CAAA;KACF,CAAA;KACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV;MACA,CAAA,GACH,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,mCACA,qDACF;MACC,CAAA,CACA;;KACL,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,eAAe,EAAE,SAAS,GAAK,CAAC;MACxD,WAAU;gBAET,EAAG,kCAAkC,eAAe;KAC/C,CAAA;IACR,EAAA,CAAA;GAED;;CACK,CAAA;AAEhB"}
1
+ {"version":3,"file":"OAuthCallbackPage.js","names":["_Fragment","OAuthCallbackPage","tr","useTr","navigate","useNavigate","searchParams","useSearchParams","useOAuthOperations","status","setStatus","useState","errorMessage","setErrorMessage","hasAttemptedRef","useRef","useEffect","timeoutId","code","state","error","errorDescription","result","completeOAuth","err","AuthLayout","Fragment","SpinnerIcon","CheckIcon","AlertIcon"],"sources":["../../../src/auth/pages/OAuthCallbackPage.tsx"],"sourcesContent":["import { useEffect, useState, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { SpinnerIcon, AlertIcon, CheckIcon } from \"../components/AuthIcons\";\n\n/**\n * OAuth Callback Page\n * Handles the redirect from OAuth providers (Google, GitHub, Microsoft)\n * Extracts the authorization code and completes the authentication flow\n */\nexport function OAuthCallbackPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { completeOAuth } = useOAuthOperations();\n\n const [status, setStatus] = useState<\"loading\" | \"success\" | \"error\">(\n \"loading\",\n );\n const [errorMessage, setErrorMessage] = useState<string>(\"\");\n\n // Ref to prevent double execution in React Strict Mode\n const hasAttemptedRef = useRef(false);\n\n useEffect(() => {\n // Prevent double execution in React Strict Mode\n if (hasAttemptedRef.current) {\n return;\n }\n hasAttemptedRef.current = true;\n\n // Timeout after 30 seconds\n const timeoutId = setTimeout(() => {\n if (status === \"loading\") {\n setStatus(\"error\");\n setErrorMessage(\n \"Request timed out. Please check if the backend is running and try again.\",\n );\n }\n }, 30000);\n\n const handleCallback = async () => {\n // Extract parameters from URL\n const code = searchParams.get(\"code\");\n const state = searchParams.get(\"state\");\n const error = searchParams.get(\"error\");\n const errorDescription = searchParams.get(\"error_description\");\n\n // Check for OAuth error response\n if (error) {\n setStatus(\"error\");\n setErrorMessage(errorDescription || `OAuth error: ${error}`);\n return;\n }\n\n // Validate required parameters\n if (!code || !state) {\n setStatus(\"error\");\n setErrorMessage(\"Missing authorization code or state parameter.\");\n return;\n }\n\n try {\n // Complete OAuth flow\n const result = await completeOAuth(code, state);\n\n if (result.success) {\n setStatus(\"success\");\n // Redirect to security dashboard after short delay\n setTimeout(() => {\n navigate(\"/auth/security\", { replace: true });\n }, 1500);\n } else if (result.requiresMFA) {\n // Redirect to MFA verification\n navigate(\"/auth/mfa-verify\", { replace: true });\n } else {\n setStatus(\"error\");\n setErrorMessage(result.error || \"Authentication failed\");\n }\n } catch (err) {\n setStatus(\"error\");\n setErrorMessage(\n err instanceof Error\n ? err.message\n : \"Authentication failed. Please try again.\",\n );\n }\n };\n\n handleCallback().finally(() => {\n clearTimeout(timeoutId);\n });\n\n return () => {\n clearTimeout(timeoutId);\n };\n }, [searchParams, completeOAuth, navigate, status]);\n\n return (\n <AuthLayout\n title={\n status === \"loading\"\n ? tr(\"auth.oauthCallback.completing\", \"Completing Sign In\")\n : status === \"success\"\n ? tr(\"auth.oauthCallback.welcome\", \"Welcome!\")\n : tr(\"auth.oauthCallback.failed\", \"Authentication Failed\")\n }\n subtitle={\n status === \"loading\"\n ? tr(\n \"auth.oauthCallback.pleaseWait\",\n \"Please wait while we complete your authentication...\",\n )\n : status === \"success\"\n ? tr(\n \"auth.oauthCallback.successMsg\",\n \"You have been successfully signed in.\",\n )\n : tr(\n \"auth.oauthCallback.problemMsg\",\n \"There was a problem signing you in.\",\n )\n }\n >\n <div className=\"flex flex-col items-center justify-center py-8 space-y-6\">\n {status === \"loading\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-info-bg-subtle flex items-center justify-center\">\n <SpinnerIcon className=\"size-8 text-text-link animate-spin\" />\n </div>\n </div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.oauthCallback.verifying\",\n \"Verifying your credentials...\",\n )}\n </p>\n </>\n )}\n\n {status === \"success\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center\">\n <CheckIcon className=\"size-8 text-status-success-text\" />\n </div>\n </div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.oauthCallback.redirecting\",\n \"Redirecting to your dashboard...\",\n )}\n </p>\n </>\n )}\n\n {status === \"error\" && (\n <>\n <div className=\"relative\">\n <div className=\"size-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <AlertIcon className=\"size-8 text-status-error-text\" />\n </div>\n </div>\n <div className=\"text-center space-y-2\">\n <p className=\"text-sm text-status-error-text font-medium\">\n {errorMessage}\n </p>\n <p className=\"text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.oauthCallback.tryAgainHint\",\n \"Please try again or use a different sign-in method.\",\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\", { replace: true })}\n className=\"px-6 py-2.5 rounded-xl bg-[var(--color-action-primary-bg)] text-white font-medium hover:bg-[var(--color-action-primary-bgHover)] transition-colors\"\n >\n {tr(\"auth.oauthCallback.backToLogin\", \"Back to Login\")}\n </button>\n </>\n )}\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,17 +1,17 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { AuthLayout as t } from "../components/AuthLayout.js";
3
- import { CheckIcon as n, EmailIcon as r, EyeIcon as i, EyeOffIcon as a, LockIcon as o, SpinnerIcon as s, XIcon as c } from "../components/AuthIcons.js";
4
- import { useCaptchaV3 as l } from "../hooks/useCaptchaV3.js";
5
- import { usePasswordOperations as u } from "../hooks/usePasswordOperations.js";
2
+ import { usePasswordOperations as t } from "../hooks/usePasswordOperations.js";
3
+ import { useCaptchaV3 as n } from "../hooks/useCaptchaV3.js";
4
+ import { AuthLayout as r } from "../components/AuthLayout.js";
5
+ import { CheckIcon as i, EmailIcon as a, EyeIcon as o, EyeOffIcon as s, LockIcon as c, SpinnerIcon as l, XIcon as u } from "../components/AuthIcons.js";
6
6
  import { useEffect as d, useState as f } from "react";
7
7
  import { useNavigate as p, useSearchParams as m } from "react-router";
8
8
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
9
9
  import { Button as v } from "@burdenoff/fe-libs/button";
10
10
  import { PagePurpose as y } from "@burdenoff/fe-libs/ui";
11
- //#region src/auth/pages/PasswordResetPage.tsx
11
+ // src/auth/pages/PasswordResetPage.tsx
12
12
  var b = 5;
13
13
  function x() {
14
- let x = e(), S = p(), [C] = m(), w = C.get("token"), T = C.get("returnTo"), { requestPasswordReset: E, resetPassword: D, isLoading: O } = u(), { executeRecaptcha: k } = l(), [A, j] = f(""), [M, N] = f(""), [P, F] = f(""), [I, L] = f(!1), [R, z] = f(!1), [B, V] = f(!1), [H, U] = f(""), [W, G] = f(b);
14
+ let x = e(), S = p(), [C] = m(), w = C.get("token"), T = C.get("returnTo"), { requestPasswordReset: E, resetPassword: D, isLoading: O } = t(), { executeRecaptcha: k } = n(), [A, j] = f(""), [M, N] = f(""), [P, F] = f(""), [I, L] = f(!1), [R, z] = f(!1), [B, V] = f(!1), [H, U] = f(""), [W, G] = f(b);
15
15
  d(() => {
16
16
  if (!B || !w) return;
17
17
  let e = T ? `/auth/login?returnTo=${encodeURIComponent(T)}` : "/auth/login", t = setInterval(() => {
@@ -68,7 +68,7 @@ function x() {
68
68
  width: "w-full"
69
69
  };
70
70
  })();
71
- return B ? /* @__PURE__ */ _(t, {
71
+ return B ? /* @__PURE__ */ _(r, {
72
72
  title: w ? x("auth.passwordReset.passwordUpdated", "Password updated") : x("auth.passwordReset.checkEmail", "Check your email"),
73
73
  subtitle: w ? x("auth.passwordReset.successSubtitle", "Your password has been successfully reset") : x("auth.passwordReset.sentInstructions", "We sent you instructions to reset your password"),
74
74
  children: [
@@ -131,7 +131,7 @@ function x() {
131
131
  })]
132
132
  })
133
133
  ]
134
- }) : /* @__PURE__ */ _(t, {
134
+ }) : /* @__PURE__ */ _(r, {
135
135
  title: w ? x("auth.passwordReset.createNewPassword", "Create new password") : x("auth.passwordReset.resetTitle", "Reset your password"),
136
136
  subtitle: w ? x("auth.passwordReset.enterNewPassword", "Enter a new password for your account") : x("auth.passwordReset.enterEmail", "Enter your email and we'll send you a reset link"),
137
137
  children: [
@@ -142,11 +142,8 @@ function x() {
142
142
  w ? /* @__PURE__ */ _("form", {
143
143
  onSubmit: async (e) => {
144
144
  if (e.preventDefault(), U(""), w) {
145
- if (M !== P) {
146
- U(x("auth.passwordReset.passwordsNoMatch", "Passwords do not match"));
147
- return;
148
- }
149
- try {
145
+ if (M !== P) U(x("auth.passwordReset.passwordsNoMatch", "Passwords do not match"));
146
+ else try {
150
147
  await D(w, M), V(!0);
151
148
  } catch (e) {
152
149
  let t = e instanceof Error ? e.message : "";
@@ -187,7 +184,7 @@ function x() {
187
184
  children: [
188
185
  /* @__PURE__ */ g("div", {
189
186
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
190
- children: /* @__PURE__ */ g(o, { className: "size-5 text-[var(--color-text-tertiary)]" })
187
+ children: /* @__PURE__ */ g(c, { className: "size-5 text-[var(--color-text-tertiary)]" })
191
188
  }),
192
189
  /* @__PURE__ */ g("input", {
193
190
  id: "newPassword",
@@ -206,7 +203,7 @@ function x() {
206
203
  onClick: () => L(!I),
207
204
  "aria-label": I ? "Hide password" : "Show password",
208
205
  className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
209
- children: g(I ? a : i, { className: "size-5" })
206
+ children: g(I ? s : o, { className: "size-5" })
210
207
  })
211
208
  ]
212
209
  }),
@@ -232,7 +229,7 @@ function x() {
232
229
  className: "grid grid-cols-2 gap-2",
233
230
  children: K.map((e) => /* @__PURE__ */ _("div", {
234
231
  className: "flex items-center gap-2",
235
- children: [e.met ? /* @__PURE__ */ g(n, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ g(c, { className: "size-4 text-[var(--color-text-tertiary)]" }), /* @__PURE__ */ g("span", {
232
+ children: [e.met ? /* @__PURE__ */ g(i, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ g(u, { className: "size-4 text-[var(--color-text-tertiary)]" }), /* @__PURE__ */ g("span", {
236
233
  className: `text-xs ${e.met ? "text-[var(--color-status-success-text)]" : "text-[var(--color-text-tertiary)]"}`,
237
234
  children: e.label
238
235
  })]
@@ -254,7 +251,7 @@ function x() {
254
251
  children: [
255
252
  /* @__PURE__ */ g("div", {
256
253
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
257
- children: /* @__PURE__ */ g(o, { className: "size-5 text-[var(--color-text-tertiary)]" })
254
+ children: /* @__PURE__ */ g(c, { className: "size-5 text-[var(--color-text-tertiary)]" })
258
255
  }),
259
256
  /* @__PURE__ */ g("input", {
260
257
  id: "confirmPassword",
@@ -274,7 +271,7 @@ function x() {
274
271
  onClick: () => z(!R),
275
272
  "aria-label": R ? x("auth.passwordReset.hideConfirmPassword", "Hide confirm password") : x("auth.passwordReset.showConfirmPassword", "Show confirm password"),
276
273
  className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
277
- children: g(R ? a : i, { className: "size-5" })
274
+ children: g(R ? s : o, { className: "size-5" })
278
275
  })
279
276
  ]
280
277
  }),
@@ -290,14 +287,15 @@ function x() {
290
287
  variant: "default",
291
288
  size: "lg",
292
289
  className: "w-full font-semibold",
293
- children: O ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(s, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: x("auth.passwordReset.resetting", "Resetting password...") })] }) : x("auth.passwordReset.resetButton", "Reset password")
290
+ children: O ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(l, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: x("auth.passwordReset.resetting", "Resetting password...") })] }) : x("auth.passwordReset.resetButton", "Reset password")
294
291
  })
295
292
  ]
296
293
  }) : /* @__PURE__ */ _("form", {
297
294
  onSubmit: async (e) => {
298
295
  e.preventDefault(), U("");
299
296
  try {
300
- await E(A, await k("password_reset")), V(!0);
297
+ let e = await k("password_reset");
298
+ await E(A, e), V(!0);
301
299
  } catch (e) {
302
300
  U(x("auth.passwordReset.sendFailed", "Failed to send reset email. Please try again.")), console.error("Failed to request password reset:", e);
303
301
  }
@@ -332,7 +330,7 @@ function x() {
332
330
  className: "relative",
333
331
  children: [/* @__PURE__ */ g("div", {
334
332
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
335
- children: /* @__PURE__ */ g(r, { className: "size-5 text-[var(--color-text-tertiary)]" })
333
+ children: /* @__PURE__ */ g(a, { className: "size-5 text-[var(--color-text-tertiary)]" })
336
334
  }), /* @__PURE__ */ g("input", {
337
335
  id: "email",
338
336
  type: "email",
@@ -353,7 +351,7 @@ function x() {
353
351
  variant: "default",
354
352
  size: "lg",
355
353
  className: "w-full font-semibold",
356
- children: O ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(s, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: x("auth.passwordReset.sending", "Sending...") })] }) : x("auth.passwordReset.sendResetLink", "Send reset link")
354
+ children: O ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(l, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: x("auth.passwordReset.sending", "Sending...") })] }) : x("auth.passwordReset.sendResetLink", "Send reset link")
357
355
  })
358
356
  ]
359
357
  }),
@@ -373,7 +371,6 @@ function x() {
373
371
  ]
374
372
  });
375
373
  }
376
- //#endregion
377
374
  export { x as PasswordResetPage };
378
375
 
379
376
  //# sourceMappingURL=PasswordResetPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordResetPage.js","names":[],"sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useSearchParams, useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { usePasswordOperations } from \"../hooks/usePasswordOperations\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n} from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Password reset page with professional design\n * Handles both requesting reset and completing reset with token\n */\nexport function PasswordResetPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n // Preserve returnTo so after reset→login the user ends up on the right product domain\n const returnTo = searchParams.get(\"returnTo\");\n const { requestPasswordReset, resetPassword, isLoading } =\n usePasswordOperations();\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [email, setEmail] = useState(\"\");\n const [newPassword, setNewPassword] = useState(\"\");\n const [confirmPassword, setConfirmPassword] = useState(\"\");\n const [showPassword, setShowPassword] = useState(false);\n const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n const [success, setSuccess] = useState(false);\n const [error, setError] = useState(\"\");\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Auto-redirect countdown after successful password reset (only for token flow)\n useEffect(() => {\n if (!success || !token) return;\n\n const loginPath = returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\";\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigate(loginPath);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [success, token, navigate, returnTo]);\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = newPassword;\n return [\n {\n label: tr(\"auth.passwordReset.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.passwordReset.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.passwordReset.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-bg-subtle0\",\n width: \"w-full\",\n };\n })();\n\n const handleRequestReset = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"password_reset\");\n await requestPasswordReset(email, recaptchaToken);\n setSuccess(true);\n } catch (err) {\n setError(\n tr(\n \"auth.passwordReset.sendFailed\",\n \"Failed to send reset email. Please try again.\",\n ),\n );\n console.error(\"Failed to request password reset:\", err);\n }\n };\n\n const handleResetPassword = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n\n if (!token) return;\n\n if (newPassword !== confirmPassword) {\n setError(\n tr(\"auth.passwordReset.passwordsNoMatch\", \"Passwords do not match\"),\n );\n return;\n }\n\n try {\n await resetPassword(token, newPassword);\n setSuccess(true);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"\";\n if (msg === \"TOKEN_EXPIRED\") {\n setError(\n tr(\n \"auth.passwordReset.errorTokenExpired\",\n \"This password reset link has expired. Please request a new one.\",\n ),\n );\n } else if (msg === \"INVALID_TOKEN\") {\n setError(\n tr(\n \"auth.passwordReset.errorInvalidToken\",\n \"This password reset link is invalid or has already been used.\",\n ),\n );\n } else {\n setError(\n tr(\n \"auth.passwordReset.errorGeneric\",\n \"Failed to reset password. Please try again.\",\n ),\n );\n }\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.passwordUpdated\", \"Password updated\")\n : tr(\"auth.passwordReset.checkEmail\", \"Check your email\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.successSubtitle\",\n \"Your password has been successfully reset\",\n )\n : tr(\n \"auth.passwordReset.sentInstructions\",\n \"We sent you instructions to reset your password\",\n )\n }\n >\n {/* Success Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-8 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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {token\n ? tr(\n \"auth.passwordReset.canNowSignIn\",\n \"You can now use your new password to sign in to your account.\",\n )\n : tr(\n \"auth.passwordReset.emailSentTo\",\n \"We sent an email to {{email}}. Click the link in the email to reset your password.\",\n ).replace(\"{{email}}\", email)}\n </p>\n\n {token ? (\n <div className=\"space-y-4\">\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.passwordReset.redirectingIn\",\n \"Redirecting to sign in in\",\n )}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.passwordReset.seconds\", \"seconds...\")}\n </p>\n <Button\n type=\"button\"\n onClick={() =>\n navigate(\n returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\",\n )\n }\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.passwordReset.signInNow\", \"Sign in now\")}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <button\n type=\"button\"\n onClick={() => setSuccess(false)}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\n \"auth.passwordReset.didntReceive\",\n \"Didn't receive the email? Try again\",\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"w-full py-3 px-4 rounded-xl text-[var(--color-text-secondary)] font-medium hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.passwordReset.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n )}\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.createNewPassword\", \"Create new password\")\n : tr(\"auth.passwordReset.resetTitle\", \"Reset your password\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.enterNewPassword\",\n \"Enter a new password for your account\",\n )\n : tr(\n \"auth.passwordReset.enterEmail\",\n \"Enter your email and we'll send you a reset link\",\n )\n }\n >\n <PagePurpose className=\"mb-5\">\n {token\n ? tr(\n \"auth.passwordReset.purposeReset\",\n \"Choose a new password for your account. Make it strong and unique — you'll use it to sign in next time.\",\n )\n : tr(\n \"auth.passwordReset.purposeRequest\",\n \"Forgot your password? Enter your email and we'll send a secure link to reset it. The link expires shortly for your safety.\",\n )}\n </PagePurpose>\n {token ? (\n <form onSubmit={handleResetPassword} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"size-5 text-[var(--color-status-error-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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* New Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"newPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.newPasswordLabel\", \"New Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"newPassword\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.newPasswordLabel\",\n \"New Password\",\n )}\n autoComplete=\"new-password\"\n value={newPassword}\n onChange={(e) => setNewPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.newPasswordPlaceholder\",\n \"Enter new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {newPassword && (\n <div className=\"space-y-3 pt-2\">\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\n \"auth.passwordReset.passwordStrength\",\n \"Password strength:\",\n )}{\" \"}\n <span className=\"font-medium\">\n {passwordStrength.level}\n </span>\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-[var(--color-status-success-text)]\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Confirm Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"confirmPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"confirmPassword\"\n type={showConfirmPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n autoComplete=\"new-password\"\n aria-invalid={\n confirmPassword.length > 0 && newPassword !== confirmPassword\n }\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.confirmPasswordPlaceholder\",\n \"Confirm new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n aria-label={\n showConfirmPassword\n ? tr(\n \"auth.passwordReset.hideConfirmPassword\",\n \"Hide confirm password\",\n )\n : tr(\n \"auth.passwordReset.showConfirmPassword\",\n \"Show confirm password\",\n )\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showConfirmPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n {confirmPassword && newPassword !== confirmPassword && (\n <p className=\"text-xs text-[var(--color-status-error-text)]\">\n {tr(\n \"auth.passwordReset.passwordsMismatch\",\n \"Passwords do not match\",\n )}\n </p>\n )}\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={\n isLoading ||\n (confirmPassword.length > 0 && newPassword !== confirmPassword)\n }\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.passwordReset.resetting\", \"Resetting password...\")}\n </span>\n </>\n ) : (\n tr(\"auth.passwordReset.resetButton\", \"Reset password\")\n )}\n </Button>\n </form>\n ) : (\n <form onSubmit={handleRequestReset} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"size-5 text-[var(--color-status-error-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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.emailLabel\",\n \"Email address\",\n )}\n autoComplete=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.passwordReset.sending\", \"Sending...\")}</span>\n </>\n ) : (\n tr(\"auth.passwordReset.sendResetLink\", \"Send reset link\")\n )}\n </Button>\n </form>\n )}\n\n {/* Back to Sign In */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.passwordReset.rememberPassword\", \"Remember your password?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.passwordReset.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PasswordResetPage.js","names":["_Fragment","REDIRECT_DELAY","PasswordResetPage","tr","useTr","navigate","useNavigate","searchParams","useSearchParams","token","returnTo","usePasswordOperations","executeCaptchaV3","useCaptchaV3","email","setEmail","useState","newPassword","setNewPassword","confirmPassword","setConfirmPassword","showPassword","setShowPassword","showConfirmPassword","setShowConfirmPassword","success","setSuccess","error","setError","countdown","setCountdown","loginPath","timer","prev","passwordChecks","password","passwordStrength","metCount","c","AuthLayout","Button","PagePurpose","resetPassword","err","msg","LockIcon","jsx","EyeOffIcon","EyeIcon","check","CheckIcon","XIcon","isLoading","Fragment","SpinnerIcon","recaptchaToken","requestPasswordReset","EmailIcon"],"sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useSearchParams, useNavigate } from \"react-router\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { usePasswordOperations } from \"../hooks/usePasswordOperations\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n} from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Password reset page with professional design\n * Handles both requesting reset and completing reset with token\n */\nexport function PasswordResetPage() {\n const tr = useTr();\n const navigate = useNavigate();\n\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n // Preserve returnTo so after reset→login the user ends up on the right product domain\n const returnTo = searchParams.get(\"returnTo\");\n const { requestPasswordReset, resetPassword, isLoading } =\n usePasswordOperations();\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [email, setEmail] = useState(\"\");\n const [newPassword, setNewPassword] = useState(\"\");\n const [confirmPassword, setConfirmPassword] = useState(\"\");\n const [showPassword, setShowPassword] = useState(false);\n const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n const [success, setSuccess] = useState(false);\n const [error, setError] = useState(\"\");\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Auto-redirect countdown after successful password reset (only for token flow)\n useEffect(() => {\n if (!success || !token) return;\n\n const loginPath = returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\";\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigate(loginPath);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [success, token, navigate, returnTo]);\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = newPassword;\n return [\n {\n label: tr(\"auth.passwordReset.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.passwordReset.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.passwordReset.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-bg-subtle0\",\n width: \"w-full\",\n };\n })();\n\n const handleRequestReset = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"password_reset\");\n await requestPasswordReset(email, recaptchaToken);\n setSuccess(true);\n } catch (err) {\n setError(\n tr(\n \"auth.passwordReset.sendFailed\",\n \"Failed to send reset email. Please try again.\",\n ),\n );\n console.error(\"Failed to request password reset:\", err);\n }\n };\n\n const handleResetPassword = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n\n if (!token) return;\n\n if (newPassword !== confirmPassword) {\n setError(\n tr(\"auth.passwordReset.passwordsNoMatch\", \"Passwords do not match\"),\n );\n return;\n }\n\n try {\n await resetPassword(token, newPassword);\n setSuccess(true);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"\";\n if (msg === \"TOKEN_EXPIRED\") {\n setError(\n tr(\n \"auth.passwordReset.errorTokenExpired\",\n \"This password reset link has expired. Please request a new one.\",\n ),\n );\n } else if (msg === \"INVALID_TOKEN\") {\n setError(\n tr(\n \"auth.passwordReset.errorInvalidToken\",\n \"This password reset link is invalid or has already been used.\",\n ),\n );\n } else {\n setError(\n tr(\n \"auth.passwordReset.errorGeneric\",\n \"Failed to reset password. Please try again.\",\n ),\n );\n }\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.passwordUpdated\", \"Password updated\")\n : tr(\"auth.passwordReset.checkEmail\", \"Check your email\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.successSubtitle\",\n \"Your password has been successfully reset\",\n )\n : tr(\n \"auth.passwordReset.sentInstructions\",\n \"We sent you instructions to reset your password\",\n )\n }\n >\n {/* Success Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-8 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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {token\n ? tr(\n \"auth.passwordReset.canNowSignIn\",\n \"You can now use your new password to sign in to your account.\",\n )\n : tr(\n \"auth.passwordReset.emailSentTo\",\n \"We sent an email to {{email}}. Click the link in the email to reset your password.\",\n ).replace(\"{{email}}\", email)}\n </p>\n\n {token ? (\n <div className=\"space-y-4\">\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.passwordReset.redirectingIn\",\n \"Redirecting to sign in in\",\n )}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.passwordReset.seconds\", \"seconds...\")}\n </p>\n <Button\n type=\"button\"\n onClick={() =>\n navigate(\n returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\",\n )\n }\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.passwordReset.signInNow\", \"Sign in now\")}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <button\n type=\"button\"\n onClick={() => setSuccess(false)}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\n \"auth.passwordReset.didntReceive\",\n \"Didn't receive the email? Try again\",\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"w-full py-3 px-4 rounded-xl text-[var(--color-text-secondary)] font-medium hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.passwordReset.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n )}\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.createNewPassword\", \"Create new password\")\n : tr(\"auth.passwordReset.resetTitle\", \"Reset your password\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.enterNewPassword\",\n \"Enter a new password for your account\",\n )\n : tr(\n \"auth.passwordReset.enterEmail\",\n \"Enter your email and we'll send you a reset link\",\n )\n }\n >\n <PagePurpose className=\"mb-5\">\n {token\n ? tr(\n \"auth.passwordReset.purposeReset\",\n \"Choose a new password for your account. Make it strong and unique — you'll use it to sign in next time.\",\n )\n : tr(\n \"auth.passwordReset.purposeRequest\",\n \"Forgot your password? Enter your email and we'll send a secure link to reset it. The link expires shortly for your safety.\",\n )}\n </PagePurpose>\n {token ? (\n <form onSubmit={handleResetPassword} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"size-5 text-[var(--color-status-error-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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* New Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"newPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.newPasswordLabel\", \"New Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"newPassword\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.newPasswordLabel\",\n \"New Password\",\n )}\n autoComplete=\"new-password\"\n value={newPassword}\n onChange={(e) => setNewPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.newPasswordPlaceholder\",\n \"Enter new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {newPassword && (\n <div className=\"space-y-3 pt-2\">\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\n \"auth.passwordReset.passwordStrength\",\n \"Password strength:\",\n )}{\" \"}\n <span className=\"font-medium\">\n {passwordStrength.level}\n </span>\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-[var(--color-status-success-text)]\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Confirm Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"confirmPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"confirmPassword\"\n type={showConfirmPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n autoComplete=\"new-password\"\n aria-invalid={\n confirmPassword.length > 0 && newPassword !== confirmPassword\n }\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.confirmPasswordPlaceholder\",\n \"Confirm new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n aria-label={\n showConfirmPassword\n ? tr(\n \"auth.passwordReset.hideConfirmPassword\",\n \"Hide confirm password\",\n )\n : tr(\n \"auth.passwordReset.showConfirmPassword\",\n \"Show confirm password\",\n )\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showConfirmPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n {confirmPassword && newPassword !== confirmPassword && (\n <p className=\"text-xs text-[var(--color-status-error-text)]\">\n {tr(\n \"auth.passwordReset.passwordsMismatch\",\n \"Passwords do not match\",\n )}\n </p>\n )}\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={\n isLoading ||\n (confirmPassword.length > 0 && newPassword !== confirmPassword)\n }\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.passwordReset.resetting\", \"Resetting password...\")}\n </span>\n </>\n ) : (\n tr(\"auth.passwordReset.resetButton\", \"Reset password\")\n )}\n </Button>\n </form>\n ) : (\n <form onSubmit={handleRequestReset} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"size-5 text-[var(--color-status-error-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 <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\n \"auth.passwordReset.emailLabel\",\n \"Email address\",\n )}\n autoComplete=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.passwordReset.sending\", \"Sending...\")}</span>\n </>\n ) : (\n tr(\"auth.passwordReset.sendResetLink\", \"Send reset link\")\n )}\n </Button>\n </form>\n )}\n\n {/* Back to Sign In */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.passwordReset.rememberPassword\", \"Remember your password?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.passwordReset.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,18 +1,18 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { AuthLayout as t } from "../components/AuthLayout.js";
3
- import { AlertIcon as n, EmailIcon as r, LockIcon as i, SpinnerIcon as a } from "../components/AuthIcons.js";
4
- import { useAccountOperations as o } from "../hooks/useAccountOperations.js";
2
+ import { useAccountOperations as t } from "../hooks/useAccountOperations.js";
3
+ import { AuthLayout as n } from "../components/AuthLayout.js";
4
+ import { AlertIcon as r, EmailIcon as i, LockIcon as a, SpinnerIcon as o } from "../components/AuthIcons.js";
5
5
  import { useEffect as s, useState as c } from "react";
6
6
  import { useLocation as l, useNavigate as u } from "react-router";
7
7
  import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
8
8
  import { Button as m } from "@burdenoff/fe-libs/button";
9
9
  import { PagePurpose as h } from "@burdenoff/fe-libs/ui";
10
- //#region src/auth/pages/ReactivatePage.tsx
10
+ // src/auth/pages/ReactivatePage.tsx
11
11
  function g() {
12
- let g = e(), _ = u(), v = l(), { reactivateAccount: y, isReactivating: b } = o(), x = v.state?.email || "", [S, C] = c(x), [w, T] = c(""), [E, D] = c(null), [O, k] = c(!1);
12
+ let g = e(), _ = u(), v = l(), { reactivateAccount: y, isReactivating: b } = t(), x = v.state?.email || "", [S, C] = c(x), [w, T] = c(""), [E, D] = c(null), [O, k] = c(!1);
13
13
  return s(() => {
14
14
  x && C(x);
15
- }, [x]), O ? /* @__PURE__ */ f(t, {
15
+ }, [x]), O ? /* @__PURE__ */ f(n, {
16
16
  title: g("auth.reactivate.successTitle", "Account Reactivated!"),
17
17
  subtitle: g("auth.reactivate.successSubtitle", "Welcome back"),
18
18
  children: /* @__PURE__ */ p("div", {
@@ -43,11 +43,11 @@ function g() {
43
43
  }),
44
44
  /* @__PURE__ */ f("div", {
45
45
  className: "flex justify-center",
46
- children: /* @__PURE__ */ f(a, { className: "size-6 text-status-success-text animate-spin" })
46
+ children: /* @__PURE__ */ f(o, { className: "size-6 text-status-success-text animate-spin" })
47
47
  })
48
48
  ]
49
49
  })
50
- }) : /* @__PURE__ */ p(t, {
50
+ }) : /* @__PURE__ */ p(n, {
51
51
  title: g("auth.reactivate.title", "Reactivate Account"),
52
52
  subtitle: g("auth.reactivate.subtitle", "Restore access to your deactivated account"),
53
53
  children: [
@@ -57,11 +57,8 @@ function g() {
57
57
  }),
58
58
  /* @__PURE__ */ p("form", {
59
59
  onSubmit: async (e) => {
60
- if (e.preventDefault(), D(null), !S || !w) {
61
- D(g("auth.reactivate.enterBoth", "Please enter both email and password"));
62
- return;
63
- }
64
- try {
60
+ if (e.preventDefault(), D(null), !S || !w) D(g("auth.reactivate.enterBoth", "Please enter both email and password"));
61
+ else try {
65
62
  let e = await y(S, w);
66
63
  e.success ? (k(!0), setTimeout(() => {
67
64
  _("/dashboard", { replace: !0 });
@@ -74,7 +71,7 @@ function g() {
74
71
  children: [
75
72
  E && /* @__PURE__ */ p("div", {
76
73
  className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
77
- children: [/* @__PURE__ */ f(n, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ f("div", {
74
+ children: [/* @__PURE__ */ f(r, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ f("div", {
78
75
  className: "flex-1",
79
76
  children: /* @__PURE__ */ f("p", {
80
77
  className: "text-sm text-[var(--color-status-error-text)]",
@@ -116,7 +113,7 @@ function g() {
116
113
  className: "relative",
117
114
  children: [/* @__PURE__ */ f("div", {
118
115
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
119
- children: /* @__PURE__ */ f(r, { className: "size-5 text-[var(--color-text-tertiary)]" })
116
+ children: /* @__PURE__ */ f(i, { className: "size-5 text-[var(--color-text-tertiary)]" })
120
117
  }), /* @__PURE__ */ f("input", {
121
118
  id: "email",
122
119
  type: "email",
@@ -139,7 +136,7 @@ function g() {
139
136
  className: "relative",
140
137
  children: [/* @__PURE__ */ f("div", {
141
138
  className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
142
- children: /* @__PURE__ */ f(i, { className: "size-5 text-[var(--color-text-tertiary)]" })
139
+ children: /* @__PURE__ */ f(a, { className: "size-5 text-[var(--color-text-tertiary)]" })
143
140
  }), /* @__PURE__ */ f("input", {
144
141
  id: "password",
145
142
  type: "password",
@@ -158,7 +155,7 @@ function g() {
158
155
  variant: "default",
159
156
  size: "lg",
160
157
  className: "w-full font-semibold",
161
- children: b ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(a, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: g("auth.reactivate.reactivating", "Reactivating…") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
158
+ children: b ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(o, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: g("auth.reactivate.reactivating", "Reactivating…") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
162
159
  className: "size-5",
163
160
  fill: "none",
164
161
  viewBox: "0 0 24 24",
@@ -220,7 +217,6 @@ function g() {
220
217
  ]
221
218
  });
222
219
  }
223
- //#endregion
224
220
  export { g as ReactivatePage };
225
221
 
226
222
  //# sourceMappingURL=ReactivatePage.js.map