@burdenoff/microfe-auth 2026.1030.1 → 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 h(e) {
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 g() {
17
- let g = t(), [_] = l(), v = _.get("token"), y = _.get("email"), { verifyEmail: b, resendVerification: x } = n(), S = e((e) => e.user), [C, w] = c("verifying"), T = s(y || void 0), [E, D] = c(!1), [O, k] = c(!1), [A, j] = c(m), M = s(!1);
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 (!v || M.current) return;
20
- let e = `auth:verifyEmail:inflight:${v}`;
21
- if (!(typeof window < "u" && window.sessionStorage.getItem(e))) {
22
- M.current = !0;
23
- try {
24
- window.sessionStorage.setItem(e, "1");
25
- } catch {}
26
- b(v).then((e) => {
27
- e.success ? (w("success"), e.email && (T.current = e.email)) : w("error");
28
- }).catch(() => w("error"));
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
- }, [v, b]), o(() => {
31
- if (C !== "success") return;
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
- j((t) => t <= 1 ? (clearInterval(e), h(T.current), 0) : t - 1);
41
+ M((t) => t <= 1 ? (clearInterval(e), g(E.current), 0) : t - 1);
34
42
  }, 1e3);
35
43
  return () => clearInterval(e);
36
- }, [C]);
37
- let N = async () => {
38
- let e = y || S?.email;
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
- D(!0);
51
+ O(!0);
44
52
  try {
45
- await x(e), k(!0);
53
+ await S(e), A(!0);
46
54
  } catch (e) {
47
55
  console.error("Failed to resend verification:", e);
48
56
  } finally {
49
- D(!1);
57
+ O(!1);
50
58
  }
51
- }, P = a(() => {
52
- h(T.current);
53
- }, []), F = a(() => {
54
- h();
59
+ }, F = a(() => {
60
+ g(E.current);
61
+ }, []), I = a(() => {
62
+ g();
55
63
  }, []);
56
- return C === "verifying" ? /* @__PURE__ */ d(r, {
57
- title: g("auth.emailVerify.verifyingTitle", "Verifying your email"),
58
- subtitle: g("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
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: g("auth.emailVerify.moment", "This will only take a moment...")
74
+ children: _("auth.emailVerify.moment", "This will only take a moment...")
67
75
  })]
68
76
  })
69
- }) : C === "success" ? /* @__PURE__ */ d(r, {
70
- title: g("auth.emailVerify.verifiedTitle", "Email verified!"),
71
- subtitle: g("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
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: g("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
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
- g("auth.emailVerify.redirecting", "Redirecting to sign in in"),
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: A
109
+ children: j
102
110
  }),
103
111
  " ",
104
- g("auth.emailVerify.seconds", "seconds...")
112
+ _("auth.emailVerify.seconds", "seconds...")
105
113
  ]
106
114
  }),
107
115
  /* @__PURE__ */ d(p, {
108
116
  type: "button",
109
- onClick: P,
117
+ onClick: F,
110
118
  variant: "default",
111
119
  size: "lg",
112
120
  className: "w-full font-semibold",
113
- children: g("auth.emailVerify.continueSignIn", "Continue to sign in now")
121
+ children: _("auth.emailVerify.continueSignIn", "Continue to sign in now")
114
122
  })
115
123
  ]
116
124
  })
117
125
  }) : /* @__PURE__ */ d(r, {
118
- title: g("auth.emailVerify.failedTitle", "Verification failed"),
119
- subtitle: g("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
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: g("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
148
+ children: _("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
141
149
  }),
142
- O ? /* @__PURE__ */ d("div", {
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: g("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
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: N,
151
- disabled: E,
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: E ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(i, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: g("auth.emailVerify.sending", "Sending…") })] }) : g("auth.emailVerify.resend", "Resend verification email")
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: F,
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: g("auth.emailVerify.backToSignIn", "Back to sign in")
169
+ children: _("auth.emailVerify.backToSignIn", "Back to sign in")
162
170
  })
163
171
  ]
164
172
  })
165
173
  });
166
174
  }
167
175
  //#endregion
