@reblu/site-next 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.js ADDED
@@ -0,0 +1,1053 @@
1
+ "use client";
2
+
3
+ // src/auth/login-form.tsx
4
+ import * as React from "react";
5
+ import { Button, Input, cn } from "@reblu/site-ui";
6
+ import { Label } from "@reblu/site-ui/label";
7
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@reblu/site-ui/tabs";
8
+
9
+ // src/auth/safe-redirect.ts
10
+ function isSafeRedirectUrl(url) {
11
+ if (!url) return false;
12
+ const clean = url.replace(/[\t\n\r]/g, "");
13
+ if (!clean) return false;
14
+ if (/^[/\\]{2}/.test(clean)) return false;
15
+ const scheme = clean.split(":")[0].toLowerCase();
16
+ return ["http", "https"].includes(scheme) || clean.startsWith("/");
17
+ }
18
+
19
+ // src/auth/login-form.tsx
20
+ import { jsx, jsxs } from "react/jsx-runtime";
21
+ var DEFAULT_COPY = {
22
+ magicLinkTabLabel: "Enlace m\xE1gico",
23
+ passwordTabLabel: "Contrase\xF1a",
24
+ emailLabel: "Correo electr\xF3nico",
25
+ emailPlaceholder: "tu@email.com",
26
+ passwordLabel: "Contrase\xF1a",
27
+ passwordPlaceholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
28
+ passwordSubmitCta: "Iniciar sesi\xF3n",
29
+ forgotPasswordCta: "\xBFOlvidaste tu contrase\xF1a?",
30
+ magicLinkSubmitCta: "Enviar enlace de acceso",
31
+ magicLinkSuccessTitle: "\xA1Enlace enviado!",
32
+ magicLinkSuccessMessage: "Revisa tu correo para acceder.",
33
+ mfaTitle: "Verificaci\xF3n en dos pasos",
34
+ mfaMessage: "Introduce el c\xF3digo de 6 d\xEDgitos de tu aplicaci\xF3n autenticadora.",
35
+ mfaCodeLabel: "C\xF3digo de verificaci\xF3n",
36
+ mfaCodePlaceholder: "123456",
37
+ mfaSubmitCta: "Verificar c\xF3digo",
38
+ invalidEmailError: "Introduce un correo electr\xF3nico v\xE1lido.",
39
+ networkError: "No se pudo enviar el enlace. Int\xE9ntalo de nuevo.",
40
+ invalidCredentialsError: "Email o contrase\xF1a incorrectos",
41
+ mfaInvalidCodeError: "C\xF3digo incorrecto. Por favor, int\xE9ntalo de nuevo."
42
+ };
43
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
44
+ function isValidEmail(value) {
45
+ return EMAIL_REGEX.test(value);
46
+ }
47
+ function LoginForm(props) {
48
+ const {
49
+ passwordLoginUrl,
50
+ mfaVerifyUrl,
51
+ magicLinkRequestUrl,
52
+ resetPasswordUrl,
53
+ copy,
54
+ className,
55
+ defaultMethod = "password",
56
+ onPasswordSubmitted,
57
+ onMagicLinkSubmitted,
58
+ onMfaSubmitted
59
+ } = props;
60
+ const c = { ...DEFAULT_COPY, ...copy };
61
+ const [method, setMethod] = React.useState(defaultMethod);
62
+ const [state, setState] = React.useState("idle");
63
+ const [error, setError] = React.useState(null);
64
+ const [email, setEmail] = React.useState("");
65
+ const [password, setPassword] = React.useState("");
66
+ const [mfa, setMfa] = React.useState(null);
67
+ const [mfaCode, setMfaCode] = React.useState("");
68
+ const resetTransientState = React.useCallback(() => {
69
+ setState("idle");
70
+ setError(null);
71
+ setMfa(null);
72
+ setMfaCode("");
73
+ }, []);
74
+ const handleTabChange = (next) => {
75
+ if (next !== "magic-link" && next !== "password") return;
76
+ setMethod(next);
77
+ resetTransientState();
78
+ };
79
+ const handleMagicLinkSubmit = async (e) => {
80
+ e.preventDefault();
81
+ setError(null);
82
+ const trimmed = email.trim().toLowerCase();
83
+ if (!isValidEmail(trimmed)) {
84
+ setError(c.invalidEmailError);
85
+ setState("error");
86
+ return;
87
+ }
88
+ setState("submitting");
89
+ try {
90
+ const res = await fetch(magicLinkRequestUrl, {
91
+ method: "POST",
92
+ credentials: "same-origin",
93
+ headers: { "Content-Type": "application/json" },
94
+ body: JSON.stringify({ email: trimmed })
95
+ });
96
+ if (res.ok) {
97
+ setState("magic-link-success");
98
+ onMagicLinkSubmitted?.(trimmed);
99
+ return;
100
+ }
101
+ setState("error");
102
+ setError(c.networkError);
103
+ } catch {
104
+ setState("error");
105
+ setError(c.networkError);
106
+ }
107
+ };
108
+ const handlePasswordSubmit = async (e) => {
109
+ e.preventDefault();
110
+ setError(null);
111
+ const trimmed = email.trim().toLowerCase();
112
+ if (!isValidEmail(trimmed)) {
113
+ setError(c.invalidEmailError);
114
+ setState("error");
115
+ return;
116
+ }
117
+ if (!password) {
118
+ setError(c.invalidCredentialsError);
119
+ setState("error");
120
+ return;
121
+ }
122
+ setState("submitting");
123
+ try {
124
+ const res = await fetch(passwordLoginUrl, {
125
+ method: "POST",
126
+ credentials: "same-origin",
127
+ headers: { "Content-Type": "application/json" },
128
+ body: JSON.stringify({ email: trimmed, password })
129
+ });
130
+ const json = await res.json().catch(() => ({}));
131
+ if (!res.ok || !json.data) {
132
+ setState("error");
133
+ setError(json.error ?? c.invalidCredentialsError);
134
+ return;
135
+ }
136
+ onPasswordSubmitted?.(trimmed);
137
+ if (json.data.mfaRequired) {
138
+ setMfa({
139
+ factorId: json.data.factorId ?? "",
140
+ accessToken: json.data.accessToken ?? "",
141
+ refreshToken: json.data.refreshToken ?? ""
142
+ });
143
+ setState("mfa-required");
144
+ return;
145
+ }
146
+ const exchangeUrl = json.data.sessionExchangeUrl;
147
+ if (!exchangeUrl || !isSafeRedirectUrl(exchangeUrl)) {
148
+ setState("error");
149
+ setError(c.networkError);
150
+ return;
151
+ }
152
+ window.location.assign(exchangeUrl);
153
+ } catch {
154
+ setState("error");
155
+ setError(c.networkError);
156
+ }
157
+ };
158
+ const handleMfaSubmit = async (e) => {
159
+ e.preventDefault();
160
+ if (!mfa) return;
161
+ setError(null);
162
+ setState("submitting");
163
+ try {
164
+ const res = await fetch(mfaVerifyUrl, {
165
+ method: "POST",
166
+ credentials: "same-origin",
167
+ headers: { "Content-Type": "application/json" },
168
+ body: JSON.stringify({
169
+ factorId: mfa.factorId,
170
+ code: mfaCode,
171
+ accessToken: mfa.accessToken,
172
+ refreshToken: mfa.refreshToken
173
+ })
174
+ });
175
+ const json = await res.json().catch(() => ({}));
176
+ if (!res.ok || !json.data?.sessionExchangeUrl || !isSafeRedirectUrl(json.data.sessionExchangeUrl)) {
177
+ setState("mfa-required");
178
+ setError(json.error ?? c.mfaInvalidCodeError);
179
+ setMfaCode("");
180
+ return;
181
+ }
182
+ onMfaSubmitted?.();
183
+ window.location.assign(json.data.sessionExchangeUrl);
184
+ } catch {
185
+ setState("mfa-required");
186
+ setError(c.networkError);
187
+ }
188
+ };
189
+ if (state === "mfa-required" || mfa && state === "submitting") {
190
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
191
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
192
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: c.mfaTitle }),
193
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: c.mfaMessage })
194
+ ] }),
195
+ /* @__PURE__ */ jsxs("form", { onSubmit: (e) => void handleMfaSubmit(e), className: "space-y-4", noValidate: true, children: [
196
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
197
+ /* @__PURE__ */ jsx(Label, { htmlFor: "login-mfa-code", children: c.mfaCodeLabel }),
198
+ /* @__PURE__ */ jsx(
199
+ Input,
200
+ {
201
+ id: "login-mfa-code",
202
+ type: "text",
203
+ inputMode: "numeric",
204
+ pattern: "\\d{6}",
205
+ maxLength: 6,
206
+ autoComplete: "one-time-code",
207
+ placeholder: c.mfaCodePlaceholder,
208
+ value: mfaCode,
209
+ onChange: (ev) => setMfaCode(ev.target.value.replace(/\D/g, "").slice(0, 6)),
210
+ disabled: state === "submitting",
211
+ "aria-describedby": error ? "login-mfa-error" : void 0,
212
+ autoFocus: true
213
+ }
214
+ ),
215
+ error && /* @__PURE__ */ jsx("p", { id: "login-mfa-error", role: "alert", className: "text-destructive text-sm", children: error })
216
+ ] }),
217
+ /* @__PURE__ */ jsx(
218
+ Button,
219
+ {
220
+ type: "submit",
221
+ className: "w-full",
222
+ disabled: state === "submitting" || mfaCode.length !== 6,
223
+ children: c.mfaSubmitCta
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsx(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ onClick: resetTransientState,
231
+ className: "text-muted-foreground hover:text-foreground w-full text-sm transition-colors",
232
+ disabled: state === "submitting",
233
+ children: "\u2190 Volver"
234
+ }
235
+ )
236
+ ] })
237
+ ] });
238
+ }
239
+ if (state === "magic-link-success") {
240
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 text-center", className), children: [
241
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: c.magicLinkSuccessTitle }),
242
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: c.magicLinkSuccessMessage })
243
+ ] });
244
+ }
245
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-6", className), children: /* @__PURE__ */ jsxs(Tabs, { value: method, onValueChange: handleTabChange, children: [
246
+ /* @__PURE__ */ jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [
247
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "password", children: c.passwordTabLabel }),
248
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "magic-link", children: c.magicLinkTabLabel })
249
+ ] }),
250
+ /* @__PURE__ */ jsx(TabsContent, { value: "password", className: "pt-4", children: /* @__PURE__ */ jsxs("form", { onSubmit: (e) => void handlePasswordSubmit(e), className: "space-y-4", noValidate: true, children: [
251
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
252
+ /* @__PURE__ */ jsx(Label, { htmlFor: "login-email-pwd", children: c.emailLabel }),
253
+ /* @__PURE__ */ jsx(
254
+ Input,
255
+ {
256
+ id: "login-email-pwd",
257
+ type: "email",
258
+ autoComplete: "email",
259
+ placeholder: c.emailPlaceholder,
260
+ value: email,
261
+ onChange: (ev) => setEmail(ev.target.value),
262
+ disabled: state === "submitting"
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
267
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
268
+ /* @__PURE__ */ jsx(Label, { htmlFor: "login-password", children: c.passwordLabel }),
269
+ /* @__PURE__ */ jsx(
270
+ "a",
271
+ {
272
+ href: resetPasswordUrl,
273
+ className: "text-muted-foreground hover:text-foreground text-xs transition-colors",
274
+ children: c.forgotPasswordCta
275
+ }
276
+ )
277
+ ] }),
278
+ /* @__PURE__ */ jsx(
279
+ Input,
280
+ {
281
+ id: "login-password",
282
+ type: "password",
283
+ autoComplete: "current-password",
284
+ placeholder: c.passwordPlaceholder,
285
+ value: password,
286
+ onChange: (ev) => setPassword(ev.target.value),
287
+ disabled: state === "submitting",
288
+ "aria-describedby": error ? "login-pwd-error" : void 0
289
+ }
290
+ ),
291
+ error && /* @__PURE__ */ jsx("p", { id: "login-pwd-error", role: "alert", className: "text-destructive text-sm", children: error })
292
+ ] }),
293
+ /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: state === "submitting", children: c.passwordSubmitCta })
294
+ ] }) }),
295
+ /* @__PURE__ */ jsx(TabsContent, { value: "magic-link", className: "pt-4", children: /* @__PURE__ */ jsxs("form", { onSubmit: (e) => void handleMagicLinkSubmit(e), className: "space-y-4", noValidate: true, children: [
296
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
297
+ /* @__PURE__ */ jsx(Label, { htmlFor: "login-email-magic", children: c.emailLabel }),
298
+ /* @__PURE__ */ jsx(
299
+ Input,
300
+ {
301
+ id: "login-email-magic",
302
+ type: "email",
303
+ autoComplete: "email",
304
+ placeholder: c.emailPlaceholder,
305
+ value: email,
306
+ onChange: (ev) => setEmail(ev.target.value),
307
+ disabled: state === "submitting",
308
+ "aria-describedby": error ? "login-magic-error" : void 0
309
+ }
310
+ ),
311
+ error && /* @__PURE__ */ jsx("p", { id: "login-magic-error", role: "alert", className: "text-destructive text-sm", children: error })
312
+ ] }),
313
+ /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: state === "submitting", children: c.magicLinkSubmitCta })
314
+ ] }) })
315
+ ] }) });
316
+ }
317
+
318
+ // src/auth/auth-callback.tsx
319
+ import * as React2 from "react";
320
+ import { Button as Button2, cn as cn2 } from "@reblu/site-ui";
321
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
322
+ var DEFAULT_COPY2 = {
323
+ loadingTitle: "Autenticando...",
324
+ loadingMessage: "Estamos verificando tu identidad.",
325
+ successMessage: "Iniciando sesi\xF3n...",
326
+ expiredTitle: "Enlace caducado",
327
+ expiredMessage: "El enlace ha expirado o ya fue utilizado. Solicita uno nuevo.",
328
+ expiredCta: "Volver al login",
329
+ networkErrorTitle: "Error de conexi\xF3n",
330
+ networkErrorMessage: "No pudimos completar el inicio de sesi\xF3n. Int\xE9ntalo de nuevo.",
331
+ networkErrorRetryCta: "Reintentar",
332
+ networkErrorBackCta: "Volver al login",
333
+ malformedTitle: "Enlace inv\xE1lido",
334
+ malformedMessage: "Enlace inv\xE1lido. Solicita uno nuevo.",
335
+ malformedCta: "Volver al login"
336
+ };
337
+ function parseHash(hash) {
338
+ const raw = hash.startsWith("#") ? hash.slice(1) : hash;
339
+ if (!raw) return null;
340
+ const params = new URLSearchParams(raw);
341
+ const access_token = params.get("access_token") ?? "";
342
+ const refresh_token = params.get("refresh_token") ?? "";
343
+ const type = params.get("type") ?? "";
344
+ if (!access_token || !refresh_token || type !== "magiclink") return null;
345
+ return { access_token, refresh_token };
346
+ }
347
+ function parseHashError(hash) {
348
+ const raw = hash.startsWith("#") ? hash.slice(1) : hash;
349
+ if (!raw) return null;
350
+ const params = new URLSearchParams(raw);
351
+ const error = params.get("error");
352
+ const errorCode = params.get("error_code");
353
+ if (!error && !errorCode) return null;
354
+ return errorCode === "otp_expired" || error === "access_denied" ? "expired" : "malformed";
355
+ }
356
+ function AuthCallback({
357
+ exchangeUrl,
358
+ loginUrl,
359
+ copy,
360
+ onSuccess,
361
+ onError,
362
+ className
363
+ }) {
364
+ const [state, setState] = React2.useState("mounting");
365
+ const [tokens, setTokens] = React2.useState(null);
366
+ const retryRef = React2.useRef(null);
367
+ const linkRef = React2.useRef(null);
368
+ const c = { ...DEFAULT_COPY2, ...copy };
369
+ const exchange = React2.useCallback(
370
+ async (t) => {
371
+ setState("exchanging");
372
+ try {
373
+ const res = await fetch(exchangeUrl, {
374
+ method: "POST",
375
+ credentials: "same-origin",
376
+ headers: { "Content-Type": "application/json" },
377
+ body: JSON.stringify(t)
378
+ });
379
+ if (res.ok) {
380
+ const raw = await res.json();
381
+ const redirectTo = raw !== null && typeof raw === "object" && "redirectTo" in raw && typeof raw.redirectTo === "string" ? raw.redirectTo : null;
382
+ if (!redirectTo) {
383
+ setState("network");
384
+ onError?.("network", void 0);
385
+ return;
386
+ }
387
+ if (!isSafeRedirectUrl(redirectTo)) {
388
+ setState("network");
389
+ onError?.("network", void 0);
390
+ return;
391
+ }
392
+ history.replaceState({}, "", window.location.pathname);
393
+ window.location.replace(redirectTo);
394
+ setState("success");
395
+ onSuccess?.(redirectTo);
396
+ return;
397
+ }
398
+ if (res.status === 401) {
399
+ setState("expired");
400
+ onError?.("expired");
401
+ return;
402
+ }
403
+ setState("network");
404
+ onError?.("network", void 0);
405
+ } catch (err) {
406
+ setState("network");
407
+ onError?.("network", err instanceof Error ? err : void 0);
408
+ }
409
+ },
410
+ [exchangeUrl, onError, onSuccess]
411
+ );
412
+ React2.useEffect(() => {
413
+ setState("parsing");
414
+ const hashError = parseHashError(window.location.hash);
415
+ if (hashError) {
416
+ setState(hashError);
417
+ onError?.(hashError);
418
+ return;
419
+ }
420
+ const parsed = parseHash(window.location.hash);
421
+ if (!parsed) {
422
+ setState("malformed");
423
+ onError?.("malformed");
424
+ return;
425
+ }
426
+ setTokens(parsed);
427
+ void exchange(parsed);
428
+ }, []);
429
+ React2.useEffect(() => {
430
+ if (state === "network") retryRef.current?.focus();
431
+ if (state === "expired" || state === "malformed") linkRef.current?.focus();
432
+ }, [state]);
433
+ const isLoading = state === "mounting" || state === "parsing" || state === "exchanging";
434
+ return /* @__PURE__ */ jsx2("main", { className: cn2("flex min-h-screen items-center justify-center", className), children: /* @__PURE__ */ jsxs2("div", { "aria-live": "polite", className: "w-full max-w-md space-y-4 p-6 text-center", children: [
435
+ isLoading && /* @__PURE__ */ jsxs2("section", { children: [
436
+ /* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold", children: c.loadingTitle }),
437
+ /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground mt-2", children: c.loadingMessage })
438
+ ] }),
439
+ state === "success" && /* @__PURE__ */ jsx2("section", { children: /* @__PURE__ */ jsx2("p", { children: c.successMessage }) }),
440
+ state === "expired" && /* @__PURE__ */ jsxs2("section", { className: "space-y-4", children: [
441
+ /* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold", children: c.expiredTitle }),
442
+ /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground", children: c.expiredMessage }),
443
+ /* @__PURE__ */ jsx2(Button2, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx2("a", { href: loginUrl, ref: linkRef, children: c.expiredCta }) })
444
+ ] }),
445
+ state === "network" && /* @__PURE__ */ jsxs2("section", { className: "space-y-4", children: [
446
+ /* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold", children: c.networkErrorTitle }),
447
+ /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground", children: c.networkErrorMessage }),
448
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
449
+ /* @__PURE__ */ jsx2(Button2, { type: "button", ref: retryRef, onClick: () => tokens && void exchange(tokens), children: c.networkErrorRetryCta }),
450
+ /* @__PURE__ */ jsx2(Button2, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx2("a", { href: loginUrl, children: c.networkErrorBackCta }) })
451
+ ] })
452
+ ] }),
453
+ state === "malformed" && /* @__PURE__ */ jsxs2("section", { className: "space-y-4", children: [
454
+ /* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold", children: c.malformedTitle }),
455
+ /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground", children: c.malformedMessage }),
456
+ /* @__PURE__ */ jsx2(Button2, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx2("a", { href: loginUrl, ref: linkRef, children: c.malformedCta }) })
457
+ ] })
458
+ ] }) });
459
+ }
460
+
461
+ // src/auth/login-errors.ts
462
+ var DEFAULT_COPY3 = {
463
+ expiredMessage: "El enlace ha expirado o ya fue utilizado.",
464
+ missingTokenMessage: "El enlace no es v\xE1lido. Solicita uno nuevo.",
465
+ genericMessage: "No se pudo completar el inicio de sesi\xF3n. Intenta de nuevo."
466
+ };
467
+ function mapLoginError(error, copy) {
468
+ if (!error) return null;
469
+ const normalized = error.trim().toLowerCase();
470
+ if (normalized === "") return null;
471
+ const c = { ...DEFAULT_COPY3, ...copy };
472
+ if (normalized.includes("email link is invalid or has expired") || normalized.includes("token has expired or is invalid")) {
473
+ return { message: c.expiredMessage, showRequestLink: true };
474
+ }
475
+ if (normalized.includes("missing_token")) {
476
+ return { message: c.missingTokenMessage, showRequestLink: true };
477
+ }
478
+ return { message: c.genericMessage, showRequestLink: true };
479
+ }
480
+
481
+ // src/auth/auth-logo.tsx
482
+ import * as React3 from "react";
483
+ import { useTheme } from "next-themes";
484
+ import { cn as cn3 } from "@reblu/site-ui";
485
+ import { jsx as jsx3 } from "react/jsx-runtime";
486
+ var DEFAULT_COPY4 = {
487
+ fallbackLabel: "Inicio de sesi\xF3n"
488
+ };
489
+ var VARIANT_CLASS = {
490
+ expanded: "h-8 w-auto",
491
+ compact: "h-8 w-8"
492
+ };
493
+ var VARIANT_INTRINSIC_SIZE = {
494
+ expanded: { width: 160, height: 32 },
495
+ compact: { width: 32, height: 32 }
496
+ };
497
+ function AuthLogo(props) {
498
+ const {
499
+ logoLightExpandedUrl,
500
+ logoDarkExpandedUrl,
501
+ logoLightSmallUrl,
502
+ logoDarkSmallUrl,
503
+ siteName,
504
+ variant = "expanded",
505
+ className,
506
+ copy,
507
+ hideTextFallback = false
508
+ } = props;
509
+ const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY4.fallbackLabel;
510
+ const { resolvedTheme } = useTheme();
511
+ const isDark = resolvedTheme === "dark";
512
+ const lightUrl = variant === "compact" ? logoLightSmallUrl : logoLightExpandedUrl;
513
+ const darkUrl = variant === "compact" ? logoDarkSmallUrl : logoDarkExpandedUrl;
514
+ const logoUrl = isDark ? darkUrl : lightUrl;
515
+ const [imgError, setImgError] = React3.useState(false);
516
+ React3.useEffect(() => {
517
+ setImgError(false);
518
+ }, [logoUrl]);
519
+ const fallbackText = siteName || fallbackLabel;
520
+ const altText = copy?.altText !== void 0 ? copy.altText : siteName || fallbackLabel;
521
+ if (logoUrl && !imgError) {
522
+ const { width, height } = VARIANT_INTRINSIC_SIZE[variant];
523
+ return /* @__PURE__ */ jsx3(
524
+ "img",
525
+ {
526
+ src: logoUrl,
527
+ alt: altText,
528
+ width,
529
+ height,
530
+ className: cn3(VARIANT_CLASS[variant], className),
531
+ onError: () => setImgError(true)
532
+ }
533
+ );
534
+ }
535
+ if (hideTextFallback) return null;
536
+ return /* @__PURE__ */ jsx3("span", { className: cn3("text-lg font-semibold text-foreground", className), children: fallbackText });
537
+ }
538
+
539
+ // src/auth/reset-password-request.tsx
540
+ import * as React4 from "react";
541
+ import { Button as Button3, Input as Input2, cn as cn4 } from "@reblu/site-ui";
542
+ import { Label as Label2 } from "@reblu/site-ui/label";
543
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
544
+ var DEFAULT_COPY5 = {
545
+ emailLabel: "Correo electr\xF3nico",
546
+ emailPlaceholder: "tu@email.com",
547
+ submitCta: "Enviar enlace de recuperaci\xF3n",
548
+ submittingCta: "Enviando...",
549
+ successTitle: "Revisa tu email",
550
+ successMessage: "Si el email existe en nuestra plataforma, recibir\xE1s un enlace para restablecer tu contrase\xF1a en los pr\xF3ximos minutos.",
551
+ backToLoginCta: "\u2190 Volver al inicio de sesi\xF3n",
552
+ invalidEmailError: "Introduce un correo electr\xF3nico v\xE1lido.",
553
+ networkError: "Error de conexi\xF3n. Por favor, int\xE9ntalo de nuevo."
554
+ };
555
+ var EMAIL_REGEX2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
556
+ function ResetPasswordRequest(props) {
557
+ const { requestUrl, loginUrl, copy, className, onSubmitted } = props;
558
+ const c = { ...DEFAULT_COPY5, ...copy };
559
+ const [email, setEmail] = React4.useState("");
560
+ const [loading, setLoading] = React4.useState(false);
561
+ const [submitted, setSubmitted] = React4.useState(false);
562
+ const [error, setError] = React4.useState(null);
563
+ const handleSubmit = async (e) => {
564
+ e.preventDefault();
565
+ setError(null);
566
+ const trimmed = email.trim().toLowerCase();
567
+ if (!EMAIL_REGEX2.test(trimmed)) {
568
+ setError(c.invalidEmailError);
569
+ return;
570
+ }
571
+ setLoading(true);
572
+ let succeeded = false;
573
+ try {
574
+ const res = await fetch(requestUrl, {
575
+ method: "POST",
576
+ credentials: "same-origin",
577
+ headers: { "Content-Type": "application/json" },
578
+ body: JSON.stringify({ email: trimmed })
579
+ });
580
+ if (!res.ok) {
581
+ const json = await res.json().catch(() => ({}));
582
+ setError(json.error ?? c.networkError);
583
+ setLoading(false);
584
+ return;
585
+ }
586
+ succeeded = true;
587
+ setSubmitted(true);
588
+ setLoading(false);
589
+ } catch {
590
+ setError(c.networkError);
591
+ setLoading(false);
592
+ return;
593
+ }
594
+ if (succeeded) {
595
+ try {
596
+ onSubmitted?.(trimmed);
597
+ } catch (err) {
598
+ console.error("[ResetPasswordRequest] onSubmitted callback threw:", err);
599
+ }
600
+ }
601
+ };
602
+ if (submitted) {
603
+ return /* @__PURE__ */ jsxs3("div", { className: cn4("space-y-4 text-center", className), children: [
604
+ /* @__PURE__ */ jsx4("h2", { className: "text-lg font-semibold", children: c.successTitle }),
605
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground text-sm", children: c.successMessage }),
606
+ /* @__PURE__ */ jsx4("a", { href: loginUrl, className: "text-primary inline-block text-sm hover:underline", children: c.backToLoginCta })
607
+ ] });
608
+ }
609
+ return /* @__PURE__ */ jsxs3(
610
+ "form",
611
+ {
612
+ onSubmit: (e) => void handleSubmit(e),
613
+ className: cn4("space-y-5", className),
614
+ noValidate: true,
615
+ children: [
616
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-1.5", children: [
617
+ /* @__PURE__ */ jsx4(Label2, { htmlFor: "reset-request-email", children: c.emailLabel }),
618
+ /* @__PURE__ */ jsx4(
619
+ Input2,
620
+ {
621
+ id: "reset-request-email",
622
+ type: "email",
623
+ autoComplete: "email",
624
+ placeholder: c.emailPlaceholder,
625
+ value: email,
626
+ onChange: (e) => setEmail(e.target.value),
627
+ disabled: loading,
628
+ "aria-describedby": error ? "reset-request-error" : void 0,
629
+ autoFocus: true
630
+ }
631
+ ),
632
+ error && /* @__PURE__ */ jsx4("p", { id: "reset-request-error", role: "alert", className: "text-destructive text-sm", children: error })
633
+ ] }),
634
+ /* @__PURE__ */ jsx4(Button3, { type: "submit", className: "w-full", disabled: loading, children: loading ? c.submittingCta : c.submitCta }),
635
+ /* @__PURE__ */ jsx4("div", { className: "text-center", children: /* @__PURE__ */ jsx4(
636
+ "a",
637
+ {
638
+ href: loginUrl,
639
+ className: "text-muted-foreground hover:text-foreground text-sm transition-colors",
640
+ children: c.backToLoginCta
641
+ }
642
+ ) })
643
+ ]
644
+ }
645
+ );
646
+ }
647
+
648
+ // src/auth/reset-password-confirm.tsx
649
+ import * as React5 from "react";
650
+ import { useRouter, useSearchParams } from "next/navigation";
651
+ import { Button as Button4, Input as Input3, cn as cn5 } from "@reblu/site-ui";
652
+ import { Label as Label3 } from "@reblu/site-ui/label";
653
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
654
+ function withQueryParam(url, key, value) {
655
+ const separator = url.includes("?") ? "&" : "?";
656
+ return `${url}${separator}${key}=${encodeURIComponent(value)}`;
657
+ }
658
+ var DEFAULT_COPY6 = {
659
+ passwordLabel: "Nueva contrase\xF1a",
660
+ passwordPlaceholder: "M\xEDnimo 8 caracteres",
661
+ confirmPasswordLabel: "Confirmar contrase\xF1a",
662
+ confirmPasswordPlaceholder: "Repite la contrase\xF1a",
663
+ mfaCodeLabel: "C\xF3digo de verificaci\xF3n (2FA)",
664
+ mfaCodePlaceholder: "6 d\xEDgitos",
665
+ mfaHelpMessage: "Tu cuenta tiene verificaci\xF3n en dos pasos. Introduce el c\xF3digo de tu app de autenticaci\xF3n para completar el cambio.",
666
+ submitCta: "Establecer nueva contrase\xF1a",
667
+ submittingCta: "Actualizando contrase\xF1a...",
668
+ passwordTooShortError: "La contrase\xF1a debe tener al menos 8 caracteres",
669
+ passwordMismatchError: "Las contrase\xF1as no coinciden",
670
+ mfaInvalidCodeError: "Introduce el c\xF3digo de 6 d\xEDgitos de tu app de autenticaci\xF3n",
671
+ genericError: "Error al restablecer la contrase\xF1a",
672
+ missingTokensTitle: "Enlace inv\xE1lido",
673
+ missingTokensMessage: "El enlace ha expirado o no es v\xE1lido. Solicita uno nuevo.",
674
+ requestNewLinkCta: "Solicitar nuevo enlace"
675
+ };
676
+ function ResetPasswordConfirm(props) {
677
+ const { confirmUrl, loginUrl, resetRequestUrl, copy, className, onSuccess } = props;
678
+ const c = { ...DEFAULT_COPY6, ...copy };
679
+ const router = useRouter();
680
+ const searchParams = useSearchParams();
681
+ const accessToken = searchParams.get("access_token");
682
+ const refreshToken = searchParams.get("refresh_token");
683
+ const hasTokens = Boolean(accessToken && refreshToken);
684
+ const [password, setPassword] = React5.useState("");
685
+ const [confirmPassword, setConfirmPassword] = React5.useState("");
686
+ const [loading, setLoading] = React5.useState(false);
687
+ const [error, setError] = React5.useState(null);
688
+ const [mfaRequired, setMfaRequired] = React5.useState(false);
689
+ const [mfaCode, setMfaCode] = React5.useState("");
690
+ const handleSubmit = async (e) => {
691
+ e.preventDefault();
692
+ setError(null);
693
+ if (!hasTokens) return;
694
+ if (password.length < 8) {
695
+ setError(c.passwordTooShortError);
696
+ return;
697
+ }
698
+ if (password !== confirmPassword) {
699
+ setError(c.passwordMismatchError);
700
+ return;
701
+ }
702
+ if (mfaRequired && !/^\d{6}$/.test(mfaCode)) {
703
+ setError(c.mfaInvalidCodeError);
704
+ return;
705
+ }
706
+ setLoading(true);
707
+ let succeeded = false;
708
+ try {
709
+ const res = await fetch(confirmUrl, {
710
+ method: "POST",
711
+ credentials: "same-origin",
712
+ headers: { "Content-Type": "application/json" },
713
+ body: JSON.stringify({
714
+ access_token: accessToken,
715
+ refresh_token: refreshToken,
716
+ new_password: password,
717
+ ...mfaRequired ? { mfa_code: mfaCode } : {}
718
+ })
719
+ });
720
+ const json = await res.json().catch(() => ({}));
721
+ if (!res.ok) {
722
+ setError(json.error ?? c.genericError);
723
+ setLoading(false);
724
+ return;
725
+ }
726
+ if (json.data?.mfaRequired) {
727
+ setMfaRequired(true);
728
+ setLoading(false);
729
+ return;
730
+ }
731
+ succeeded = true;
732
+ setLoading(false);
733
+ } catch {
734
+ setError(c.genericError);
735
+ setLoading(false);
736
+ return;
737
+ }
738
+ if (succeeded) {
739
+ router.replace(withQueryParam(loginUrl, "reset", "success"));
740
+ try {
741
+ onSuccess?.();
742
+ } catch (err) {
743
+ console.error("[ResetPasswordConfirm] onSuccess callback threw:", err);
744
+ }
745
+ }
746
+ };
747
+ if (!hasTokens) {
748
+ return /* @__PURE__ */ jsxs4("div", { className: cn5("space-y-4 text-center", className), children: [
749
+ /* @__PURE__ */ jsx5("h2", { className: "text-lg font-semibold", children: c.missingTokensTitle }),
750
+ /* @__PURE__ */ jsx5("p", { className: "text-muted-foreground text-sm", children: c.missingTokensMessage }),
751
+ /* @__PURE__ */ jsx5("a", { href: resetRequestUrl, className: "text-primary inline-block text-sm hover:underline", children: c.requestNewLinkCta })
752
+ ] });
753
+ }
754
+ return /* @__PURE__ */ jsxs4(
755
+ "form",
756
+ {
757
+ onSubmit: (e) => void handleSubmit(e),
758
+ className: cn5("space-y-5", className),
759
+ noValidate: true,
760
+ children: [
761
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
762
+ /* @__PURE__ */ jsx5(Label3, { htmlFor: "reset-confirm-password", children: c.passwordLabel }),
763
+ /* @__PURE__ */ jsx5(
764
+ Input3,
765
+ {
766
+ id: "reset-confirm-password",
767
+ type: "password",
768
+ autoComplete: "new-password",
769
+ placeholder: c.passwordPlaceholder,
770
+ value: password,
771
+ onChange: (e) => setPassword(e.target.value),
772
+ disabled: loading,
773
+ minLength: 8,
774
+ "aria-describedby": error ? "reset-confirm-error" : void 0,
775
+ autoFocus: true
776
+ }
777
+ )
778
+ ] }),
779
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
780
+ /* @__PURE__ */ jsx5(Label3, { htmlFor: "reset-confirm-password-2", children: c.confirmPasswordLabel }),
781
+ /* @__PURE__ */ jsx5(
782
+ Input3,
783
+ {
784
+ id: "reset-confirm-password-2",
785
+ type: "password",
786
+ autoComplete: "new-password",
787
+ placeholder: c.confirmPasswordPlaceholder,
788
+ value: confirmPassword,
789
+ onChange: (e) => setConfirmPassword(e.target.value),
790
+ disabled: loading,
791
+ minLength: 8,
792
+ "aria-describedby": error ? "reset-confirm-error" : void 0
793
+ }
794
+ )
795
+ ] }),
796
+ mfaRequired && /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
797
+ /* @__PURE__ */ jsx5(Label3, { htmlFor: "reset-confirm-mfa", children: c.mfaCodeLabel }),
798
+ /* @__PURE__ */ jsx5(
799
+ Input3,
800
+ {
801
+ id: "reset-confirm-mfa",
802
+ type: "text",
803
+ inputMode: "numeric",
804
+ autoComplete: "one-time-code",
805
+ placeholder: c.mfaCodePlaceholder,
806
+ value: mfaCode,
807
+ onChange: (e) => setMfaCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
808
+ disabled: loading,
809
+ maxLength: 6,
810
+ "aria-describedby": error ? "reset-confirm-error" : void 0,
811
+ autoFocus: true
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx5("p", { className: "text-muted-foreground text-xs", children: c.mfaHelpMessage })
815
+ ] }),
816
+ error && /* @__PURE__ */ jsx5("p", { id: "reset-confirm-error", role: "alert", className: "text-destructive text-sm", children: error }),
817
+ /* @__PURE__ */ jsx5(
818
+ Button4,
819
+ {
820
+ type: "submit",
821
+ className: "w-full",
822
+ disabled: loading || !password || !confirmPassword || mfaRequired && mfaCode.length !== 6,
823
+ children: loading ? c.submittingCta : c.submitCta
824
+ }
825
+ )
826
+ ]
827
+ }
828
+ );
829
+ }
830
+
831
+ // src/auth/set-password.tsx
832
+ import * as React6 from "react";
833
+ import { useSearchParams as useSearchParams2 } from "next/navigation";
834
+ import { Button as Button5, Input as Input4, cn as cn6 } from "@reblu/site-ui";
835
+ import { Label as Label4 } from "@reblu/site-ui/label";
836
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
837
+ var DEFAULT_COPY7 = {
838
+ passwordLabel: "Contrase\xF1a",
839
+ passwordPlaceholder: "M\xEDnimo 8 caracteres",
840
+ confirmPasswordLabel: "Confirmar contrase\xF1a",
841
+ confirmPasswordPlaceholder: "Repite la contrase\xF1a",
842
+ submitCta: "Activar cuenta",
843
+ submittingCta: "Activando cuenta...",
844
+ passwordTooShortError: "La contrase\xF1a debe tener al menos 8 caracteres",
845
+ passwordMismatchError: "Las contrase\xF1as no coinciden",
846
+ genericError: "Error al establecer la contrase\xF1a",
847
+ missingTokenTitle: "Enlace inv\xE1lido",
848
+ missingTokenMessage: "El enlace de invitaci\xF3n ha expirado o no es v\xE1lido.",
849
+ backToLoginCta: "\u2190 Volver al inicio de sesi\xF3n"
850
+ };
851
+ function SetPassword(props) {
852
+ const { submitUrl, loginUrl, copy, className, onSuccess } = props;
853
+ const c = { ...DEFAULT_COPY7, ...copy };
854
+ const searchParams = useSearchParams2();
855
+ const sessionExchangeToken = searchParams.get("session_exchange_token");
856
+ const [password, setPassword] = React6.useState("");
857
+ const [confirmPassword, setConfirmPassword] = React6.useState("");
858
+ const [loading, setLoading] = React6.useState(false);
859
+ const [error, setError] = React6.useState(null);
860
+ const handleSubmit = async (e) => {
861
+ e.preventDefault();
862
+ setError(null);
863
+ if (!sessionExchangeToken) return;
864
+ if (password.length < 8) {
865
+ setError(c.passwordTooShortError);
866
+ return;
867
+ }
868
+ if (password !== confirmPassword) {
869
+ setError(c.passwordMismatchError);
870
+ return;
871
+ }
872
+ setLoading(true);
873
+ let sessionExchangeUrl = null;
874
+ try {
875
+ const res = await fetch(submitUrl, {
876
+ method: "POST",
877
+ credentials: "same-origin",
878
+ headers: { "Content-Type": "application/json" },
879
+ body: JSON.stringify({
880
+ session_exchange_token: sessionExchangeToken,
881
+ new_password: password
882
+ })
883
+ });
884
+ const json = await res.json().catch(() => ({}));
885
+ if (!res.ok || !json.data?.sessionExchangeUrl) {
886
+ setError(json.error ?? c.genericError);
887
+ setLoading(false);
888
+ return;
889
+ }
890
+ if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {
891
+ setError(c.genericError);
892
+ setLoading(false);
893
+ return;
894
+ }
895
+ sessionExchangeUrl = json.data.sessionExchangeUrl;
896
+ } catch {
897
+ setError(c.genericError);
898
+ setLoading(false);
899
+ return;
900
+ }
901
+ window.location.assign(sessionExchangeUrl);
902
+ try {
903
+ onSuccess?.(sessionExchangeUrl);
904
+ } catch (err) {
905
+ console.error("[SetPassword] onSuccess callback threw:", err);
906
+ }
907
+ };
908
+ if (!sessionExchangeToken) {
909
+ return /* @__PURE__ */ jsxs5("div", { className: cn6("space-y-4 text-center", className), children: [
910
+ /* @__PURE__ */ jsx6("h2", { className: "text-lg font-semibold", children: c.missingTokenTitle }),
911
+ /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground text-sm", children: c.missingTokenMessage }),
912
+ /* @__PURE__ */ jsx6("a", { href: loginUrl, className: "text-primary inline-block text-sm hover:underline", children: c.backToLoginCta })
913
+ ] });
914
+ }
915
+ return /* @__PURE__ */ jsxs5(
916
+ "form",
917
+ {
918
+ onSubmit: (e) => void handleSubmit(e),
919
+ className: cn6("space-y-5", className),
920
+ noValidate: true,
921
+ children: [
922
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-1.5", children: [
923
+ /* @__PURE__ */ jsx6(Label4, { htmlFor: "set-password-password", children: c.passwordLabel }),
924
+ /* @__PURE__ */ jsx6(
925
+ Input4,
926
+ {
927
+ id: "set-password-password",
928
+ type: "password",
929
+ autoComplete: "new-password",
930
+ placeholder: c.passwordPlaceholder,
931
+ value: password,
932
+ onChange: (e) => setPassword(e.target.value),
933
+ disabled: loading,
934
+ minLength: 8,
935
+ "aria-describedby": error ? "set-password-error" : void 0,
936
+ autoFocus: true
937
+ }
938
+ )
939
+ ] }),
940
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-1.5", children: [
941
+ /* @__PURE__ */ jsx6(Label4, { htmlFor: "set-password-confirm", children: c.confirmPasswordLabel }),
942
+ /* @__PURE__ */ jsx6(
943
+ Input4,
944
+ {
945
+ id: "set-password-confirm",
946
+ type: "password",
947
+ autoComplete: "new-password",
948
+ placeholder: c.confirmPasswordPlaceholder,
949
+ value: confirmPassword,
950
+ onChange: (e) => setConfirmPassword(e.target.value),
951
+ disabled: loading,
952
+ minLength: 8,
953
+ "aria-describedby": error ? "set-password-error" : void 0
954
+ }
955
+ )
956
+ ] }),
957
+ error && /* @__PURE__ */ jsx6("p", { id: "set-password-error", role: "alert", className: "text-destructive text-sm", children: error }),
958
+ /* @__PURE__ */ jsx6(Button5, { type: "submit", className: "w-full", disabled: loading || !password || !confirmPassword, children: loading ? c.submittingCta : c.submitCta })
959
+ ]
960
+ }
961
+ );
962
+ }
963
+
964
+ // src/auth/auth-confirm.tsx
965
+ import * as React7 from "react";
966
+ import { useSearchParams as useSearchParams3 } from "next/navigation";
967
+ import { cn as cn7 } from "@reblu/site-ui";
968
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
969
+ var DEFAULT_COPY8 = {
970
+ loadingMessage: "Confirmando tu email...",
971
+ successTitle: "Email confirmado",
972
+ successMessage: "Tu direcci\xF3n de email ha sido verificada correctamente.",
973
+ successCta: "Iniciar sesi\xF3n",
974
+ errorTitle: "Error al confirmar",
975
+ errorMessage: "El enlace de confirmaci\xF3n no es v\xE1lido o ha expirado.",
976
+ errorCta: "Volver al inicio de sesi\xF3n"
977
+ };
978
+ function AuthConfirm(props) {
979
+ const { confirmUrl, loginUrl, copy, className } = props;
980
+ const c = { ...DEFAULT_COPY8, ...copy };
981
+ const searchParams = useSearchParams3();
982
+ const success = searchParams.get("success");
983
+ const tokenHash = searchParams.get("token_hash");
984
+ const type = searchParams.get("type");
985
+ const [status, setStatus] = React7.useState(success === "true" ? "success" : "loading");
986
+ const [errorMessage, setErrorMessage] = React7.useState(null);
987
+ React7.useEffect(() => {
988
+ if (success === "true") {
989
+ setStatus("success");
990
+ return;
991
+ }
992
+ if (tokenHash && type) {
993
+ const controller = new AbortController();
994
+ setStatus("loading");
995
+ fetch(confirmUrl, {
996
+ method: "POST",
997
+ credentials: "same-origin",
998
+ headers: { "Content-Type": "application/json" },
999
+ body: JSON.stringify({ token_hash: tokenHash, type }),
1000
+ signal: controller.signal
1001
+ }).then(async (res) => {
1002
+ const json = await res.json().catch(() => ({}));
1003
+ if (!res.ok) {
1004
+ setStatus("error");
1005
+ setErrorMessage(json.error ?? c.errorMessage);
1006
+ return;
1007
+ }
1008
+ if (json.data?.sessionExchangeUrl) {
1009
+ if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {
1010
+ setStatus("error");
1011
+ setErrorMessage(c.errorMessage);
1012
+ return;
1013
+ }
1014
+ window.location.assign(json.data.sessionExchangeUrl);
1015
+ return;
1016
+ }
1017
+ setStatus("success");
1018
+ }).catch((err) => {
1019
+ if (err instanceof Error && err.name === "AbortError") return;
1020
+ setStatus("error");
1021
+ setErrorMessage(c.errorMessage);
1022
+ });
1023
+ return () => controller.abort();
1024
+ }
1025
+ setStatus("error");
1026
+ }, [success, tokenHash, type, confirmUrl]);
1027
+ if (status === "loading") {
1028
+ return /* @__PURE__ */ jsx7("div", { className: cn7("space-y-4 text-center", className), children: /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground text-sm", children: c.loadingMessage }) });
1029
+ }
1030
+ if (status === "error") {
1031
+ return /* @__PURE__ */ jsxs6("div", { className: cn7("space-y-4 text-center", className), children: [
1032
+ /* @__PURE__ */ jsx7("h2", { className: "text-lg font-semibold", children: c.errorTitle }),
1033
+ /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground text-sm", children: errorMessage ?? c.errorMessage }),
1034
+ /* @__PURE__ */ jsx7("a", { href: loginUrl, className: "text-primary inline-block text-sm hover:underline", children: c.errorCta })
1035
+ ] });
1036
+ }
1037
+ return /* @__PURE__ */ jsxs6("div", { className: cn7("space-y-4 text-center", className), children: [
1038
+ /* @__PURE__ */ jsx7("h2", { className: "text-lg font-semibold", children: c.successTitle }),
1039
+ /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground text-sm", children: c.successMessage }),
1040
+ /* @__PURE__ */ jsx7("a", { href: loginUrl, className: "text-primary inline-block text-sm hover:underline", children: c.successCta })
1041
+ ] });
1042
+ }
1043
+ export {
1044
+ AuthCallback,
1045
+ AuthConfirm,
1046
+ AuthLogo,
1047
+ LoginForm,
1048
+ ResetPasswordConfirm,
1049
+ ResetPasswordRequest,
1050
+ SetPassword,
1051
+ mapLoginError
1052
+ };
1053
+ //# sourceMappingURL=auth.js.map