@burdenoff/microfe-auth 2026.1023.2 → 2026.1031.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -8,54 +8,62 @@ import { useSearchParams as l } from "react-router";
|
|
|
8
8
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
9
|
import { Button as p } from "@burdenoff/fe-libs/button";
|
|
10
10
|
//#region src/auth/pages/EmailVerifyPage.tsx
|
|
11
|
-
var m = 5;
|
|
12
|
-
function
|
|
11
|
+
var m = 5, h = /* @__PURE__ */ new Map();
|
|
12
|
+
function g(e) {
|
|
13
13
|
let t = "/auth/login", n = e ? `${t}?email=${encodeURIComponent(e)}` : t;
|
|
14
14
|
window.location.href = n;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
let
|
|
16
|
+
function _() {
|
|
17
|
+
let _ = t(), [v] = l(), y = v.get("token"), b = v.get("email"), { verifyEmail: x, resendVerification: S } = n(), C = e((e) => e.user), [w, T] = c("verifying"), E = s(b || void 0), [D, O] = c(!1), [k, A] = c(!1), [j, M] = c(m), N = s(null);
|
|
18
18
|
o(() => {
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
19
|
+
if (!y) return;
|
|
20
|
+
try {
|
|
21
|
+
window.sessionStorage.removeItem(`auth:verifyEmail:inflight:${y}`);
|
|
22
|
+
} catch {}
|
|
23
|
+
if (N.current?.token !== y) {
|
|
24
|
+
let e = h.get(y);
|
|
25
|
+
e || (e = Promise.resolve().then(() => x(y)).finally(() => h.delete(y)), h.set(y, e)), N.current = {
|
|
26
|
+
token: y,
|
|
27
|
+
request: e
|
|
28
|
+
}, T("verifying");
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
let e = !0;
|
|
31
|
+
return N.current.request.then((t) => {
|
|
32
|
+
e && (t.success ? (T("success"), t.email && (E.current = t.email)) : T("error"));
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
e && T("error");
|
|
35
|
+
}), () => {
|
|
36
|
+
e = !1;
|
|
37
|
+
};
|
|
38
|
+
}, [y, x]), o(() => {
|
|
39
|
+
if (w !== "success") return;
|
|
32
40
|
let e = setInterval(() => {
|
|
33
|
-
|
|
41
|
+
M((t) => t <= 1 ? (clearInterval(e), g(E.current), 0) : t - 1);
|
|
34
42
|
}, 1e3);
|
|
35
43
|
return () => clearInterval(e);
|
|
36
|
-
}, [
|
|
37
|
-
let
|
|
38
|
-
let e =
|
|
44
|
+
}, [w]);
|
|
45
|
+
let P = async () => {
|
|
46
|
+
let e = b || C?.email;
|
|
39
47
|
if (!e) {
|
|
40
48
|
console.error("No email available for resend");
|
|
41
49
|
return;
|
|
42
50
|
}
|
|
43
|
-
|
|
51
|
+
O(!0);
|
|
44
52
|
try {
|
|
45
|
-
await
|
|
53
|
+
await S(e), A(!0);
|
|
46
54
|
} catch (e) {
|
|
47
55
|
console.error("Failed to resend verification:", e);
|
|
48
56
|
} finally {
|
|
49
|
-
|
|
57
|
+
O(!1);
|
|
50
58
|
}
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
}, []),
|
|
54
|
-
|
|
59
|
+
}, F = a(() => {
|
|
60
|
+
g(E.current);
|
|
61
|
+
}, []), I = a(() => {
|
|
62
|
+
g();
|
|
55
63
|
}, []);
|
|
56
|
-
return
|
|
57
|
-
title:
|
|
58
|
-
subtitle:
|
|
64
|
+
return w === "verifying" ? /* @__PURE__ */ d(r, {
|
|
65
|
+
title: _("auth.emailVerify.verifyingTitle", "Verifying your email"),
|
|
66
|
+
subtitle: _("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
|
|
59
67
|
children: /* @__PURE__ */ f("div", {
|
|
60
68
|
className: "flex flex-col items-center justify-center py-12",
|
|
61
69
|
children: [/* @__PURE__ */ d("div", {
|
|
@@ -63,12 +71,12 @@ function g() {
|
|
|
63
71
|
children: /* @__PURE__ */ d(i, { className: "size-8 text-[var(--color-text-link)] animate-spin" })
|
|
64
72
|
}), /* @__PURE__ */ d("p", {
|
|
65
73
|
className: "text-[var(--color-text-secondary)]",
|
|
66
|
-
children:
|
|
74
|
+
children: _("auth.emailVerify.moment", "This will only take a moment...")
|
|
67
75
|
})]
|
|
68
76
|
})
|
|
69
|
-
}) :
|
|
70
|
-
title:
|
|
71
|
-
subtitle:
|
|
77
|
+
}) : w === "success" ? /* @__PURE__ */ d(r, {
|
|
78
|
+
title: _("auth.emailVerify.verifiedTitle", "Email verified!"),
|
|
79
|
+
subtitle: _("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
|
|
72
80
|
children: /* @__PURE__ */ f("div", {
|
|
73
81
|
className: "flex flex-col items-center justify-center py-6",
|
|
74
82
|
children: [
|
|
@@ -89,34 +97,34 @@ function g() {
|
|
|
89
97
|
}),
|
|
90
98
|
/* @__PURE__ */ d("p", {
|
|
91
99
|
className: "text-center text-[var(--color-text-secondary)] mb-6",
|
|
92
|
-
children:
|
|
100
|
+
children: _("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
|
|
93
101
|
}),
|
|
94
102
|
/* @__PURE__ */ f("p", {
|
|
95
103
|
className: "text-center text-sm text-[var(--color-text-tertiary)] mb-6",
|
|
96
104
|
children: [
|
|
97
|
-
|
|
105
|
+
_("auth.emailVerify.redirecting", "Redirecting to sign in in"),
|
|
98
106
|
" ",
|
|
99
107
|
/* @__PURE__ */ d("span", {
|
|
100
108
|
className: "font-semibold text-[var(--color-text-primary)]",
|
|
101
|
-
children:
|
|
109
|
+
children: j
|
|
102
110
|
}),
|
|
103
111
|
" ",
|
|
104
|
-
|
|
112
|
+
_("auth.emailVerify.seconds", "seconds...")
|
|
105
113
|
]
|
|
106
114
|
}),
|
|
107
115
|
/* @__PURE__ */ d(p, {
|
|
108
116
|
type: "button",
|
|
109
|
-
onClick:
|
|
117
|
+
onClick: F,
|
|
110
118
|
variant: "default",
|
|
111
119
|
size: "lg",
|
|
112
120
|
className: "w-full font-semibold",
|
|
113
|
-
children:
|
|
121
|
+
children: _("auth.emailVerify.continueSignIn", "Continue to sign in now")
|
|
114
122
|
})
|
|
115
123
|
]
|
|
116
124
|
})
|
|
117
125
|
}) : /* @__PURE__ */ d(r, {
|
|
118
|
-
title:
|
|
119
|
-
subtitle:
|
|
126
|
+
title: _("auth.emailVerify.failedTitle", "Verification failed"),
|
|
127
|
+
subtitle: _("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
|
|
120
128
|
children: /* @__PURE__ */ f("div", {
|
|
121
129
|
className: "flex flex-col items-center justify-center py-6",
|
|
122
130
|
children: [
|
|
@@ -137,34 +145,34 @@ function g() {
|
|
|
137
145
|
}),
|
|
138
146
|
/* @__PURE__ */ d("p", {
|
|
139
147
|
className: "text-center text-[var(--color-text-secondary)] mb-8",
|
|
140
|
-
children:
|
|
148
|
+
children: _("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
|
|
141
149
|
}),
|
|
142
|
-
|
|
150
|
+
k ? /* @__PURE__ */ d("div", {
|
|
143
151
|
className: "w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4",
|
|
144
152
|
children: /* @__PURE__ */ d("p", {
|
|
145
153
|
className: "text-sm text-[var(--color-status-success-text)]",
|
|
146
|
-
children:
|
|
154
|
+
children: _("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
|
|
147
155
|
})
|
|
148
156
|
}) : /* @__PURE__ */ d(p, {
|
|
149
157
|
type: "button",
|
|
150
|
-
onClick:
|
|
151
|
-
disabled:
|
|
158
|
+
onClick: P,
|
|
159
|
+
disabled: D,
|
|
152
160
|
variant: "default",
|
|
153
161
|
size: "lg",
|
|
154
162
|
className: "mb-4 w-full font-semibold",
|
|
155
|
-
children:
|
|
163
|
+
children: D ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: _("auth.emailVerify.sending", "Sending…") })] }) : _("auth.emailVerify.resend", "Resend verification email")
|
|
156
164
|
}),
|
|
157
165
|
/* @__PURE__ */ d("button", {
|
|
158
166
|
type: "button",
|
|
159
|
-
onClick:
|
|
167
|
+
onClick: I,
|
|
160
168
|
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",
|
|
161
|
-
children:
|
|
169
|
+
children: _("auth.emailVerify.backToSignIn", "Back to sign in")
|
|
162
170
|
})
|
|
163
171
|
]
|
|
164
172
|
})
|
|
165
173
|
});
|
|
166
174
|
}
|
|
167
175
|
//#endregion
|
|
168
|
-
export {
|
|
176
|
+
export { _ as EmailVerifyPage };
|
|
169
177
|
|
|
170
178
|
//# sourceMappingURL=EmailVerifyPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmailVerifyPage.js","names":[],"sources":["../../../src/auth/pages/EmailVerifyPage.tsx"],"sourcesContent":["import { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useSearchParams } from \"react-router\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { SpinnerIcon } from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Navigate to login page with optional email pre-fill\n * Uses window.location.href for reliable navigation (works in new tabs)\n */\nfunction navigateToLogin(email?: string) {\n const baseUrl = \"/auth/login\";\n const url = email ? `${baseUrl}?email=${encodeURIComponent(email)}` : baseUrl;\n window.location.href = url;\n}\n\n/**\n * Email verification page with professional design\n */\nexport function EmailVerifyPage() {\n const tr = useTr();\n\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const urlEmail = searchParams.get(\"email\");\n const { verifyEmail, resendVerification } = useAuthOperations();\n const user = useAuthStore((state) => state.user);\n const [status, setStatus] = useState<\"verifying\" | \"success\" | \"error\">(\n \"verifying\",\n );\n const verifiedEmailRef = useRef<string | undefined>(urlEmail || undefined);\n const [isResending, setIsResending] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Guard against duplicate calls. useRef alone resets across remounts\n // (StrictMode, Suspense resolution of the lazy Auth root, HMR), so we\n // additionally key on the token via sessionStorage — any remount for the\n // same token short-circuits instead of firing a second mutation.\n const verificationInitiatedRef = useRef(false);\n\n useEffect(() => {\n if (!token || verificationInitiatedRef.current) return;\n\n const storageKey = `auth:verifyEmail:inflight:${token}`;\n if (\n typeof window !== \"undefined\" &&\n window.sessionStorage.getItem(storageKey)\n ) {\n return;\n }\n verificationInitiatedRef.current = true;\n try {\n window.sessionStorage.setItem(storageKey, \"1\");\n } catch {\n // sessionStorage unavailable (private mode, SSR); ref guard still applies.\n }\n\n verifyEmail(token)\n .then((result) => {\n if (result.success) {\n setStatus(\"success\");\n if (result.email) verifiedEmailRef.current = result.email;\n } else {\n setStatus(\"error\");\n }\n })\n .catch(() => setStatus(\"error\"));\n }, [token, verifyEmail]);\n\n // Auto-redirect countdown after successful verification\n useEffect(() => {\n if (status !== \"success\") return;\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigateToLogin(verifiedEmailRef.current);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [status]);\n\n const handleResend = async () => {\n const resendEmail = urlEmail || user?.email;\n if (!resendEmail) {\n console.error(\"No email available for resend\");\n return;\n }\n setIsResending(true);\n try {\n await resendVerification(resendEmail);\n setResendSuccess(true);\n } catch (err) {\n console.error(\"Failed to resend verification:\", err);\n } finally {\n setIsResending(false);\n }\n };\n\n const handleContinueToLogin = useCallback(() => {\n navigateToLogin(verifiedEmailRef.current);\n }, []);\n\n const handleBackToLogin = useCallback(() => {\n navigateToLogin();\n }, []);\n\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifyingTitle\", \"Verifying your email\")}\n subtitle={tr(\n \"auth.emailVerify.verifyingSubtitle\",\n \"Please wait while we confirm your email address\",\n )}\n >\n {/* Loading Spinner */}\n <div className=\"flex flex-col items-center justify-center py-12\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6\">\n <SpinnerIcon className=\"size-8 text-[var(--color-text-link)] animate-spin\" />\n </div>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\"auth.emailVerify.moment\", \"This will only take a moment...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifiedTitle\", \"Email verified!\")}\n subtitle={tr(\n \"auth.emailVerify.verifiedSubtitle\",\n \"Your email address has been confirmed\",\n )}\n >\n {/* Success Icon */}\n <div className=\"flex flex-col items-center justify-center py-6\">\n <div className=\"size-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6\">\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 <p className=\"text-center text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.emailVerify.successMessage\",\n \"Your email has been successfully verified. You can now sign in to access all features.\",\n )}\n </p>\n\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)] mb-6\">\n {tr(\"auth.emailVerify.redirecting\", \"Redirecting to sign in in\")}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.emailVerify.seconds\", \"seconds...\")}\n </p>\n\n <Button\n type=\"button\"\n onClick={handleContinueToLogin}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.emailVerify.continueSignIn\", \"Continue to sign in now\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.failedTitle\", \"Verification failed\")}\n subtitle={tr(\n \"auth.emailVerify.failedSubtitle\",\n \"We couldn't verify your email address\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Error Icon */}\n <div className=\"size-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-[var(--color-status-error-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=\"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\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {tr(\n \"auth.emailVerify.invalidLink\",\n \"The verification link is invalid or has expired. Please request a new verification email.\",\n )}\n </p>\n\n {resendSuccess ? (\n <div className=\"w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.emailVerify.emailSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </p>\n </div>\n ) : (\n <Button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n variant=\"default\"\n size=\"lg\"\n className=\"mb-4 w-full font-semibold\"\n >\n {isResending ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.emailVerify.sending\", \"Sending…\")}</span>\n </>\n ) : (\n tr(\"auth.emailVerify.resend\", \"Resend verification email\")\n )}\n </Button>\n )}\n\n <button\n type=\"button\"\n onClick={handleBackToLogin}\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(\"auth.emailVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EmailVerifyPage.js","names":[],"sources":["../../../src/auth/pages/EmailVerifyPage.tsx"],"sourcesContent":["import { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useSearchParams } from \"react-router\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { SpinnerIcon } from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\ntype VerificationRequest = ReturnType<\n ReturnType<typeof useAuthOperations>[\"verifyEmail\"]\n>;\n// Share only pending work across remounts; never persist bearer tokens.\nconst pendingVerifications = new Map<string, VerificationRequest>();\n\n/**\n * Navigate to login page with optional email pre-fill\n * Uses window.location.href for reliable navigation (works in new tabs)\n */\nfunction navigateToLogin(email?: string) {\n const baseUrl = \"/auth/login\";\n const url = email ? `${baseUrl}?email=${encodeURIComponent(email)}` : baseUrl;\n window.location.href = url;\n}\n\n/**\n * Email verification page with professional design\n */\nexport function EmailVerifyPage() {\n const tr = useTr();\n\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const urlEmail = searchParams.get(\"email\");\n const { verifyEmail, resendVerification } = useAuthOperations();\n const user = useAuthStore((state) => state.user);\n const [status, setStatus] = useState<\"verifying\" | \"success\" | \"error\">(\n \"verifying\",\n );\n const verifiedEmailRef = useRef<string | undefined>(urlEmail || undefined);\n const [isResending, setIsResending] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n const verificationRef = useRef<{\n token: string;\n request: VerificationRequest;\n } | null>(null);\n\n useEffect(() => {\n if (!token) return;\n try {\n // Remove the marker left by older versions, without touching other data.\n window.sessionStorage.removeItem(`auth:verifyEmail:inflight:${token}`);\n } catch {\n // Verification must also work when browser storage is unavailable.\n }\n\n if (verificationRef.current?.token !== token) {\n let request = pendingVerifications.get(token);\n if (!request) {\n request = Promise.resolve()\n .then(() => verifyEmail(token))\n .finally(() => pendingVerifications.delete(token));\n pendingVerifications.set(token, request);\n }\n verificationRef.current = { token, request };\n setStatus(\"verifying\");\n }\n\n let active = true;\n verificationRef.current.request\n .then((result) => {\n if (!active) return;\n if (result.success) {\n setStatus(\"success\");\n if (result.email) verifiedEmailRef.current = result.email;\n } else {\n setStatus(\"error\");\n }\n })\n .catch(() => {\n if (active) setStatus(\"error\");\n });\n return () => {\n active = false;\n };\n }, [token, verifyEmail]);\n\n // Auto-redirect countdown after successful verification\n useEffect(() => {\n if (status !== \"success\") return;\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigateToLogin(verifiedEmailRef.current);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [status]);\n\n const handleResend = async () => {\n const resendEmail = urlEmail || user?.email;\n if (!resendEmail) {\n console.error(\"No email available for resend\");\n return;\n }\n setIsResending(true);\n try {\n await resendVerification(resendEmail);\n setResendSuccess(true);\n } catch (err) {\n console.error(\"Failed to resend verification:\", err);\n } finally {\n setIsResending(false);\n }\n };\n\n const handleContinueToLogin = useCallback(() => {\n navigateToLogin(verifiedEmailRef.current);\n }, []);\n\n const handleBackToLogin = useCallback(() => {\n navigateToLogin();\n }, []);\n\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifyingTitle\", \"Verifying your email\")}\n subtitle={tr(\n \"auth.emailVerify.verifyingSubtitle\",\n \"Please wait while we confirm your email address\",\n )}\n >\n {/* Loading Spinner */}\n <div className=\"flex flex-col items-center justify-center py-12\">\n <div className=\"size-16 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center mb-6\">\n <SpinnerIcon className=\"size-8 text-[var(--color-text-link)] animate-spin\" />\n </div>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\"auth.emailVerify.moment\", \"This will only take a moment...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.verifiedTitle\", \"Email verified!\")}\n subtitle={tr(\n \"auth.emailVerify.verifiedSubtitle\",\n \"Your email address has been confirmed\",\n )}\n >\n {/* Success Icon */}\n <div className=\"flex flex-col items-center justify-center py-6\">\n <div className=\"size-20 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center mb-6\">\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 <p className=\"text-center text-[var(--color-text-secondary)] mb-6\">\n {tr(\n \"auth.emailVerify.successMessage\",\n \"Your email has been successfully verified. You can now sign in to access all features.\",\n )}\n </p>\n\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)] mb-6\">\n {tr(\"auth.emailVerify.redirecting\", \"Redirecting to sign in in\")}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.emailVerify.seconds\", \"seconds...\")}\n </p>\n\n <Button\n type=\"button\"\n onClick={handleContinueToLogin}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {tr(\"auth.emailVerify.continueSignIn\", \"Continue to sign in now\")}\n </Button>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n return (\n <AuthLayout\n title={tr(\"auth.emailVerify.failedTitle\", \"Verification failed\")}\n subtitle={tr(\n \"auth.emailVerify.failedSubtitle\",\n \"We couldn't verify your email address\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Error Icon */}\n <div className=\"size-20 rounded-full bg-[var(--color-status-error-bg-subtle)] flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-[var(--color-status-error-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=\"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\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {tr(\n \"auth.emailVerify.invalidLink\",\n \"The verification link is invalid or has expired. Please request a new verification email.\",\n )}\n </p>\n\n {resendSuccess ? (\n <div className=\"w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4\">\n <p className=\"text-sm text-[var(--color-status-success-text)]\">\n {tr(\n \"auth.emailVerify.emailSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </p>\n </div>\n ) : (\n <Button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n variant=\"default\"\n size=\"lg\"\n className=\"mb-4 w-full font-semibold\"\n >\n {isResending ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>{tr(\"auth.emailVerify.sending\", \"Sending…\")}</span>\n </>\n ) : (\n tr(\"auth.emailVerify.resend\", \"Resend verification email\")\n )}\n </Button>\n )}\n\n <button\n type=\"button\"\n onClick={handleBackToLogin}\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(\"auth.emailVerify.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|