@burdenoff/microfe-auth 2026.830.1 → 2026.830.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.
Files changed (64) hide show
  1. package/dist/auth/AuthRoot.js +43 -41
  2. package/dist/auth/AuthRoot.js.map +1 -1
  3. package/dist/auth/components/AuthLayout.js +65 -67
  4. package/dist/auth/components/AuthLayout.js.map +1 -1
  5. package/dist/auth/components/CaptchaV2.js +21 -24
  6. package/dist/auth/components/CaptchaV2.js.map +1 -1
  7. package/dist/auth/components/ConfirmActionDialog.js +33 -32
  8. package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
  9. package/dist/auth/components/profile/UserProfileMenu.js +44 -46
  10. package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
  11. package/dist/auth/components/workspace/WorkspaceSelector.js +62 -68
  12. package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
  13. package/dist/auth/pages/AuditLogPage.js +65 -68
  14. package/dist/auth/pages/AuditLogPage.js.map +1 -1
  15. package/dist/auth/pages/EmailVerifyPage.js +51 -54
  16. package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
  17. package/dist/auth/pages/LoginPage.js +223 -225
  18. package/dist/auth/pages/LoginPage.js.map +1 -1
  19. package/dist/auth/pages/MagicLinkPage.js +67 -69
  20. package/dist/auth/pages/MagicLinkPage.js.map +1 -1
  21. package/dist/auth/pages/MfaSetupPage.js +85 -88
  22. package/dist/auth/pages/MfaSetupPage.js.map +1 -1
  23. package/dist/auth/pages/MfaVerifyPage.js +60 -63
  24. package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
  25. package/dist/auth/pages/OAuthCallbackPage.js +37 -40
  26. package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
  27. package/dist/auth/pages/PasswordResetPage.js +102 -105
  28. package/dist/auth/pages/PasswordResetPage.js.map +1 -1
  29. package/dist/auth/pages/ReactivatePage.js +54 -57
  30. package/dist/auth/pages/ReactivatePage.js.map +1 -1
  31. package/dist/auth/pages/RegisterPage.js +171 -174
  32. package/dist/auth/pages/RegisterPage.js.map +1 -1
  33. package/dist/auth/pages/SSOClearPage.js +21 -20
  34. package/dist/auth/pages/SSOClearPage.js.map +1 -1
  35. package/dist/auth/pages/SSODepositPage.js +27 -26
  36. package/dist/auth/pages/SSODepositPage.js.map +1 -1
  37. package/dist/auth/pages/SSORelayCallbackPage.js +19 -18
  38. package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
  39. package/dist/auth/pages/SecurityPage.js +247 -250
  40. package/dist/auth/pages/SecurityPage.js.map +1 -1
  41. package/dist/auth/pages/SessionsPage.js +95 -98
  42. package/dist/auth/pages/SessionsPage.js.map +1 -1
  43. package/dist/auth/pages/SsoCallbackPage.js +53 -56
  44. package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
  45. package/dist/auth/pages/SsoConfigPage.js +296 -299
  46. package/dist/auth/pages/SsoConfigPage.js.map +1 -1
  47. package/dist/auth/pages/SsoLoginPage.js +65 -68
  48. package/dist/auth/pages/SsoLoginPage.js.map +1 -1
  49. package/dist/auth/pages/UserDetailsPage.js +124 -127
  50. package/dist/auth/pages/UserDetailsPage.js.map +1 -1
  51. package/dist/auth/pages/UserProfilePage.js +171 -174
  52. package/dist/auth/pages/UserProfilePage.js.map +1 -1
  53. package/dist/auth/pages/UsersPage.js +322 -325
  54. package/dist/auth/pages/UsersPage.js.map +1 -1
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +2 -1
  57. package/dist/shared/hooks/index.d.ts +1 -6
  58. package/dist/shared/hooks/useTr.d.ts +16 -0
  59. package/dist/shared/hooks/useTr.js +14 -0
  60. package/dist/shared/hooks/useTr.js.map +1 -0
  61. package/dist/translations/en.d.ts +806 -248
  62. package/dist/translations/en.js +870 -0
  63. package/dist/translations/en.js.map +1 -0
  64. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  import { useAuth as e } from "../providers/AuthProvider.js";
2
2
  import { useAuthStore as t } from "../store/authStore.js";