168
- export { g as EmailVerifyPage };
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":";;;;;;;;;;AASA,IAAM,IAAiB;AAMvB,SAAS,EAAgB,GAAgB;CACvC,IAAM,IAAU,eACV,IAAM,IAAQ,GAAG,EAAQ,SAAS,mBAAmB,CAAK,MAAM;CACtE,OAAO,SAAS,OAAO;AACzB;AAKA,SAAgB,IAAkB;CAChC,IAAM,IAAK,EAAM,GAEX,CAAC,KAAgB,EAAgB,GACjC,IAAQ,EAAa,IAAI,OAAO,GAChC,IAAW,EAAa,IAAI,OAAO,GACnC,EAAE,gBAAa,0BAAuB,EAAkB,GACxD,IAAO,GAAc,MAAU,EAAM,IAAI,GACzC,CAAC,GAAQ,KAAa,EAC1B,WACF,GACM,IAAmB,EAA2B,KAAY,KAAA,CAAS,GACnE,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAe,KAAoB,EAAS,EAAK,GAClD,CAAC,GAAW,KAAgB,EAAS,CAAc,GAMnD,IAA2B,EAAO,EAAK;CAgC7C,AA9BA,QAAgB;EACd,IAAI,CAAC,KAAS,EAAyB,SAAS;EAEhD,IAAM,IAAa,6BAA6B;EAE9C,aAAO,SAAW,OAClB,OAAO,eAAe,QAAQ,CAAU,IAI1C;KAAyB,UAAU;GACnC,IAAI;IACF,OAAO,eAAe,QAAQ,GAAY,GAAG;GAC/C,QAAQ,CAER;GAEA,EAAY,CAAK,EACd,MAAM,MAAW;IAChB,AAAI,EAAO,WACT,EAAU,SAAS,GACf,EAAO,UAAO,EAAiB,UAAU,EAAO,UAEpD,EAAU,OAAO;GAErB,CAAC,EACA,YAAY,EAAU,OAAO,CAAC;EAhBE;CAiBrC,GAAG,CAAC,GAAO,CAAW,CAAC,GAGvB,QAAgB;EACd,IAAI,MAAW,WAAW;EAE1B,IAAM,IAAQ,kBAAkB;GAC9B,GAAc,MACR,KAAQ,KACV,cAAc,CAAK,GACnB,EAAgB,EAAiB,OAAO,GACjC,KAEF,IAAO,CACf;EACH,GAAG,GAAI;EAEP,aAAa,cAAc,CAAK;CAClC,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAe,YAAY;EAC/B,IAAM,IAAc,KAAY,GAAM;EACtC,IAAI,CAAC,GAAa;GAChB,QAAQ,MAAM,+BAA+B;GAC7C;EACF;EACA,EAAe,EAAI;EACnB,IAAI;GAEF,AADA,MAAM,EAAmB,CAAW,GACpC,EAAiB,EAAI;EACvB,SAAS,GAAK;GACZ,QAAQ,MAAM,kCAAkC,CAAG;EACrD,UAAU;GACR,EAAe,EAAK;EACtB;CACF,GAEM,IAAwB,QAAkB;EAC9C,EAAgB,EAAiB,OAAO;CAC1C,GAAG,CAAC,CAAC,GAEC,IAAoB,QAAkB;EAC1C,EAAgB;CAClB,GAAG,CAAC,CAAC;CAiFL,OA/EI,MAAW,cAEX,kBAAC,GAAD;EACE,OAAO,EAAG,mCAAmC,sBAAsB;EACnE,UAAU,EACR,sCACA,iDACF;YAGA,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAa,WAAU,oDAAqD,CAAA;GACzE,CAAA,GACL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,2BAA2B,iCAAiC;GAC/D,CAAA,CACA;;CACK,CAAA,IAIZ,MAAW,YAEX,kBAAC,GAAD;EACE,OAAO,EAAG,kCAAkC,iBAAiB;EAC7D,UAAU,EACR,qCACA,uCACF;YAGA,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IAEL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,mCACA,wFACF;IACC,CAAA;IAEH,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,gCAAgC,2BAA2B;MAAG;MAClE,kBAAC,QAAD;OAAM,WAAU;iBACb;MACG,CAAA;MAAE;MACP,EAAG,4BAA4B,YAAY;KAC3C;;IAEH,kBAAC,GAAD;KACE,MAAK;KACL,SAAS;KACT,SAAQ;KACR,MAAK;KACL,WAAU;eAET,EAAG,mCAAmC,yBAAyB;IAC1D,CAAA;GACL;;CACK,CAAA,IAMd,kBAAC,GAAD;EACE,OAAO,EAAG,gCAAgC,qBAAqB;EAC/D,UAAU,EACR,mCACA,uCACF;YAEA,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IAEL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,gCACA,2FACF;IACC,CAAA;IAEF,IACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,8BACA,mDACF;KACC,CAAA;IACA,CAAA,IAEL,kBAAC,GAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,SAAQ;KACR,MAAK;KACL,WAAU;eAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,WAAU,sBAAuB,CAAA,GAC9C,kBAAC,QAAD,EAAA,UAAO,EAAG,4BAA4B,UAAU,EAAQ,CAAA,CACxD,EAAA,CAAA,IAEF,EAAG,2BAA2B,2BAA2B;IAErD,CAAA;IAGV,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET,EAAG,iCAAiC,iBAAiB;IAChD,CAAA;GACL;;CACK,CAAA;AAEhB"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-auth",
3
- "version": "2026.1030.1",
3
+ "version": "2026.1031.1",
4
4
  "description": "Auth microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "imports": {