@burdenoff/microfe-auth 2026.828.2 → 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.
- package/dist/auth/AuthRoot.js +43 -41
- package/dist/auth/AuthRoot.js.map +1 -1
- package/dist/auth/components/AuthLayout.js +65 -67
- package/dist/auth/components/AuthLayout.js.map +1 -1
- package/dist/auth/components/CaptchaV2.js +21 -24
- package/dist/auth/components/CaptchaV2.js.map +1 -1
- package/dist/auth/components/ConfirmActionDialog.js +33 -32
- package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
- package/dist/auth/components/profile/UserProfileMenu.js +44 -46
- package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
- package/dist/auth/components/workspace/WorkspaceSelector.js +62 -68
- package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
- package/dist/auth/pages/AuditLogPage.js +65 -68
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +51 -54
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +223 -225
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +67 -69
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +85 -88
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +60 -63
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/OAuthCallbackPage.js +37 -40
- package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +102 -105
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +54 -57
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +171 -174
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSOClearPage.js +21 -20
- package/dist/auth/pages/SSOClearPage.js.map +1 -1
- package/dist/auth/pages/SSODepositPage.js +27 -26
- package/dist/auth/pages/SSODepositPage.js.map +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js +19 -18
- package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +247 -250
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +95 -98
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +53 -56
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +296 -299
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +65 -68
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js +124 -127
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +171 -174
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +322 -325
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/shared/hooks/index.d.ts +1 -6
- package/dist/shared/hooks/useTr.d.ts +16 -0
- package/dist/shared/hooks/useTr.js +14 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/dist/translations/en.d.ts +806 -248
- package/dist/translations/en.js +870 -0
- package/dist/translations/en.js.map +1 -0
- 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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
36
|
-
let { data: t } = await
|
|
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),
|
|
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" }),
|
|
51
|
-
|
|
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..."),
|
|
52
|
+
console.info("[MagicLink] Token already used but user is authenticated, redirecting..."), T("/", { replace: !0 });
|
|
56
53
|
return;
|
|
57
54
|
}
|
|
58
|
-
|
|
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
|
-
|
|
60
|
+
z,
|
|
61
|
+
O,
|
|
64
62
|
k,
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
T,
|
|
64
|
+
w
|
|
67
65
|
]);
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
}, [
|
|
71
|
-
title:
|
|
72
|
-
subtitle:
|
|
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:
|
|
75
|
+
children: w("auth.magicLink.verifying", "Verifying magic link...")
|
|
78
76
|
})]
|
|
79
77
|
})
|
|
80
|
-
}) :
|
|
81
|
-
title:
|
|
82
|
-
subtitle:
|
|
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(
|
|
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:
|
|
90
|
+
children: P
|
|
93
91
|
}),
|
|
94
92
|
/* @__PURE__ */ h("p", {
|
|
95
93
|
className: "text-sm text-[var(--color-text-secondary)]",
|
|
96
|
-
children:
|
|
94
|
+
children: w("auth.magicLink.redirecting", "Redirecting to application...")
|
|
97
95
|
})
|
|
98
96
|
]
|
|
99
97
|
})
|
|
100
|
-
}) :
|
|
101
|
-
title:
|
|
102
|
-
subtitle:
|
|
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(
|
|
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:
|
|
110
|
+
children: P
|
|
113
111
|
}),
|
|
114
112
|
/* @__PURE__ */ h("button", {
|
|
115
113
|
type: "button",
|
|
116
114
|
onClick: () => {
|
|
117
|
-
|
|
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:
|
|
118
|
+
children: w("auth.magicLink.tryAgain", "Try again")
|
|
121
119
|
})
|
|
122
120
|
]
|
|
123
121
|
})
|
|
124
|
-
}) :
|
|
125
|
-
title:
|
|
126
|
-
subtitle:
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
147
|
+
N("form"), F("");
|
|
150
148
|
},
|
|
151
149
|
className: "text-[var(--color-accent-primary)] hover:underline",
|
|
152
|
-
children:
|
|
150
|
+
children: w("auth.magicLink.differentEmail", "Use a different email")
|
|
153
151
|
})
|
|
154
152
|
]
|
|
155
153
|
})
|
|
156
|
-
}) : /* @__PURE__ */ g(
|
|
157
|
-
title:
|
|
158
|
-
subtitle:
|
|
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:
|
|
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(), !
|
|
165
|
-
|
|
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
|
|
170
|
-
e.sendMagicLink.success ? (
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
190
|
-
onChange: (e) =>
|
|
191
|
-
placeholder: "you@example.com",
|
|
192
|
-
"aria-label":
|
|
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
|
-
|
|
195
|
+
P && /* @__PURE__ */ h("p", {
|
|
198
196
|
className: "text-sm text-[var(--color-status-error)]",
|
|
199
|
-
children:
|
|
197
|
+
children: P
|
|
200
198
|
}),
|
|
201
199
|
/* @__PURE__ */ h(_, {
|
|
202
200
|
type: "submit",
|
|
203
|
-
disabled:
|
|
201
|
+
disabled: R,
|
|
204
202
|
variant: "default",
|
|
205
203
|
size: "lg",
|
|
206
204
|
className: "w-full font-semibold",
|
|
207
|
-
children:
|
|
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: () =>
|
|
211
|
+
onClick: () => T("/auth/login"),
|
|
214
212
|
className: "text-sm text-[var(--color-accent-primary)] hover:underline",
|
|
215
|
-
children:
|
|
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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"}
|