@lalternative/auth 0.6.0 → 0.8.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/index.js CHANGED
@@ -21,6 +21,7 @@ function AuthField({
21
21
  hint,
22
22
  description,
23
23
  invalid = false,
24
+ fieldClassName = "border-foreground/25 bg-background",
24
25
  ...props
25
26
  }) {
26
27
  const id = useId();
@@ -50,13 +51,13 @@ function AuthField({
50
51
  "aria-invalid": invalid || void 0,
51
52
  "aria-describedby": descriptionId,
52
53
  className: [
53
- "h-[52px] w-full rounded-xl border bg-background px-4 text-base",
54
+ "h-[52px] w-full rounded-lg border px-4 text-base",
54
55
  "sm:h-11 sm:text-sm",
55
56
  isPassword ? "pr-12" : "",
56
57
  "text-foreground placeholder:text-muted-foreground/70",
57
58
  "transition-[border-color,box-shadow] duration-150 ease-out",
58
59
  "focus-visible:outline-none focus-visible:ring-[3px]",
59
- invalid ? "border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20" : "border-input focus-visible:border-ring focus-visible:ring-ring/15",
60
+ invalid ? "border-destructive bg-background focus-visible:border-destructive focus-visible:ring-destructive/20" : `${fieldClassName} focus-visible:border-ring focus-visible:ring-ring/15`,
60
61
  "disabled:cursor-not-allowed disabled:opacity-60"
61
62
  ].filter(Boolean).join(" ")
62
63
  }
@@ -105,7 +106,8 @@ function AuthSubmit({
105
106
  pending = false,
106
107
  disabled = false,
107
108
  pendingLabel,
108
- children
109
+ children,
110
+ className = "bg-primary text-primary-foreground hover:bg-primary/90"
109
111
  }) {
110
112
  return /* @__PURE__ */ jsxs2(
111
113
  "button",
@@ -114,10 +116,11 @@ function AuthSubmit({
114
116
  disabled: disabled || pending,
115
117
  "aria-busy": pending || void 0,
116
118
  className: [
117
- "relative flex h-[52px] w-full items-center justify-center rounded-xl sm:h-11",
118
- "bg-primary text-base font-medium text-primary-foreground sm:text-sm",
119
+ "relative flex h-[52px] w-full items-center justify-center rounded-lg sm:h-11",
120
+ "text-base font-medium sm:text-sm",
121
+ className,
119
122
  "shadow-sm transition-[background-color,transform,box-shadow] duration-150 ease-out",
120
- "hover:bg-primary/90 active:translate-y-px",
123
+ "active:translate-y-px",
121
124
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
122
125
  "disabled:pointer-events-none disabled:opacity-55"
123
126
  ].join(" "),
@@ -138,8 +141,25 @@ function AuthSubmit({
138
141
  // src/components/login-form.tsx
139
142
  import { useState as useState2 } from "react";
140
143
 
144
+ // src/components/auth-alert.tsx
145
+ import { jsx as jsx3 } from "react/jsx-runtime";
146
+ function AuthAlert({ children, tone = "error" }) {
147
+ return /* @__PURE__ */ jsx3(
148
+ "div",
149
+ {
150
+ role: tone === "error" ? "alert" : "status",
151
+ "aria-live": tone === "error" ? "assertive" : "polite",
152
+ className: children ? [
153
+ "rounded-lg border px-3.5 py-3 text-sm leading-snug",
154
+ tone === "error" ? "border-destructive/25 bg-destructive/10 text-destructive" : "border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
155
+ ].join(" ") : void 0,
156
+ children
157
+ }
158
+ );
159
+ }
160
+
141
161
  // src/components/social-buttons.tsx
142
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
162
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
143
163
  var LABELS = {
144
164
  google: "Continuer avec Google",
145
165
  github: "Continuer avec GitHub"
@@ -155,58 +175,65 @@ function SocialButtons({
155
175
  const copy = { ...LABELS, ...labels };
156
176
  return /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
157
177
  /* @__PURE__ */ jsxs3("div", { "aria-hidden": "true", className: "flex items-center gap-3", children: [
158
- /* @__PURE__ */ jsx3("span", { className: "h-px flex-1 bg-border" }),
159
- /* @__PURE__ */ jsx3("span", { className: "text-[11px] uppercase tracking-[0.14em] text-muted-foreground", children: separator }),
160
- /* @__PURE__ */ jsx3("span", { className: "h-px flex-1 bg-border" })
178
+ /* @__PURE__ */ jsx4("span", { className: "h-px flex-1 bg-border" }),
179
+ /* @__PURE__ */ jsx4("span", { className: "text-[11px] uppercase tracking-[0.14em] text-muted-foreground", children: separator }),
180
+ /* @__PURE__ */ jsx4("span", { className: "h-px flex-1 bg-border" })
161
181
  ] }),
162
- /* @__PURE__ */ jsx3("div", { className: "space-y-2.5", children: providers.map((provider) => /* @__PURE__ */ jsxs3(
163
- "button",
182
+ /* @__PURE__ */ jsx4(
183
+ "div",
164
184
  {
165
- type: "button",
166
- onClick: () => onSelect(provider),
167
- disabled,
168
- className: [
169
- "flex h-[52px] w-full items-center justify-center gap-2.5 rounded-xl sm:h-11",
170
- "border border-input bg-background text-base font-medium text-foreground sm:text-sm",
171
- "transition-[background-color,border-color,transform] duration-150 ease-out",
172
- "hover:border-foreground/20 hover:bg-accent active:translate-y-px",
173
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
174
- "disabled:pointer-events-none disabled:opacity-55"
175
- ].join(" "),
176
- children: [
177
- /* @__PURE__ */ jsx3(ProviderMark, { provider }),
178
- copy[provider] ?? provider
179
- ]
180
- },
181
- provider
182
- )) })
185
+ className: providers.length > 1 ? "grid gap-2.5 sm:grid-cols-2" : "grid gap-2.5",
186
+ children: providers.map((provider) => /* @__PURE__ */ jsxs3(
187
+ "button",
188
+ {
189
+ type: "button",
190
+ onClick: () => onSelect(provider),
191
+ disabled,
192
+ "aria-label": copy[provider] ?? provider,
193
+ className: [
194
+ "flex h-[52px] w-full items-center justify-center gap-2.5 rounded-lg sm:h-11",
195
+ "border border-foreground/25 bg-background text-base font-medium text-foreground sm:text-sm",
196
+ "transition-[background-color,border-color,transform] duration-150 ease-out",
197
+ "hover:border-foreground/20 hover:bg-accent active:translate-y-px",
198
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
199
+ "disabled:pointer-events-none disabled:opacity-55"
200
+ ].join(" "),
201
+ children: [
202
+ /* @__PURE__ */ jsx4(ProviderMark, { provider }),
203
+ /* @__PURE__ */ jsx4("span", { className: providers.length > 1 ? "sm:hidden" : "", children: copy[provider] ?? provider })
204
+ ]
205
+ },
206
+ provider
207
+ ))
208
+ }
209
+ )
183
210
  ] });
184
211
  }
185
212
  function ProviderMark({ provider }) {
186
213
  if (provider === "google") {
187
214
  return /* @__PURE__ */ jsxs3("svg", { width: "17", height: "17", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
188
- /* @__PURE__ */ jsx3(
215
+ /* @__PURE__ */ jsx4(
189
216
  "path",
190
217
  {
191
218
  fill: "#4285F4",
192
219
  d: "M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z"
193
220
  }
194
221
  ),
195
- /* @__PURE__ */ jsx3(
222
+ /* @__PURE__ */ jsx4(
196
223
  "path",
197
224
  {
198
225
  fill: "#34A853",
199
226
  d: "M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.02-3.7H.96v2.33A9 9 0 0 0 9 18Z"
200
227
  }
201
228
  ),
202
- /* @__PURE__ */ jsx3(
229
+ /* @__PURE__ */ jsx4(
203
230
  "path",
204
231
  {
205
232
  fill: "#FBBC05",
206
233
  d: "M3.98 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.02-2.33Z"
207
234
  }
208
235
  ),
209
- /* @__PURE__ */ jsx3(
236
+ /* @__PURE__ */ jsx4(
210
237
  "path",
211
238
  {
212
239
  fill: "#EA4335",
@@ -215,11 +242,11 @@ function ProviderMark({ provider }) {
215
242
  )
216
243
  ] });
217
244
  }
218
- return /* @__PURE__ */ jsx3("svg", { width: "17", height: "17", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("path", { d: "M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0Z" }) });
245
+ return /* @__PURE__ */ jsx4("svg", { width: "17", height: "17", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0Z" }) });
219
246
  }
220
247
 
221
248
  // src/components/login-form.tsx
222
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
249
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
223
250
  var DEFAULTS = {
224
251
  title: "Connexion",
225
252
  subtitle: "Content de te revoir. Entre tes identifiants pour continuer.",
@@ -248,13 +275,18 @@ function LoginForm({
248
275
  socialProviders = [],
249
276
  coreTokenUrl = "/api/auth/core-token",
250
277
  labels,
278
+ submitClassName,
279
+ fieldClassName,
280
+ error: externalError,
251
281
  authClient
252
282
  }) {
253
283
  const t = { ...DEFAULTS, ...labels };
254
284
  const [email, setEmail] = useState2("");
255
285
  const [password, setPassword] = useState2("");
256
- const [error, setError] = useState2();
286
+ const [ownError, setOwnError] = useState2();
257
287
  const [isPending, setIsPending] = useState2(false);
288
+ const error = ownError ?? externalError;
289
+ const setError = setOwnError;
258
290
  const handleSubmit = async (e) => {
259
291
  e.preventDefault();
260
292
  if (!email.trim()) {
@@ -300,21 +332,10 @@ function LoginForm({
300
332
  );
301
333
  }
302
334
  };
303
- return /* @__PURE__ */ jsxs4("div", { className: "space-y-7", children: [
304
- /* @__PURE__ */ jsxs4("header", { className: "space-y-1.5", children: [
305
- /* @__PURE__ */ jsx4("h1", { className: "text-[26px] font-semibold leading-tight tracking-[-0.02em] text-foreground sm:text-2xl", children: t.title }),
306
- /* @__PURE__ */ jsx4("p", { className: "text-balance text-sm leading-relaxed text-muted-foreground", children: t.subtitle })
307
- ] }),
308
- error && /* @__PURE__ */ jsx4(
309
- "div",
310
- {
311
- role: "alert",
312
- className: "rounded-xl border border-destructive/25 bg-destructive/10 px-3.5 py-3 text-sm leading-snug text-destructive",
313
- children: error
314
- }
315
- ),
335
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-6", children: [
336
+ /* @__PURE__ */ jsx5(AuthAlert, { children: error }),
316
337
  /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
317
- /* @__PURE__ */ jsx4(
338
+ /* @__PURE__ */ jsx5(
318
339
  AuthField,
319
340
  {
320
341
  label: t.emailPlaceholder,
@@ -327,10 +348,11 @@ function LoginForm({
327
348
  autoComplete: "email",
328
349
  autoCapitalize: "none",
329
350
  spellCheck: false,
330
- invalid: !!error
351
+ invalid: !!error,
352
+ fieldClassName
331
353
  }
332
354
  ),
333
- /* @__PURE__ */ jsx4(
355
+ /* @__PURE__ */ jsx5(
334
356
  AuthField,
335
357
  {
336
358
  label: t.passwordPlaceholder,
@@ -341,7 +363,8 @@ function LoginForm({
341
363
  disabled: isPending,
342
364
  autoComplete: "current-password",
343
365
  invalid: !!error,
344
- hint: /* @__PURE__ */ jsx4(
366
+ fieldClassName,
367
+ hint: /* @__PURE__ */ jsx5(
345
368
  "a",
346
369
  {
347
370
  href: forgotPasswordUrl,
@@ -351,17 +374,18 @@ function LoginForm({
351
374
  )
352
375
  }
353
376
  ),
354
- /* @__PURE__ */ jsx4(
377
+ /* @__PURE__ */ jsx5(
355
378
  AuthSubmit,
356
379
  {
357
380
  pending: isPending,
358
381
  disabled: !email.trim() || !password,
359
382
  pendingLabel: t.submitPending,
383
+ className: submitClassName,
360
384
  children: t.submit
361
385
  }
362
386
  )
363
387
  ] }),
364
- /* @__PURE__ */ jsx4(
388
+ /* @__PURE__ */ jsx5(
365
389
  SocialButtons,
366
390
  {
367
391
  providers: socialProviders,
@@ -372,7 +396,7 @@ function LoginForm({
372
396
  /* @__PURE__ */ jsxs4("p", { className: "text-center text-sm text-muted-foreground", children: [
373
397
  t.noAccount,
374
398
  " ",
375
- /* @__PURE__ */ jsx4(
399
+ /* @__PURE__ */ jsx5(
376
400
  "a",
377
401
  {
378
402
  href: registerUrl,
@@ -383,10 +407,14 @@ function LoginForm({
383
407
  ] })
384
408
  ] });
385
409
  }
410
+ LoginForm.defaults = {
411
+ title: DEFAULTS.title,
412
+ subtitle: DEFAULTS.subtitle
413
+ };
386
414
 
387
415
  // src/components/register-form.tsx
388
416
  import { useState as useState3 } from "react";
389
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
417
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
390
418
  var MIN_PASSWORD_LENGTH = 8;
391
419
  var DEFAULTS2 = {
392
420
  title: "Cr\xE9er un compte",
@@ -395,6 +423,8 @@ var DEFAULTS2 = {
395
423
  emailPlaceholder: "Adresse e-mail",
396
424
  passwordPlaceholder: "Mot de passe",
397
425
  passwordHint: `Au moins ${MIN_PASSWORD_LENGTH} caract\xE8res.`,
426
+ confirmPlaceholder: "Confirme le mot de passe",
427
+ passwordMismatch: "Les deux mots de passe ne correspondent pas",
398
428
  submit: "Cr\xE9er mon compte",
399
429
  submitPending: "Cr\xE9ation\u2026",
400
430
  haveAccount: "Tu as d\xE9j\xE0 un compte ?",
@@ -411,15 +441,22 @@ function RegisterForm({
411
441
  socialCallbackUrl = "/",
412
442
  socialProviders = [],
413
443
  labels,
444
+ submitClassName,
445
+ fieldClassName,
446
+ error: externalError,
414
447
  authClient
415
448
  }) {
416
449
  const t = { ...DEFAULTS2, ...labels };
417
450
  const [name, setName] = useState3("");
418
451
  const [email, setEmail] = useState3("");
419
452
  const [password, setPassword] = useState3("");
420
- const [error, setError] = useState3();
453
+ const [confirmPassword, setConfirmPassword] = useState3("");
454
+ const [ownError, setOwnError] = useState3();
421
455
  const [isPending, setIsPending] = useState3(false);
456
+ const error = ownError ?? externalError;
457
+ const setError = setOwnError;
422
458
  const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH;
459
+ const mismatch = confirmPassword.length > 0 && confirmPassword !== password;
423
460
  const handleSubmit = async (e) => {
424
461
  e.preventDefault();
425
462
  if (!name.trim()) {
@@ -434,6 +471,10 @@ function RegisterForm({
434
471
  setError(t.passwordTooShort);
435
472
  return;
436
473
  }
474
+ if (password !== confirmPassword) {
475
+ setError(t.passwordMismatch);
476
+ return;
477
+ }
437
478
  setError(void 0);
438
479
  setIsPending(true);
439
480
  try {
@@ -464,21 +505,10 @@ function RegisterForm({
464
505
  setError(err instanceof Error ? err.message : t.signUpFailed);
465
506
  }
466
507
  };
467
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-7", children: [
468
- /* @__PURE__ */ jsxs5("header", { className: "space-y-1.5", children: [
469
- /* @__PURE__ */ jsx5("h1", { className: "text-[26px] font-semibold leading-tight tracking-[-0.02em] text-foreground sm:text-2xl", children: t.title }),
470
- /* @__PURE__ */ jsx5("p", { className: "text-balance text-sm leading-relaxed text-muted-foreground", children: t.subtitle })
471
- ] }),
472
- error && /* @__PURE__ */ jsx5(
473
- "div",
474
- {
475
- role: "alert",
476
- className: "rounded-xl border border-destructive/25 bg-destructive/10 px-3.5 py-3 text-sm leading-snug text-destructive",
477
- children: error
478
- }
479
- ),
508
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
509
+ /* @__PURE__ */ jsx6(AuthAlert, { children: error }),
480
510
  /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
481
- /* @__PURE__ */ jsx5(
511
+ /* @__PURE__ */ jsx6(
482
512
  AuthField,
483
513
  {
484
514
  label: t.namePlaceholder,
@@ -487,10 +517,11 @@ function RegisterForm({
487
517
  onChange: (e) => setName(e.target.value),
488
518
  required: true,
489
519
  disabled: isPending,
490
- autoComplete: "name"
520
+ autoComplete: "name",
521
+ fieldClassName
491
522
  }
492
523
  ),
493
- /* @__PURE__ */ jsx5(
524
+ /* @__PURE__ */ jsx6(
494
525
  AuthField,
495
526
  {
496
527
  label: t.emailPlaceholder,
@@ -502,10 +533,11 @@ function RegisterForm({
502
533
  disabled: isPending,
503
534
  autoComplete: "email",
504
535
  autoCapitalize: "none",
505
- spellCheck: false
536
+ spellCheck: false,
537
+ fieldClassName
506
538
  }
507
539
  ),
508
- /* @__PURE__ */ jsx5(
540
+ /* @__PURE__ */ jsx6(
509
541
  AuthField,
510
542
  {
511
543
  label: t.passwordPlaceholder,
@@ -516,13 +548,36 @@ function RegisterForm({
516
548
  disabled: isPending,
517
549
  autoComplete: "new-password",
518
550
  description: t.passwordHint,
519
- invalid: tooShort
551
+ invalid: tooShort,
552
+ fieldClassName
520
553
  }
521
554
  ),
522
- /* @__PURE__ */ jsx5(AuthSubmit, { pending: isPending, pendingLabel: t.submitPending, children: t.submit }),
523
- legal && /* @__PURE__ */ jsx5("p", { className: "text-center text-xs leading-relaxed text-muted-foreground", children: legal })
555
+ /* @__PURE__ */ jsx6(
556
+ AuthField,
557
+ {
558
+ label: t.confirmPlaceholder,
559
+ type: "password",
560
+ value: confirmPassword,
561
+ onChange: (e) => setConfirmPassword(e.target.value),
562
+ required: true,
563
+ disabled: isPending,
564
+ autoComplete: "new-password",
565
+ invalid: mismatch,
566
+ fieldClassName
567
+ }
568
+ ),
569
+ /* @__PURE__ */ jsx6(
570
+ AuthSubmit,
571
+ {
572
+ pending: isPending,
573
+ pendingLabel: t.submitPending,
574
+ className: submitClassName,
575
+ children: t.submit
576
+ }
577
+ ),
578
+ legal && /* @__PURE__ */ jsx6("p", { className: "text-center text-xs leading-relaxed text-muted-foreground", children: legal })
524
579
  ] }),
525
- /* @__PURE__ */ jsx5(
580
+ /* @__PURE__ */ jsx6(
526
581
  SocialButtons,
527
582
  {
528
583
  providers: socialProviders,
@@ -533,7 +588,7 @@ function RegisterForm({
533
588
  /* @__PURE__ */ jsxs5("p", { className: "text-center text-sm text-muted-foreground", children: [
534
589
  t.haveAccount,
535
590
  " ",
536
- /* @__PURE__ */ jsx5(
591
+ /* @__PURE__ */ jsx6(
537
592
  "a",
538
593
  {
539
594
  href: loginUrl,
@@ -544,15 +599,92 @@ function RegisterForm({
544
599
  ] })
545
600
  ] });
546
601
  }
602
+ RegisterForm.defaults = {
603
+ title: DEFAULTS2.title,
604
+ subtitle: DEFAULTS2.subtitle
605
+ };
547
606
 
548
607
  // src/components/verify-email-form.tsx
549
608
  import { useState as useState4 } from "react";
550
- import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
609
+
610
+ // src/components/auth-otp-field.tsx
611
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
612
+ var OTP_LENGTH = 6;
613
+ function AuthOtpField({
614
+ label,
615
+ value,
616
+ onChange,
617
+ disabled = false,
618
+ invalid = false,
619
+ id,
620
+ fieldClassName = "border-foreground/25 bg-background"
621
+ }) {
622
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
623
+ /* @__PURE__ */ jsx7(
624
+ "label",
625
+ {
626
+ htmlFor: id,
627
+ className: "block text-sm font-medium leading-none text-foreground",
628
+ children: label
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsx7(
632
+ "input",
633
+ {
634
+ id,
635
+ type: "text",
636
+ inputMode: "numeric",
637
+ pattern: "[0-9]*",
638
+ maxLength: OTP_LENGTH,
639
+ value,
640
+ onChange: (e) => onChange(e.target.value.replace(/\D/g, "")),
641
+ required: true,
642
+ disabled,
643
+ autoComplete: "one-time-code",
644
+ "aria-invalid": invalid || void 0,
645
+ className: [
646
+ "h-[52px] w-full rounded-lg border px-4 sm:h-11",
647
+ "text-center font-mono text-lg tracking-[0.4em]",
648
+ "text-foreground",
649
+ "transition-[border-color,box-shadow] duration-150 ease-out",
650
+ "focus-visible:outline-none focus-visible:ring-[3px]",
651
+ invalid ? "border-destructive bg-background focus-visible:border-destructive focus-visible:ring-destructive/20" : `${fieldClassName} focus-visible:border-ring focus-visible:ring-ring/15`,
652
+ "disabled:cursor-not-allowed disabled:opacity-60"
653
+ ].join(" ")
654
+ }
655
+ )
656
+ ] });
657
+ }
658
+
659
+ // src/components/verify-email-form.tsx
660
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
661
+ var DEFAULTS3 = {
662
+ title: "V\xE9rifie ton adresse",
663
+ subtitle: "Entre le code \xE0 6 chiffres envoy\xE9 \xE0",
664
+ subtitleNoEmail: "Entre le code \xE0 6 chiffres re\xE7u par e-mail.",
665
+ codePlaceholder: "Code de v\xE9rification",
666
+ submit: "V\xE9rifier",
667
+ submitPending: "V\xE9rification\u2026",
668
+ resend: "Renvoyer le code",
669
+ resendPending: "Envoi\u2026",
670
+ resent: "Un nouveau code vient de t'\xEAtre envoy\xE9.",
671
+ alreadyVerified: "Adresse d\xE9j\xE0 v\xE9rifi\xE9e ?",
672
+ login: "Se connecter",
673
+ codeRequired: "Entre le code \xE0 6 chiffres",
674
+ invalidCode: "Code invalide. R\xE9essaie.",
675
+ resendFailed: "L'envoi du code a \xE9chou\xE9",
676
+ missingEmail: "Adresse e-mail introuvable. Recommence l'inscription."
677
+ };
551
678
  function VerifyEmailForm({
552
679
  email,
553
680
  onSuccess,
681
+ loginUrl = "/login",
682
+ labels,
683
+ submitClassName,
684
+ fieldClassName,
554
685
  authClient
555
686
  }) {
687
+ const t = { ...DEFAULTS3, ...labels };
556
688
  const [otp, setOtp] = useState4("");
557
689
  const [isVerifying, setIsVerifying] = useState4(false);
558
690
  const [isResending, setIsResending] = useState4(false);
@@ -560,29 +692,29 @@ function VerifyEmailForm({
560
692
  const [error, setError] = useState4();
561
693
  const handleVerify = async (e) => {
562
694
  e.preventDefault();
563
- if (!otp.trim() || otp.length < 6) {
564
- setError("Please enter the 6-digit code");
695
+ if (otp.length < OTP_LENGTH) {
696
+ setError(t.codeRequired);
565
697
  return;
566
698
  }
567
699
  setError(void 0);
700
+ setResendMessage(void 0);
568
701
  setIsVerifying(true);
569
702
  try {
570
703
  const res = await authClient.emailOtp.verifyEmail({ email, otp });
571
- console.log("[verify-email] response:", JSON.stringify(res?.data), "error:", JSON.stringify(res?.error));
572
704
  if (res?.error) {
573
- setError(res.error.message ?? "Invalid code. Please try again.");
705
+ setError(res.error.message ?? t.invalidCode);
574
706
  return;
575
707
  }
576
708
  onSuccess?.();
577
709
  } catch (err) {
578
- setError(err instanceof Error ? err.message : "Invalid code. Please try again.");
710
+ setError(err instanceof Error ? err.message : t.invalidCode);
579
711
  } finally {
580
712
  setIsVerifying(false);
581
713
  }
582
714
  };
583
715
  const handleResend = async () => {
584
716
  if (!email) {
585
- setError("Email address is not available. Please register again.");
717
+ setError(t.missingEmail);
586
718
  return;
587
719
  }
588
720
  setError(void 0);
@@ -593,165 +725,203 @@ function VerifyEmailForm({
593
725
  email,
594
726
  type: "email-verification"
595
727
  });
596
- setResendMessage("A new code has been sent to your inbox.");
728
+ setResendMessage(t.resent);
597
729
  } catch (err) {
598
- setError(err instanceof Error ? err.message : "Failed to resend code.");
730
+ setError(err instanceof Error ? err.message : t.resendFailed);
599
731
  } finally {
600
732
  setIsResending(false);
601
733
  }
602
734
  };
603
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-8", children: [
604
- /* @__PURE__ */ jsxs6("div", { children: [
605
- /* @__PURE__ */ jsx6("h1", { className: "text-2xl font-bold tracking-tight", children: "Verify your email" }),
606
- /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-muted-foreground", children: email ? /* @__PURE__ */ jsxs6(Fragment, { children: [
607
- "Enter the 6-digit code sent to",
608
- " ",
609
- /* @__PURE__ */ jsx6("span", { className: "font-medium text-foreground", children: email })
610
- ] }) : "Enter the 6-digit code sent to your email" })
611
- ] }),
612
- error && /* @__PURE__ */ jsx6("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
613
- resendMessage && /* @__PURE__ */ jsx6("div", { className: "rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700", children: resendMessage }),
614
- /* @__PURE__ */ jsxs6("form", { onSubmit: handleVerify, className: "space-y-4", noValidate: true, children: [
615
- /* @__PURE__ */ jsx6(
616
- "input",
735
+ return /* @__PURE__ */ jsxs7("div", { className: "space-y-6", children: [
736
+ /* @__PURE__ */ jsx8(AuthAlert, { children: error }),
737
+ /* @__PURE__ */ jsx8(AuthAlert, { tone: "success", children: resendMessage }),
738
+ /* @__PURE__ */ jsxs7("form", { onSubmit: handleVerify, className: "space-y-5", noValidate: true, children: [
739
+ /* @__PURE__ */ jsx8(
740
+ AuthOtpField,
617
741
  {
618
- type: "text",
619
- inputMode: "numeric",
620
- pattern: "[0-9]*",
621
- maxLength: 6,
742
+ id: "verify-email-otp",
743
+ label: t.codePlaceholder,
622
744
  value: otp,
623
- onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
624
- placeholder: "000000",
625
- required: true,
745
+ onChange: setOtp,
626
746
  disabled: isVerifying,
627
- autoComplete: "one-time-code",
628
- className: "flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-center text-lg tracking-[0.4em] font-mono ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
747
+ fieldClassName
629
748
  }
630
749
  ),
631
- /* @__PURE__ */ jsx6(
632
- "button",
750
+ /* @__PURE__ */ jsx8(
751
+ AuthSubmit,
633
752
  {
634
- type: "submit",
635
- disabled: isVerifying || otp.length < 6,
636
- className: "inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50",
637
- children: isVerifying ? "Verifying..." : "Verify email"
753
+ pending: isVerifying,
754
+ disabled: otp.length < OTP_LENGTH,
755
+ pendingLabel: t.submitPending,
756
+ className: submitClassName,
757
+ children: t.submit
638
758
  }
639
- ),
640
- /* @__PURE__ */ jsx6(
759
+ )
760
+ ] }),
761
+ /* @__PURE__ */ jsxs7("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
762
+ /* @__PURE__ */ jsx8(
641
763
  "button",
642
764
  {
643
765
  type: "button",
644
766
  onClick: handleResend,
645
767
  disabled: isResending,
646
- className: "inline-flex h-11 w-full items-center justify-center rounded-md border border-input bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
647
- children: isResending ? "Sending..." : "Resend code"
768
+ className: "rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-55",
769
+ children: isResending ? t.resendPending : t.resend
648
770
  }
649
- )
650
- ] }),
651
- /* @__PURE__ */ jsxs6("p", { className: "text-center text-sm text-muted-foreground", children: [
652
- "Already verified?",
653
- " ",
654
- /* @__PURE__ */ jsx6(
655
- "a",
656
- {
657
- href: "/login",
658
- className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
659
- children: "Sign in"
660
- }
661
- )
771
+ ),
772
+ /* @__PURE__ */ jsxs7("p", { children: [
773
+ t.alreadyVerified,
774
+ " ",
775
+ /* @__PURE__ */ jsx8(
776
+ "a",
777
+ {
778
+ href: loginUrl,
779
+ className: "rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
780
+ children: t.login
781
+ }
782
+ )
783
+ ] })
662
784
  ] })
663
785
  ] });
664
786
  }
787
+ VerifyEmailForm.defaults = {
788
+ title: DEFAULTS3.title,
789
+ subtitle: DEFAULTS3.subtitle
790
+ };
665
791
 
666
792
  // src/components/forgot-password-form.tsx
667
793
  import { useState as useState5 } from "react";
668
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
794
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
795
+ var DEFAULTS4 = {
796
+ title: "Mot de passe oubli\xE9 ?",
797
+ subtitle: "Entre ton adresse e-mail et nous t'enverrons un code pour le r\xE9initialiser.",
798
+ emailPlaceholder: "Adresse e-mail",
799
+ submit: "Envoyer le code",
800
+ submitPending: "Envoi\u2026",
801
+ rememberPassword: "Tu t'en souviens finalement ?",
802
+ login: "Se connecter",
803
+ emailRequired: "Renseigne ton adresse e-mail",
804
+ sendFailed: "L'envoi du code a \xE9chou\xE9"
805
+ };
669
806
  function ForgotPasswordForm({
670
807
  onSuccess,
671
808
  loginUrl = "/login",
809
+ labels,
810
+ submitClassName,
811
+ fieldClassName,
672
812
  authClient
673
813
  }) {
814
+ const t = { ...DEFAULTS4, ...labels };
674
815
  const [email, setEmail] = useState5("");
675
816
  const [error, setError] = useState5();
676
817
  const [isPending, setIsPending] = useState5(false);
677
818
  const handleSubmit = async (e) => {
678
819
  e.preventDefault();
679
820
  if (!email.trim()) {
680
- setError("Please enter your email address");
821
+ setError(t.emailRequired);
681
822
  return;
682
823
  }
683
824
  setError(void 0);
684
825
  setIsPending(true);
685
826
  try {
686
827
  const res = await authClient.emailOtp.sendVerificationOtp({
687
- email,
828
+ email: email.trim(),
688
829
  type: "forget-password"
689
830
  });
690
831
  if (res?.error) {
691
- setError(res.error.message ?? "Failed to send reset code");
832
+ setError(res.error.message ?? t.sendFailed);
692
833
  return;
693
834
  }
694
- onSuccess?.(email);
835
+ onSuccess?.(email.trim());
695
836
  } catch (err) {
696
- setError(err instanceof Error ? err.message : "Failed to send reset code");
837
+ setError(err instanceof Error ? err.message : t.sendFailed);
697
838
  } finally {
698
839
  setIsPending(false);
699
840
  }
700
841
  };
701
- return /* @__PURE__ */ jsxs7("div", { className: "space-y-8", children: [
702
- /* @__PURE__ */ jsxs7("div", { children: [
703
- /* @__PURE__ */ jsx7("h1", { className: "text-2xl font-bold tracking-tight", children: "Forgot your password?" }),
704
- /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-muted-foreground", children: "Enter your email address and we'll send you a code to reset your password." })
705
- ] }),
706
- error && /* @__PURE__ */ jsx7("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
707
- /* @__PURE__ */ jsxs7("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
708
- /* @__PURE__ */ jsx7(
709
- "input",
842
+ return /* @__PURE__ */ jsxs8("div", { className: "space-y-6", children: [
843
+ /* @__PURE__ */ jsx9(AuthAlert, { children: error }),
844
+ /* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
845
+ /* @__PURE__ */ jsx9(
846
+ AuthField,
710
847
  {
848
+ label: t.emailPlaceholder,
711
849
  type: "email",
850
+ inputMode: "email",
712
851
  value: email,
713
852
  onChange: (e) => setEmail(e.target.value),
714
- placeholder: "Email address",
715
853
  required: true,
716
854
  disabled: isPending,
717
855
  autoComplete: "email",
718
- className: "flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
856
+ autoCapitalize: "none",
857
+ spellCheck: false,
858
+ invalid: !!error
719
859
  }
720
860
  ),
721
- /* @__PURE__ */ jsx7(
722
- "button",
861
+ /* @__PURE__ */ jsx9(
862
+ AuthSubmit,
723
863
  {
724
- type: "submit",
725
- disabled: isPending || !email.trim(),
726
- className: "inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50",
727
- children: isPending ? "Sending..." : "Send reset code"
864
+ pending: isPending,
865
+ disabled: !email.trim(),
866
+ pendingLabel: t.submitPending,
867
+ className: submitClassName,
868
+ children: t.submit
728
869
  }
729
870
  )
730
871
  ] }),
731
- /* @__PURE__ */ jsxs7("p", { className: "text-center text-sm text-muted-foreground", children: [
732
- "Remember your password?",
872
+ /* @__PURE__ */ jsxs8("p", { className: "text-center text-sm text-muted-foreground", children: [
873
+ t.rememberPassword,
733
874
  " ",
734
- /* @__PURE__ */ jsx7(
875
+ /* @__PURE__ */ jsx9(
735
876
  "a",
736
877
  {
737
878
  href: loginUrl,
738
- className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
739
- children: "Sign in"
879
+ className: "rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
880
+ children: t.login
740
881
  }
741
882
  )
742
883
  ] })
743
884
  ] });
744
885
  }
886
+ ForgotPasswordForm.defaults = {
887
+ title: DEFAULTS4.title,
888
+ subtitle: DEFAULTS4.subtitle
889
+ };
745
890
 
746
891
  // src/components/reset-password-form.tsx
747
892
  import { useState as useState6 } from "react";
748
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
893
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
894
+ var MIN_PASSWORD_LENGTH2 = 8;
895
+ var DEFAULTS5 = {
896
+ title: "Nouveau mot de passe",
897
+ subtitle: "Entre le code \xE0 6 chiffres re\xE7u par e-mail et ton nouveau mot de passe.",
898
+ codePlaceholder: "Code de v\xE9rification",
899
+ passwordPlaceholder: "Nouveau mot de passe",
900
+ passwordHint: `Au moins ${MIN_PASSWORD_LENGTH2} caract\xE8res.`,
901
+ confirmPlaceholder: "Confirme le mot de passe",
902
+ submit: "R\xE9initialiser",
903
+ submitPending: "R\xE9initialisation\u2026",
904
+ resend: "Renvoyer le code",
905
+ resendPending: "Envoi\u2026",
906
+ resent: "Un nouveau code vient de t'\xEAtre envoy\xE9.",
907
+ rememberPassword: "Tu t'en souviens finalement ?",
908
+ login: "Se connecter",
909
+ codeRequired: "Entre le code \xE0 6 chiffres",
910
+ passwordTooShort: `Le mot de passe doit faire au moins ${MIN_PASSWORD_LENGTH2} caract\xE8res`,
911
+ passwordMismatch: "Les deux mots de passe ne correspondent pas",
912
+ resetFailed: "La r\xE9initialisation a \xE9chou\xE9",
913
+ resendFailed: "L'envoi du code a \xE9chou\xE9"
914
+ };
749
915
  function ResetPasswordForm({
750
916
  email,
751
917
  onSuccess,
752
918
  loginUrl = "/login",
919
+ labels,
920
+ submitClassName,
921
+ fieldClassName,
753
922
  authClient
754
923
  }) {
924
+ const t = { ...DEFAULTS5, ...labels };
755
925
  const [otp, setOtp] = useState6("");
756
926
  const [password, setPassword] = useState6("");
757
927
  const [confirmPassword, setConfirmPassword] = useState6("");
@@ -759,21 +929,24 @@ function ResetPasswordForm({
759
929
  const [isResetting, setIsResetting] = useState6(false);
760
930
  const [isResending, setIsResending] = useState6(false);
761
931
  const [resendMessage, setResendMessage] = useState6();
932
+ const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH2;
933
+ const mismatch = confirmPassword.length > 0 && confirmPassword !== password;
762
934
  const handleSubmit = async (e) => {
763
935
  e.preventDefault();
764
- if (!otp.trim() || otp.length < 6) {
765
- setError("Please enter the 6-digit code");
936
+ if (otp.length < OTP_LENGTH) {
937
+ setError(t.codeRequired);
766
938
  return;
767
939
  }
768
- if (password.length < 8) {
769
- setError("Password must be at least 8 characters");
940
+ if (password.length < MIN_PASSWORD_LENGTH2) {
941
+ setError(t.passwordTooShort);
770
942
  return;
771
943
  }
772
944
  if (password !== confirmPassword) {
773
- setError("Passwords do not match");
945
+ setError(t.passwordMismatch);
774
946
  return;
775
947
  }
776
948
  setError(void 0);
949
+ setResendMessage(void 0);
777
950
  setIsResetting(true);
778
951
  try {
779
952
  const res = await fetch("/api/auth/email-otp/reset-password", {
@@ -783,14 +956,12 @@ function ResetPasswordForm({
783
956
  });
784
957
  if (!res.ok) {
785
958
  const body = await res.json().catch(() => null);
786
- setError(body?.message ?? "Failed to reset password");
959
+ setError(body?.message ?? t.resetFailed);
787
960
  return;
788
961
  }
789
962
  onSuccess?.();
790
963
  } catch (err) {
791
- setError(
792
- err instanceof Error ? err.message : "Failed to reset password"
793
- );
964
+ setError(err instanceof Error ? err.message : t.resetFailed);
794
965
  } finally {
795
966
  setIsResetting(false);
796
967
  }
@@ -804,125 +975,161 @@ function ResetPasswordForm({
804
975
  email,
805
976
  type: "forget-password"
806
977
  });
807
- setResendMessage("A new code has been sent to your inbox.");
978
+ setResendMessage(t.resent);
808
979
  } catch (err) {
809
- setError(err instanceof Error ? err.message : "Failed to resend code.");
980
+ setError(err instanceof Error ? err.message : t.resendFailed);
810
981
  } finally {
811
982
  setIsResending(false);
812
983
  }
813
984
  };
814
- return /* @__PURE__ */ jsxs8("div", { className: "space-y-8", children: [
815
- /* @__PURE__ */ jsxs8("div", { children: [
816
- /* @__PURE__ */ jsx8("h1", { className: "text-2xl font-bold tracking-tight", children: "Reset your password" }),
817
- /* @__PURE__ */ jsxs8("p", { className: "mt-1 text-sm text-muted-foreground", children: [
818
- "Enter the 6-digit code sent to",
819
- " ",
820
- /* @__PURE__ */ jsx8("span", { className: "font-medium text-foreground", children: email }),
821
- " and your new password."
822
- ] })
823
- ] }),
824
- error && /* @__PURE__ */ jsx8("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
825
- resendMessage && /* @__PURE__ */ jsx8("div", { className: "rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700", children: resendMessage }),
826
- /* @__PURE__ */ jsxs8("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
827
- /* @__PURE__ */ jsx8(
828
- "input",
985
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-6", children: [
986
+ /* @__PURE__ */ jsx10(AuthAlert, { children: error }),
987
+ /* @__PURE__ */ jsx10(AuthAlert, { tone: "success", children: resendMessage }),
988
+ /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
989
+ /* @__PURE__ */ jsx10(
990
+ AuthOtpField,
829
991
  {
830
- type: "text",
831
- inputMode: "numeric",
832
- pattern: "[0-9]*",
833
- maxLength: 6,
992
+ id: "reset-password-otp",
993
+ label: t.codePlaceholder,
834
994
  value: otp,
835
- onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
836
- placeholder: "000000",
837
- required: true,
995
+ onChange: setOtp,
838
996
  disabled: isResetting,
839
- autoComplete: "one-time-code",
840
- className: "flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-center text-lg tracking-[0.4em] font-mono ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
997
+ fieldClassName
841
998
  }
842
999
  ),
843
- /* @__PURE__ */ jsx8(
844
- "input",
1000
+ /* @__PURE__ */ jsx10(
1001
+ AuthField,
845
1002
  {
1003
+ label: t.passwordPlaceholder,
846
1004
  type: "password",
847
1005
  value: password,
848
1006
  onChange: (e) => setPassword(e.target.value),
849
- placeholder: "New password",
850
1007
  required: true,
851
1008
  disabled: isResetting,
852
1009
  autoComplete: "new-password",
853
- className: "flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
1010
+ description: t.passwordHint,
1011
+ invalid: tooShort
854
1012
  }
855
1013
  ),
856
- /* @__PURE__ */ jsx8(
857
- "input",
1014
+ /* @__PURE__ */ jsx10(
1015
+ AuthField,
858
1016
  {
1017
+ label: t.confirmPlaceholder,
859
1018
  type: "password",
860
1019
  value: confirmPassword,
861
1020
  onChange: (e) => setConfirmPassword(e.target.value),
862
- placeholder: "Confirm new password",
863
1021
  required: true,
864
1022
  disabled: isResetting,
865
1023
  autoComplete: "new-password",
866
- className: "flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
1024
+ invalid: mismatch
867
1025
  }
868
1026
  ),
869
- /* @__PURE__ */ jsx8(
870
- "button",
1027
+ /* @__PURE__ */ jsx10(
1028
+ AuthSubmit,
871
1029
  {
872
- type: "submit",
873
- disabled: isResetting || otp.length < 6 || !password,
874
- className: "inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50",
875
- children: isResetting ? "Resetting..." : "Reset password"
1030
+ pending: isResetting,
1031
+ disabled: otp.length < OTP_LENGTH || !password || !confirmPassword,
1032
+ pendingLabel: t.submitPending,
1033
+ className: submitClassName,
1034
+ children: t.submit
876
1035
  }
877
- ),
878
- /* @__PURE__ */ jsx8(
1036
+ )
1037
+ ] }),
1038
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
1039
+ /* @__PURE__ */ jsx10(
879
1040
  "button",
880
1041
  {
881
1042
  type: "button",
882
1043
  onClick: handleResend,
883
1044
  disabled: isResending,
884
- className: "inline-flex h-11 w-full items-center justify-center rounded-md border border-input bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
885
- children: isResending ? "Sending..." : "Resend code"
1045
+ className: "rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-55",
1046
+ children: isResending ? t.resendPending : t.resend
886
1047
  }
887
- )
888
- ] }),
889
- /* @__PURE__ */ jsxs8("p", { className: "text-center text-sm text-muted-foreground", children: [
890
- "Remember your password?",
891
- " ",
892
- /* @__PURE__ */ jsx8(
893
- "a",
894
- {
895
- href: loginUrl,
896
- className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
897
- children: "Sign in"
898
- }
899
- )
1048
+ ),
1049
+ /* @__PURE__ */ jsxs9("p", { children: [
1050
+ t.rememberPassword,
1051
+ " ",
1052
+ /* @__PURE__ */ jsx10(
1053
+ "a",
1054
+ {
1055
+ href: loginUrl,
1056
+ className: "rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1057
+ children: t.login
1058
+ }
1059
+ )
1060
+ ] })
900
1061
  ] })
901
1062
  ] });
902
1063
  }
1064
+ ResetPasswordForm.defaults = {
1065
+ title: DEFAULTS5.title,
1066
+ subtitle: DEFAULTS5.subtitle
1067
+ };
1068
+
1069
+ // src/components/auth-heading.tsx
1070
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1071
+ function AuthHeading({
1072
+ title,
1073
+ subtitle,
1074
+ titleClassName = "text-2xl font-semibold tracking-tight"
1075
+ }) {
1076
+ return /* @__PURE__ */ jsxs10("header", { className: "space-y-1.5 text-center", children: [
1077
+ /* @__PURE__ */ jsx11("h1", { className: `leading-tight text-foreground ${titleClassName}`, children: title }),
1078
+ subtitle && /* @__PURE__ */ jsx11("p", { className: "text-balance text-sm leading-relaxed text-muted-foreground", children: subtitle })
1079
+ ] });
1080
+ }
903
1081
 
904
1082
  // src/components/auth-layout.tsx
905
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1083
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
906
1084
  function AuthLayout({
907
1085
  logo,
1086
+ panel,
908
1087
  title,
909
1088
  subtitle,
1089
+ titleClassName,
910
1090
  children,
911
1091
  footer
912
1092
  }) {
913
- return /* @__PURE__ */ jsx9("div", { className: "flex min-h-dvh flex-col bg-background px-5 pb-10 pt-12 sm:items-center sm:bg-muted/40 sm:px-6 sm:py-16", children: /* @__PURE__ */ jsxs9("div", { className: "mx-auto w-full max-w-[420px]", children: [
914
- /* @__PURE__ */ jsxs9("div", { className: "mb-7 text-center sm:mb-6", children: [
915
- logo && /* @__PURE__ */ jsx9("div", { className: "mb-5 flex justify-center", children: logo }),
916
- /* @__PURE__ */ jsx9("p", { className: "text-sm font-medium tracking-[0.02em] text-foreground", children: title }),
917
- subtitle && /* @__PURE__ */ jsx9("p", { className: "mt-1 text-balance text-sm text-muted-foreground", children: subtitle })
1093
+ return /* @__PURE__ */ jsx12("div", { className: "flex min-h-dvh flex-col bg-background px-5 pb-10 pt-12 sm:items-center sm:bg-muted/40 sm:px-6 sm:py-16", children: /* @__PURE__ */ jsxs11("div", { className: `mx-auto w-full ${panel ? "max-w-4xl" : "max-w-[420px]"}`, children: [
1094
+ (logo || title) && /* @__PURE__ */ jsxs11("div", { className: "mb-7 space-y-5", children: [
1095
+ logo && /* @__PURE__ */ jsx12("div", { className: "flex justify-center", children: logo }),
1096
+ title && /* @__PURE__ */ jsx12(
1097
+ AuthHeading,
1098
+ {
1099
+ title,
1100
+ subtitle,
1101
+ titleClassName
1102
+ }
1103
+ )
918
1104
  ] }),
919
- /* @__PURE__ */ jsx9("div", { className: "sm:rounded-2xl sm:border sm:border-border/70 sm:bg-card sm:p-8 sm:shadow-lg sm:shadow-black/5", children }),
920
- footer && /* @__PURE__ */ jsx9("div", { className: "mt-6 text-center text-xs leading-relaxed text-muted-foreground", children: footer })
1105
+ /* @__PURE__ */ jsxs11(
1106
+ "div",
1107
+ {
1108
+ className: [
1109
+ "sm:rounded-lg sm:border sm:border-border/70 sm:bg-card sm:shadow-sm",
1110
+ panel ? "sm:overflow-hidden md:grid md:grid-cols-2" : ""
1111
+ ].filter(Boolean).join(" "),
1112
+ children: [
1113
+ panel && // Decorative: it must never carry information the form does not,
1114
+ // so it is hidden from assistive tech rather than described.
1115
+ /* @__PURE__ */ jsx12(
1116
+ "div",
1117
+ {
1118
+ "aria-hidden": "true",
1119
+ className: "relative hidden overflow-hidden bg-muted md:block [&>*]:absolute [&>*]:inset-0 [&>*]:h-full [&>*]:w-full [&>img]:object-cover",
1120
+ children: panel
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsx12("div", { className: "flex flex-col justify-center sm:p-8", children })
1124
+ ]
1125
+ }
1126
+ ),
1127
+ footer && /* @__PURE__ */ jsx12("div", { className: "mt-6 text-center text-xs leading-relaxed text-muted-foreground", children: footer })
921
1128
  ] }) });
922
1129
  }
923
1130
 
924
1131
  // src/components/invitation-notice.tsx
925
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1132
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
926
1133
  function defaultSupportEmail() {
927
1134
  if (typeof window === "undefined") return void 0;
928
1135
  const host = window.location.hostname;
@@ -942,15 +1149,15 @@ function InvitationNotice({
942
1149
  action
943
1150
  }) {
944
1151
  const contact = supportEmail ?? defaultSupportEmail();
945
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-8", children: [
946
- /* @__PURE__ */ jsxs10("div", { children: [
947
- /* @__PURE__ */ jsx10("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
948
- /* @__PURE__ */ jsx10("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
1152
+ return /* @__PURE__ */ jsxs12("div", { className: "space-y-8", children: [
1153
+ /* @__PURE__ */ jsxs12("div", { children: [
1154
+ /* @__PURE__ */ jsx13("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
1155
+ /* @__PURE__ */ jsx13("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
949
1156
  ] }),
950
- contact ? /* @__PURE__ */ jsxs10("p", { className: "text-sm text-muted-foreground", children: [
1157
+ contact ? /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
951
1158
  "\xC9crivez-nous \xE0",
952
1159
  " ",
953
- /* @__PURE__ */ jsx10(
1160
+ /* @__PURE__ */ jsx13(
954
1161
  "a",
955
1162
  {
956
1163
  href: `mailto:${contact}`,