@burdenoff/microfe-auth 2026.1103.1 → 2026.1107.2
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.
- package/dist/auth/components/AuthLayout.js +4 -3
- package/dist/auth/components/AuthLayout.js.map +1 -1
- package/dist/auth/hooks/useAuthOperations.d.ts +1 -4
- package/dist/auth/hooks/useDelegationOperations.d.ts +2 -8
- package/dist/auth/hooks/useMyWorkspaces.d.ts +1 -4
- package/dist/auth/hooks/useSessionOperations.d.ts +7 -14
- package/dist/auth/hooks/useSsoOperations.d.ts +1 -4
- package/dist/auth/pages/LoginPage.js +4 -0
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +1 -1
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +2 -1
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +2 -0
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +3 -1
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +3 -1
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +2 -1
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/utils/validation.js.map +1 -1
- package/dist/generated/global-operations.d.ts +602 -553
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +2 -21
- package/dist/generated/wspace-operations.d.ts +89 -14
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.d.ts +4 -23
- package/dist/microfe-auth.css +2 -2
- package/dist/test/happydom.d.ts +1 -0
- package/package.json +73 -57
|
@@ -1 +1 @@
|
|
|
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":";;;;;;;;;;;;AAqBA,SAAgBC,IAAgB;CAC9B,IAAMC,IAAKC,EAAM,GAEXC,IAAWC,EAAY,GACvB,EACJ,cACA,gBAAgBC,GAChB,iBACEC,EAAiB,GACf,CAACC,GAAMC,KAAWC,EAAS;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE,CAAC,GACnD,CAACC,GAAOC,KAAYF,EAAwB,IAAI,GAChDG,IAAYC,EAAoC,CAAC,CAAC,GAGlDC,IACJ,OAAO,SAAW,MACd,eAAe,QAAQC,CAAgB,IACvC,MACAC,IAAiBX,KAAoBS;CAG3C,QAAgB;EACd,EAAU,QAAQ,EAAE,EAAE,MAAM,GAGJ,eAAe,QAAQG,CAAiB,GAC5C,eAAe,QAAQC,CAAY;CAGzD,GAAG,CAACJ,CAAkB,CAAC;CAEvB,IAAMK,KAAgB,GAAe,MAAkB;EAErD,IAAMC,IAAQC,EAAM,QAAQ,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,GAEzCC,IAAU,CAAC,GAAGf,CAAI;EAUxB,IATA,EAAQgB,KAASH,GACjBZ,EAAQc,CAAO,GAGXF,KAASG,IAAQ,KACnBX,EAAU,QAAQW,IAAQ,EAAE,EAAE,MAAM,GAIlCH,KAASG,MAAU,GAAG;GACxB,IAAMC,IAAWF,EAAQ,KAAK,EAAE;GAChC,AAAIE,EAAS,WAAW,KACtBC,EAAaD,CAAQ;EAEzB;CACF,GAEME,KAAiB,GAAe,MAA2B;EAC/D,AAAIC,EAAE,QAAQ,eAAe,CAACpB,EAAKgB,MAAUA,IAAQ,KACnDX,EAAU,QAAQW,IAAQ,EAAE,EAAE,MAAM;CAExC,GAEMK,KAAe,MAA4B;EAC/C,EAAE,eAAe;EACjB,IAAMC,IAAa,EAAE,cAClB,QAAQ,MAAM,CAAC,CACf,QAAQ,OAAO,EAAE,CAAC,CAClB,MAAM,GAAG,CAAC,GACPP,IAAU,CAAC,GAAGf,CAAI;EAIxB,AAHAsB,EAAW,MAAM,EAAE,CAAC,CAAC,SAAS,GAAM,MAAM;GACxC,AAAIC,IAAI,MAAG,EAAQA,KAAKC;EAC1B,CAAC,GACDvB,EAAQc,CAAO;EAGf,IAAMU,IAAY,KAAK,IAAIH,EAAW,SAAS,GAAG,CAAC;EAInD,AAHAjB,EAAU,QAAQoB,EAAU,EAAE,MAAM,GAGhCH,EAAW,WAAW,KACxBJ,EAAaI,CAAU;CAE3B,GAEMJ,IAAe,OAAO,MAAsB;EAChD,IAAMQ,IAAeT,KAAYjB,EAAK,KAAK,EAAE;EAC7C,IAAI0B,EAAa,WAAW,GAI5B;OAFAtB,EAAS,IAAI,GAET,CAACK,GACH,EACEf,EACE,iCACA,2CACF,CACF;QAIF,IAAI;IACF,IAAMiC,IAAS,MAAMC,EAAU;KAC7B,aAAanB;KACb,MAAMiB;KACN,QAAQ;IACV,CAAC;IAED,IAAIC,EAAO,SAAS;KAElB,IAAME,IAAc,eAAe,QAAQlB,CAAY;KAQvD,AAPA,eAAe,WAAWH,CAAgB,GAC1C,eAAe,WAAWE,CAAiB,GAC3C,eAAe,WAAWoB,CAAmB,GAC7C,eAAe,WAAWnB,CAAY,GAItCf,EADoBiC,KAAe,KACb,EAAE,SAAS,GAAK,CAAC;IACzC,OAAO;KAEL,IAAIF,EAAO,UAAU,yBAAyB;MAS5C,AAPA,eAAe,WAAWnB,CAAgB,GAC1C,eAAe,WAAWE,CAAiB,GAC3C,eAAe,WAAWoB,CAAmB,GAC7C,eAAe,WAAWnB,CAAY,GACtCoB,EAAa,SAAS,CAAC,CAAC,eAAe,EAAK,GAG5CnC,EAAS,eAAe;OACtB,SAAS;OACT,OAAO,EACL,SAASF,EACP,wCACA,0DACF,EACF;MACF,CAAC;MACD;KACF;KAIA,IAAMsC,IAASL,EAAO,SAAS,IACzBM,IACJ,gDAAgD,KAAKD,CAAM;KAU7D,AATA5B,EACE,CAAC4B,KAAUC,IACPvC,EACE,8BACA,8CACF,IACAsC,CACN,GACA/B,EAAQ;MAAC;MAAI;MAAI;MAAI;MAAI;MAAI;KAAE,CAAC,GAChCI,EAAU,QAAQ,EAAE,EAAE,MAAM;IAC9B;GACF,SAAS6B,GAAK;IAKZ,IAJA,QAAQ,MAAM,4BAA4BA,CAAG,IAGxBA,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,EAAA,CACnD,SAAS,uBAAuB,GAAG;KASlD,AAPA,eAAe,WAAW1B,CAAgB,GAC1C,eAAe,WAAWE,CAAiB,GAC3C,eAAe,WAAWoB,CAAmB,GAC7C,eAAe,WAAWnB,CAAY,GACtCoB,EAAa,SAAS,CAAC,CAAC,eAAe,EAAK,GAG5CnC,EAAS,eAAe;MACtB,SAAS;MACT,OAAO,EACL,SAASF,EACP,wCACA,0DACF,EACF;KACF,CAAC;KACD;IACF;IASA,AAPAU,EACEV,EACE,qCACA,wCACF,CACF,GACAO,EAAQ;KAAC;KAAI;KAAI;KAAI;KAAI;KAAI;IAAE,CAAC,GAChCI,EAAU,QAAQ,EAAE,EAAE,MAAM;GAC9B;EA7FA;CA8FF;CAEA,OACE,kBAAC8B,GAAD;EACE,OAAOzC,EAAG,wBAAwB,2BAA2B;EAC7D,UAAUA,EACR,2BACA,oDACF;EALF,UAAA;GAQE,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;MAEP,UAAA,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;GACF,CAAA;GAEL,kBAAC0C,GAAD;IAAa,WAAU;IACpB,UAAA1C,EACC,0BACA,gIACF;GACW,CAAA;GACb,kBAAC,QAAD;IACE,WAAW,MAAM;KAEf,AADA,EAAE,eAAe,GACjBwB,EAAa;IACf;IACA,WAAU;IALZ,UAAA;KAQGf,KACC,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAACkC,GAAD,EAAW,WAAU,oEAAqE,CAAA,GAC1F,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAlC;MACA,CAAA,CACA;;KAIP,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OACd,UAAAT,EAAG,mCAAmC,mBAAmB;MACrD,CAAA,GACP,kBAAC,OAAD;OAAK,WAAU;OACZ,UAAAM,EAAK,KAAK,GAAO,MAChB,kBAAC,SAAD;QAEE,MAAM,MAAO;SACX,EAAU,QAAQgB,KAASsB;QAC7B;QACA,MAAK;QACL,WAAU;QACV,cAAa;QACb,WAAW;QACX,cACE5C,EACE,iCACA,yBACF,IAAI,IAAIsB,IAAQ;QAElB,OAAOH;QACP,WAAW,MAAMD,EAAaI,GAAO,EAAE,OAAO,KAAK;QACnD,YAAY,MAAMG,EAAcH,GAAO,CAAC;QACxC,SAASK;QACT,WAAU;OACX,GAnBML,CAmBN,CACF;MACE,CAAA,CACF;;KAGL,kBAACuB,GAAD;MACE,MAAK;MACL,UAAUC,KAAaxC,EAAK,KAAK,EAAE,CAAC,CAAC,WAAW;MAChD,SAAQ;MACR,MAAK;MACL,WAAU;MAET,UAAAwC,IACC,kBAAAhD,GAAA,EAAA,UAAA,CACE,kBAACkD,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAC9C,kBAAC,QAAD,EAAA,UAAOhD,EAAG,4BAA4B,YAAY,EAAQ,CAAA,CAC1D,EAAA,CAAA,IAEFA,EAAG,yBAAyB,QAAQ;KAEhC,CAAA;KAGR,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAA,EACC,6BACA,sCACF;MACC,CAAA,GACH,kBAAC,UAAD;OACE,MAAK;OACL,eAAeE,EAAS,oBAAoB;OAC5C,WAAU;OAET,UAAAF,EAAG,kCAAkC,qBAAqB;MACrD,CAAA,CACL;;IACD;;GAGN,kBAAC,KAAD;IAAG,WAAU;IACX,UAAA,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;MAOb,AALA,eAAe,WAAWc,CAAgB,GAC1C,eAAe,WAAWE,CAAiB,GAC3C,eAAe,WAAWoB,CAAmB,GAC7C,eAAe,WAAWnB,CAAY,GACtCoB,EAAa,SAAS,CAAC,CAAC,eAAe,EAAK,GAC5CnC,EAAS,aAAa;KACxB;KACA,WAAU;KAET,UAAAF,EAAG,+BAA+B,iBAAiB;IAC9C,CAAA;GACP,CAAA;EACO;;AAEhB"}
|
|
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","handleChange","digit","value","newCode","index","fullCode","handleSubmit","handleKeyDown","e","handlePaste","pastedData","i","char","lastIndex","codeToSubmit","result","verifyMFA","ssoRedirect","MFA_REDIRECT","MFA_PENDING_TOKEN","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 the first input on mount (and when the SSO challenge changes).\n useEffect(() => {\n inputRefs.current[0]?.focus();\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\n role=\"alert\"\n 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 >\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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"}
|
|
@@ -154,6 +154,7 @@ function x() {
|
|
|
154
154
|
className: "space-y-5",
|
|
155
155
|
children: [
|
|
156
156
|
H && /* @__PURE__ */ _("div", {
|
|
157
|
+
role: "alert",
|
|
157
158
|
className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
|
|
158
159
|
children: [/* @__PURE__ */ g("svg", {
|
|
159
160
|
className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5",
|
|
@@ -303,6 +304,7 @@ function x() {
|
|
|
303
304
|
className: "space-y-5",
|
|
304
305
|
children: [
|
|
305
306
|
H && /* @__PURE__ */ _("div", {
|
|
307
|
+
role: "alert",
|
|
306
308
|
className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
|
|
307
309
|
children: [/* @__PURE__ */ g("svg", {
|
|
308
310
|
className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5",
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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\n role=\"alert\"\n 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 >\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\n role=\"alert\"\n 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 >\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":";;;;;;;;;;;AAkBA,IAAMC,IAAiB;AAMvB,SAAgBC,IAAoB;CAClC,IAAMC,IAAKC,EAAM,GACXC,IAAWC,EAAY,GAEvB,CAACC,KAAgBC,EAAgB,GACjCC,IAAQF,EAAa,IAAI,OAAO,GAEhCG,IAAWH,EAAa,IAAI,UAAU,GACtC,EAAE,yBAAsB,kBAAe,iBAC3CI,EAAsB,GAClB,EAAE,kBAAkBC,MAAqBC,EAAa,GACtD,CAACC,GAAOC,KAAYC,EAAS,EAAE,GAC/B,CAACC,GAAaC,KAAkBF,EAAS,EAAE,GAC3C,CAACG,GAAiBC,KAAsBJ,EAAS,EAAE,GACnD,CAACK,GAAcC,KAAmBN,EAAS,EAAK,GAChD,CAACO,GAAqBC,KAA0BR,EAAS,EAAK,GAC9D,CAACS,GAASC,KAAcV,EAAS,EAAK,GACtC,CAACW,GAAOC,KAAYZ,EAAS,EAAE,GAC/B,CAACa,GAAWC,KAAgBd,EAASf,CAAc;CAGzD,QAAgB;EACd,IAAI,CAACwB,KAAW,CAAChB,GAAO;EAExB,IAAMsB,IAAYrB,IACd,wBAAwB,mBAAmBA,CAAQ,MACnD,eACEsB,IAAQ,kBAAkB;GAC9B,GAAc,MACRC,KAAQ,KACV,cAAcD,CAAK,GACnB3B,EAAS0B,CAAS,GACX,KAEFE,IAAO,CACf;EACH,GAAG,GAAI;EAEP,aAAa,cAAcD,CAAK;CAClC,GAAG;EAACP;EAAShB;EAAOJ;EAAUK;CAAQ,CAAC;CAGvC,IAAMwB,WAAwB;EAC5B,IAAMC,IAAWlB;EACjB,OAAO;GACL;IACE,OAAOd,EAAG,6BAA6B,wBAAwB;IAC/D,KAAKgC,EAAS,UAAU;GAC1B;GACA;IACE,OAAOhC,EAAG,kCAAkC,sBAAsB;IAClE,KAAK,QAAQ,KAAKgC,CAAQ;GAC5B;GACA;IACE,OAAOhC,EAAG,kCAAkC,sBAAsB;IAClE,KAAK,QAAQ,KAAKgC,CAAQ;GAC5B;GACA;IACE,OAAOhC,EAAG,+BAA+B,YAAY;IACrD,KAAK,KAAK,KAAKgC,CAAQ;GACzB;GACA;IACE,OAAOhC,EAAG,gCAAgC,uBAAuB;IACjE,KAAK,yBAAyB,KAAKgC,CAAQ;GAC7C;EACF;CACF,EAAA,CAAG,GAEGC,WAA0B;EAC9B,IAAMC,IAAWH,EAAe,QAAQ,MAAMI,EAAE,GAAG,CAAC,CAAC;EAmBrD,OAlBID,KAAY,IACP;GACL,OAAOlC,EAAG,2CAA2C,MAAM;GAC3D,OAAO;GACP,OAAO;EACT,IACEkC,KAAY,IACP;GACL,OAAOlC,EAAG,2CAA2C,MAAM;GAC3D,OAAO;GACP,OAAO;EACT,IACEkC,KAAY,IACP;GACL,OAAOlC,EAAG,2CAA2C,MAAM;GAC3D,OAAO;GACP,OAAO;EACT,IACK;GACL,OAAOA,EAAG,6CAA6C,QAAQ;GAC/D,OAAO;GACP,OAAO;EACT;CACF,EAAA,CAAG;CAwKH,OAxGIsB,IAEA,kBAACc,GAAD;EACE,OACE9B,IACIN,EAAG,sCAAsC,kBAAkB,IAC3DA,EAAG,iCAAiC,kBAAkB;EAE5D,UACEM,IACIN,EACE,sCACA,2CACF,IACAA,EACE,uCACA,iDACF;EAfR,UAAA;GAmBE,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;MAEP,UAAA,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;GACF,CAAA;GAEL,kBAAC,KAAD;IAAG,WAAU;IACV,UAAAM,IACGN,EACE,mCACA,+DACF,IACAA,EACE,kCACA,oFACF,CAAC,CAAC,QAAQ,aAAaW,CAAK;GAC/B,CAAA;GAEFL,IACC,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KAAb,UAAA;MACGN,EACC,oCACA,2BACF;MAAG;MACH,kBAAC,QAAD;OAAM,WAAU;OACb,UAAA0B;MACG,CAAA;MAAE;MACP1B,EAAG,8BAA8B,YAAY;KAC7C;IACH,CAAA,GAAA,kBAACqC,GAAD;KACE,MAAK;KACL,eACEnC,EACEK,IACI,wBAAwB,mBAAmBA,CAAQ,MACnD,aACN;KAEF,SAAQ;KACR,MAAK;KACL,WAAU;KAET,UAAAP,EAAG,gCAAgC,aAAa;IAC3C,CAAA,CACL;GAEL,CAAA,IAAA,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAeuB,EAAW,EAAK;KAC/B,WAAU;KAET,UAAAvB,EACC,mCACA,qCACF;IACM,CAAA,GACR,kBAAC,UAAD;KACE,MAAK;KACL,eAAeE,EAAS,aAAa;KACrC,WAAU;KAET,UAAAF,EAAG,mCAAmC,iBAAiB;IAClD,CAAA,CACL;;EAEG;C,KAKd,kBAACoC,GAAD;EACE,OACE9B,IACIN,EAAG,wCAAwC,qBAAqB,IAChEA,EAAG,iCAAiC,qBAAqB;EAE/D,UACEM,IACIN,EACE,uCACA,uCACF,IACAA,EACE,iCACA,kDACF;EAfR,UAAA;GAkBE,kBAACsC,GAAD;IAAa,WAAU;IACpB,UAAAhC,IACGN,EACE,mCACA,yGACF,IACAA,EACE,qCACA,4HACF;GACO,CAAA;GACZM,IACC,kBAAC,QAAD;IAAM,UAAU,OAlLa,MAAuB;KAIxD,IAHA,EAAE,eAAe,GACjBmB,EAAS,EAAE,GAENnB,GAEL;UAAIQ,MAAgBE,GAClB,EACEhB,EAAG,uCAAuC,wBAAwB,CACpE;WAIF,IAAI;OAEF,AADA,MAAMuC,EAAcjC,GAAOQ,CAAW,GACtCS,EAAW,EAAI;MACjB,SAASiB,GAAK;OACZ,IAAMC,IAAMD,aAAe,QAAQA,EAAI,UAAU;OACjD,AACEf,EADEgB,MAAQ,kBAERzC,EACE,wCACA,iEACF,IAEOyC,MAAQ,kBAEfzC,EACE,wCACA,+DACF,IAIAA,EACE,mCACA,6CACF,CACF;MAEJ;KA7BA;IA8BF;IAyI2C,WAAU;IAA/C,UAAA;KAEGwB,KACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;MAFZ,UAAA,CAIE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;OAEP,UAAA,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;OACH,CAAA;MACE,CAAA,GACL,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAA;MACA,CAAA,CACA;;KAIP,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;QAET,UAAAxB,EAAG,uCAAuC,cAAc;OACpD,CAAA;OACP,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA;SACE,kBAAC,OAAD;UAAK,WAAU;UACb,UAAA,kBAAC0C,GAAD,EAAU,WAAU,2CAA4C,CAAA;SAC7D,CAAA;SACL,kBAAC,SAAD;UACE,IAAG;UACH,MAAMxB,IAAe,SAAS;UAC9B,UAAA;UACA,iBAAc;UACd,cAAYlB,EACV,uCACA,cACF;UACA,cAAa;UACb,OAAOc;UACP,WAAW,MAAMC,EAAe,EAAE,OAAO,KAAK;UAC9C,WAAU;UACV,aAAaf,EACX,6CACA,oBACF;SACD,CAAA;SACD,kBAAC,UAAD;UACE,MAAK;UACL,eAAemB,EAAgB,CAACD,CAAY;UAC5C,cAAYA,IAAe,kBAAkB;UAC7C,WAAU;UAET,UACCyB,EADDzB,IACE0B,IAEAC,GAFD,EAAY,WAAU,SAAU,CAEH;SAEzB,CAAA;QACL;;OAGJ/B,KACC,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,OAAD;UAAK,WAAU;UACb,UAAA,kBAAC,OAAD,EACE,WAAW,UAAUmB,EAAiB,MAAM,GAAGA,EAAiB,MAAM,8BACvE,CAAA;SACE,CAAA,GACL,kBAAC,KAAD;UAAG,WAAU;UAAb,UAAA;WACGjC,EACC,uCACA,oBACF;WAAG;WACH,kBAAC,QAAD;YAAM,WAAU;YACb,UAAAiC,EAAiB;WACd,CAAA;UACL;SACA,CAAA,CAAA;QAEL,CAAA,GAAA,kBAAC,OAAD;SAAK,WAAU;SACZ,UAAAF,EAAe,KAAK,MACnB,kBAAC,OAAD;UAAuB,WAAU;UAAjC,UAAA,CACGe,EAAM,MACL,kBAACC,GAAD,EAAW,WAAU,kCAAmC,CAAA,IAExD,kBAACC,GAAD,EAAO,WAAU,2CAA4C,CAAA,GAE/D,kBAAC,QAAD;WACE,WAAW,WACTF,EAAM,MACF,4CACA;WAGL,UAAAA,EAAM;UACH,CAAA,CACH;SAfK,GAAAA,EAAM,KAeX,CACN;QACE,CAAA,CACF;;MAEJ;;KAGL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;QAET,UAAA9C,EACC,2CACA,kBACF;OACK,CAAA;OACP,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA;SACE,kBAAC,OAAD;UAAK,WAAU;UACb,UAAA,kBAAC0C,GAAD,EAAU,WAAU,2CAA4C,CAAA;SAC7D,CAAA;SACL,kBAAC,SAAD;UACE,IAAG;UACH,MAAMtB,IAAsB,SAAS;UACrC,UAAA;UACA,iBAAc;UACd,cAAYpB,EACV,2CACA,kBACF;UACA,cAAa;UACb,gBACEgB,EAAgB,SAAS,KAAKF,MAAgBE;UAEhD,OAAOA;UACP,WAAW,MAAMC,EAAmB,EAAE,OAAO,KAAK;UAClD,WAAU;UACV,aAAajB,EACX,iDACA,sBACF;SACD,CAAA;SACD,kBAAC,UAAD;UACE,MAAK;UACL,eAAeqB,EAAuB,CAACD,CAAmB;UAC1D,cACEA,IACIpB,EACE,0CACA,uBACF,IACAA,EACE,0CACA,uBACF;UAEN,WAAU;UAET,UACC2C,EADDvB,IACEwB,IAEAC,GAFD,EAAY,WAAU,SAAU,CAEH;SAEzB,CAAA;QACL;;OACJ7B,KAAmBF,MAAgBE,KAClC,kBAAC,KAAD;QAAG,WAAU;QACV,UAAAhB,EACC,wCACA,wBACF;OACC,CAAA;MAEF;;KAGL,kBAACqC,GAAD;MACE,MAAK;MACL,UACEY,KACCjC,EAAgB,SAAS,KAAKF,MAAgBE;MAEjD,SAAQ;MACR,MAAK;MACL,WAAU;MAET,UAAAiC,IACC,kBAAApD,GAAA,EAAA,UAAA,CACE,kBAACsD,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAC9C,kBAAC,QAAD,EAAA,UACGnD,EAAG,gCAAgC,uBAAuB,EACvD,CAAA,CACN,EAAA,CAAA,IAEFA,EAAG,kCAAkC,gBAAgB;KAEjD,CAAA;IACJ;GAEN,CAAA,IAAA,kBAAC,QAAD;IAAM,UAAU,OApZY,MAAuB;KAEvD,AADA,EAAE,eAAe,GACjByB,EAAS,EAAE;KACX,IAAI;MAEF,IAAM2B,IAAiB,MAAM3C,EAAiB,gBAAgB;MAE9D,AADA,MAAM4C,EAAqB1C,GAAOyC,CAAc,GAChD7B,EAAW,EAAI;KACjB,SAASiB,GAAK;MAOZ,AANAf,EACEzB,EACE,iCACA,+CACF,CACF,GACA,QAAQ,MAAM,qCAAqCwC,CAAG;KACxD;IACF;IAmY0C,WAAU;IAA9C,UAAA;KAEGhB,KACC,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;MAFZ,UAAA,CAIE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;OAEP,UAAA,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;OACH,CAAA;MACE,CAAA,GACL,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAA;MACA,CAAA,CACA;;KAIP,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;OAET,UAAAxB,EAAG,iCAAiC,eAAe;MAC/C,CAAA,GACP,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;QACb,UAAA,kBAACsD,GAAD,EAAW,WAAU,2CAA4C,CAAA;OAC9D,CAAA,GACL,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,UAAA;QACA,iBAAc;QACd,cAAYtD,EACV,iCACA,eACF;QACA,cAAa;QACb,OAAOW;QACP,WAAW,MAAMC,EAAS,EAAE,OAAO,KAAK;QACxC,WAAU;QACV,aAAaZ,EACX,uCACA,iBACF;OACD,CAAA,CACE;MACF,CAAA,CAAA;;KAGL,kBAACqC,GAAD;MACE,MAAK;MACL,UAAUY;MACV,SAAQ;MACR,MAAK;MACL,WAAU;MAET,UAAAA,IACC,kBAAApD,GAAA,EAAA,UAAA,CACE,kBAACsD,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAC9C,kBAAC,QAAD,EAAA,UAAOnD,EAAG,8BAA8B,YAAY,EAAQ,CAAA,CAC5D,EAAA,CAAA,IAEFA,EAAG,oCAAoC,iBAAiB;KAEpD,CAAA;IACJ;;GAIR,kBAAC,KAAD;IAAG,WAAU;IAAb,UAAA;KACGA,EAAG,uCAAuC,yBAAyB;KAAG;KACvE,kBAAC,UAAD;MACE,MAAK;MACL,eAAeE,EAAS,aAAa;MACrC,WAAU;MAET,UAAAF,EAAG,6BAA6B,SAAS;KACpC,CAAA;IACP;;EACO;;AAEhB"}
|
|
@@ -70,6 +70,7 @@ function g() {
|
|
|
70
70
|
className: "space-y-5",
|
|
71
71
|
children: [
|
|
72
72
|
E && /* @__PURE__ */ p("div", {
|
|
73
|
+
role: "alert",
|
|
73
74
|
className: "flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
|
|
74
75
|
children: [/* @__PURE__ */ f(r, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ f("div", {
|
|
75
76
|
className: "flex-1",
|
|
@@ -80,6 +81,7 @@ function g() {
|
|
|
80
81
|
})]
|
|
81
82
|
}),
|
|
82
83
|
/* @__PURE__ */ f("div", {
|
|
84
|
+
role: "status",
|
|
83
85
|
className: "p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]",
|
|
84
86
|
children: /* @__PURE__ */ p("div", {
|
|
85
87
|
className: "flex items-start gap-3",
|
|
@@ -206,7 +208,7 @@ function g() {
|
|
|
206
208
|
}),
|
|
207
209
|
/* @__PURE__ */ p("div", {
|
|
208
210
|
className: "mt-6 p-4 rounded-xl bg-[var(--color-bg-muted)] border border-[var(--color-border-default)]",
|
|
209
|
-
children: [/* @__PURE__ */ f("
|
|
211
|
+
children: [/* @__PURE__ */ f("h2", {
|
|
210
212
|
className: "text-sm font-medium text-[var(--color-text-primary)] mb-2",
|
|
211
213
|
children: g("auth.reactivate.cantReactivate", "Can't reactivate your account?")
|
|
212
214
|
}), /* @__PURE__ */ f("p", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactivatePage.js","names":["_Fragment","ReactivatePage","tr","useTr","navigate","useNavigate","location","useLocation","useAccountOperations","emailFromState","email","setEmail","useState","password","setPassword","error","setError","success","setSuccess","useEffect","AuthLayout","SpinnerIcon","PagePurpose","result","reactivateAccount","err","AlertIcon","EmailIcon","LockIcon","Button","isReactivating","Fragment"],"sources":["../../../src/auth/pages/ReactivatePage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useLocation } from \"react-router\";\nimport { useAccountOperations } from \"../hooks/useAccountOperations\";\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 SpinnerIcon,\n AlertIcon,\n} from \"../components/AuthIcons\";\n\n/**\n * Account Reactivation Page\n * Allows users to reactivate their deactivated accounts\n */\nexport function ReactivatePage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const location = useLocation();\n const { reactivateAccount, isReactivating } = useAccountOperations();\n\n // Get email from navigation state (passed from login page)\n const emailFromState = (location.state as { email?: string })?.email || \"\";\n\n const [email, setEmail] = useState(emailFromState);\n const [password, setPassword] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Update email if passed from login page\n useEffect(() => {\n if (emailFromState) {\n setEmail(emailFromState);\n }\n }, [emailFromState]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!email || !password) {\n setError(\n tr(\"auth.reactivate.enterBoth\", \"Please enter both email and password\"),\n );\n return;\n }\n\n try {\n const result = await reactivateAccount(email, password);\n\n if (result.success) {\n setSuccess(true);\n // Redirect to dashboard after short delay\n setTimeout(() => {\n navigate(\"/dashboard\", { replace: true });\n }, 2000);\n } else {\n setError(\n result.error ||\n tr(\"auth.reactivate.failed\", \"Failed to reactivate account\"),\n );\n }\n } catch (err) {\n console.error(\"Reactivation error:\", err);\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.reactivate.unexpectedError\",\n \"An unexpected error occurred\",\n ),\n );\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={tr(\"auth.reactivate.successTitle\", \"Account Reactivated!\")}\n subtitle={tr(\"auth.reactivate.successSubtitle\", \"Welcome back\")}\n >\n <div className=\"text-center py-8\">\n <div className=\"size-20 mx-auto mb-6 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-10 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\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.reactivate.accountActive\", \"Your account is now active\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.reactivate.redirecting\",\n \"You can now access all features. Redirecting to dashboard...\",\n )}\n </p>\n\n <div className=\"flex justify-center\">\n <SpinnerIcon className=\"size-6 text-status-success-text animate-spin\" />\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.reactivate.title\", \"Reactivate Account\")}\n subtitle={tr(\n \"auth.reactivate.subtitle\",\n \"Restore access to your deactivated account\",\n )}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.reactivate.purpose\",\n \"Reactivate an account you previously deactivated. Confirm your email and password to restore full access — deleted accounts can't be recovered here.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} 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 <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Banner */}\n <div className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\">\n <div className=\"flex items-start gap-3\">\n <svg\n className=\"size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <p className=\"text-sm text-[var(--color-status-warning-text)] font-medium\">\n {tr(\"auth.reactivate.deactivated\", \"Account Deactivated?\")}\n </p>\n <p className=\"text-xs text-[var(--color-status-warning-text)] mt-1\">\n {tr(\n \"auth.reactivate.instructions\",\n \"If you previously deactivated your account, you can restore it here. Enter your email and password to reactivate.\",\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.reactivate.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-label={tr(\"auth.reactivate.emailLabel\", \"Email Address\")}\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-status-success-bg/20 focus:border-status-success-border transition-all\"\n placeholder={tr(\n \"auth.reactivate.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.reactivate.passwordLabel\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type=\"password\"\n required\n aria-label={tr(\"auth.reactivate.passwordLabel\", \"Password\")}\n value={password}\n onChange={(e) => setPassword(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-status-success-bg/20 focus:border-status-success-border transition-all\"\n placeholder={tr(\n \"auth.reactivate.passwordPlaceholder\",\n \"Enter your password\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isReactivating || !email || !password}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isReactivating ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.reactivate.reactivating\", \"Reactivating…\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\"\n />\n </svg>\n <span>\n {tr(\"auth.reactivate.reactivateBtn\", \"Reactivate Account\")}\n </span>\n </>\n )}\n </Button>\n </form>\n\n {/* Links */}\n <div className=\"mt-8 pt-6 border-t border-[var(--color-border-default)] space-y-4\">\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\n \"auth.reactivate.accountIsActive\",\n \"Remember your account is active?\",\n )}{\" \"}\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.reactivate.signInInstead\", \"Sign in instead\")}\n </span>\n </button>\n </div>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.reactivate.forgotPassword\", \"Forgot your password?\")}{\" \"}\n <span className=\"font-semibold text-accent-purple hover:text-accent-purple\">\n {tr(\"auth.reactivate.resetIt\", \"Reset it\")}\n </span>\n </button>\n </div>\n </div>\n\n {/* Help Text */}\n <div className=\"mt-6 p-4 rounded-xl bg-[var(--color-bg-muted)] border border-[var(--color-border-default)]\">\n <h4 className=\"text-sm font-medium text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.reactivate.cantReactivate\",\n \"Can't reactivate your account?\",\n )}\n </h4>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.reactivate.deletedHelp\",\n \"If your account was deleted (not just deactivated), it cannot be restored. Contact support if you believe this is an error.\",\n )}\n </p>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReactivatePage.js","names":["_Fragment","ReactivatePage","tr","useTr","navigate","useNavigate","location","useLocation","useAccountOperations","emailFromState","email","setEmail","useState","password","setPassword","error","setError","success","setSuccess","useEffect","AuthLayout","SpinnerIcon","PagePurpose","result","reactivateAccount","err","AlertIcon","EmailIcon","LockIcon","Button","isReactivating","Fragment"],"sources":["../../../src/auth/pages/ReactivatePage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useLocation } from \"react-router\";\nimport { useAccountOperations } from \"../hooks/useAccountOperations\";\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 SpinnerIcon,\n AlertIcon,\n} from \"../components/AuthIcons\";\n\n/**\n * Account Reactivation Page\n * Allows users to reactivate their deactivated accounts\n */\nexport function ReactivatePage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const location = useLocation();\n const { reactivateAccount, isReactivating } = useAccountOperations();\n\n // Get email from navigation state (passed from login page)\n const emailFromState = (location.state as { email?: string })?.email || \"\";\n\n const [email, setEmail] = useState(emailFromState);\n const [password, setPassword] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Update email if passed from login page\n useEffect(() => {\n if (emailFromState) {\n setEmail(emailFromState);\n }\n }, [emailFromState]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!email || !password) {\n setError(\n tr(\"auth.reactivate.enterBoth\", \"Please enter both email and password\"),\n );\n return;\n }\n\n try {\n const result = await reactivateAccount(email, password);\n\n if (result.success) {\n setSuccess(true);\n // Redirect to dashboard after short delay\n setTimeout(() => {\n navigate(\"/dashboard\", { replace: true });\n }, 2000);\n } else {\n setError(\n result.error ||\n tr(\"auth.reactivate.failed\", \"Failed to reactivate account\"),\n );\n }\n } catch (err) {\n console.error(\"Reactivation error:\", err);\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.reactivate.unexpectedError\",\n \"An unexpected error occurred\",\n ),\n );\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={tr(\"auth.reactivate.successTitle\", \"Account Reactivated!\")}\n subtitle={tr(\"auth.reactivate.successSubtitle\", \"Welcome back\")}\n >\n <div className=\"text-center py-8\">\n <div className=\"size-20 mx-auto mb-6 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"size-10 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\n <h2 className=\"text-xl font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\"auth.reactivate.accountActive\", \"Your account is now active\")}\n </h2>\n <p className=\"text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.reactivate.redirecting\",\n \"You can now access all features. Redirecting to dashboard...\",\n )}\n </p>\n\n <div className=\"flex justify-center\">\n <SpinnerIcon className=\"size-6 text-status-success-text animate-spin\" />\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.reactivate.title\", \"Reactivate Account\")}\n subtitle={tr(\n \"auth.reactivate.subtitle\",\n \"Restore access to your deactivated account\",\n )}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.reactivate.purpose\",\n \"Reactivate an account you previously deactivated. Confirm your email and password to restore full access — deleted accounts can't be recovered here.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div\n role=\"alert\"\n 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 >\n <AlertIcon className=\"size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n </div>\n )}\n\n {/* Info Banner */}\n <div\n role=\"status\"\n className=\"p-4 rounded-xl bg-[var(--color-status-warning-bg-subtle)] border border-[var(--color-status-warning-border)]\"\n >\n <div className=\"flex items-start gap-3\">\n <svg\n className=\"size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <div>\n <p className=\"text-sm text-[var(--color-status-warning-text)] font-medium\">\n {tr(\"auth.reactivate.deactivated\", \"Account Deactivated?\")}\n </p>\n <p className=\"text-xs text-[var(--color-status-warning-text)] mt-1\">\n {tr(\n \"auth.reactivate.instructions\",\n \"If you previously deactivated your account, you can restore it here. Enter your email and password to reactivate.\",\n )}\n </p>\n </div>\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.reactivate.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-label={tr(\"auth.reactivate.emailLabel\", \"Email Address\")}\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-status-success-bg/20 focus:border-status-success-border transition-all\"\n placeholder={tr(\n \"auth.reactivate.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.reactivate.passwordLabel\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type=\"password\"\n required\n aria-label={tr(\"auth.reactivate.passwordLabel\", \"Password\")}\n value={password}\n onChange={(e) => setPassword(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-status-success-bg/20 focus:border-status-success-border transition-all\"\n placeholder={tr(\n \"auth.reactivate.passwordPlaceholder\",\n \"Enter your password\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isReactivating || !email || !password}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isReactivating ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.reactivate.reactivating\", \"Reactivating…\")}</span>\n </>\n ) : (\n <>\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\"\n />\n </svg>\n <span>\n {tr(\"auth.reactivate.reactivateBtn\", \"Reactivate Account\")}\n </span>\n </>\n )}\n </Button>\n </form>\n\n {/* Links */}\n <div className=\"mt-8 pt-6 border-t border-[var(--color-border-default)] space-y-4\">\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\n \"auth.reactivate.accountIsActive\",\n \"Remember your account is active?\",\n )}{\" \"}\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.reactivate.signInInstead\", \"Sign in instead\")}\n </span>\n </button>\n </div>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.reactivate.forgotPassword\", \"Forgot your password?\")}{\" \"}\n <span className=\"font-semibold text-accent-purple hover:text-accent-purple\">\n {tr(\"auth.reactivate.resetIt\", \"Reset it\")}\n </span>\n </button>\n </div>\n </div>\n\n {/* Help Text */}\n <div className=\"mt-6 p-4 rounded-xl bg-[var(--color-bg-muted)] border border-[var(--color-border-default)]\">\n <h2 className=\"text-sm font-medium text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.reactivate.cantReactivate\",\n \"Can't reactivate your account?\",\n )}\n </h2>\n <p className=\"text-xs text-[var(--color-text-secondary)]\">\n {tr(\n \"auth.reactivate.deletedHelp\",\n \"If your account was deleted (not just deactivated), it cannot be restored. Contact support if you believe this is an error.\",\n )}\n </p>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -151,7 +151,7 @@ function y() {
|
|
|
151
151
|
})
|
|
152
152
|
})
|
|
153
153
|
}),
|
|
154
|
-
/* @__PURE__ */ g("
|
|
154
|
+
/* @__PURE__ */ g("h2", {
|
|
155
155
|
className: "text-lg font-semibold text-[var(--color-text-primary)] mb-2",
|
|
156
156
|
children: y("auth.register.accountCreated", "Account created successfully!")
|
|
157
157
|
}),
|
|
@@ -251,6 +251,7 @@ function y() {
|
|
|
251
251
|
})]
|
|
252
252
|
}),
|
|
253
253
|
(I && !q && !J && !Y || P.length > 0) && /* @__PURE__ */ _("div", {
|
|
254
|
+
role: "alert",
|
|
254
255
|
className: "flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
255
256
|
children: [/* @__PURE__ */ g(c, { className: "size-5 text-status-error-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ _("div", {
|
|
256
257
|
className: "space-y-1",
|
|
@@ -488,6 +489,7 @@ function y() {
|
|
|
488
489
|
})]
|
|
489
490
|
}),
|
|
490
491
|
R && /* @__PURE__ */ _("div", {
|
|
492
|
+
role: "alert",
|
|
491
493
|
className: "flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border",
|
|
492
494
|
children: [/* @__PURE__ */ g(c, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ g("p", {
|
|
493
495
|
className: "text-sm text-status-error-text",
|