@burdenoff/microfe-auth 2026.527.1 → 2026.527.3
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/AuthRoutes.js +1 -1
- package/dist/auth/AuthRoutes.js.map +1 -1
- package/dist/auth/components/AuthLayout.js +14 -14
- package/dist/auth/components/AuthLayout.js.map +1 -1
- package/dist/auth/components/CaptchaV2.js +1 -4
- package/dist/auth/components/CaptchaV2.js.map +1 -1
- package/dist/auth/components/ConfirmActionDialog.js +3 -3
- package/dist/auth/components/ConfirmActionDialog.js.map +1 -1
- package/dist/auth/components/profile/UserProfileMenu.js +10 -6
- package/dist/auth/components/profile/UserProfileMenu.js.map +1 -1
- package/dist/auth/components/workspace/WorkspaceSelector.js +108 -98
- package/dist/auth/components/workspace/WorkspaceSelector.js.map +1 -1
- package/dist/auth/hooks/index.js +2 -2
- package/dist/auth/hooks/useContextSwitcherData.js +1 -1
- package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
- package/dist/auth/hooks/useMyWorkspaces.js +1 -1
- package/dist/auth/hooks/useMyWorkspaces.js.map +1 -1
- package/dist/auth/hooks/useWorkspaceToken.js +24 -24
- package/dist/auth/hooks/useWorkspaceToken.js.map +1 -1
- package/dist/auth/index.js +4 -4
- package/dist/auth/pages/AuditLogPage.js +10 -12
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +30 -28
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +23 -19
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +97 -98
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +26 -21
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +22 -21
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/OAuthCallbackPage.js +13 -7
- package/dist/auth/pages/OAuthCallbackPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +28 -22
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +19 -15
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +203 -194
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SSOClearPage.js +1 -1
- package/dist/auth/pages/SSOClearPage.js.map +1 -1
- package/dist/auth/pages/SSODepositPage.js +1 -1
- package/dist/auth/pages/SSODepositPage.js.map +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js +1 -1
- package/dist/auth/pages/SSORelayCallbackPage.js.map +1 -1
- package/dist/auth/pages/SSORelayPage.js +40 -34
- package/dist/auth/pages/SSORelayPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +22 -22
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +24 -28
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +27 -20
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +51 -28
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +17 -14
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js +16 -17
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +38 -26
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +56 -45
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/index.js +11 -11
- package/dist/microfe-auth.css +1 -1
- package/package.json +1 -1
|
@@ -3,18 +3,18 @@ import { useAuthStore as t } from "../store/authStore.js";
|
|
|
3
3
|
import { AuthenticateMagicLinkDocument as n, SendMagicLinkDocument as r } from "../../generated/global-operations.js";
|
|
4
4
|
import { AuthLayout as i } from "../components/AuthLayout.js";
|
|
5
5
|
import { AlertIcon as a, CheckIcon as o, EmailIcon as s } from "../components/AuthIcons.js";
|
|
6
|
-
import { useCallback as c, useEffect as l,
|
|
7
|
-
import { useNavigate as
|
|
8
|
-
import { useI18n as
|
|
9
|
-
import { jsx as
|
|
10
|
-
import { loginProfile as
|
|
11
|
-
import { useMutation as
|
|
12
|
-
import { useEventBus as
|
|
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-dom";
|
|
8
|
+
import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
9
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
10
|
+
import { loginProfile as _ } from "@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager";
|
|
11
|
+
import { useMutation as v } from "@apollo/client/react";
|
|
12
|
+
import { useEventBus as y } from "@burdenoff/fe-libs/shared/events";
|
|
13
13
|
//#region src/auth/pages/MagicLinkPage.tsx
|
|
14
|
-
function
|
|
14
|
+
function b(e) {
|
|
15
15
|
return { ...e };
|
|
16
16
|
}
|
|
17
|
-
function
|
|
17
|
+
function x(e) {
|
|
18
18
|
try {
|
|
19
19
|
let t = window.location.hostname.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0, n = t ? `; domain=${t}` : "", r = window.location.protocol === "https:" ? "; secure" : "", i = JSON.stringify({
|
|
20
20
|
id: e,
|
|
@@ -23,190 +23,189 @@ function b(e) {
|
|
|
23
23
|
document.cookie = `bf-sso=${encodeURIComponent(i)}${n}; path=/; samesite=lax${r}; max-age=86400`, document.cookie = `bf-sso-profile=${encodeURIComponent(i)}${n}; path=/; samesite=lax${r}; max-age=86400`;
|
|
24
24
|
} catch {}
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
let { t:
|
|
28
|
-
let n =
|
|
26
|
+
function S() {
|
|
27
|
+
let { t: S } = m(), C = (e, t) => {
|
|
28
|
+
let n = S(e);
|
|
29
29
|
return n === e ? t : n;
|
|
30
|
-
},
|
|
30
|
+
}, w = f(), [T] = p(), E = T.get("token"), D = e(), O = y(), [k, A] = d(""), [j, M] = d(E ? "verifying" : "form"), [N, P] = d(""), F = u(!1), [I, { loading: L }] = v(r), [R] = v(n), z = c(async (e) => {
|
|
31
31
|
let n = t.getState();
|
|
32
32
|
try {
|
|
33
|
-
|
|
33
|
+
M("verifying"), n.setLoading(!0);
|
|
34
34
|
let { data: t } = await R({ variables: { token: e } }), r = t.authenticateMagicLink;
|
|
35
35
|
if (!r.success || !r.user) throw Error(r.error || "Authentication failed");
|
|
36
36
|
n.setUser(r.user), n.setAuthTokens(r.token ?? "", r.refreshToken ?? ""), n.setAuthenticated(!0), n.setMfaRequired(!1);
|
|
37
37
|
let i = r.expiresIn ? Date.now() + r.expiresIn * 1e3 : void 0;
|
|
38
|
-
|
|
38
|
+
_(b(r.user), {
|
|
39
39
|
accessToken: r.token ?? "",
|
|
40
40
|
refreshToken: r.refreshToken ?? "",
|
|
41
41
|
expiresAt: i
|
|
42
|
-
}),
|
|
42
|
+
}), x(r.user.id), O.emit("auth.signed_in", {
|
|
43
43
|
userId: r.user.id,
|
|
44
44
|
tenantId: r.user.tenantId,
|
|
45
45
|
workspaceId: r.user.defaultWorkspaceId,
|
|
46
46
|
mfaEnabled: r.user.mfaEnabled,
|
|
47
47
|
emailVerified: r.user.emailVerified
|
|
48
|
-
}, { component: "MagicLinkPage" }),
|
|
49
|
-
|
|
48
|
+
}, { component: "MagicLinkPage" }), D.onAuthSuccess?.(r.user, r.token, r.refreshToken), M("success"), P("You have been signed in successfully!"), setTimeout(() => {
|
|
49
|
+
w("/", { replace: !0 });
|
|
50
50
|
}, 1500);
|
|
51
51
|
} catch (e) {
|
|
52
52
|
if (n.isAuthenticated && n.user) {
|
|
53
|
-
console.info("[MagicLink] Token already used but user is authenticated, redirecting..."),
|
|
53
|
+
console.info("[MagicLink] Token already used but user is authenticated, redirecting..."), w("/", { replace: !0 });
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
M("error"), P(e instanceof Error ? e.message : "Invalid or expired magic link");
|
|
57
57
|
} finally {
|
|
58
58
|
n.setLoading(!1);
|
|
59
59
|
}
|
|
60
60
|
}, [
|
|
61
61
|
R,
|
|
62
|
-
E,
|
|
63
62
|
D,
|
|
64
|
-
|
|
63
|
+
O,
|
|
64
|
+
w
|
|
65
65
|
]);
|
|
66
66
|
return l(() => {
|
|
67
|
-
|
|
68
|
-
}, [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]), A === "verifying" ? /* @__PURE__ */ m(i, {
|
|
73
|
-
title: S("auth.magicLink.signingIn", "Signing you in..."),
|
|
74
|
-
subtitle: S("auth.magicLink.verifyingSubtitle", "Please wait while we verify your magic link"),
|
|
75
|
-
children: /* @__PURE__ */ h("div", {
|
|
67
|
+
E && !F.current && (F.current = !0, z(E));
|
|
68
|
+
}, [E, z]), j === "verifying" ? /* @__PURE__ */ h(i, {
|
|
69
|
+
title: C("auth.magicLink.signingIn", "Signing you in..."),
|
|
70
|
+
subtitle: C("auth.magicLink.verifyingSubtitle", "Please wait while we verify your magic link"),
|
|
71
|
+
children: /* @__PURE__ */ g("div", {
|
|
76
72
|
className: "flex flex-col items-center py-8",
|
|
77
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ h("div", { className: "animate-spin rounded-full size-12 border-b-2 border-[var(--color-accent-primary)]" }), /* @__PURE__ */ h("p", {
|
|
78
74
|
className: "mt-4 text-[var(--color-text-secondary)]",
|
|
79
|
-
children:
|
|
75
|
+
children: C("auth.magicLink.verifying", "Verifying magic link...")
|
|
80
76
|
})]
|
|
81
77
|
})
|
|
82
|
-
}) :
|
|
83
|
-
title:
|
|
84
|
-
subtitle:
|
|
85
|
-
children: /* @__PURE__ */
|
|
78
|
+
}) : j === "success" ? /* @__PURE__ */ h(i, {
|
|
79
|
+
title: C("auth.magicLink.welcomeBack", "Welcome back!"),
|
|
80
|
+
subtitle: C("auth.magicLink.signedInSuccess", "You have been signed in successfully"),
|
|
81
|
+
children: /* @__PURE__ */ g("div", {
|
|
86
82
|
className: "flex flex-col items-center py-8",
|
|
87
83
|
children: [
|
|
88
|
-
/* @__PURE__ */
|
|
89
|
-
className: "
|
|
90
|
-
children: /* @__PURE__ */
|
|
84
|
+
/* @__PURE__ */ h("div", {
|
|
85
|
+
className: "size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4",
|
|
86
|
+
children: /* @__PURE__ */ h(o, { className: "size-8 text-[var(--color-status-success)]" })
|
|
91
87
|
}),
|
|
92
|
-
/* @__PURE__ */
|
|
88
|
+
/* @__PURE__ */ h("p", {
|
|
93
89
|
className: "text-[var(--color-text-primary)] text-center mb-4",
|
|
94
|
-
children:
|
|
90
|
+
children: N
|
|
95
91
|
}),
|
|
96
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ h("p", {
|
|
97
93
|
className: "text-sm text-[var(--color-text-secondary)]",
|
|
98
|
-
children:
|
|
94
|
+
children: C("auth.magicLink.redirecting", "Redirecting to application...")
|
|
99
95
|
})
|
|
100
96
|
]
|
|
101
97
|
})
|
|
102
|
-
}) :
|
|
103
|
-
title:
|
|
104
|
-
subtitle:
|
|
105
|
-
children: /* @__PURE__ */
|
|
98
|
+
}) : j === "error" ? /* @__PURE__ */ h(i, {
|
|
99
|
+
title: C("auth.magicLink.oops", "Oops!"),
|
|
100
|
+
subtitle: C("auth.magicLink.somethingWrong", "Something went wrong"),
|
|
101
|
+
children: /* @__PURE__ */ g("div", {
|
|
106
102
|
className: "flex flex-col items-center py-8",
|
|
107
103
|
children: [
|
|
108
|
-
/* @__PURE__ */
|
|
109
|
-
className: "
|
|
110
|
-
children: /* @__PURE__ */
|
|
104
|
+
/* @__PURE__ */ h("div", {
|
|
105
|
+
className: "size-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4",
|
|
106
|
+
children: /* @__PURE__ */ h(a, { className: "size-8 text-[var(--color-status-error)]" })
|
|
111
107
|
}),
|
|
112
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ h("p", {
|
|
113
109
|
className: "text-[var(--color-status-error)] text-center mb-4",
|
|
114
|
-
children:
|
|
110
|
+
children: N
|
|
115
111
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ h("button", {
|
|
113
|
+
type: "button",
|
|
117
114
|
onClick: () => {
|
|
118
|
-
|
|
115
|
+
M("form"), P("");
|
|
119
116
|
},
|
|
120
117
|
className: "px-4 py-2 rounded-lg bg-[var(--color-accent-primary)] text-white hover:bg-[var(--color-accent-primary-hover)]",
|
|
121
|
-
children:
|
|
118
|
+
children: C("auth.magicLink.tryAgain", "Try again")
|
|
122
119
|
})
|
|
123
120
|
]
|
|
124
121
|
})
|
|
125
|
-
}) :
|
|
126
|
-
title:
|
|
127
|
-
subtitle:
|
|
128
|
-
children: /* @__PURE__ */
|
|
122
|
+
}) : j === "sent" ? /* @__PURE__ */ h(i, {
|
|
123
|
+
title: C("auth.magicLink.checkEmail", "Check your email"),
|
|
124
|
+
subtitle: C("auth.magicLink.sentMagicLink", "We've sent you a magic link"),
|
|
125
|
+
children: /* @__PURE__ */ g("div", {
|
|
129
126
|
className: "flex flex-col items-center py-8",
|
|
130
127
|
children: [
|
|
131
|
-
/* @__PURE__ */
|
|
132
|
-
className: "
|
|
133
|
-
children: /* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ h("div", {
|
|
129
|
+
className: "size-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4",
|
|
130
|
+
children: /* @__PURE__ */ h(s, { className: "size-8 text-[var(--color-status-success)]" })
|
|
134
131
|
}),
|
|
135
|
-
/* @__PURE__ */
|
|
132
|
+
/* @__PURE__ */ h("p", {
|
|
136
133
|
className: "text-[var(--color-text-primary)] text-center mb-2",
|
|
137
|
-
children:
|
|
134
|
+
children: C("auth.magicLink.sentTo", "We've sent a magic link to:")
|
|
138
135
|
}),
|
|
139
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ h("p", {
|
|
140
137
|
className: "text-[var(--color-accent-primary)] font-medium mb-4",
|
|
141
|
-
children:
|
|
138
|
+
children: k
|
|
142
139
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ h("p", {
|
|
144
141
|
className: "text-sm text-[var(--color-text-secondary)] text-center mb-6",
|
|
145
|
-
children:
|
|
142
|
+
children: C("auth.magicLink.linkExpires", "Click the link in the email to sign in. The link expires in 24 hours.")
|
|
146
143
|
}),
|
|
147
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ h("button", {
|
|
145
|
+
type: "button",
|
|
148
146
|
onClick: () => {
|
|
149
|
-
|
|
147
|
+
M("form"), P("");
|
|
150
148
|
},
|
|
151
149
|
className: "text-[var(--color-accent-primary)] hover:underline",
|
|
152
|
-
children:
|
|
150
|
+
children: C("auth.magicLink.differentEmail", "Use a different email")
|
|
153
151
|
})
|
|
154
152
|
]
|
|
155
153
|
})
|
|
156
|
-
}) : /* @__PURE__ */
|
|
157
|
-
title:
|
|
158
|
-
subtitle:
|
|
159
|
-
children: /* @__PURE__ */
|
|
154
|
+
}) : /* @__PURE__ */ h(i, {
|
|
155
|
+
title: C("auth.magicLink.title", "Sign in with Magic Link"),
|
|
156
|
+
subtitle: C("auth.magicLink.subtitle", "No password needed - we'll email you a link"),
|
|
157
|
+
children: /* @__PURE__ */ g("form", {
|
|
160
158
|
onSubmit: async (e) => {
|
|
161
|
-
if (e.preventDefault(), !
|
|
162
|
-
|
|
159
|
+
if (e.preventDefault(), !k) {
|
|
160
|
+
P(C("auth.magicLink.enterEmail", "Please enter your email address"));
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
163
|
try {
|
|
166
|
-
let { data: e } = await I({ variables: { email:
|
|
167
|
-
e.sendMagicLink.success ? (
|
|
164
|
+
let { data: e } = await I({ variables: { email: k } });
|
|
165
|
+
e.sendMagicLink.success ? (M("sent"), P(e.sendMagicLink.message || "Magic link sent! Check your email.")) : P(e.sendMagicLink.message || "Failed to send magic link");
|
|
168
166
|
} catch (e) {
|
|
169
|
-
|
|
167
|
+
P(e instanceof Error ? e.message : "Failed to send magic link");
|
|
170
168
|
}
|
|
171
169
|
},
|
|
172
170
|
className: "space-y-6",
|
|
173
171
|
children: [
|
|
174
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
175
173
|
htmlFor: "email",
|
|
176
174
|
className: "block text-sm font-medium text-[var(--color-text-primary)] mb-2",
|
|
177
|
-
children:
|
|
178
|
-
}), /* @__PURE__ */
|
|
175
|
+
children: C("auth.magicLink.emailLabel", "Email address")
|
|
176
|
+
}), /* @__PURE__ */ g("div", {
|
|
179
177
|
className: "relative",
|
|
180
|
-
children: [/* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ h("div", {
|
|
181
179
|
className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none",
|
|
182
|
-
children: /* @__PURE__ */
|
|
183
|
-
}), /* @__PURE__ */
|
|
180
|
+
children: /* @__PURE__ */ h(s, { className: "size-5 text-[var(--color-text-secondary)]" })
|
|
181
|
+
}), /* @__PURE__ */ h("input", {
|
|
184
182
|
id: "email",
|
|
185
183
|
type: "email",
|
|
186
|
-
value:
|
|
187
|
-
onChange: (e) =>
|
|
184
|
+
value: k,
|
|
185
|
+
onChange: (e) => A(e.target.value),
|
|
188
186
|
placeholder: "you@example.com",
|
|
187
|
+
"aria-label": C("auth.magicLink.emailLabel", "Email address"),
|
|
189
188
|
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",
|
|
190
189
|
required: !0
|
|
191
190
|
})]
|
|
192
191
|
})] }),
|
|
193
|
-
|
|
192
|
+
N && /* @__PURE__ */ h("p", {
|
|
194
193
|
className: "text-sm text-[var(--color-status-error)]",
|
|
195
|
-
children:
|
|
194
|
+
children: N
|
|
196
195
|
}),
|
|
197
|
-
/* @__PURE__ */
|
|
196
|
+
/* @__PURE__ */ h("button", {
|
|
198
197
|
type: "submit",
|
|
199
198
|
disabled: L,
|
|
200
199
|
className: "w-full py-3 px-4 rounded-lg bg-[var(--color-accent-primary)] text-white font-medium hover:bg-[var(--color-accent-primary-hover)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
201
|
-
children: L ?
|
|
200
|
+
children: L ? C("auth.magicLink.sending", "Sending...") : C("auth.magicLink.sendButton", "Send Magic Link")
|
|
202
201
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
202
|
+
/* @__PURE__ */ h("div", {
|
|
204
203
|
className: "text-center",
|
|
205
|
-
children: /* @__PURE__ */
|
|
204
|
+
children: /* @__PURE__ */ h("button", {
|
|
206
205
|
type: "button",
|
|
207
|
-
onClick: () =>
|
|
206
|
+
onClick: () => w("/auth/login"),
|
|
208
207
|
className: "text-sm text-[var(--color-accent-primary)] hover:underline",
|
|
209
|
-
children:
|
|
208
|
+
children: C("auth.magicLink.passwordInstead", "Sign in with password instead")
|
|
210
209
|
})
|
|
211
210
|
})
|
|
212
211
|
]
|
|
@@ -214,6 +213,6 @@ function x() {
|
|
|
214
213
|
});
|
|
215
214
|
}
|
|
216
215
|
//#endregion
|
|
217
|
-
export {
|
|
216
|
+
export { S as MagicLinkPage };
|
|
218
217
|
|
|
219
218
|
//# sourceMappingURL=MagicLinkPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MagicLinkPage.js","names":[],"sources":["../../../src/auth/pages/MagicLinkPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\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 { 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 [hasVerified, setHasVerified] = useState(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 && !hasVerified) {\n setHasVerified(true);\n handleVerifyToken(token);\n }\n }, [token, hasVerified, 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 h-12 w-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=\"w-16 h-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <CheckIcon className=\"w-8 h-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=\"w-16 h-16 rounded-full bg-[var(--color-status-error-subtle)] flex items-center justify-center mb-4\">\n <AlertIcon className=\"w-8 h-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 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=\"w-16 h-16 rounded-full bg-[var(--color-status-success-subtle)] flex items-center justify-center mb-4\">\n <EmailIcon className=\"w-8 h-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 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 <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=\"h-5 w-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 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 className=\"w-full py-3 px-4 rounded-lg bg-[var(--color-accent-primary)] text-white font-medium hover:bg-[var(--color-accent-primary-hover)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\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 { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useNavigate, useSearchParams } from \"react-router-dom\";\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 { 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 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 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 <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 className=\"w-full py-3 px-4 rounded-lg bg-[var(--color-accent-primary)] text-white font-medium hover:bg-[var(--color-accent-primary-hover)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent-primary)] focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\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,7 +1,6 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import "../hooks/
|
|
4
|
-
import { AlertIcon as n, SpinnerIcon as r } from "../components/AuthIcons.js";
|
|
2
|
+
import { AlertIcon as t, SpinnerIcon as n } from "../components/AuthIcons.js";
|
|
3
|
+
import { useMfaOperations as r } from "../hooks/useMfaOperations.js";
|
|
5
4
|
import { QRCodeSVG as i } from "../../node_modules/qrcode.react/lib/esm/index.js";
|
|
6
5
|
import { ConfirmActionDialog as a } from "../components/ConfirmActionDialog.js";
|
|
7
6
|
import { nativeCopyText as o, nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
|
|
@@ -13,7 +12,7 @@ function m() {
|
|
|
13
12
|
let { t: m } = u(), h = (e, t) => {
|
|
14
13
|
let n = m(e);
|
|
15
14
|
return n === e ? t : n;
|
|
16
|
-
}, { setupMFA: g, enableMFA: _, disableMFA: v, isLoading: y } =
|
|
15
|
+
}, { setupMFA: g, enableMFA: _, disableMFA: v, isLoading: y } = r(), b = e((e) => e.user), x = e((e) => e.isAuthenticated), [S, C] = l(null), [w, T] = l(""), [E, D] = l(null), [O, k] = l(null), [A, j] = l(!1), [M, N] = l(!1), P = async (e) => {
|
|
17
16
|
try {
|
|
18
17
|
D(null), k(null), C(await g(e));
|
|
19
18
|
} catch (e) {
|
|
@@ -54,7 +53,7 @@ function m() {
|
|
|
54
53
|
children: /* @__PURE__ */ p("div", {
|
|
55
54
|
className: "flex items-center gap-3",
|
|
56
55
|
children: [/* @__PURE__ */ f("svg", {
|
|
57
|
-
className: "
|
|
56
|
+
className: "size-5 text-[var(--color-status-success-text)]",
|
|
58
57
|
fill: "none",
|
|
59
58
|
viewBox: "0 0 24 24",
|
|
60
59
|
stroke: "currentColor",
|
|
@@ -72,12 +71,13 @@ function m() {
|
|
|
72
71
|
}),
|
|
73
72
|
E && /* @__PURE__ */ p("div", {
|
|
74
73
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]",
|
|
75
|
-
children: [/* @__PURE__ */ f(
|
|
74
|
+
children: [/* @__PURE__ */ f(t, { className: "size-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5" }), /* @__PURE__ */ p("div", {
|
|
76
75
|
className: "flex-1",
|
|
77
76
|
children: [/* @__PURE__ */ f("p", {
|
|
78
77
|
className: "text-sm text-[var(--color-status-error-text)]",
|
|
79
78
|
children: E
|
|
80
79
|
}), /* @__PURE__ */ f("button", {
|
|
80
|
+
type: "button",
|
|
81
81
|
onClick: () => D(null),
|
|
82
82
|
className: "mt-2 text-sm text-[var(--color-status-error-text)] hover:underline transition-colors duration-200",
|
|
83
83
|
children: h("auth.mfaSetup.dismiss", "Dismiss")
|
|
@@ -89,9 +89,9 @@ function m() {
|
|
|
89
89
|
children: [/* @__PURE__ */ p("div", {
|
|
90
90
|
className: "flex items-center gap-3 mb-6",
|
|
91
91
|
children: [/* @__PURE__ */ f("div", {
|
|
92
|
-
className: "
|
|
92
|
+
className: "size-10 rounded-full bg-[var(--color-bg-accent)] flex items-center justify-center",
|
|
93
93
|
children: /* @__PURE__ */ f("svg", {
|
|
94
|
-
className: "
|
|
94
|
+
className: "size-5 text-[var(--color-text-link)]",
|
|
95
95
|
fill: "none",
|
|
96
96
|
viewBox: "0 0 24 24",
|
|
97
97
|
stroke: "currentColor",
|
|
@@ -121,7 +121,7 @@ function m() {
|
|
|
121
121
|
size: 192,
|
|
122
122
|
level: "M",
|
|
123
123
|
includeMargin: !1,
|
|
124
|
-
className: "
|
|
124
|
+
className: "size-48"
|
|
125
125
|
})
|
|
126
126
|
})
|
|
127
127
|
}),
|
|
@@ -138,7 +138,7 @@ function m() {
|
|
|
138
138
|
/* @__PURE__ */ p("div", {
|
|
139
139
|
className: "flex items-start gap-3 mb-3",
|
|
140
140
|
children: [/* @__PURE__ */ f("svg", {
|
|
141
|
-
className: "
|
|
141
|
+
className: "size-5 text-[var(--color-status-warning-text)] flex-shrink-0 mt-0.5",
|
|
142
142
|
fill: "none",
|
|
143
143
|
viewBox: "0 0 24 24",
|
|
144
144
|
stroke: "currentColor",
|
|
@@ -165,9 +165,10 @@ function m() {
|
|
|
165
165
|
". ",
|
|
166
166
|
e
|
|
167
167
|
]
|
|
168
|
-
},
|
|
168
|
+
}, e))
|
|
169
169
|
}),
|
|
170
170
|
/* @__PURE__ */ f("button", {
|
|
171
|
+
type: "button",
|
|
171
172
|
onClick: async () => {
|
|
172
173
|
S?.backupCodes && (s("light"), await o(S.backupCodes.join("\n")) ? (j(!0), c("success"), setTimeout(() => j(!1), 2e3)) : c("error"));
|
|
173
174
|
},
|
|
@@ -196,12 +197,14 @@ function m() {
|
|
|
196
197
|
/* @__PURE__ */ p("div", {
|
|
197
198
|
className: "flex gap-3",
|
|
198
199
|
children: [/* @__PURE__ */ f("button", {
|
|
200
|
+
type: "button",
|
|
199
201
|
onClick: () => {
|
|
200
202
|
C(null), T(""), D(null);
|
|
201
203
|
},
|
|
202
204
|
className: "flex-1 py-3 px-4 rounded-xl border border-[var(--color-border-default)] text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-muted)] transition-colors duration-200",
|
|
203
205
|
children: h("auth.mfaSetup.cancel", "Cancel")
|
|
204
206
|
}), /* @__PURE__ */ f("button", {
|
|
207
|
+
type: "button",
|
|
205
208
|
onClick: async () => {
|
|
206
209
|
if (w.length !== 6) {
|
|
207
210
|
D(h("auth.mfaSetup.enter6Digit", "Please enter a 6-digit code"));
|
|
@@ -215,7 +218,7 @@ function m() {
|
|
|
215
218
|
},
|
|
216
219
|
disabled: y || w.length !== 6,
|
|
217
220
|
className: "auth-primary-button flex flex-1 items-center justify-center gap-2 px-4 py-3 font-medium",
|
|
218
|
-
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(
|
|
221
|
+
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.verifying", "Verifying...") })] }) : h("auth.mfaSetup.enable2fa", "Enable 2FA")
|
|
219
222
|
})]
|
|
220
223
|
})
|
|
221
224
|
]
|
|
@@ -226,9 +229,9 @@ function m() {
|
|
|
226
229
|
/* @__PURE__ */ p("div", {
|
|
227
230
|
className: "flex items-center gap-4 mb-6",
|
|
228
231
|
children: [/* @__PURE__ */ f("div", {
|
|
229
|
-
className: "
|
|
232
|
+
className: "size-12 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center",
|
|
230
233
|
children: /* @__PURE__ */ f("svg", {
|
|
231
|
-
className: "
|
|
234
|
+
className: "size-6 text-[var(--color-status-success-text)]",
|
|
232
235
|
fill: "none",
|
|
233
236
|
viewBox: "0 0 24 24",
|
|
234
237
|
stroke: "currentColor",
|
|
@@ -255,18 +258,19 @@ function m() {
|
|
|
255
258
|
})
|
|
256
259
|
}),
|
|
257
260
|
/* @__PURE__ */ f("button", {
|
|
261
|
+
type: "button",
|
|
258
262
|
onClick: () => N(!0),
|
|
259
263
|
disabled: y,
|
|
260
264
|
className: "w-full py-3 px-4 rounded-xl border border-[var(--color-status-error-border)] text-[var(--color-status-error-text)] font-medium bg-[var(--color-bg-surface)] hover:bg-[var(--color-status-error-bg-subtle)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center gap-2",
|
|
261
|
-
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(
|
|
265
|
+
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.disabling", "Disabling...") })] }) : h("auth.mfaSetup.disableTwoFactor", "Disable Two-Factor Authentication")
|
|
262
266
|
})
|
|
263
267
|
] }) : /* @__PURE__ */ p(d, { children: [
|
|
264
268
|
/* @__PURE__ */ p("div", {
|
|
265
269
|
className: "flex items-center gap-4 mb-6",
|
|
266
270
|
children: [/* @__PURE__ */ f("div", {
|
|
267
|
-
className: "
|
|
271
|
+
className: "size-12 rounded-full bg-[var(--color-bg-muted)] flex items-center justify-center",
|
|
268
272
|
children: /* @__PURE__ */ f("svg", {
|
|
269
|
-
className: "
|
|
273
|
+
className: "size-6 text-[var(--color-text-tertiary)]",
|
|
270
274
|
fill: "none",
|
|
271
275
|
viewBox: "0 0 24 24",
|
|
272
276
|
stroke: "currentColor",
|
|
@@ -300,11 +304,12 @@ function m() {
|
|
|
300
304
|
})]
|
|
301
305
|
}),
|
|
302
306
|
/* @__PURE__ */ f("button", {
|
|
307
|
+
type: "button",
|
|
303
308
|
onClick: () => P("TOTP"),
|
|
304
309
|
disabled: y,
|
|
305
310
|
className: "auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-medium",
|
|
306
|
-
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(
|
|
307
|
-
className: "
|
|
311
|
+
children: y ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(n, { className: "size-5 animate-spin" }), /* @__PURE__ */ f("span", { children: h("auth.mfa.settingUp", "Setting up...") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f("svg", {
|
|
312
|
+
className: "size-5",
|
|
308
313
|
fill: "none",
|
|
309
314
|
viewBox: "0 0 24 24",
|
|
310
315
|
stroke: "currentColor",
|
|
@@ -325,8 +330,8 @@ function m() {
|
|
|
325
330
|
className: "text-center py-16 border-2 border-dashed border-border-default rounded-xl",
|
|
326
331
|
children: [
|
|
327
332
|
/* @__PURE__ */ f("div", {
|
|
328
|
-
className: "
|
|
329
|
-
children: /* @__PURE__ */ f(
|
|
333
|
+
className: "size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
|
|
334
|
+
children: /* @__PURE__ */ f(t, { className: "size-8 text-status-warning-text" })
|
|
330
335
|
}),
|
|
331
336
|
/* @__PURE__ */ f("h2", {
|
|
332
337
|
className: "text-xl font-semibold text-text-primary mb-2",
|