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