3
- import { AuthenticateMagicLinkDocument as n, SendMagicLinkDocument as r } from "../../generated/global-operations.js";
4
- import { AuthLayout as i } from "../components/AuthLayout.js";
5
- import { AlertIcon as a, CheckIcon as o, EmailIcon as s } from "../components/AuthIcons.js";
6
- import { useCallback as c, useEffect as l, useRef as u, useState as d } from "react";
7
- import { useNavigate as f, useSearchParams as p } from "react-router";
8
- import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell";
3
+ import { useTr as n } from "../../shared/hooks/useTr.js";
4
+ import { AuthenticateMagicLinkDocument as r, SendMagicLinkDocument as i } from "../../generated/global-operations.js";
5
+ import { AuthLayout as a } from "../components/AuthLayout.js";
6
+ import { AlertIcon as o, CheckIcon as s, EmailIcon as c } from "../components/AuthIcons.js";
7
+ import { useCallback as l, useEffect as u, useRef as d, useState as f } from "react";
8
+ import { useNavigate as p, useSearchParams as m } from "react-router";
9
9
  import { jsx as h, jsxs as g } from "react/jsx-runtime";
10
10
  import { Button as _ } from "@burdenoff/fe-libs/button";
11
11
  import { PagePurpose as v } from "@burdenoff/fe-libs/ui";
@@ -26,14 +26,11 @@ function C(e) {
26
26
  } catch {}
27
27
  }
