@burdenoff/microfe-auth 2026.717.1 → 2026.717.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.
|
@@ -70,10 +70,10 @@ function we(e) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
function y() {
|
|
73
|
-
let v = ce(), y = se(), [b] = le(), { t:
|
|
74
|
-
let n =
|
|
73
|
+
let v = ce(), y = se(), [b] = le(), { t: Te } = de(), x = f((e, t) => {
|
|
74
|
+
let n = Te(e);
|
|
75
75
|
return n === e ? t : n;
|
|
76
|
-
}, [
|
|
76
|
+
}, [Te]), { signIn: Ee, isLoading: S, error: C, errorCode: w, resendVerification: De } = n(), { completeAuthFlow: Oe } = r(), { setWorkspace: T, setOrganization: E } = ue(), { defaultPostLoginPath: D } = e(), { initiateGoogleOAuth: ke, initiateMicrosoftOAuth: Ae, isProviderConfigured: O } = i(), k = t((e) => e.mfaRequired), { executeRecaptcha: je } = ne(), [A, j] = m(!1), [Me, Ne] = m(!1), Pe = f((e) => {
|
|
77
77
|
if (typeof window > "u" || be()) return e;
|
|
78
78
|
try {
|
|
79
79
|
let t = fe(), n = new URL(t.url), r = window.location.origin;
|
|
@@ -95,7 +95,7 @@ function y() {
|
|
|
95
95
|
} catch {
|
|
96
96
|
return e;
|
|
97
97
|
}
|
|
98
|
-
}, []),
|
|
98
|
+
}, []), M = f((e) => {
|
|
99
99
|
if (typeof window < "u") {
|
|
100
100
|
try {
|
|
101
101
|
sessionStorage.setItem("bf-post-login-return-to", e);
|
|
@@ -104,33 +104,35 @@ function y() {
|
|
|
104
104
|
v(xe(e));
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
-
window.location.assign(
|
|
107
|
+
window.location.assign(Pe(e));
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
v(e);
|
|
111
|
-
}, [v,
|
|
111
|
+
}, [v, Pe]), [N, Fe] = m(!1), [P, F] = m(!1), [Ie, I] = m(!1), [Le, L] = m(!1), [R, z] = m(!1), [B, V] = m(!1), [Re, ze] = m(!1), [Be, H] = m(!1), [Ve, U] = m(null), [He, W] = m(null), [G, K] = m({
|
|
112
112
|
email: "",
|
|
113
113
|
password: "",
|
|
114
114
|
rememberMe: !1
|
|
115
|
-
}), [q, J] = m(null), [Ue, Y] = m(!1), X = b.get("mode") === "add-account", Z = b.get("returnTo"), Q = ie(Z);
|
|
115
|
+
}), [q, J] = m(null), [Ue, Y] = m(!1), X = b.get("mode") === "add-account", Z = b.get("returnTo"), Q = ie(Z), $ = b.get("reason");
|
|
116
116
|
p(() => {
|
|
117
|
+
$ === "session-expired" ? W(x("auth.login.sessionExpired", "Your session has expired. Please sign in again.")) : $ === "logout" && W(x("auth.login.signedOut", "You have been signed out."));
|
|
118
|
+
}, [$, x]), p(() => {
|
|
117
119
|
!Q.current && Z && (Q.current = Z);
|
|
118
120
|
}, [Z]), p(() => {
|
|
119
|
-
if (X ||
|
|
121
|
+
if (X || A) return;
|
|
120
122
|
let e = ye();
|
|
121
123
|
if (e) {
|
|
122
124
|
let n = he(e), r = ge(e);
|
|
123
125
|
if (n?.accessToken && r && Se(n.accessToken)) {
|
|
124
|
-
t.getState().loadFromProfile(e),
|
|
126
|
+
t.getState().loadFromProfile(e), M(Ce(Z) ?? "/");
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
let n = oe();
|
|
129
|
-
n && (
|
|
131
|
+
n && (Ne(!0), window.location.replace(n));
|
|
130
132
|
}, [
|
|
131
133
|
X,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
A,
|
|
135
|
+
M,
|
|
134
136
|
Z
|
|
135
137
|
]), p(() => {
|
|
136
138
|
try {
|
|
@@ -146,7 +148,7 @@ function y() {
|
|
|
146
148
|
}
|
|
147
149
|
}, []), p(() => {
|
|
148
150
|
let e = y.state;
|
|
149
|
-
e?.message &&
|
|
151
|
+
e?.message && W(e.message), e?.email && K((t) => ({
|
|
150
152
|
...t,
|
|
151
153
|
email: e.email
|
|
152
154
|
})), e && window.history.replaceState({}, document.title);
|
|
@@ -170,26 +172,26 @@ function y() {
|
|
|
170
172
|
...t,
|
|
171
173
|
email: e
|
|
172
174
|
})), b.get("expired") === "true") {
|
|
173
|
-
|
|
175
|
+
W(x("auth.login.sessionExpired", "Your session has expired. Please sign in again."));
|
|
174
176
|
let e = window.location.pathname;
|
|
175
177
|
window.history.replaceState({}, document.title, e);
|
|
176
178
|
}
|
|
177
|
-
}, [b,
|
|
178
|
-
|
|
179
|
-
}, [
|
|
179
|
+
}, [b, x]), p(() => {
|
|
180
|
+
k && v("/auth/mfa-verify");
|
|
181
|
+
}, [k, v]);
|
|
180
182
|
let Ge = async (e) => {
|
|
181
|
-
e.preventDefault(),
|
|
183
|
+
e.preventDefault(), F(!1), I(!1), L(!1), z(!1), V(!1), Y(!1), H(!1), J(null);
|
|
182
184
|
let n = (G.email ?? "").trim();
|
|
183
185
|
if (!n) {
|
|
184
|
-
J(
|
|
186
|
+
J(x("auth.login.enterEmailOrUsername", "Please enter your email or username"));
|
|
185
187
|
return;
|
|
186
188
|
}
|
|
187
189
|
if (n.includes("@") && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(n)) {
|
|
188
|
-
J(
|
|
190
|
+
J(x("auth.login.invalidEmail", "Please enter a valid email address"));
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
if (!G.password || G.password.length < 1) {
|
|
192
|
-
J(
|
|
194
|
+
J(x("auth.login.enterPassword", "Please enter your password"));
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
195
197
|
if (X && ve().some((e) => e.email.toLowerCase() === n.toLowerCase())) {
|
|
@@ -198,14 +200,14 @@ function y() {
|
|
|
198
200
|
}
|
|
199
201
|
Y(!1);
|
|
200
202
|
try {
|
|
201
|
-
let e = await
|
|
203
|
+
let e = await je("login"), t = await Ee({
|
|
202
204
|
email: n,
|
|
203
205
|
password: G.password,
|
|
204
206
|
rememberMe: G.rememberMe,
|
|
205
207
|
...e ? { recaptchaToken: e } : {}
|
|
206
208
|
});
|
|
207
209
|
if (t.requiresMFA) {
|
|
208
|
-
let e = we(Q.current || Z || (
|
|
210
|
+
let e = we(Q.current || Z || (D ?? "/dashboard"));
|
|
209
211
|
try {
|
|
210
212
|
sessionStorage.setItem(re, e);
|
|
211
213
|
} catch {}
|
|
@@ -227,10 +229,10 @@ function y() {
|
|
|
227
229
|
} catch (e) {
|
|
228
230
|
console.warn("[LoginPage] Failed to save/clear remembered email:", e);
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
let e = we(Q.current || Z || (
|
|
232
|
+
j(!0);
|
|
233
|
+
let e = we(Q.current || Z || (D ?? "/dashboard"));
|
|
232
234
|
try {
|
|
233
|
-
let n = await Promise.race([
|
|
235
|
+
let n = await Promise.race([Oe(t.token), new Promise((e) => {
|
|
234
236
|
window.setTimeout(() => {
|
|
235
237
|
e({
|
|
236
238
|
success: !1,
|
|
@@ -238,75 +240,75 @@ function y() {
|
|
|
238
240
|
});
|
|
239
241
|
}, 5e3);
|
|
240
242
|
})]);
|
|
241
|
-
n.success ? (n.workspace && (
|
|
243
|
+
n.success ? (n.workspace && (T(n.workspace.id), n.workspace.organizationId && E(n.workspace.organizationId)), M(e)) : n.error?.includes("No workspaces found") ? v("/onboarding/create-workspace") : (n.workspace && (T(n.workspace.id), n.workspace.organizationId && E(n.workspace.organizationId)), console.warn("Auth flow warning:", n.error), M(e));
|
|
242
244
|
} catch (t) {
|
|
243
|
-
console.error("Auth flow error (login succeeded):", t),
|
|
245
|
+
console.error("Auth flow error (login succeeded):", t), M(e);
|
|
244
246
|
} finally {
|
|
245
|
-
|
|
247
|
+
j(!1);
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
} catch (e) {
|
|
249
251
|
let n = e instanceof Error ? e.message : String(e);
|
|
250
252
|
n.includes("reCAPTCHA") && J(n);
|
|
251
253
|
let r = t.getState().errorCode, i = We(n, r);
|
|
252
|
-
i === "DELETED" ?
|
|
254
|
+
i === "DELETED" ? I(!0) : i === "SUSPENDED" ? L(!0) : i === "OAUTH_ONLY" ? z(!0) : i === "INACTIVE" ? F(!0) : i === "EMAIL_NOT_VERIFIED" && V(!0);
|
|
253
255
|
}
|
|
254
256
|
};
|
|
255
257
|
p(() => {
|
|
256
|
-
if (
|
|
257
|
-
let e = We(
|
|
258
|
-
e === "DELETED" ?
|
|
258
|
+
if (C) {
|
|
259
|
+
let e = We(C, w);
|
|
260
|
+
e === "DELETED" ? I(!0) : e === "SUSPENDED" ? L(!0) : e === "OAUTH_ONLY" ? z(!0) : e === "INACTIVE" ? F(!0) : e === "EMAIL_NOT_VERIFIED" && V(!0);
|
|
259
261
|
}
|
|
260
|
-
}, [
|
|
262
|
+
}, [C, w]);
|
|
261
263
|
let Ke = () => {
|
|
262
|
-
|
|
264
|
+
U(null);
|
|
263
265
|
try {
|
|
264
|
-
|
|
266
|
+
ke();
|
|
265
267
|
} catch (e) {
|
|
266
|
-
|
|
268
|
+
U(e instanceof Error ? e.message : x("auth.login.googleSignInFailed", "Failed to initiate Google sign in"));
|
|
267
269
|
}
|
|
268
270
|
}, qe = () => {
|
|
269
|
-
|
|
271
|
+
U(null);
|
|
270
272
|
try {
|
|
271
|
-
|
|
273
|
+
Ae();
|
|
272
274
|
} catch (e) {
|
|
273
|
-
|
|
275
|
+
U(e instanceof Error ? e.message : x("auth.login.microsoftSignInFailed", "Failed to initiate Microsoft sign in"));
|
|
274
276
|
}
|
|
275
277
|
}, Je = async () => {
|
|
276
|
-
|
|
278
|
+
ze(!0), H(!1);
|
|
277
279
|
try {
|
|
278
|
-
(await
|
|
280
|
+
(await De((G.email ?? "").trim())).success ? H(!0) : J(x("auth.login.resendFailed", "Failed to resend verification email. Please try again."));
|
|
279
281
|
} catch (e) {
|
|
280
|
-
console.error("Failed to resend verification email:", e), J(e instanceof Error ? e.message :
|
|
282
|
+
console.error("Failed to resend verification email:", e), J(e instanceof Error ? e.message : x("auth.login.resendFailed", "Failed to resend verification email. Please try again."));
|
|
281
283
|
} finally {
|
|
282
|
-
|
|
284
|
+
ze(!1);
|
|
283
285
|
}
|
|
284
|
-
}, Ye =
|
|
286
|
+
}, Ye = O("google"), Xe = O("microsoft"), Ze = [{
|
|
285
287
|
name: "Google",
|
|
286
288
|
icon: l,
|
|
287
289
|
onClick: Ke,
|
|
288
290
|
disabled: !Ye,
|
|
289
|
-
tooltip: Ye ? void 0 :
|
|
291
|
+
tooltip: Ye ? void 0 : x("auth.login.googleNotConfigured", "Google sign in is not configured")
|
|
290
292
|
}, {
|
|
291
293
|
name: "Microsoft",
|
|
292
294
|
icon: te,
|
|
293
295
|
onClick: qe,
|
|
294
|
-
disabled:
|
|
295
|
-
tooltip:
|
|
296
|
+
disabled: !Xe,
|
|
297
|
+
tooltip: Xe ? void 0 : x("auth.login.microsoftNotConfigured", "Microsoft sign in is not configured")
|
|
296
298
|
}];
|
|
297
|
-
return
|
|
298
|
-
title:
|
|
299
|
-
subtitle:
|
|
299
|
+
return Me ? /* @__PURE__ */ g(ae, {}) : /* @__PURE__ */ _(a, {
|
|
300
|
+
title: x("auth.login.welcomeBack", "Welcome back"),
|
|
301
|
+
subtitle: x("auth.login.enterCredentials", "Enter your credentials to continue"),
|
|
300
302
|
children: [
|
|
301
303
|
/* @__PURE__ */ g(me, {
|
|
302
304
|
className: "mb-5",
|
|
303
|
-
children:
|
|
305
|
+
children: x("auth.login.purpose", "Sign in to reach your workspaces, agents, and settings. Use your email and password, a social provider, or a passwordless magic link.")
|
|
304
306
|
}),
|
|
305
307
|
/* @__PURE__ */ _("form", {
|
|
306
308
|
onSubmit: Ge,
|
|
307
309
|
className: "space-y-5",
|
|
308
310
|
children: [
|
|
309
|
-
|
|
311
|
+
He && /* @__PURE__ */ _("div", {
|
|
310
312
|
className: "flex items-start gap-3 p-4 rounded-xl border bg-status-info-bg-subtle border-status-info-border",
|
|
311
313
|
children: [/* @__PURE__ */ g("svg", {
|
|
312
314
|
className: "size-5 flex-shrink-0 mt-0.5 text-text-link",
|
|
@@ -321,7 +323,7 @@ function y() {
|
|
|
321
323
|
})
|
|
322
324
|
}), /* @__PURE__ */ g("p", {
|
|
323
325
|
className: "text-sm text-status-info-text",
|
|
324
|
-
children:
|
|
326
|
+
children: He
|
|
325
327
|
})]
|
|
326
328
|
}),
|
|
327
329
|
q && /* @__PURE__ */ _("div", {
|
|
@@ -338,11 +340,11 @@ function y() {
|
|
|
338
340
|
children: [
|
|
339
341
|
/* @__PURE__ */ g("p", {
|
|
340
342
|
className: "text-sm font-medium text-status-warning-text",
|
|
341
|
-
children:
|
|
343
|
+
children: x("auth.login.duplicateAccount", "This account is already signed in.")
|
|
342
344
|
}),
|
|
343
345
|
/* @__PURE__ */ g("p", {
|
|
344
346
|
className: "mt-1 text-sm text-status-warning-text/80",
|
|
345
|
-
children:
|
|
347
|
+
children: x("auth.login.duplicateAccountHint", "Please use a different account, or go back and switch to this account from the profile menu.")
|
|
346
348
|
}),
|
|
347
349
|
(Q.current || Z) && /* @__PURE__ */ _("button", {
|
|
348
350
|
type: "button",
|
|
@@ -359,29 +361,29 @@ function y() {
|
|
|
359
361
|
strokeWidth: 2,
|
|
360
362
|
d: "M10 19l-7-7m0 0l7-7m-7 7h18"
|
|
361
363
|
})
|
|
362
|
-
}),
|
|
364
|
+
}), x("auth.login.goBackSwitchAccount", "Go back and switch account")]
|
|
363
365
|
})
|
|
364
366
|
]
|
|
365
367
|
})]
|
|
366
368
|
}),
|
|
367
|
-
|
|
368
|
-
className: `flex items-start gap-3 p-4 rounded-xl border ${
|
|
369
|
-
children: [/* @__PURE__ */ g(o, { className: `size-5 flex-shrink-0 mt-0.5 ${
|
|
369
|
+
C && !q && !Ue && /* @__PURE__ */ _("div", {
|
|
370
|
+
className: `flex items-start gap-3 p-4 rounded-xl border ${P || R || B ? "bg-status-warning-bg-subtle border-status-warning-border" : "bg-status-error-bg-subtle border-status-error-border"}`,
|
|
371
|
+
children: [/* @__PURE__ */ g(o, { className: `size-5 flex-shrink-0 mt-0.5 ${P || R || B ? "text-status-warning-text" : "text-status-error-text"}` }), /* @__PURE__ */ _("div", {
|
|
370
372
|
className: "flex-1",
|
|
371
373
|
children: [
|
|
372
374
|
/* @__PURE__ */ g("p", {
|
|
373
|
-
className: `text-sm ${
|
|
374
|
-
children:
|
|
375
|
+
className: `text-sm ${P || R || B ? "text-status-warning-text" : "text-status-error-text"}`,
|
|
376
|
+
children: Ie ? x("auth.login.accountDeleted", "This account has been permanently deleted and cannot be recovered.") : Le ? x("auth.login.accountSuspended", "Your account has been suspended. Please contact support for assistance.") : P ? x("auth.login.accountDeactivated", "Your account has been deactivated.") : R ? x("auth.login.oauthOnly", "This account uses OAuth sign-in. Please use Google or Microsoft to sign in.") : B ? x("auth.login.emailNotVerified", "Please verify your email before signing in. Check your inbox for the verification link.") : C
|
|
375
377
|
}),
|
|
376
|
-
|
|
378
|
+
P && /* @__PURE__ */ g("button", {
|
|
377
379
|
type: "button",
|
|
378
380
|
onClick: () => v("/auth/reactivate", { state: { email: G.email } }),
|
|
379
381
|
className: "mt-2 text-sm font-semibold text-status-warning-text hover:text-status-warning-text underline underline-offset-2",
|
|
380
|
-
children:
|
|
382
|
+
children: x("auth.login.clickReactivate", "Click here to reactivate your account")
|
|
381
383
|
}),
|
|
382
|
-
|
|
384
|
+
B && /* @__PURE__ */ g("div", {
|
|
383
385
|
className: "mt-3",
|
|
384
|
-
children:
|
|
386
|
+
children: Be ? /* @__PURE__ */ _("div", {
|
|
385
387
|
className: "flex items-center gap-2 text-sm text-status-success-text",
|
|
386
388
|
children: [/* @__PURE__ */ g("svg", {
|
|
387
389
|
className: "size-4",
|
|
@@ -394,13 +396,13 @@ function y() {
|
|
|
394
396
|
strokeWidth: 2,
|
|
395
397
|
d: "M5 13l4 4L19 7"
|
|
396
398
|
})
|
|
397
|
-
}),
|
|
399
|
+
}), x("auth.login.verificationSent", "Verification email sent! Please check your inbox.")]
|
|
398
400
|
}) : /* @__PURE__ */ g("button", {
|
|
399
401
|
type: "button",
|
|
400
402
|
onClick: Je,
|
|
401
|
-
disabled:
|
|
403
|
+
disabled: Re,
|
|
402
404
|
className: "inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
|
|
403
|
-
children:
|
|
405
|
+
children: Re ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(d, { className: "size-4 animate-spin" }), x("auth.login.sending", "Sending...")] }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("svg", {
|
|
404
406
|
className: "size-4",
|
|
405
407
|
fill: "none",
|
|
406
408
|
viewBox: "0 0 24 24",
|
|
@@ -411,21 +413,21 @@ function y() {
|
|
|
411
413
|
strokeWidth: 2,
|
|
412
414
|
d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
413
415
|
})
|
|
414
|
-
}),
|
|
416
|
+
}), x("auth.login.resendVerification", "Resend verification email")] })
|
|
415
417
|
})
|
|
416
418
|
}),
|
|
417
|
-
|
|
419
|
+
R && /* @__PURE__ */ _("div", {
|
|
418
420
|
className: "mt-3 flex flex-col gap-2",
|
|
419
|
-
children: [
|
|
421
|
+
children: [O("google") && /* @__PURE__ */ _("button", {
|
|
420
422
|
type: "button",
|
|
421
423
|
onClick: Ke,
|
|
422
424
|
className: "inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm",
|
|
423
|
-
children: [/* @__PURE__ */ g(l, { className: "size-5" }),
|
|
424
|
-
}),
|
|
425
|
+
children: [/* @__PURE__ */ g(l, { className: "size-5" }), x("auth.login.signInWithGoogle", "Sign in with Google")]
|
|
426
|
+
}), O("microsoft") && /* @__PURE__ */ _("button", {
|
|
425
427
|
type: "button",
|
|
426
428
|
onClick: qe,
|
|
427
429
|
className: "inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm",
|
|
428
|
-
children: [/* @__PURE__ */ g(te, { className: "size-5" }),
|
|
430
|
+
children: [/* @__PURE__ */ g(te, { className: "size-5" }), x("auth.login.signInWithMicrosoft", "Sign in with Microsoft")]
|
|
429
431
|
})]
|
|
430
432
|
})
|
|
431
433
|
]
|
|
@@ -436,7 +438,7 @@ function y() {
|
|
|
436
438
|
children: [/* @__PURE__ */ g("label", {
|
|
437
439
|
htmlFor: "email",
|
|
438
440
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
439
|
-
children:
|
|
441
|
+
children: x("auth.login.emailLabel", "Email")
|
|
440
442
|
}), /* @__PURE__ */ _("div", {
|
|
441
443
|
className: "relative",
|
|
442
444
|
children: [/* @__PURE__ */ g("div", {
|
|
@@ -448,14 +450,14 @@ function y() {
|
|
|
448
450
|
autoComplete: "email",
|
|
449
451
|
required: !0,
|
|
450
452
|
"aria-required": "true",
|
|
451
|
-
"aria-label":
|
|
453
|
+
"aria-label": x("auth.login.emailLabel", "Email"),
|
|
452
454
|
value: G.email,
|
|
453
455
|
onChange: (e) => K({
|
|
454
456
|
...G,
|
|
455
457
|
email: e.target.value
|
|
456
458
|
}),
|
|
457
459
|
className: "block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
458
|
-
placeholder:
|
|
460
|
+
placeholder: x("auth.login.emailPlaceholder", "you@example.com")
|
|
459
461
|
})]
|
|
460
462
|
})]
|
|
461
463
|
}),
|
|
@@ -464,7 +466,7 @@ function y() {
|
|
|
464
466
|
children: [/* @__PURE__ */ g("label", {
|
|
465
467
|
htmlFor: "password",
|
|
466
468
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
467
|
-
children:
|
|
469
|
+
children: x("auth.login.passwordLabel", "Password")
|
|
468
470
|
}), /* @__PURE__ */ _("div", {
|
|
469
471
|
className: "relative",
|
|
470
472
|
children: [
|
|
@@ -474,25 +476,25 @@ function y() {
|
|
|
474
476
|
}),
|
|
475
477
|
/* @__PURE__ */ g("input", {
|
|
476
478
|
id: "password",
|
|
477
|
-
type:
|
|
479
|
+
type: N ? "text" : "password",
|
|
478
480
|
autoComplete: "current-password",
|
|
479
481
|
required: !0,
|
|
480
482
|
"aria-required": "true",
|
|
481
|
-
"aria-label":
|
|
483
|
+
"aria-label": x("auth.login.passwordLabel", "Password"),
|
|
482
484
|
value: G.password,
|
|
483
485
|
onChange: (e) => K({
|
|
484
486
|
...G,
|
|
485
487
|
password: e.target.value
|
|
486
488
|
}),
|
|
487
489
|
className: "block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
488
|
-
placeholder:
|
|
490
|
+
placeholder: x("auth.login.passwordPlaceholder", "Enter your password")
|
|
489
491
|
}),
|
|
490
492
|
/* @__PURE__ */ g("button", {
|
|
491
493
|
type: "button",
|
|
492
|
-
onClick: () =>
|
|
493
|
-
"aria-label":
|
|
494
|
+
onClick: () => Fe(!N),
|
|
495
|
+
"aria-label": N ? "Hide password" : "Show password",
|
|
494
496
|
className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
|
|
495
|
-
children: g(
|
|
497
|
+
children: g(N ? c : ee, { className: "size-5" })
|
|
496
498
|
})
|
|
497
499
|
]
|
|
498
500
|
})]
|
|
@@ -505,7 +507,7 @@ function y() {
|
|
|
505
507
|
className: "relative",
|
|
506
508
|
children: [/* @__PURE__ */ g("input", {
|
|
507
509
|
type: "checkbox",
|
|
508
|
-
"aria-label":
|
|
510
|
+
"aria-label": x("auth.login.rememberMe", "Remember me"),
|
|
509
511
|
checked: G.rememberMe,
|
|
510
512
|
onChange: (e) => {
|
|
511
513
|
let t = e.target.checked;
|
|
@@ -536,22 +538,22 @@ function y() {
|
|
|
536
538
|
})]
|
|
537
539
|
}), /* @__PURE__ */ g("span", {
|
|
538
540
|
className: "text-sm text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)] transition-colors",
|
|
539
|
-
children:
|
|
541
|
+
children: x("auth.login.rememberMe", "Remember me")
|
|
540
542
|
})]
|
|
541
543
|
}), /* @__PURE__ */ g("button", {
|
|
542
544
|
type: "button",
|
|
543
545
|
onClick: () => v("/auth/reset-password"),
|
|
544
546
|
className: "text-sm font-medium text-text-link hover:text-text-linkHover transition-colors",
|
|
545
|
-
children:
|
|
547
|
+
children: x("auth.login.forgotPassword", "Forgot password?")
|
|
546
548
|
})]
|
|
547
549
|
}),
|
|
548
550
|
/* @__PURE__ */ g(pe, {
|
|
549
551
|
type: "submit",
|
|
550
|
-
disabled:
|
|
552
|
+
disabled: S || A,
|
|
551
553
|
variant: "default",
|
|
552
554
|
size: "lg",
|
|
553
555
|
className: "w-full font-semibold",
|
|
554
|
-
children:
|
|
556
|
+
children: S || A ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(d, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: A ? x("auth.login.settingUpWorkspace", "Setting up workspace...") : x("auth.login.signingIn", "Signing in...") })] }) : x("auth.login.signIn", "Sign in")
|
|
555
557
|
})
|
|
556
558
|
]
|
|
557
559
|
}),
|
|
@@ -564,20 +566,20 @@ function y() {
|
|
|
564
566
|
className: "relative flex justify-center",
|
|
565
567
|
children: /* @__PURE__ */ g("span", {
|
|
566
568
|
className: "px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]",
|
|
567
|
-
children:
|
|
569
|
+
children: x("auth.login.orContinueWith", "or continue with")
|
|
568
570
|
})
|
|
569
571
|
})]
|
|
570
572
|
}),
|
|
571
|
-
|
|
573
|
+
Ve && /* @__PURE__ */ _("div", {
|
|
572
574
|
className: "flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border",
|
|
573
575
|
children: [/* @__PURE__ */ g(o, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ g("p", {
|
|
574
576
|
className: "text-sm text-status-error-text",
|
|
575
|
-
children:
|
|
577
|
+
children: Ve
|
|
576
578
|
})]
|
|
577
579
|
}),
|
|
578
580
|
/* @__PURE__ */ g("div", {
|
|
579
581
|
className: "grid grid-cols-3 gap-3",
|
|
580
|
-
children:
|
|
582
|
+
children: Ze.map((e) => /* @__PURE__ */ _("button", {
|
|
581
583
|
type: "button",
|
|
582
584
|
onClick: e.onClick,
|
|
583
585
|
disabled: e.disabled,
|
|
@@ -597,11 +599,11 @@ function y() {
|
|
|
597
599
|
onClick: () => v("/auth/magic-link"),
|
|
598
600
|
className: "text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors",
|
|
599
601
|
children: [
|
|
600
|
-
|
|
602
|
+
x("auth.login.preferPasswordless", "Prefer passwordless login?"),
|
|
601
603
|
" ",
|
|
602
604
|
/* @__PURE__ */ g("span", {
|
|
603
605
|
className: "font-semibold text-text-link hover:text-text-linkHover",
|
|
604
|
-
children:
|
|
606
|
+
children: x("auth.login.signInMagicLink", "Sign in with Magic Link")
|
|
605
607
|
})
|
|
606
608
|
]
|
|
607
609
|
})
|
|
@@ -609,13 +611,13 @@ function y() {
|
|
|
609
611
|
/* @__PURE__ */ _("p", {
|
|
610
612
|
className: "mt-6 text-center text-sm text-[var(--color-text-secondary)]",
|
|
611
613
|
children: [
|
|
612
|
-
|
|
614
|
+
x("auth.login.dontHaveAccount", "Don't have an account?"),
|
|
613
615
|
" ",
|
|
614
616
|
/* @__PURE__ */ g("button", {
|
|
615
617
|
type: "button",
|
|
616
618
|
onClick: () => v("/auth/register"),
|
|
617
619
|
className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
|
|
618
|
-
children:
|
|
620
|
+
children: x("auth.login.signUp", "Sign up")
|
|
619
621
|
})
|
|
620
622
|
]
|
|
621
623
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginPage.js","names":[],"sources":["../../../src/auth/pages/LoginPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useNavigate, useSearchParams, useLocation } from \"react-router-dom\";\nimport {\n useI18n,\n useActiveContext,\n} from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n getSSORedirectUrl,\n SSORedirectSpinner,\n} from \"@burdenoff/fe-libs/shared/components\";\nimport { resolveAuthBridgeConfig } from \"@burdenoff/fe-libs/shared/config\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\n\nimport {\n getProfileTokens,\n getProfileUser,\n getProfiles,\n getProfileWorkspaceToken,\n resolveActiveProfileId,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useMyWorkspaces } from \"../hooks/useMyWorkspaces\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n SpinnerIcon,\n GoogleIcon,\n // GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { MFA_REDIRECT } from \"../constants/sessionKeys\";\nimport type { SignInInput } from \"../types\";\n\n/**\n * Detect whether the bundle is running inside a native shell (Capacitor on\n * iOS/Android, or Electron). Cross-origin SSO deposit/relay detours only make\n * sense in a multi-origin web context — in a native shell there is one product\n * origin, tokens already live in localStorage, and `window.location.assign` to\n * an external https URL escapes the embedded webview.\n *\n * Detection is runtime-only and avoids importing any platform-specific module\n * so this stays valid when microfe-auth is consumed by a pure web shell too.\n */\nfunction isNativeShell(): boolean {\n if (typeof window === \"undefined\") return false;\n const w = window as typeof window & {\n Capacitor?: { isNativePlatform?: () => boolean };\n };\n if (typeof w.Capacitor?.isNativePlatform === \"function\") {\n try {\n if (w.Capacitor.isNativePlatform()) return true;\n } catch {\n /* fall through */\n }\n }\n if (\n typeof navigator !== \"undefined\" &&\n /Electron/i.test(navigator.userAgent || \"\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction toSameOriginPath(target: string): string {\n if (typeof window === \"undefined\") return target;\n try {\n const url = new URL(target, window.location.origin);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction hasValidAccessToken(token: string): boolean {\n try {\n const raw = token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded)) as { exp?: unknown };\n return typeof payload.exp === \"number\" && payload.exp > Date.now() / 1000;\n } catch {\n return false;\n }\n}\n\nfunction decodeSameOriginReturnTo(returnTo: string | null): string | null {\n if (!returnTo) return null;\n try {\n const decoded = decodeURIComponent(returnTo);\n const url = new URL(decoded, window.location.origin);\n if (url.origin !== window.location.origin) return null;\n return stripStaleSsoParams(url, { includeOrigin: false });\n } catch {\n return null;\n }\n}\n\nfunction stripStaleSsoParams(\n url: URL,\n options: { includeOrigin: boolean },\n): string {\n for (const param of [\"sso_failed\", \"sso-failed\", \"sso-attempted\"] as const) {\n url.searchParams.delete(param);\n }\n\n if (options.includeOrigin) {\n return url.toString();\n }\n\n const search = url.searchParams.toString();\n return `${url.pathname}${search ? `?${search}` : \"\"}${url.hash}`;\n}\n\nfunction cleanReturnToValue(returnTo: string): string {\n try {\n const decoded = decodeURIComponent(returnTo);\n const url = new URL(decoded, window.location.origin);\n // Preserve cross-origin absolute returnTo values (e.g., redirects back to\n // another product's app shell) — only strip stale SSO params.\n const isCrossOrigin = url.origin !== window.location.origin;\n return stripStaleSsoParams(url, { includeOrigin: isCrossOrigin });\n } catch {\n return returnTo;\n }\n}\n\n/**\n * Login page with professional design\n * Provides email/password login and social OAuth options\n */\nexport function LoginPage() {\n const navigate = useNavigate();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t],\n );\n const { signIn, isLoading, error, errorCode, resendVerification } =\n useAuthOperations();\n const { completeAuthFlow } = useMyWorkspaces();\n const { setWorkspace, setOrganization } = useActiveContext();\n const { defaultPostLoginPath } = useAuth();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const mfaRequired = useAuthStore((state) => state.mfaRequired);\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [isCompletingAuth, setIsCompletingAuth] = useState(false);\n const [isSsoRedirecting, setIsSsoRedirecting] = useState(false);\n\n /**\n * Build the URL that should receive the post-login redirect.\n *\n * For cross-eTLD+1 SSO on alpha/prod we detour through the central bridge\n * host (alphaapp/app.burdenoff.com) so the freshly-issued tokens land in\n * the bridge origin's first-party (unpartitioned) localStorage. Without\n * this step, a later direct visit to the bridge host has no local tokens\n * and no shared cookie (cross-eTLD+1 cookies don't propagate), so the user\n * sees the login page again.\n *\n * Skipped when:\n * - Already on the bridge host origin (would be a no-op self-redirect).\n * - The user's intended destination is itself on the bridge host\n * (likely an in-product navigation, no deposit needed).\n * - The freshly-stored profile snapshot can't be read (best-effort only —\n * login still succeeds via the direct redirect).\n */\n const buildPostLoginUrl = useCallback((intendedPath: string): string => {\n if (typeof window === \"undefined\") return intendedPath;\n // Native shells (Electron, Capacitor iOS/Android) run as a single origin —\n // skip the cross-eTLD+1 deposit detour, which would otherwise navigate to\n // an external https URL and escape the embedded webview.\n if (isNativeShell()) return intendedPath;\n try {\n const bridge = resolveAuthBridgeConfig();\n const bridgeUrl = new URL(bridge.url);\n const currentOrigin = window.location.origin;\n if (bridgeUrl.origin === currentOrigin) return intendedPath;\n\n // intendedPath can be a path or an absolute URL; resolve to absolute\n // against current origin so the bridge host knows where to send the\n // user back.\n const absoluteIntended = new URL(intendedPath, currentOrigin).href;\n\n const profileId = resolveActiveProfileId();\n if (!profileId) return intendedPath;\n const tokens = getProfileTokens(profileId);\n const user = getProfileUser(profileId);\n if (!tokens || !user) return intendedPath;\n const workspaceToken = getProfileWorkspaceToken(profileId);\n\n const payload = {\n profileId,\n tokens: JSON.stringify({ ...tokens, workspaceToken }),\n user: JSON.stringify(user),\n profiles: JSON.stringify(getProfiles()),\n };\n const encoded = btoa(JSON.stringify(payload));\n const deposit = new URL(\"/auth/sso-deposit\", bridgeUrl.origin);\n deposit.searchParams.set(\"returnTo\", absoluteIntended);\n return `${deposit.href}#sso=${encoded}`;\n } catch {\n return intendedPath;\n }\n }, []);\n\n const redirectAfterLogin = useCallback(\n (path: string) => {\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(\"bf-post-login-return-to\", path);\n } catch {\n // Ignore storage failures; redirect still proceeds.\n }\n // In native shells use SPA navigation so the embedded webview stays in\n // the app — no full reload, no chance of the OS opening the URL\n // externally. Tokens are already in this origin's localStorage.\n if (isNativeShell()) {\n navigate(toSameOriginPath(path));\n return;\n }\n window.location.assign(buildPostLoginUrl(path));\n return;\n }\n navigate(path);\n },\n [navigate, buildPostLoginUrl],\n );\n const [showPassword, setShowPassword] = useState(false);\n const [isAccountInactive, setIsAccountInactive] = useState(false);\n const [isAccountDeleted, setIsAccountDeleted] = useState(false);\n const [isAccountSuspended, setIsAccountSuspended] = useState(false);\n const [isOAuthOnlyAccount, setIsOAuthOnlyAccount] = useState(false);\n const [isEmailNotVerified, setIsEmailNotVerified] = useState(false);\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [infoMessage, setInfoMessage] = useState<string | null>(null);\n const [formData, setFormData] = useState<\n SignInInput & { rememberMe?: boolean }\n >({\n email: \"\",\n password: \"\",\n rememberMe: false,\n });\n const [validationError, setValidationError] = useState<string | null>(null);\n const [isDuplicateAccount, setIsDuplicateAccount] = useState(false);\n\n // Detect add-account mode and returnTo from URL params\n const isAddAccountMode = searchParams.get(\"mode\") === \"add-account\";\n const returnTo = searchParams.get(\"returnTo\");\n const initialReturnToRef = useRef<string | null>(returnTo);\n\n useEffect(() => {\n if (!initialReturnToRef.current && returnTo) {\n initialReturnToRef.current = returnTo;\n }\n }, [returnTo]);\n\n useEffect(() => {\n if (isAddAccountMode || isCompletingAuth) return;\n\n const profileId = resolveActiveProfileId();\n if (profileId) {\n const tokens = getProfileTokens(profileId);\n const user = getProfileUser(profileId);\n if (\n tokens?.accessToken &&\n user &&\n hasValidAccessToken(tokens.accessToken)\n ) {\n useAuthStore.getState().loadFromProfile(profileId);\n redirectAfterLogin(decodeSameOriginReturnTo(returnTo) ?? \"/\");\n return;\n }\n }\n\n const ssoUrl = getSSORedirectUrl();\n if (ssoUrl) {\n setIsSsoRedirecting(true);\n window.location.replace(ssoUrl);\n }\n }, [isAddAccountMode, isCompletingAuth, redirectAfterLogin, returnTo]);\n\n // Load remembered email on mount and clean up any legacy plaintext password storage\n useEffect(() => {\n try {\n // Security cleanup: remove any previously stored plaintext passwords\n localStorage.removeItem(\"remembered_password\");\n\n const rememberedEmail = localStorage.getItem(\"remembered_email\");\n\n if (rememberedEmail) {\n setFormData((prev) => ({\n ...prev,\n email: rememberedEmail,\n rememberMe: true,\n }));\n }\n } catch (error) {\n console.warn(\"[LoginPage] Failed to load remembered email:\", error);\n }\n }, []);\n\n // Check for redirect message and email from location state (e.g., from expired MFA or SSO redirect)\n useEffect(() => {\n const state = location.state as { message?: string; email?: string } | null;\n if (state?.message) {\n setInfoMessage(state.message);\n }\n if (state?.email) {\n setFormData((prev) => ({ ...prev, email: state.email }));\n }\n // Clear state to prevent showing message on refresh\n if (state) {\n window.history.replaceState({}, document.title);\n }\n }, [location.state]);\n\n // Determine error type from GraphQL error code (primary) or message (fallback).\n // The backend sends structured codes in extensions.code which are propagated\n // through useAuthOperations → authStore.errorCode.\n const checkErrorType = (\n err: string | null,\n code?: string | null,\n ):\n | \"DELETED\"\n | \"SUSPENDED\"\n | \"OAUTH_ONLY\"\n | \"INACTIVE\"\n | \"EMAIL_NOT_VERIFIED\"\n | null => {\n // Primary: match on GraphQL error code from extensions\n if (code) {\n switch (code) {\n case \"ACCOUNT_DELETED\":\n return \"DELETED\";\n case \"ACCOUNT_SUSPENDED\":\n return \"SUSPENDED\";\n case \"OAUTH_ONLY_ACCOUNT\":\n return \"OAUTH_ONLY\";\n case \"ACCOUNT_INACTIVE\":\n return \"INACTIVE\";\n case \"EMAIL_NOT_VERIFIED\":\n return \"EMAIL_NOT_VERIFIED\";\n default:\n break;\n }\n }\n\n // Fallback: match on error message for non-GraphQL errors or legacy responses\n if (!err) return null;\n const lowerErr = err.toLowerCase();\n\n if (\n lowerErr.includes(\"deleted\") ||\n lowerErr.includes(\"permanently deleted\")\n ) {\n return \"DELETED\";\n }\n if (lowerErr.includes(\"suspended\")) {\n return \"SUSPENDED\";\n }\n if (\n lowerErr.includes(\"oauth provider\") ||\n lowerErr.includes(\"oauth-only\") ||\n lowerErr.includes(\"oauth only\")\n ) {\n return \"OAUTH_ONLY\";\n }\n if (\n lowerErr.includes(\"inactive\") ||\n lowerErr.includes(\"deactivated\") ||\n lowerErr.includes(\"not active\")\n ) {\n return \"INACTIVE\";\n }\n if (\n lowerErr.includes(\"verify your email\") ||\n lowerErr.includes(\"email not verified\") ||\n lowerErr.includes(\"email_not_verified\")\n ) {\n return \"EMAIL_NOT_VERIFIED\";\n }\n return null;\n };\n\n // Pre-fill email from URL query parameter (e.g., after email verification)\n useEffect(() => {\n const emailFromUrl = searchParams.get(\"email\");\n if (emailFromUrl) {\n setFormData((prev) => ({ ...prev, email: emailFromUrl }));\n }\n\n // Check for expired session redirect\n const isExpired = searchParams.get(\"expired\");\n if (isExpired === \"true\") {\n setInfoMessage(\n tr(\n \"auth.login.sessionExpired\",\n \"Your session has expired. Please sign in again.\",\n ),\n );\n // Clear the query param to prevent showing on refresh\n const newUrl = window.location.pathname;\n window.history.replaceState({}, document.title, newUrl);\n }\n }, [searchParams, tr]);\n\n // Redirect to MFA verification when required\n useEffect(() => {\n if (mfaRequired) {\n navigate(\"/auth/mfa-verify\");\n }\n }, [mfaRequired, navigate]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsAccountInactive(false);\n setIsAccountDeleted(false);\n setIsAccountSuspended(false);\n setIsOAuthOnlyAccount(false);\n setIsEmailNotVerified(false);\n setIsDuplicateAccount(false);\n setResendSuccess(false);\n setValidationError(null);\n\n // Client-side validation\n const emailTrimmed = (formData.email ?? \"\").trim();\n if (!emailTrimmed) {\n setValidationError(\n tr(\n \"auth.login.enterEmailOrUsername\",\n \"Please enter your email or username\",\n ),\n );\n return;\n }\n\n // Basic email format validation (if it contains @)\n if (emailTrimmed.includes(\"@\")) {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(emailTrimmed)) {\n setValidationError(\n tr(\"auth.login.invalidEmail\", \"Please enter a valid email address\"),\n );\n return;\n }\n }\n\n if (!formData.password || formData.password.length < 1) {\n setValidationError(\n tr(\"auth.login.enterPassword\", \"Please enter your password\"),\n );\n return;\n }\n\n // In add-account mode, check if this email is already signed in\n if (isAddAccountMode) {\n const existingProfiles = getProfiles();\n const alreadySignedIn = existingProfiles.some(\n (p) => p.email.toLowerCase() === emailTrimmed.toLowerCase(),\n );\n if (alreadySignedIn) {\n setIsDuplicateAccount(true);\n setValidationError(null);\n return;\n }\n }\n setIsDuplicateAccount(false);\n\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"login\");\n\n const result = await signIn({\n email: emailTrimmed,\n password: formData.password,\n rememberMe: formData.rememberMe,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n });\n\n // If MFA is required, the useEffect above will handle redirect\n if (result.requiresMFA) {\n // Persist the post-login destination so MfaVerifyPage honors\n // `returnTo` and `defaultPostLoginPath` after the user completes\n // verification — otherwise the MFA path falls back to \"/\" and the\n // host-configured home (e.g. \"/vibecontrols\") is lost.\n const effectiveReturnTo = initialReturnToRef.current || returnTo;\n const postMfaPath = effectiveReturnTo\n ? cleanReturnToValue(effectiveReturnTo)\n : cleanReturnToValue(defaultPostLoginPath ?? \"/dashboard\");\n try {\n sessionStorage.setItem(MFA_REDIRECT, postMfaPath);\n } catch {\n // sessionStorage may be unavailable (private mode, quota) — the\n // MFA page still works, it just defaults to \"/\" on completion.\n }\n return;\n }\n\n // Successful login - complete auth flow (fetch workspaces + issue token)\n if (result.success) {\n try {\n const profileId = result.user?.id;\n if (profileId) {\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 =\n window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: profileId,\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 }\n } catch {\n // Best effort only. Login can continue without the fast-path cookie.\n }\n\n // Handle Remember Me: Save or clear email only (never store passwords)\n try {\n if (formData.rememberMe) {\n localStorage.setItem(\"remembered_email\", emailTrimmed);\n } else {\n localStorage.removeItem(\"remembered_email\");\n }\n } catch (error) {\n console.warn(\n \"[LoginPage] Failed to save/clear remembered email:\",\n error,\n );\n }\n\n setIsCompletingAuth(true);\n // In add-account mode, redirect back to where the user was\n const effectiveReturnTo = initialReturnToRef.current || returnTo;\n const postLoginPath = effectiveReturnTo\n ? cleanReturnToValue(effectiveReturnTo)\n : cleanReturnToValue(defaultPostLoginPath ?? \"/dashboard\");\n try {\n type AuthFlowResult = Awaited<ReturnType<typeof completeAuthFlow>>;\n // Pass the token directly to avoid race condition with React state updates\n const authFlowResult = await Promise.race<AuthFlowResult>([\n completeAuthFlow(result.token),\n new Promise<AuthFlowResult>((resolve) => {\n window.setTimeout(() => {\n resolve({\n success: false,\n error: \"Auth flow timed out after login\",\n });\n }, 5000);\n }),\n ]);\n\n if (authFlowResult.success) {\n // Sync workspace and organization to ActiveContext\n // This is critical for global search and other workspace-scoped features\n if (authFlowResult.workspace) {\n setWorkspace(authFlowResult.workspace.id);\n if (authFlowResult.workspace.organizationId) {\n setOrganization(authFlowResult.workspace.organizationId);\n }\n }\n\n // Full auth flow completed - redirect to dashboard or returnTo\n redirectAfterLogin(postLoginPath);\n } else if (authFlowResult.error?.includes(\"No workspaces found\")) {\n // No workspaces - redirect to onboarding/workspace creation\n navigate(\"/onboarding/create-workspace\");\n } else {\n // Partial success or error - still sync workspace if available and redirect\n if (authFlowResult.workspace) {\n setWorkspace(authFlowResult.workspace.id);\n if (authFlowResult.workspace.organizationId) {\n setOrganization(authFlowResult.workspace.organizationId);\n }\n }\n console.warn(\"Auth flow warning:\", authFlowResult.error);\n redirectAfterLogin(postLoginPath);\n }\n } catch (authFlowError) {\n // Auth flow failed but login succeeded - still redirect\n console.error(\"Auth flow error (login succeeded):\", authFlowError);\n redirectAfterLogin(postLoginPath);\n } finally {\n setIsCompletingAuth(false);\n }\n }\n } catch (err) {\n if (import.meta.env.DEV) {\n console.error(\"Sign in failed:\", err);\n }\n // Check error type using error code (from store) and message\n const errorMsg = err instanceof Error ? err.message : String(err);\n if (errorMsg.includes(\"reCAPTCHA\")) {\n setValidationError(errorMsg);\n }\n const storeErrorCode = useAuthStore.getState().errorCode;\n const errorType = checkErrorType(errorMsg, storeErrorCode);\n if (errorType === \"DELETED\") {\n setIsAccountDeleted(true);\n } else if (errorType === \"SUSPENDED\") {\n setIsAccountSuspended(true);\n } else if (errorType === \"OAUTH_ONLY\") {\n setIsOAuthOnlyAccount(true);\n } else if (errorType === \"INACTIVE\") {\n setIsAccountInactive(true);\n } else if (errorType === \"EMAIL_NOT_VERIFIED\") {\n setIsEmailNotVerified(true);\n }\n }\n };\n\n // Also check the error from useAuthOperations\n useEffect(() => {\n if (error) {\n const errorType = checkErrorType(error, errorCode);\n if (errorType === \"DELETED\") {\n setIsAccountDeleted(true);\n } else if (errorType === \"SUSPENDED\") {\n setIsAccountSuspended(true);\n } else if (errorType === \"OAUTH_ONLY\") {\n setIsOAuthOnlyAccount(true);\n } else if (errorType === \"INACTIVE\") {\n setIsAccountInactive(true);\n } else if (errorType === \"EMAIL_NOT_VERIFIED\") {\n setIsEmailNotVerified(true);\n }\n }\n }, [error, errorCode]);\n\n /**\n * Handle Google OAuth login\n */\n const handleGoogleLogin = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.googleSignInFailed\",\n \"Failed to initiate Google sign in\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth login\n */\n const handleMicrosoftLogin = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.microsoftSignInFailed\",\n \"Failed to initiate Microsoft sign in\",\n ),\n );\n }\n };\n\n /**\n * Handle resend verification email\n */\n const handleResendVerification = async () => {\n setIsResendingVerification(true);\n setResendSuccess(false);\n try {\n const emailTrimmed = (formData.email ?? \"\").trim();\n const result = await resendVerification(emailTrimmed);\n if (result.success) {\n setResendSuccess(true);\n } else {\n setValidationError(\n tr(\n \"auth.login.resendFailed\",\n \"Failed to resend verification email. Please try again.\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to resend verification email:\", err);\n setValidationError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.resendFailed\",\n \"Failed to resend verification email. Please try again.\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleLogin,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.login.googleNotConfigured\",\n \"Google sign in is not configured\",\n ),\n },\n // {\n // name: \"GitHub\",\n // icon: GitHubIcon,\n // onClick: () => {},\n // disabled: true,\n // tooltip: tr(\n // \"auth.login.githubNotConfigured\",\n // \"GitHub sign in is not configured\",\n // ),\n // },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftLogin,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.login.microsoftNotConfigured\",\n \"Microsoft sign in is not configured\",\n ),\n },\n ];\n\n if (isSsoRedirecting) {\n return <SSORedirectSpinner />;\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.login.welcomeBack\", \"Welcome back\")}\n subtitle={tr(\n \"auth.login.enterCredentials\",\n \"Enter your credentials to continue\",\n )}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.login.purpose\",\n \"Sign in to reach your workspaces, agents, and settings. Use your email and password, a social provider, or a passwordless magic link.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Info Message (e.g., from expired MFA redirect) */}\n {infoMessage && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-info-bg-subtle border-status-info-border\">\n <svg\n className=\"size-5 flex-shrink-0 mt-0.5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-sm text-status-info-text\">{infoMessage}</p>\n </div>\n )}\n\n {/* Validation Error */}\n {validationError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-warning-text\" />\n <p className=\"text-sm text-status-warning-text\">\n {validationError}\n </p>\n </div>\n )}\n\n {/* Duplicate Account Warning (Add Account mode) */}\n {isDuplicateAccount && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-warning-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.login.duplicateAccount\",\n \"This account is already signed in.\",\n )}\n </p>\n <p className=\"mt-1 text-sm text-status-warning-text/80\">\n {tr(\n \"auth.login.duplicateAccountHint\",\n \"Please use a different account, or go back and switch to this account from the profile menu.\",\n )}\n </p>\n {(initialReturnToRef.current || returnTo) && (\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n decodeURIComponent(\n initialReturnToRef.current || returnTo || \"/dashboard\",\n ),\n )\n }\n className=\"mt-3 inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\n \"auth.login.goBackSwitchAccount\",\n \"Go back and switch account\",\n )}\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Error Alert */}\n {error && !validationError && !isDuplicateAccount && (\n <div\n className={`flex items-start gap-3 p-4 rounded-xl border ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"bg-status-warning-bg-subtle border-status-warning-border\"\n : \"bg-status-error-bg-subtle border-status-error-border\"\n }`}\n >\n <AlertIcon\n className={`size-5 flex-shrink-0 mt-0.5 ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n />\n <div className=\"flex-1\">\n <p\n className={`text-sm ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n >\n {isAccountDeleted\n ? tr(\n \"auth.login.accountDeleted\",\n \"This account has been permanently deleted and cannot be recovered.\",\n )\n : isAccountSuspended\n ? tr(\n \"auth.login.accountSuspended\",\n \"Your account has been suspended. Please contact support for assistance.\",\n )\n : isAccountInactive\n ? tr(\n \"auth.login.accountDeactivated\",\n \"Your account has been deactivated.\",\n )\n : isOAuthOnlyAccount\n ? tr(\n \"auth.login.oauthOnly\",\n \"This account uses OAuth sign-in. Please use Google or Microsoft to sign in.\",\n )\n : isEmailNotVerified\n ? tr(\n \"auth.login.emailNotVerified\",\n \"Please verify your email before signing in. Check your inbox for the verification link.\",\n )\n : error}\n </p>\n {isAccountInactive && (\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"mt-2 text-sm font-semibold text-status-warning-text hover:text-status-warning-text underline underline-offset-2\"\n >\n {tr(\n \"auth.login.clickReactivate\",\n \"Click here to reactivate your account\",\n )}\n </button>\n )}\n {isEmailNotVerified && (\n <div className=\"mt-3\">\n {resendSuccess ? (\n <div className=\"flex items-center gap-2 text-sm text-status-success-text\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n {tr(\n \"auth.login.verificationSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={handleResendVerification}\n disabled={isResendingVerification}\n className=\"inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.login.sending\", \"Sending...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n {tr(\n \"auth.login.resendVerification\",\n \"Resend verification email\",\n )}\n </>\n )}\n </button>\n )}\n </div>\n )}\n {isOAuthOnlyAccount && (\n <div className=\"mt-3 flex flex-col gap-2\">\n {isProviderConfigured(\"google\") && (\n <button\n type=\"button\"\n onClick={handleGoogleLogin}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm\"\n >\n <GoogleIcon className=\"size-5\" />\n {tr(\"auth.login.signInWithGoogle\", \"Sign in with Google\")}\n </button>\n )}\n {isProviderConfigured(\"microsoft\") && (\n <button\n type=\"button\"\n onClick={handleMicrosoftLogin}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm\"\n >\n <MicrosoftIcon className=\"size-5\" />\n {tr(\n \"auth.login.signInWithMicrosoft\",\n \"Sign in with Microsoft\",\n )}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.login.emailLabel\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.login.emailLabel\", \"Email\")}\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\"auth.login.emailPlaceholder\", \"you@example.com\")}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.login.passwordLabel\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n autoComplete=\"current-password\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.login.passwordLabel\", \"Password\")}\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.login.passwordPlaceholder\",\n \"Enter your password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n </div>\n\n {/* Remember & Forgot */}\n <div className=\"flex items-center justify-between\">\n <label className=\"flex items-center gap-2 cursor-pointer group\">\n <div className=\"relative\">\n <input\n type=\"checkbox\"\n aria-label={tr(\"auth.login.rememberMe\", \"Remember me\")}\n checked={formData.rememberMe}\n onChange={(e) => {\n const isChecked = e.target.checked;\n setFormData({ ...formData, rememberMe: isChecked });\n\n // Clear saved email immediately when unchecked\n if (!isChecked) {\n try {\n localStorage.removeItem(\"remembered_email\");\n } catch (error) {\n console.warn(\n \"[LoginPage] Failed to clear remembered email:\",\n error,\n );\n }\n }\n }}\n className=\"sr-only peer\"\n />\n <div className=\"size-5 rounded-md border-2 border-[var(--color-border-default)] bg-[var(--color-bg-surface)] peer-checked:bg-status-info-bg-subtle0 peer-checked:border-action-primary-bg transition-all peer-focus:ring-2 peer-focus:ring-action-primary-bg/20\">\n <svg\n className=\"size-full text-white opacity-0 peer-checked:opacity-100 transition-opacity p-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n <span className=\"text-sm text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)] transition-colors\">\n {tr(\"auth.login.rememberMe\", \"Remember me\")}\n </span>\n </label>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.login.forgotPassword\", \"Forgot password?\")}\n </button>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading || isCompletingAuth}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading || isCompletingAuth ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {isCompletingAuth\n ? tr(\n \"auth.login.settingUpWorkspace\",\n \"Setting up workspace...\",\n )\n : tr(\"auth.login.signingIn\", \"Signing in...\")}\n </span>\n </>\n ) : (\n tr(\"auth.login.signIn\", \"Sign in\")\n )}\n </Button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.login.orContinueWith\", \"or continue with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n aria-label={provider.name}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Enterprise SSO Option — temporarily hidden */}\n {/* <div className=\"mt-6\">\n <button type=\"button\"\n onClick={() => navigate(\"/auth/sso/login\")}\n className=\"w-full flex items-center justify-center gap-3 px-4 py-3 rounded-xl border-2 border-dashed border-[var(--color-border-default)] hover:border-action-primary-bg bg-[var(--color-bg-surface)] hover:bg-status-info-bg-subtle transition-all duration-200 group\"\n >\n <svg\n className=\"size-5 text-[var(--color-text-tertiary)] group-hover:text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n <span className=\"text-sm font-medium text-[var(--color-text-secondary)] group-hover:text-text-link\">\n {tr(\"auth.login.enterpriseSso\", \"Sign in with Enterprise SSO\")}\n </span>\n </button>\n </div> */}\n\n {/* Magic Link Option */}\n <div className=\"mt-4 text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/magic-link\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.login.preferPasswordless\", \"Prefer passwordless login?\")}{\" \"}\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.login.signInMagicLink\", \"Sign in with Magic Link\")}\n </span>\n </button>\n </div>\n\n {/* Sign Up Link */}\n <p className=\"mt-6 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.login.dontHaveAccount\", \"Don't have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/register\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.login.signUp\", \"Sign up\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LoginPage.js","names":[],"sources":["../../../src/auth/pages/LoginPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useNavigate, useSearchParams, useLocation } from \"react-router-dom\";\nimport {\n useI18n,\n useActiveContext,\n} from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n getSSORedirectUrl,\n SSORedirectSpinner,\n} from \"@burdenoff/fe-libs/shared/components\";\nimport { resolveAuthBridgeConfig } from \"@burdenoff/fe-libs/shared/config\";\nimport { Button } from \"@burdenoff/fe-libs/button\";\nimport { PagePurpose } from \"@burdenoff/fe-libs/ui\";\n\nimport {\n getProfileTokens,\n getProfileUser,\n getProfiles,\n getProfileWorkspaceToken,\n resolveActiveProfileId,\n} from \"@burdenoff/fe-libs/shared/providers/shell/ProfileStorageManager\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useMyWorkspaces } from \"../hooks/useMyWorkspaces\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n SpinnerIcon,\n GoogleIcon,\n // GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { MFA_REDIRECT } from \"../constants/sessionKeys\";\nimport type { SignInInput } from \"../types\";\n\n/**\n * Detect whether the bundle is running inside a native shell (Capacitor on\n * iOS/Android, or Electron). Cross-origin SSO deposit/relay detours only make\n * sense in a multi-origin web context — in a native shell there is one product\n * origin, tokens already live in localStorage, and `window.location.assign` to\n * an external https URL escapes the embedded webview.\n *\n * Detection is runtime-only and avoids importing any platform-specific module\n * so this stays valid when microfe-auth is consumed by a pure web shell too.\n */\nfunction isNativeShell(): boolean {\n if (typeof window === \"undefined\") return false;\n const w = window as typeof window & {\n Capacitor?: { isNativePlatform?: () => boolean };\n };\n if (typeof w.Capacitor?.isNativePlatform === \"function\") {\n try {\n if (w.Capacitor.isNativePlatform()) return true;\n } catch {\n /* fall through */\n }\n }\n if (\n typeof navigator !== \"undefined\" &&\n /Electron/i.test(navigator.userAgent || \"\")\n ) {\n return true;\n }\n return false;\n}\n\nfunction toSameOriginPath(target: string): string {\n if (typeof window === \"undefined\") return target;\n try {\n const url = new URL(target, window.location.origin);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction hasValidAccessToken(token: string): boolean {\n try {\n const raw = token.split(\".\")[1].replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, \"=\");\n const payload = JSON.parse(atob(padded)) as { exp?: unknown };\n return typeof payload.exp === \"number\" && payload.exp > Date.now() / 1000;\n } catch {\n return false;\n }\n}\n\nfunction decodeSameOriginReturnTo(returnTo: string | null): string | null {\n if (!returnTo) return null;\n try {\n const decoded = decodeURIComponent(returnTo);\n const url = new URL(decoded, window.location.origin);\n if (url.origin !== window.location.origin) return null;\n return stripStaleSsoParams(url, { includeOrigin: false });\n } catch {\n return null;\n }\n}\n\nfunction stripStaleSsoParams(\n url: URL,\n options: { includeOrigin: boolean },\n): string {\n for (const param of [\"sso_failed\", \"sso-failed\", \"sso-attempted\"] as const) {\n url.searchParams.delete(param);\n }\n\n if (options.includeOrigin) {\n return url.toString();\n }\n\n const search = url.searchParams.toString();\n return `${url.pathname}${search ? `?${search}` : \"\"}${url.hash}`;\n}\n\nfunction cleanReturnToValue(returnTo: string): string {\n try {\n const decoded = decodeURIComponent(returnTo);\n const url = new URL(decoded, window.location.origin);\n // Preserve cross-origin absolute returnTo values (e.g., redirects back to\n // another product's app shell) — only strip stale SSO params.\n const isCrossOrigin = url.origin !== window.location.origin;\n return stripStaleSsoParams(url, { includeOrigin: isCrossOrigin });\n } catch {\n return returnTo;\n }\n}\n\n/**\n * Login page with professional design\n * Provides email/password login and social OAuth options\n */\nexport function LoginPage() {\n const navigate = useNavigate();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t],\n );\n const { signIn, isLoading, error, errorCode, resendVerification } =\n useAuthOperations();\n const { completeAuthFlow } = useMyWorkspaces();\n const { setWorkspace, setOrganization } = useActiveContext();\n const { defaultPostLoginPath } = useAuth();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const mfaRequired = useAuthStore((state) => state.mfaRequired);\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [isCompletingAuth, setIsCompletingAuth] = useState(false);\n const [isSsoRedirecting, setIsSsoRedirecting] = useState(false);\n\n /**\n * Build the URL that should receive the post-login redirect.\n *\n * For cross-eTLD+1 SSO on alpha/prod we detour through the central bridge\n * host (alphaapp/app.burdenoff.com) so the freshly-issued tokens land in\n * the bridge origin's first-party (unpartitioned) localStorage. Without\n * this step, a later direct visit to the bridge host has no local tokens\n * and no shared cookie (cross-eTLD+1 cookies don't propagate), so the user\n * sees the login page again.\n *\n * Skipped when:\n * - Already on the bridge host origin (would be a no-op self-redirect).\n * - The user's intended destination is itself on the bridge host\n * (likely an in-product navigation, no deposit needed).\n * - The freshly-stored profile snapshot can't be read (best-effort only —\n * login still succeeds via the direct redirect).\n */\n const buildPostLoginUrl = useCallback((intendedPath: string): string => {\n if (typeof window === \"undefined\") return intendedPath;\n // Native shells (Electron, Capacitor iOS/Android) run as a single origin —\n // skip the cross-eTLD+1 deposit detour, which would otherwise navigate to\n // an external https URL and escape the embedded webview.\n if (isNativeShell()) return intendedPath;\n try {\n const bridge = resolveAuthBridgeConfig();\n const bridgeUrl = new URL(bridge.url);\n const currentOrigin = window.location.origin;\n if (bridgeUrl.origin === currentOrigin) return intendedPath;\n\n // intendedPath can be a path or an absolute URL; resolve to absolute\n // against current origin so the bridge host knows where to send the\n // user back.\n const absoluteIntended = new URL(intendedPath, currentOrigin).href;\n\n const profileId = resolveActiveProfileId();\n if (!profileId) return intendedPath;\n const tokens = getProfileTokens(profileId);\n const user = getProfileUser(profileId);\n if (!tokens || !user) return intendedPath;\n const workspaceToken = getProfileWorkspaceToken(profileId);\n\n const payload = {\n profileId,\n tokens: JSON.stringify({ ...tokens, workspaceToken }),\n user: JSON.stringify(user),\n profiles: JSON.stringify(getProfiles()),\n };\n const encoded = btoa(JSON.stringify(payload));\n const deposit = new URL(\"/auth/sso-deposit\", bridgeUrl.origin);\n deposit.searchParams.set(\"returnTo\", absoluteIntended);\n return `${deposit.href}#sso=${encoded}`;\n } catch {\n return intendedPath;\n }\n }, []);\n\n const redirectAfterLogin = useCallback(\n (path: string) => {\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(\"bf-post-login-return-to\", path);\n } catch {\n // Ignore storage failures; redirect still proceeds.\n }\n // In native shells use SPA navigation so the embedded webview stays in\n // the app — no full reload, no chance of the OS opening the URL\n // externally. Tokens are already in this origin's localStorage.\n if (isNativeShell()) {\n navigate(toSameOriginPath(path));\n return;\n }\n window.location.assign(buildPostLoginUrl(path));\n return;\n }\n navigate(path);\n },\n [navigate, buildPostLoginUrl],\n );\n const [showPassword, setShowPassword] = useState(false);\n const [isAccountInactive, setIsAccountInactive] = useState(false);\n const [isAccountDeleted, setIsAccountDeleted] = useState(false);\n const [isAccountSuspended, setIsAccountSuspended] = useState(false);\n const [isOAuthOnlyAccount, setIsOAuthOnlyAccount] = useState(false);\n const [isEmailNotVerified, setIsEmailNotVerified] = useState(false);\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [infoMessage, setInfoMessage] = useState<string | null>(null);\n const [formData, setFormData] = useState<\n SignInInput & { rememberMe?: boolean }\n >({\n email: \"\",\n password: \"\",\n rememberMe: false,\n });\n const [validationError, setValidationError] = useState<string | null>(null);\n const [isDuplicateAccount, setIsDuplicateAccount] = useState(false);\n\n // Detect add-account mode and returnTo from URL params\n const isAddAccountMode = searchParams.get(\"mode\") === \"add-account\";\n const returnTo = searchParams.get(\"returnTo\");\n const initialReturnToRef = useRef<string | null>(returnTo);\n\n // Tell the user WHY they are back on the login page. `session-expired` is\n // set by the shell's forced logout (expired session whose refresh failed);\n // `logout` covers explicit sign-outs and the SSO-clear fallback (which can\n // rewrite a session-expired redirect's reason on the cross-product bounce).\n // Without this the auto-logout looked like a silent, unexplained kick-out.\n const reasonParam = searchParams.get(\"reason\");\n useEffect(() => {\n if (reasonParam === \"session-expired\") {\n setInfoMessage(\n tr(\n \"auth.login.sessionExpired\",\n \"Your session has expired. Please sign in again.\",\n ),\n );\n } else if (reasonParam === \"logout\") {\n setInfoMessage(tr(\"auth.login.signedOut\", \"You have been signed out.\"));\n }\n }, [reasonParam, tr]);\n\n useEffect(() => {\n if (!initialReturnToRef.current && returnTo) {\n initialReturnToRef.current = returnTo;\n }\n }, [returnTo]);\n\n useEffect(() => {\n if (isAddAccountMode || isCompletingAuth) return;\n\n const profileId = resolveActiveProfileId();\n if (profileId) {\n const tokens = getProfileTokens(profileId);\n const user = getProfileUser(profileId);\n if (\n tokens?.accessToken &&\n user &&\n hasValidAccessToken(tokens.accessToken)\n ) {\n useAuthStore.getState().loadFromProfile(profileId);\n redirectAfterLogin(decodeSameOriginReturnTo(returnTo) ?? \"/\");\n return;\n }\n }\n\n const ssoUrl = getSSORedirectUrl();\n if (ssoUrl) {\n setIsSsoRedirecting(true);\n window.location.replace(ssoUrl);\n }\n }, [isAddAccountMode, isCompletingAuth, redirectAfterLogin, returnTo]);\n\n // Load remembered email on mount and clean up any legacy plaintext password storage\n useEffect(() => {\n try {\n // Security cleanup: remove any previously stored plaintext passwords\n localStorage.removeItem(\"remembered_password\");\n\n const rememberedEmail = localStorage.getItem(\"remembered_email\");\n\n if (rememberedEmail) {\n setFormData((prev) => ({\n ...prev,\n email: rememberedEmail,\n rememberMe: true,\n }));\n }\n } catch (error) {\n console.warn(\"[LoginPage] Failed to load remembered email:\", error);\n }\n }, []);\n\n // Check for redirect message and email from location state (e.g., from expired MFA or SSO redirect)\n useEffect(() => {\n const state = location.state as { message?: string; email?: string } | null;\n if (state?.message) {\n setInfoMessage(state.message);\n }\n if (state?.email) {\n setFormData((prev) => ({ ...prev, email: state.email }));\n }\n // Clear state to prevent showing message on refresh\n if (state) {\n window.history.replaceState({}, document.title);\n }\n }, [location.state]);\n\n // Determine error type from GraphQL error code (primary) or message (fallback).\n // The backend sends structured codes in extensions.code which are propagated\n // through useAuthOperations → authStore.errorCode.\n const checkErrorType = (\n err: string | null,\n code?: string | null,\n ):\n | \"DELETED\"\n | \"SUSPENDED\"\n | \"OAUTH_ONLY\"\n | \"INACTIVE\"\n | \"EMAIL_NOT_VERIFIED\"\n | null => {\n // Primary: match on GraphQL error code from extensions\n if (code) {\n switch (code) {\n case \"ACCOUNT_DELETED\":\n return \"DELETED\";\n case \"ACCOUNT_SUSPENDED\":\n return \"SUSPENDED\";\n case \"OAUTH_ONLY_ACCOUNT\":\n return \"OAUTH_ONLY\";\n case \"ACCOUNT_INACTIVE\":\n return \"INACTIVE\";\n case \"EMAIL_NOT_VERIFIED\":\n return \"EMAIL_NOT_VERIFIED\";\n default:\n break;\n }\n }\n\n // Fallback: match on error message for non-GraphQL errors or legacy responses\n if (!err) return null;\n const lowerErr = err.toLowerCase();\n\n if (\n lowerErr.includes(\"deleted\") ||\n lowerErr.includes(\"permanently deleted\")\n ) {\n return \"DELETED\";\n }\n if (lowerErr.includes(\"suspended\")) {\n return \"SUSPENDED\";\n }\n if (\n lowerErr.includes(\"oauth provider\") ||\n lowerErr.includes(\"oauth-only\") ||\n lowerErr.includes(\"oauth only\")\n ) {\n return \"OAUTH_ONLY\";\n }\n if (\n lowerErr.includes(\"inactive\") ||\n lowerErr.includes(\"deactivated\") ||\n lowerErr.includes(\"not active\")\n ) {\n return \"INACTIVE\";\n }\n if (\n lowerErr.includes(\"verify your email\") ||\n lowerErr.includes(\"email not verified\") ||\n lowerErr.includes(\"email_not_verified\")\n ) {\n return \"EMAIL_NOT_VERIFIED\";\n }\n return null;\n };\n\n // Pre-fill email from URL query parameter (e.g., after email verification)\n useEffect(() => {\n const emailFromUrl = searchParams.get(\"email\");\n if (emailFromUrl) {\n setFormData((prev) => ({ ...prev, email: emailFromUrl }));\n }\n\n // Check for expired session redirect\n const isExpired = searchParams.get(\"expired\");\n if (isExpired === \"true\") {\n setInfoMessage(\n tr(\n \"auth.login.sessionExpired\",\n \"Your session has expired. Please sign in again.\",\n ),\n );\n // Clear the query param to prevent showing on refresh\n const newUrl = window.location.pathname;\n window.history.replaceState({}, document.title, newUrl);\n }\n }, [searchParams, tr]);\n\n // Redirect to MFA verification when required\n useEffect(() => {\n if (mfaRequired) {\n navigate(\"/auth/mfa-verify\");\n }\n }, [mfaRequired, navigate]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setIsAccountInactive(false);\n setIsAccountDeleted(false);\n setIsAccountSuspended(false);\n setIsOAuthOnlyAccount(false);\n setIsEmailNotVerified(false);\n setIsDuplicateAccount(false);\n setResendSuccess(false);\n setValidationError(null);\n\n // Client-side validation\n const emailTrimmed = (formData.email ?? \"\").trim();\n if (!emailTrimmed) {\n setValidationError(\n tr(\n \"auth.login.enterEmailOrUsername\",\n \"Please enter your email or username\",\n ),\n );\n return;\n }\n\n // Basic email format validation (if it contains @)\n if (emailTrimmed.includes(\"@\")) {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(emailTrimmed)) {\n setValidationError(\n tr(\"auth.login.invalidEmail\", \"Please enter a valid email address\"),\n );\n return;\n }\n }\n\n if (!formData.password || formData.password.length < 1) {\n setValidationError(\n tr(\"auth.login.enterPassword\", \"Please enter your password\"),\n );\n return;\n }\n\n // In add-account mode, check if this email is already signed in\n if (isAddAccountMode) {\n const existingProfiles = getProfiles();\n const alreadySignedIn = existingProfiles.some(\n (p) => p.email.toLowerCase() === emailTrimmed.toLowerCase(),\n );\n if (alreadySignedIn) {\n setIsDuplicateAccount(true);\n setValidationError(null);\n return;\n }\n }\n setIsDuplicateAccount(false);\n\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"login\");\n\n const result = await signIn({\n email: emailTrimmed,\n password: formData.password,\n rememberMe: formData.rememberMe,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n });\n\n // If MFA is required, the useEffect above will handle redirect\n if (result.requiresMFA) {\n // Persist the post-login destination so MfaVerifyPage honors\n // `returnTo` and `defaultPostLoginPath` after the user completes\n // verification — otherwise the MFA path falls back to \"/\" and the\n // host-configured home (e.g. \"/vibecontrols\") is lost.\n const effectiveReturnTo = initialReturnToRef.current || returnTo;\n const postMfaPath = effectiveReturnTo\n ? cleanReturnToValue(effectiveReturnTo)\n : cleanReturnToValue(defaultPostLoginPath ?? \"/dashboard\");\n try {\n sessionStorage.setItem(MFA_REDIRECT, postMfaPath);\n } catch {\n // sessionStorage may be unavailable (private mode, quota) — the\n // MFA page still works, it just defaults to \"/\" on completion.\n }\n return;\n }\n\n // Successful login - complete auth flow (fetch workspaces + issue token)\n if (result.success) {\n try {\n const profileId = result.user?.id;\n if (profileId) {\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 =\n window.location.protocol === \"https:\" ? \"; secure\" : \"\";\n const ssoValue = JSON.stringify({\n id: profileId,\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 }\n } catch {\n // Best effort only. Login can continue without the fast-path cookie.\n }\n\n // Handle Remember Me: Save or clear email only (never store passwords)\n try {\n if (formData.rememberMe) {\n localStorage.setItem(\"remembered_email\", emailTrimmed);\n } else {\n localStorage.removeItem(\"remembered_email\");\n }\n } catch (error) {\n console.warn(\n \"[LoginPage] Failed to save/clear remembered email:\",\n error,\n );\n }\n\n setIsCompletingAuth(true);\n // In add-account mode, redirect back to where the user was\n const effectiveReturnTo = initialReturnToRef.current || returnTo;\n const postLoginPath = effectiveReturnTo\n ? cleanReturnToValue(effectiveReturnTo)\n : cleanReturnToValue(defaultPostLoginPath ?? \"/dashboard\");\n try {\n type AuthFlowResult = Awaited<ReturnType<typeof completeAuthFlow>>;\n // Pass the token directly to avoid race condition with React state updates\n const authFlowResult = await Promise.race<AuthFlowResult>([\n completeAuthFlow(result.token),\n new Promise<AuthFlowResult>((resolve) => {\n window.setTimeout(() => {\n resolve({\n success: false,\n error: \"Auth flow timed out after login\",\n });\n }, 5000);\n }),\n ]);\n\n if (authFlowResult.success) {\n // Sync workspace and organization to ActiveContext\n // This is critical for global search and other workspace-scoped features\n if (authFlowResult.workspace) {\n setWorkspace(authFlowResult.workspace.id);\n if (authFlowResult.workspace.organizationId) {\n setOrganization(authFlowResult.workspace.organizationId);\n }\n }\n\n // Full auth flow completed - redirect to dashboard or returnTo\n redirectAfterLogin(postLoginPath);\n } else if (authFlowResult.error?.includes(\"No workspaces found\")) {\n // No workspaces - redirect to onboarding/workspace creation\n navigate(\"/onboarding/create-workspace\");\n } else {\n // Partial success or error - still sync workspace if available and redirect\n if (authFlowResult.workspace) {\n setWorkspace(authFlowResult.workspace.id);\n if (authFlowResult.workspace.organizationId) {\n setOrganization(authFlowResult.workspace.organizationId);\n }\n }\n console.warn(\"Auth flow warning:\", authFlowResult.error);\n redirectAfterLogin(postLoginPath);\n }\n } catch (authFlowError) {\n // Auth flow failed but login succeeded - still redirect\n console.error(\"Auth flow error (login succeeded):\", authFlowError);\n redirectAfterLogin(postLoginPath);\n } finally {\n setIsCompletingAuth(false);\n }\n }\n } catch (err) {\n if (import.meta.env.DEV) {\n console.error(\"Sign in failed:\", err);\n }\n // Check error type using error code (from store) and message\n const errorMsg = err instanceof Error ? err.message : String(err);\n if (errorMsg.includes(\"reCAPTCHA\")) {\n setValidationError(errorMsg);\n }\n const storeErrorCode = useAuthStore.getState().errorCode;\n const errorType = checkErrorType(errorMsg, storeErrorCode);\n if (errorType === \"DELETED\") {\n setIsAccountDeleted(true);\n } else if (errorType === \"SUSPENDED\") {\n setIsAccountSuspended(true);\n } else if (errorType === \"OAUTH_ONLY\") {\n setIsOAuthOnlyAccount(true);\n } else if (errorType === \"INACTIVE\") {\n setIsAccountInactive(true);\n } else if (errorType === \"EMAIL_NOT_VERIFIED\") {\n setIsEmailNotVerified(true);\n }\n }\n };\n\n // Also check the error from useAuthOperations\n useEffect(() => {\n if (error) {\n const errorType = checkErrorType(error, errorCode);\n if (errorType === \"DELETED\") {\n setIsAccountDeleted(true);\n } else if (errorType === \"SUSPENDED\") {\n setIsAccountSuspended(true);\n } else if (errorType === \"OAUTH_ONLY\") {\n setIsOAuthOnlyAccount(true);\n } else if (errorType === \"INACTIVE\") {\n setIsAccountInactive(true);\n } else if (errorType === \"EMAIL_NOT_VERIFIED\") {\n setIsEmailNotVerified(true);\n }\n }\n }, [error, errorCode]);\n\n /**\n * Handle Google OAuth login\n */\n const handleGoogleLogin = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.googleSignInFailed\",\n \"Failed to initiate Google sign in\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth login\n */\n const handleMicrosoftLogin = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.microsoftSignInFailed\",\n \"Failed to initiate Microsoft sign in\",\n ),\n );\n }\n };\n\n /**\n * Handle resend verification email\n */\n const handleResendVerification = async () => {\n setIsResendingVerification(true);\n setResendSuccess(false);\n try {\n const emailTrimmed = (formData.email ?? \"\").trim();\n const result = await resendVerification(emailTrimmed);\n if (result.success) {\n setResendSuccess(true);\n } else {\n setValidationError(\n tr(\n \"auth.login.resendFailed\",\n \"Failed to resend verification email. Please try again.\",\n ),\n );\n }\n } catch (err) {\n console.error(\"Failed to resend verification email:\", err);\n setValidationError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.login.resendFailed\",\n \"Failed to resend verification email. Please try again.\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleLogin,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.login.googleNotConfigured\",\n \"Google sign in is not configured\",\n ),\n },\n // {\n // name: \"GitHub\",\n // icon: GitHubIcon,\n // onClick: () => {},\n // disabled: true,\n // tooltip: tr(\n // \"auth.login.githubNotConfigured\",\n // \"GitHub sign in is not configured\",\n // ),\n // },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftLogin,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.login.microsoftNotConfigured\",\n \"Microsoft sign in is not configured\",\n ),\n },\n ];\n\n if (isSsoRedirecting) {\n return <SSORedirectSpinner />;\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.login.welcomeBack\", \"Welcome back\")}\n subtitle={tr(\n \"auth.login.enterCredentials\",\n \"Enter your credentials to continue\",\n )}\n >\n <PagePurpose className=\"mb-5\">\n {tr(\n \"auth.login.purpose\",\n \"Sign in to reach your workspaces, agents, and settings. Use your email and password, a social provider, or a passwordless magic link.\",\n )}\n </PagePurpose>\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Info Message (e.g., from expired MFA redirect) */}\n {infoMessage && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-info-bg-subtle border-status-info-border\">\n <svg\n className=\"size-5 flex-shrink-0 mt-0.5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-sm text-status-info-text\">{infoMessage}</p>\n </div>\n )}\n\n {/* Validation Error */}\n {validationError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-warning-text\" />\n <p className=\"text-sm text-status-warning-text\">\n {validationError}\n </p>\n </div>\n )}\n\n {/* Duplicate Account Warning (Add Account mode) */}\n {isDuplicateAccount && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-warning-bg-subtle border-status-warning-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-warning-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.login.duplicateAccount\",\n \"This account is already signed in.\",\n )}\n </p>\n <p className=\"mt-1 text-sm text-status-warning-text/80\">\n {tr(\n \"auth.login.duplicateAccountHint\",\n \"Please use a different account, or go back and switch to this account from the profile menu.\",\n )}\n </p>\n {(initialReturnToRef.current || returnTo) && (\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n decodeURIComponent(\n initialReturnToRef.current || returnTo || \"/dashboard\",\n ),\n )\n }\n className=\"mt-3 inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"\n />\n </svg>\n {tr(\n \"auth.login.goBackSwitchAccount\",\n \"Go back and switch account\",\n )}\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Error Alert */}\n {error && !validationError && !isDuplicateAccount && (\n <div\n className={`flex items-start gap-3 p-4 rounded-xl border ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"bg-status-warning-bg-subtle border-status-warning-border\"\n : \"bg-status-error-bg-subtle border-status-error-border\"\n }`}\n >\n <AlertIcon\n className={`size-5 flex-shrink-0 mt-0.5 ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n />\n <div className=\"flex-1\">\n <p\n className={`text-sm ${\n isAccountInactive || isOAuthOnlyAccount || isEmailNotVerified\n ? \"text-status-warning-text\"\n : \"text-status-error-text\"\n }`}\n >\n {isAccountDeleted\n ? tr(\n \"auth.login.accountDeleted\",\n \"This account has been permanently deleted and cannot be recovered.\",\n )\n : isAccountSuspended\n ? tr(\n \"auth.login.accountSuspended\",\n \"Your account has been suspended. Please contact support for assistance.\",\n )\n : isAccountInactive\n ? tr(\n \"auth.login.accountDeactivated\",\n \"Your account has been deactivated.\",\n )\n : isOAuthOnlyAccount\n ? tr(\n \"auth.login.oauthOnly\",\n \"This account uses OAuth sign-in. Please use Google or Microsoft to sign in.\",\n )\n : isEmailNotVerified\n ? tr(\n \"auth.login.emailNotVerified\",\n \"Please verify your email before signing in. Check your inbox for the verification link.\",\n )\n : error}\n </p>\n {isAccountInactive && (\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"mt-2 text-sm font-semibold text-status-warning-text hover:text-status-warning-text underline underline-offset-2\"\n >\n {tr(\n \"auth.login.clickReactivate\",\n \"Click here to reactivate your account\",\n )}\n </button>\n )}\n {isEmailNotVerified && (\n <div className=\"mt-3\">\n {resendSuccess ? (\n <div className=\"flex items-center gap-2 text-sm text-status-success-text\">\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n {tr(\n \"auth.login.verificationSent\",\n \"Verification email sent! Please check your inbox.\",\n )}\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={handleResendVerification}\n disabled={isResendingVerification}\n className=\"inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-status-warning-border bg-bg-surface text-status-warning-text text-sm font-medium hover:bg-status-warning-bg-subtle hover:border-status-warning-border transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" />\n {tr(\"auth.login.sending\", \"Sending...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n {tr(\n \"auth.login.resendVerification\",\n \"Resend verification email\",\n )}\n </>\n )}\n </button>\n )}\n </div>\n )}\n {isOAuthOnlyAccount && (\n <div className=\"mt-3 flex flex-col gap-2\">\n {isProviderConfigured(\"google\") && (\n <button\n type=\"button\"\n onClick={handleGoogleLogin}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm\"\n >\n <GoogleIcon className=\"size-5\" />\n {tr(\"auth.login.signInWithGoogle\", \"Sign in with Google\")}\n </button>\n )}\n {isProviderConfigured(\"microsoft\") && (\n <button\n type=\"button\"\n onClick={handleMicrosoftLogin}\n className=\"inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl border border-border-default bg-bg-surface text-text-primary text-sm font-medium hover:bg-bg-canvas hover:border-border-default transition-all shadow-sm\"\n >\n <MicrosoftIcon className=\"size-5\" />\n {tr(\n \"auth.login.signInWithMicrosoft\",\n \"Sign in with Microsoft\",\n )}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.login.emailLabel\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.login.emailLabel\", \"Email\")}\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\"auth.login.emailPlaceholder\", \"you@example.com\")}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.login.passwordLabel\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n autoComplete=\"current-password\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.login.passwordLabel\", \"Password\")}\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.login.passwordPlaceholder\",\n \"Enter your password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n </div>\n\n {/* Remember & Forgot */}\n <div className=\"flex items-center justify-between\">\n <label className=\"flex items-center gap-2 cursor-pointer group\">\n <div className=\"relative\">\n <input\n type=\"checkbox\"\n aria-label={tr(\"auth.login.rememberMe\", \"Remember me\")}\n checked={formData.rememberMe}\n onChange={(e) => {\n const isChecked = e.target.checked;\n setFormData({ ...formData, rememberMe: isChecked });\n\n // Clear saved email immediately when unchecked\n if (!isChecked) {\n try {\n localStorage.removeItem(\"remembered_email\");\n } catch (error) {\n console.warn(\n \"[LoginPage] Failed to clear remembered email:\",\n error,\n );\n }\n }\n }}\n className=\"sr-only peer\"\n />\n <div className=\"size-5 rounded-md border-2 border-[var(--color-border-default)] bg-[var(--color-bg-surface)] peer-checked:bg-status-info-bg-subtle0 peer-checked:border-action-primary-bg transition-all peer-focus:ring-2 peer-focus:ring-action-primary-bg/20\">\n <svg\n className=\"size-full text-white opacity-0 peer-checked:opacity-100 transition-opacity p-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={3}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n <span className=\"text-sm text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)] transition-colors\">\n {tr(\"auth.login.rememberMe\", \"Remember me\")}\n </span>\n </label>\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/reset-password\")}\n className=\"text-sm font-medium text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.login.forgotPassword\", \"Forgot password?\")}\n </button>\n </div>\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n disabled={isLoading || isCompletingAuth}\n variant=\"default\"\n size=\"lg\"\n className=\"w-full font-semibold\"\n >\n {isLoading || isCompletingAuth ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {isCompletingAuth\n ? tr(\n \"auth.login.settingUpWorkspace\",\n \"Setting up workspace...\",\n )\n : tr(\"auth.login.signingIn\", \"Signing in...\")}\n </span>\n </>\n ) : (\n tr(\"auth.login.signIn\", \"Sign in\")\n )}\n </Button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.login.orContinueWith\", \"or continue with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n aria-label={provider.name}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Enterprise SSO Option — temporarily hidden */}\n {/* <div className=\"mt-6\">\n <button type=\"button\"\n onClick={() => navigate(\"/auth/sso/login\")}\n className=\"w-full flex items-center justify-center gap-3 px-4 py-3 rounded-xl border-2 border-dashed border-[var(--color-border-default)] hover:border-action-primary-bg bg-[var(--color-bg-surface)] hover:bg-status-info-bg-subtle transition-all duration-200 group\"\n >\n <svg\n className=\"size-5 text-[var(--color-text-tertiary)] group-hover:text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n <span className=\"text-sm font-medium text-[var(--color-text-secondary)] group-hover:text-text-link\">\n {tr(\"auth.login.enterpriseSso\", \"Sign in with Enterprise SSO\")}\n </span>\n </button>\n </div> */}\n\n {/* Magic Link Option */}\n <div className=\"mt-4 text-center\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/magic-link\")}\n className=\"text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.login.preferPasswordless\", \"Prefer passwordless login?\")}{\" \"}\n <span className=\"font-semibold text-text-link hover:text-text-linkHover\">\n {tr(\"auth.login.signInMagicLink\", \"Sign in with Magic Link\")}\n </span>\n </button>\n </div>\n\n {/* Sign Up Link */}\n <p className=\"mt-6 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.login.dontHaveAccount\", \"Don't have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/register\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.login.signUp\", \"Sign up\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|