28
28
  function w() {
29
- let { t: w } = m(), T = (e, t) => {
30
- let n = w(e);
31
- return n === e ? t : n;
32
- }, E = f(), [D] = p(), O = D.get("token"), k = e(), A = x(), [j, M] = d(""), [N, P] = d(O ? "verifying" : "form"), [F, I] = d(""), L = u(!1), [R, { loading: z }] = b(r), [B] = b(n), V = c(async (e) => {
29
+ let w = n(), T = p(), [E] = m(), D = E.get("token"), O = e(), k = x(), [A, j] = f(""), [M, N] = f(D ? "verifying" : "form"), [P, F] = f(""), I = d(!1), [L, { loading: R }] = b(i), [z] = b(r), B = l(async (e) => {
33
30
  let n = t.getState();
34
31
  try {
35
- P("verifying"), n.setLoading(!0);
36
- let { data: t } = await B({ variables: { token: e } }), r = t.authenticateMagicLink;
32
+ N("verifying"), n.setLoading(!0);
33
+ let { data: t } = await z({ variables: { token: e } }), r = t.authenticateMagicLink;
37
34
  if (!r.success || !r.user) throw Error(r.error || "Authentication failed");
38
35
  n.setUser(r.user), n.setAuthTokens(r.token ?? "", r.refreshToken ?? ""), n.setAuthenticated(!0), n.setMfaRequired(!1);
39
36
  let i = r.expiresIn ? Date.now() + r.expiresIn * 1e3 : void 0;
@@ -41,135 +38,136 @@ function w() {
41
38
  accessToken: r.token ?? "",
42
39
  refreshToken: r.refreshToken ?? "",
43
40
  expiresAt: i
44
- }), C(r.user.id), A.emit("auth.signed_in", {
41
+ }), C(r.user.id), k.emit("auth.signed_in", {
45
42
  userId: r.user.id,
46
43
  tenantId: r.user.tenantId,
47
44
  workspaceId: r.user.defaultWorkspaceId,
48
45
  mfaEnabled: r.user.mfaEnabled,
49
46
  emailVerified: r.user.emailVerified
50
- }, { component: "MagicLinkPage" }), k.onAuthSuccess?.(r.user, r.token, r.refreshToken), P("success"), I("You have been signed in successfully!"), setTimeout(() => {
51
- E("/", { replace: !0 });
47
+ }, { component: "MagicLinkPage" }), O.onAuthSuccess?.(r.user, r.token, r.refreshToken), N("success"), F(w("auth.magicLink.signedInMessage", "You have been signed in successfully!")), setTimeout(() => {
48
+ T("/", { replace: !0 });
52
49
  }, 1500);
53
50
  } catch (e) {
54
51
  if (n.isAuthenticated && n.user) {
55
- console.info("[MagicLink] Token already used but user is authenticated, redirecting..."), E("/", { replace: !0 });
52
+ console.info("[MagicLink] Token already used but user is authenticated, redirecting..."), T("/", { replace: !0 });
56
53
  return;
57
54
  }
58
- P("error"), I(e instanceof Error ? e.message : "Invalid or expired magic link");
55
+ N("error"), F(e instanceof Error ? e.message : w("auth.magicLink.invalidOrExpired", "Invalid or expired magic link"));
59
56
  } finally {
60
57
  n.setLoading(!1);
61
58
  }
62
59
  }, [
63
- B,
60
+ z,
61
+ O,
64
62
  k,
65
- A,
66
- E
63
+ T,
64
+ w
67
65
  ]);
68
- return l(() => {
69
- O && !L.current && (L.current = !0, V(O));
70
- }, [O, V]), N === "verifying" ? /* @__PURE__ */ h(i, {
71
- title: T("auth.magicLink.signingIn", "Signing you in..."),
72
- subtitle: T("auth.magicLink.verifyingSubtitle", "Please wait while we verify your magic link"),
66
+ return u(() => {
67
+ D && !I.current && (I.current = !0, B(D));
68
+ }, [D, B]), M === "verifying" ? /* @__PURE__ */ h(a, {
69
+ title: w("auth.magicLink.signingIn", "Signing you in..."),
70
+ subtitle: w("auth.magicLink.verifyingSubtitle", "Please wait while we verify your magic link"),
73
71
  children: /* @__PURE__ */ g("div", {
74
72
  className: "flex flex-col items-center py-8",
75
73
  children: [/* @__PURE__ */ h("div", { className: "animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]" }), /* @__PURE__ */ h("p", {
76
74
  className: "mt-4 text-[var(--color-text-secondary)]",
77
- children: T("auth.magicLink.verifying", "Verifying magic link...")
75
+ children: w("auth.magicLink.verifying", "Verifying magic link...")
78
76
  })]
79
77
  })
80
- }) : N === "success" ? /* @__PURE__ */ h(i, {
81
- title: T("auth.magicLink.welcomeBack", "Welcome back!"),
82
- subtitle: T("auth.magicLink.signedInSuccess", "You have been signed in successfully"),
78
+ }) : M === "success" ? /* @__PURE__ */ h(a, {
79
+ title: w("auth.magicLink.welcomeBack", "Welcome back!"),
80
+ subtitle: w("auth.magicLink.signedInSuccess", "You have been signed in successfully"),
83
81
  children: /* @__PURE__ */ g("div", {
84
82
  className: "flex flex-col items-center py-8",
85
83
  children: [
86
84
  /* @__PURE__ */ h("div", {
87
85
  className: "size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4",
88
- children: /* @__PURE__ */ h(o, { className: "size-8 text-[var(--color-status-success)]" })
86
+ children: /* @__PURE__ */ h(s, { className: "size-8 text-[var(--color-status-success)]" })
89
87
  }),
90
88
  /* @__PURE__ */ h("p", {
91
89
  className: "text-[var(--color-text-primary)] text-center mb-4",
92
- children: F
90
+ children: P
93
91
  }),
94
92
  /* @__PURE__ */ h("p", {
95
93
  className: "text-sm text-[var(--color-text-secondary)]",
96
- children: T("auth.magicLink.redirecting", "Redirecting to application...")
94
+ children: w("auth.magicLink.redirecting", "Redirecting to application...")
97
95
  })
98
96
  ]
99
97
  })
100
- }) : N === "error" ? /* @__PURE__ */ h(i, {
101
- title: T("auth.magicLink.oops", "Oops!"),
102
- subtitle: T("auth.magicLink.somethingWrong", "Something went wrong"),
98
+ }) : M === "error" ? /* @__PURE__ */ h(a, {
99
+ title: w("auth.magicLink.oops", "Oops!"),
100
+ subtitle: w("auth.magicLink.somethingWrong", "Something went wrong"),
103
101
  children: /* @__PURE__ */ g("div", {
104
102
  className: "flex flex-col items-center py-8",
105
103
  children: [
106
104
  /* @__PURE__ */ h("div", {
107
105
  className: "size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4",
108
- children: /* @__PURE__ */ h(a, { className: "size-8 text-[var(--color-status-error)]" })
106
+ children: /* @__PURE__ */ h(o, { className: "size-8 text-[var(--color-status-error)]" })
109
107
  }),
110
108
  /* @__PURE__ */ h("p", {
111
109
  className: "text-[var(--color-status-error)] text-center mb-4",
112
- children: F
110
+ children: P
113
111
  }),
114
112
  /* @__PURE__ */ h("button", {
115
113
  type: "button",
116
114
  onClick: () => {
117
- P("form"), I("");
115
+ N("form"), F("");
118
116
  },
119
117
  className: "px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]",
120
- children: T("auth.magicLink.tryAgain", "Try again")
118
+ children: w("auth.magicLink.tryAgain", "Try again")
121
119
  })
122
120
  ]
123
121
  })
124
- }) : N === "sent" ? /* @__PURE__ */ h(i, {
125
- title: T("auth.magicLink.checkEmail", "Check your email"),
126
- subtitle: T("auth.magicLink.sentMagicLink", "We've sent you a magic link"),
122
+ }) : M === "sent" ? /* @__PURE__ */ h(a, {
123
+ title: w("auth.magicLink.checkEmail", "Check your email"),
124
+ subtitle: w("auth.magicLink.sentMagicLink", "We've sent you a magic link"),
127
125
  children: /* @__PURE__ */ g("div", {
128
126
  className: "flex flex-col items-center py-8",
129
127
  children: [
130
128
  /* @__PURE__ */ h("div", {
131
129
  className: "size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4",
132
- children: /* @__PURE__ */ h(s, { className: "size-8 text-[var(--color-status-success)]" })
130
+ children: /* @__PURE__ */ h(c, { className: "size-8 text-[var(--color-status-success)]" })
133
131
  }),
134
132
  /* @__PURE__ */ h("p", {
135
133
  className: "text-[var(--color-text-primary)] text-center mb-2",
136
- children: T("auth.magicLink.sentTo", "We've sent a magic link to:")
134
+ children: w("auth.magicLink.sentTo", "We've sent a magic link to:")
137
135
  }),
138
136
  /* @__PURE__ */ h("p", {
139
137
  className: "text-[var(--color-accent-primary)] font-medium mb-4",
140
- children: j
138
+ children: A
141
139
  }),
142
140
  /* @__PURE__ */ h("p", {
143
141
  className: "text-sm text-[var(--color-text-secondary)] text-center mb-6",
144
- children: T("auth.magicLink.linkExpires", "Click the link in the email to sign in. The link expires in 24 hours.")
142
+ children: w("auth.magicLink.linkExpires", "Click the link in the email to sign in. The link expires in 24 hours.")
145
143
  }),
146
144
  /* @__PURE__ */ h("button", {
147
145
  type: "button",
148
146
  onClick: () => {
149
- P("form"), I("");
147
+ N("form"), F("");
150
148
  },
151
149
  className: "text-[var(--color-accent-primary)] hover:underline",
152
- children: T("auth.magicLink.differentEmail", "Use a different email")
150
+ children: w("auth.magicLink.differentEmail", "Use a different email")
153
151
  })
154
152
  ]
155
153
  })
156
- }) : /* @__PURE__ */ g(i, {
157
- title: T("auth.magicLink.title", "Sign in with Magic Link"),
158
- subtitle: T("auth.magicLink.subtitle", "No password needed - we'll email you a link"),
154
+ }) : /* @__PURE__ */ g(a, {
155
+ title: w("auth.magicLink.title", "Sign in with Magic Link"),
156
+ subtitle: w("auth.magicLink.subtitle", "No password needed - we'll email you a link"),
159
157
  children: [/* @__PURE__ */ h(v, {
160
158
  className: "mb-6",
161
- children: T("auth.magicLink.purpose", "Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.")
159
+ children: w("auth.magicLink.purpose", "Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.")
162
160
  }), /* @__PURE__ */ g("form", {
163
161
  onSubmit: async (e) => {
164
- if (e.preventDefault(), !j) {
165
- I(T("auth.magicLink.enterEmail", "Please enter your email address"));
162
+ if (e.preventDefault(), !A) {
163
+ F(w("auth.magicLink.enterEmail", "Please enter your email address"));
166
164
  return;
167
165
  }
168
166
  try {
169
- let { data: e } = await R({ variables: { email: j } });
170
- e.sendMagicLink.success ? (P("sent"), I(e.sendMagicLink.message || "Magic link sent! Check your email.")) : I(e.sendMagicLink.message || "Failed to send magic link");
167
+ let { data: e } = await L({ variables: { email: A } });
168
+ e.sendMagicLink.success ? (N("sent"), F(e.sendMagicLink.message || w("auth.magicLink.sentSuccess", "Magic link sent! Check your email."))) : F(e.sendMagicLink.message || w("auth.magicLink.sendFailed", "Failed to send magic link"));
171
169
  } catch (e) {
172
- I(e instanceof Error ? e.message : "Failed to send magic link");
170
+ F(e instanceof Error ? e.message : w("auth.magicLink.sendFailed", "Failed to send magic link"));
173
171
  }
174
172
  },
175
173
  className: "space-y-6",
@@ -177,42 +175,42 @@ function w() {
177
175
  /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
178
176
  htmlFor: "email",
179
177
  className: "block text-sm font-medium text-[var(--color-text-primary)] mb-2",
180
- children: T("auth.magicLink.emailLabel", "Email address")
178
+ children: w("auth.magicLink.emailLabel", "Email address")
181
179
  }), /* @__PURE__ */ g("div", {
182
180
  className: "relative",
183
181
  children: [/* @__PURE__ */ h("div", {
184
182
  className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none",
185
- children: /* @__PURE__ */ h(s, { className: "size-5 text-[var(--color-text-secondary)]" })
183
+ children: /* @__PURE__ */ h(c, { className: "size-5 text-[var(--color-text-secondary)]" })
186
184
  }), /* @__PURE__ */ h("input", {
187
185
  id: "email",
188
186
  type: "email",
189
- value: j,
190
- onChange: (e) => M(e.target.value),
191
- placeholder: "you@example.com",
192
- "aria-label": T("auth.magicLink.emailLabel", "Email address"),
187
+ value: A,
188
+ onChange: (e) => j(e.target.value),
189
+ placeholder: w("auth.magicLink.emailPlaceholder", "you@example.com"),
190
+ "aria-label": w("auth.magicLink.emailLabel", "Email address"),
193
191
  className: "block w-full pl-[3.25rem] pr-4 py-3 border border-[var(--color-border-primary)] rounded-lg bg-[var(--color-background-base)] text-[var(--color-text-primary)] placeholder-[var(--color-text-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:border-transparent",
194
192
  required: !0
195
193
  })]
196
194
  })] }),
197
- F && /* @__PURE__ */ h("p", {
195
+ P && /* @__PURE__ */ h("p", {
198
196
  className: "text-sm text-[var(--color-status-error)]",
199
- children: F
197
+ children: P
200
198
  }),
201
199
  /* @__PURE__ */ h(_, {
202
200
  type: "submit",
203
- disabled: z,
201
+ disabled: R,
204
202
  variant: "default",
205
203
  size: "lg",
206
204
  className: "w-full font-semibold",
207
- children: z ? T("auth.magicLink.sending", "Sending...") : T("auth.magicLink.sendButton", "Send Magic Link")
205
+ children: R ? w("auth.magicLink.sending", "Sending...") : w("auth.magicLink.sendButton", "Send Magic Link")
208
206
  }),
209
207
  /* @__PURE__ */ h("div", {
210
208
  className: "text-center",
211
209
  children: /* @__PURE__ */ h("button", {
212
210
  type: "button",
213
- onClick: () => E("/auth/login"),
211
+ onClick: () => T("/auth/login"),
214
212
  className: "text-sm text-[var(--color-accent-primary)] hover:underline",
215
- children: T("auth.magicLink.passwordInstead", "Sign in with password instead")
213
+ children: w("auth.magicLink.passwordInstead", "Sign in with password instead")
216
214
  })
217
215
  })
218
216
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"MagicLinkPage.js","names":[],"sources":["../../../src/auth/pages/MagicLinkPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { EmailIcon, CheckIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { MagicLinkSendResult, AuthResult, User } from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nimport {\n SendMagicLinkDocument,\n AuthenticateMagicLinkDocument,\n} from \"../../generated/global-operations\";\n// Response types for GraphQL mutations\ninterface SendMagicLinkResponse {\n sendMagicLink: MagicLinkSendResult;\n}\n\ninterface AuthenticateMagicLinkResponse {\n authenticateMagicLink: AuthResult;\n}\n\n// GraphQL Operations\n\ntype PageStatus = \"form\" | \"sent\" | \"verifying\" | \"success\" | \"error\";\n\n/**\n * Magic Link authentication page\n * Supports both sending magic links and verifying them via token\n */\nexport function MagicLinkPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const context = useAuth();\n const eventBus = useEventBus();\n\n const [email, setEmail] = useState(\"\");\n const [status, setStatus] = useState<PageStatus>(\n token ? \"verifying\" : \"form\",\n );\n const [message, setMessage] = useState(\"\");\n const hasVerifiedRef = useRef(false);\n\n const [sendMagicLink, { loading: sendLoading }] =\n useMutation<SendMagicLinkResponse>(SendMagicLinkDocument);\n const [authenticateMagicLink] = useMutation<AuthenticateMagicLinkResponse>(\n AuthenticateMagicLinkDocument,\n );\n\n const handleVerifyToken = useCallback(\n async (tokenValue: string) => {\n const store = useAuthStore.getState();\n\n try {\n setStatus(\"verifying\");\n store.setLoading(true);\n\n const { data } = await authenticateMagicLink({\n variables: { token: tokenValue },\n });\n const result = data!.authenticateMagicLink;\n\n if (!result.success || !result.user) {\n throw new Error(result.error || \"Authentication failed\");\n }\n\n store.setUser(result.user);\n store.setAuthTokens(result.token ?? \"\", result.refreshToken ?? \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token ?? \"\",\n refreshToken: result.refreshToken ?? \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"MagicLinkPage\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n\n setStatus(\"success\");\n setMessage(\"You have been signed in successfully!\");\n\n // Redirect to dashboard after successful authentication\n setTimeout(() => {\n navigate(\"/\", { replace: true });\n }, 1500);\n } catch (err) {\n // If user is already authenticated (token was already used), redirect to dashboard\n if (store.isAuthenticated && store.user) {\n console.info(\n \"[MagicLink] Token already used but user is authenticated, redirecting...\",\n );\n navigate(\"/\", { replace: true });\n return;\n }\n\n setStatus(\"error\");\n const errorMessage =\n err instanceof Error ? err.message : \"Invalid or expired magic link\";\n setMessage(errorMessage);\n } finally {\n store.setLoading(false);\n }\n },\n [authenticateMagicLink, context, eventBus, navigate],\n );\n\n // Handle token verification on page load (only once)\n useEffect(() => {\n if (token && !hasVerifiedRef.current) {\n hasVerifiedRef.current = true;\n handleVerifyToken(token);\n }\n }, [token, handleVerifyToken]);\n\n const handleSendMagicLink = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!email) {\n setMessage(\n tr(\"auth.magicLink.enterEmail\", \"Please enter your email address\"),\n );\n return;\n }\n\n try {\n const { data } = await sendMagicLink({ variables: { email } });\n\n if (data!.sendMagicLink.success) {\n setStatus(\"sent\");\n setMessage(\n data!.sendMagicLink.message || \"Magic link sent! Check your email.\",\n );\n } else {\n setMessage(data!.sendMagicLink.message || \"Failed to send magic link\");\n }\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : \"Failed to send magic link\";\n setMessage(errorMessage);\n }\n };\n\n // Verifying state\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.signingIn\", \"Signing you in...\")}\n subtitle={tr(\n \"auth.magicLink.verifyingSubtitle\",\n \"Please wait while we verify your magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]\" />\n <p className=\"mt-4 text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.verifying\", \"Verifying magic link...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Success state\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.welcomeBack\", \"Welcome back!\")}\n subtitle={tr(\n \"auth.magicLink.signedInSuccess\",\n \"You have been signed in successfully\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <CheckIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-4\">\n {message}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.redirecting\", \"Redirecting to application...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (status === \"error\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.oops\", \"Oops!\")}\n subtitle={tr(\"auth.magicLink.somethingWrong\", \"Something went wrong\")}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4\">\n <AlertIcon className=\"size-8 text-[var(--color-status-error)]\" />\n </div>\n <p className=\"text-[var(--color-status-error)] text-center mb-4\">\n {message}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]\"\n >\n {tr(\"auth.magicLink.tryAgain\", \"Try again\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Sent state\n if (status === \"sent\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.magicLink.sentMagicLink\",\n \"We've sent you a magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <EmailIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-2\">\n {tr(\"auth.magicLink.sentTo\", \"We've sent a magic link to:\")}\n </p>\n <p className=\"text-[var(--color-accent-primary)] font-medium mb-4\">\n {email}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)] text-center mb-6\">\n {tr(\n \"auth.magicLink.linkExpires\",\n \"Click the link in the email to sign in. The link expires in 24 hours.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\"auth.magicLink.differentEmail\", \"Use a different email\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.title\", \"Sign in with Magic Link\")}\n subtitle={tr(\n \"auth.magicLink.subtitle\",\n \"No password needed - we'll email you a link\",\n )}\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.magicLink.purpose\",\n \"Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSendMagicLink} className=\"space-y-6\">\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n aria-label={tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n className=\"block w-full pl-[3.25rem] pr-4 py-3 border border-[var(--color-border-primary)] rounded-lg bg-[var(--color-background-base)] text-[var(--color-text-primary)] placeholder-[var(--color-text-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:border-transparent\"\n required\n />\n </div>\n </div>\n\n {message && (\n <p className=\"text-sm text-[var(--color-status-error)]\">{message}</p>\n )}\n\n <Button\n type=\"submit\"\n disabled={sendLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {sendLoading\n ? tr(\"auth.magicLink.sending\", \"Sending...\")\n : tr(\"auth.magicLink.sendButton\", \"Send Magic Link\")}\n </Button>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\n \"auth.magicLink.passwordInstead\",\n \"Sign in with password instead\",\n )}\n </button>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MagicLinkPage.js","names":[],"sources":["../../../src/auth/pages/MagicLinkPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useTr } from \"../../shared/hooks/useTr\";\nimport { useNavigate, useSearchParams } from \"react-router\";\nimport { useMutation } from \"@apollo/client/react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport {\n loginProfile,\n type ProfileUser,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { EmailIcon, CheckIcon, AlertIcon } from \"../components/AuthIcons\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { MagicLinkSendResult, AuthResult, User } from \"../types\";\n\nfunction toProfileUser(user: User): ProfileUser {\n return { ...user };\n}\n\nfunction setShellSsoCookie(userId: string): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n const domainPart = domain ? `; domain=${domain}` : \"\";\n const securePart = window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: userId,\n origin: window.location.origin,\n });\n document.cookie = `bf-sso=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n document.cookie = `bf-sso-profile=${encodeURIComponent(ssoValue)}${domainPart}; path=/; samesite=lax${securePart}; max-age=86400`;\n } catch {\n // Best effort only.\n }\n}\n\nimport {\n SendMagicLinkDocument,\n AuthenticateMagicLinkDocument,\n} from \"../../generated/global-operations\";\n// Response types for GraphQL mutations\ninterface SendMagicLinkResponse {\n sendMagicLink: MagicLinkSendResult;\n}\n\ninterface AuthenticateMagicLinkResponse {\n authenticateMagicLink: AuthResult;\n}\n\n// GraphQL Operations\n\ntype PageStatus = \"form\" | \"sent\" | \"verifying\" | \"success\" | \"error\";\n\n/**\n * Magic Link authentication page\n * Supports both sending magic links and verifying them via token\n */\nexport function MagicLinkPage() {\n const tr = useTr();\n\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n const context = useAuth();\n const eventBus = useEventBus();\n\n const [email, setEmail] = useState(\"\");\n const [status, setStatus] = useState<PageStatus>(\n token ? \"verifying\" : \"form\",\n );\n const [message, setMessage] = useState(\"\");\n const hasVerifiedRef = useRef(false);\n\n const [sendMagicLink, { loading: sendLoading }] =\n useMutation<SendMagicLinkResponse>(SendMagicLinkDocument);\n const [authenticateMagicLink] = useMutation<AuthenticateMagicLinkResponse>(\n AuthenticateMagicLinkDocument,\n );\n\n const handleVerifyToken = useCallback(\n async (tokenValue: string) => {\n const store = useAuthStore.getState();\n\n try {\n setStatus(\"verifying\");\n store.setLoading(true);\n\n const { data } = await authenticateMagicLink({\n variables: { token: tokenValue },\n });\n const result = data!.authenticateMagicLink;\n\n if (!result.success || !result.user) {\n throw new Error(result.error || \"Authentication failed\");\n }\n\n store.setUser(result.user);\n store.setAuthTokens(result.token ?? \"\", result.refreshToken ?? \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n const expiresAt = result.expiresIn\n ? Date.now() + result.expiresIn * 1000\n : undefined;\n loginProfile(toProfileUser(result.user), {\n accessToken: result.token ?? \"\",\n refreshToken: result.refreshToken ?? \"\",\n expiresAt,\n });\n setShellSsoCookie(result.user.id);\n\n eventBus.emit(\n \"auth.signed_in\",\n {\n userId: result.user.id,\n tenantId: result.user.tenantId,\n workspaceId: result.user.defaultWorkspaceId,\n mfaEnabled: result.user.mfaEnabled,\n emailVerified: result.user.emailVerified,\n },\n { component: \"MagicLinkPage\" },\n );\n\n context.onAuthSuccess?.(result.user, result.token, result.refreshToken);\n\n setStatus(\"success\");\n setMessage(\n tr(\n \"auth.magicLink.signedInMessage\",\n \"You have been signed in successfully!\",\n ),\n );\n\n // Redirect to dashboard after successful authentication\n setTimeout(() => {\n navigate(\"/\", { replace: true });\n }, 1500);\n } catch (err) {\n // If user is already authenticated (token was already used), redirect to dashboard\n if (store.isAuthenticated && store.user) {\n console.info(\n \"[MagicLink] Token already used but user is authenticated, redirecting...\",\n );\n navigate(\"/\", { replace: true });\n return;\n }\n\n setStatus(\"error\");\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\n \"auth.magicLink.invalidOrExpired\",\n \"Invalid or expired magic link\",\n );\n setMessage(errorMessage);\n } finally {\n store.setLoading(false);\n }\n },\n [authenticateMagicLink, context, eventBus, navigate, tr],\n );\n\n // Handle token verification on page load (only once)\n useEffect(() => {\n if (token && !hasVerifiedRef.current) {\n hasVerifiedRef.current = true;\n handleVerifyToken(token);\n }\n }, [token, handleVerifyToken]);\n\n const handleSendMagicLink = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!email) {\n setMessage(\n tr(\"auth.magicLink.enterEmail\", \"Please enter your email address\"),\n );\n return;\n }\n\n try {\n const { data } = await sendMagicLink({ variables: { email } });\n\n if (data!.sendMagicLink.success) {\n setStatus(\"sent\");\n setMessage(\n data!.sendMagicLink.message ||\n tr(\n \"auth.magicLink.sentSuccess\",\n \"Magic link sent! Check your email.\",\n ),\n );\n } else {\n setMessage(\n data!.sendMagicLink.message ||\n tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\"),\n );\n }\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr(\"auth.magicLink.sendFailed\", \"Failed to send magic link\");\n setMessage(errorMessage);\n }\n };\n\n // Verifying state\n if (status === \"verifying\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.signingIn\", \"Signing you in...\")}\n subtitle={tr(\n \"auth.magicLink.verifyingSubtitle\",\n \"Please wait while we verify your magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]\" />\n <p className=\"mt-4 text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.verifying\", \"Verifying magic link...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Success state\n if (status === \"success\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.welcomeBack\", \"Welcome back!\")}\n subtitle={tr(\n \"auth.magicLink.signedInSuccess\",\n \"You have been signed in successfully\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <CheckIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-4\">\n {message}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.magicLink.redirecting\", \"Redirecting to application...\")}\n </p>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (status === \"error\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.oops\", \"Oops!\")}\n subtitle={tr(\"auth.magicLink.somethingWrong\", \"Something went wrong\")}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4\">\n <AlertIcon className=\"size-8 text-[var(--color-status-error)]\" />\n </div>\n <p className=\"text-[var(--color-status-error)] text-center mb-4\">\n {message}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]\"\n >\n {tr(\"auth.magicLink.tryAgain\", \"Try again\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Sent state\n if (status === \"sent\") {\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.magicLink.sentMagicLink\",\n \"We've sent you a magic link\",\n )}\n >\n <div className=\"flex flex-col items-center py-8\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <EmailIcon className=\"size-8 text-[var(--color-status-success)]\" />\n </div>\n <p className=\"text-[var(--color-text-primary)] text-center mb-2\">\n {tr(\"auth.magicLink.sentTo\", \"We've sent a magic link to:\")}\n </p>\n <p className=\"text-[var(--color-accent-primary)] font-medium mb-4\">\n {email}\n </p>\n <p className=\"text-sm text-[var(--color-text-secondary)] text-center mb-6\">\n {tr(\n \"auth.magicLink.linkExpires\",\n \"Click the link in the email to sign in. The link expires in 24 hours.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => {\n setStatus(\"form\");\n setMessage(\"\");\n }}\n className=\"text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\"auth.magicLink.differentEmail\", \"Use a different email\")}\n </button>\n </div>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout\n title={tr(\"auth.magicLink.title\", \"Sign in with Magic Link\")}\n subtitle={tr(\n \"auth.magicLink.subtitle\",\n \"No password needed - we'll email you a link\",\n )}\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.magicLink.purpose\",\n \"Skip the password — we'll email you a one-time link that signs you in instantly. Great when you don't remember your password or are on a trusted device.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSendMagicLink} className=\"space-y-6\">\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-2\"\n >\n {tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-secondary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder={tr(\n \"auth.magicLink.emailPlaceholder\",\n \"you@example.com\",\n )}\n aria-label={tr(\"auth.magicLink.emailLabel\", \"Email address\")}\n className=\"block w-full pl-[3.25rem] pr-4 py-3 border border-[var(--color-border-primary)] rounded-lg bg-[var(--color-background-base)] text-[var(--color-text-primary)] placeholder-[var(--color-text-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:border-transparent\"\n required\n />\n </div>\n </div>\n\n {message && (\n <p className=\"text-sm text-[var(--color-status-error)]\">{message}</p>\n )}\n\n <Button\n type=\"submit\"\n disabled={sendLoading}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {sendLoading\n ? tr(\"auth.magicLink.sending\", \"Sending...\")\n : tr(\"auth.magicLink.sendButton\", \"Send Magic Link\")}\n </Button>\n\n <div className=\"text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"text-sm text-[var(--color-accent-primary)] hover:underline\"\n >\n {tr(\n \"auth.magicLink.passwordInstead\",\n \"Sign in with password instead\",\n )}\n </button>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}