@lalternative/auth 0.5.0 → 0.7.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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isInvitationFailure
3
- } from "./chunk-73BHM4PZ.js";
3
+ } from "./chunk-6URDBMQY.js";
4
4
 
5
5
  // src/hooks/use-session.ts
6
6
  function useSession(authClient) {
@@ -138,8 +138,38 @@ function AuthSubmit({
138
138
  // src/components/login-form.tsx
139
139
  import { useState as useState2 } from "react";
140
140
 
141
+ // src/components/auth-alert.tsx
142
+ import { jsx as jsx3 } from "react/jsx-runtime";
143
+ function AuthAlert({ children, tone = "error" }) {
144
+ return /* @__PURE__ */ jsx3(
145
+ "div",
146
+ {
147
+ role: tone === "error" ? "alert" : "status",
148
+ "aria-live": tone === "error" ? "assertive" : "polite",
149
+ className: children ? [
150
+ "rounded-xl border px-3.5 py-3 text-sm leading-snug",
151
+ 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"
152
+ ].join(" ") : void 0,
153
+ children
154
+ }
155
+ );
156
+ }
157
+
158
+ // src/components/auth-heading.tsx
159
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
160
+ function AuthHeading({
161
+ title,
162
+ subtitle,
163
+ titleClassName = "text-2xl font-semibold tracking-tight"
164
+ }) {
165
+ return /* @__PURE__ */ jsxs3("header", { className: "space-y-1.5 text-center", children: [
166
+ /* @__PURE__ */ jsx4("h1", { className: `leading-tight text-foreground ${titleClassName}`, children: title }),
167
+ subtitle && /* @__PURE__ */ jsx4("p", { className: "text-balance text-sm leading-relaxed text-muted-foreground", children: subtitle })
168
+ ] });
169
+ }
170
+
141
171
  // src/components/social-buttons.tsx
142
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
172
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
143
173
  var LABELS = {
144
174
  google: "Continuer avec Google",
145
175
  github: "Continuer avec GitHub"
@@ -153,60 +183,67 @@ function SocialButtons({
153
183
  }) {
154
184
  if (providers.length === 0) return null;
155
185
  const copy = { ...LABELS, ...labels };
156
- return /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
157
- /* @__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" })
186
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
187
+ /* @__PURE__ */ jsxs4("div", { "aria-hidden": "true", className: "flex items-center gap-3", children: [
188
+ /* @__PURE__ */ jsx5("span", { className: "h-px flex-1 bg-border" }),
189
+ /* @__PURE__ */ jsx5("span", { className: "text-[11px] uppercase tracking-[0.14em] text-muted-foreground", children: separator }),
190
+ /* @__PURE__ */ jsx5("span", { className: "h-px flex-1 bg-border" })
161
191
  ] }),
162
- /* @__PURE__ */ jsx3("div", { className: "space-y-2.5", children: providers.map((provider) => /* @__PURE__ */ jsxs3(
163
- "button",
192
+ /* @__PURE__ */ jsx5(
193
+ "div",
164
194
  {
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
- )) })
195
+ className: providers.length > 1 ? "grid gap-2.5 sm:grid-cols-2" : "grid gap-2.5",
196
+ children: providers.map((provider) => /* @__PURE__ */ jsxs4(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ onClick: () => onSelect(provider),
201
+ disabled,
202
+ "aria-label": copy[provider] ?? provider,
203
+ className: [
204
+ "flex h-[52px] w-full items-center justify-center gap-2.5 rounded-xl sm:h-11",
205
+ "border border-input bg-background text-base font-medium text-foreground sm:text-sm",
206
+ "transition-[background-color,border-color,transform] duration-150 ease-out",
207
+ "hover:border-foreground/20 hover:bg-accent active:translate-y-px",
208
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
209
+ "disabled:pointer-events-none disabled:opacity-55"
210
+ ].join(" "),
211
+ children: [
212
+ /* @__PURE__ */ jsx5(ProviderMark, { provider }),
213
+ /* @__PURE__ */ jsx5("span", { className: providers.length > 1 ? "sm:hidden" : "", children: copy[provider] ?? provider })
214
+ ]
215
+ },
216
+ provider
217
+ ))
218
+ }
219
+ )
183
220
  ] });
184
221
  }
185
222
  function ProviderMark({ provider }) {
186
223
  if (provider === "google") {
187
- return /* @__PURE__ */ jsxs3("svg", { width: "17", height: "17", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
188
- /* @__PURE__ */ jsx3(
224
+ return /* @__PURE__ */ jsxs4("svg", { width: "17", height: "17", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
225
+ /* @__PURE__ */ jsx5(
189
226
  "path",
190
227
  {
191
228
  fill: "#4285F4",
192
229
  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
230
  }
194
231
  ),
195
- /* @__PURE__ */ jsx3(
232
+ /* @__PURE__ */ jsx5(
196
233
  "path",
197
234
  {
198
235
  fill: "#34A853",
199
236
  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
237
  }
201
238
  ),
202
- /* @__PURE__ */ jsx3(
239
+ /* @__PURE__ */ jsx5(
203
240
  "path",
204
241
  {
205
242
  fill: "#FBBC05",
206
243
  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
244
  }
208
245
  ),
209
- /* @__PURE__ */ jsx3(
246
+ /* @__PURE__ */ jsx5(
210
247
  "path",
211
248
  {
212
249
  fill: "#EA4335",
@@ -215,11 +252,11 @@ function ProviderMark({ provider }) {
215
252
  )
216
253
  ] });
217
254
  }
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" }) });
255
+ return /* @__PURE__ */ jsx5("svg", { width: "17", height: "17", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("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
256
  }
220
257
 
221
258
  // src/components/login-form.tsx
222
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
259
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
223
260
  var DEFAULTS = {
224
261
  title: "Connexion",
225
262
  subtitle: "Content de te revoir. Entre tes identifiants pour continuer.",
@@ -248,13 +285,17 @@ function LoginForm({
248
285
  socialProviders = [],
249
286
  coreTokenUrl = "/api/auth/core-token",
250
287
  labels,
288
+ titleClassName,
289
+ error: externalError,
251
290
  authClient
252
291
  }) {
253
292
  const t = { ...DEFAULTS, ...labels };
254
293
  const [email, setEmail] = useState2("");
255
294
  const [password, setPassword] = useState2("");
256
- const [error, setError] = useState2();
295
+ const [ownError, setOwnError] = useState2();
257
296
  const [isPending, setIsPending] = useState2(false);
297
+ const error = ownError ?? externalError;
298
+ const setError = setOwnError;
258
299
  const handleSubmit = async (e) => {
259
300
  e.preventDefault();
260
301
  if (!email.trim()) {
@@ -300,21 +341,18 @@ function LoginForm({
300
341
  );
301
342
  }
302
343
  };
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",
344
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-7", children: [
345
+ /* @__PURE__ */ jsx6(
346
+ AuthHeading,
310
347
  {
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
348
+ title: t.title,
349
+ subtitle: t.subtitle,
350
+ titleClassName
314
351
  }
315
352
  ),
316
- /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
317
- /* @__PURE__ */ jsx4(
353
+ /* @__PURE__ */ jsx6(AuthAlert, { children: error }),
354
+ /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
355
+ /* @__PURE__ */ jsx6(
318
356
  AuthField,
319
357
  {
320
358
  label: t.emailPlaceholder,
@@ -330,7 +368,7 @@ function LoginForm({
330
368
  invalid: !!error
331
369
  }
332
370
  ),
333
- /* @__PURE__ */ jsx4(
371
+ /* @__PURE__ */ jsx6(
334
372
  AuthField,
335
373
  {
336
374
  label: t.passwordPlaceholder,
@@ -341,7 +379,7 @@ function LoginForm({
341
379
  disabled: isPending,
342
380
  autoComplete: "current-password",
343
381
  invalid: !!error,
344
- hint: /* @__PURE__ */ jsx4(
382
+ hint: /* @__PURE__ */ jsx6(
345
383
  "a",
346
384
  {
347
385
  href: forgotPasswordUrl,
@@ -351,7 +389,7 @@ function LoginForm({
351
389
  )
352
390
  }
353
391
  ),
354
- /* @__PURE__ */ jsx4(
392
+ /* @__PURE__ */ jsx6(
355
393
  AuthSubmit,
356
394
  {
357
395
  pending: isPending,
@@ -361,7 +399,7 @@ function LoginForm({
361
399
  }
362
400
  )
363
401
  ] }),
364
- /* @__PURE__ */ jsx4(
402
+ /* @__PURE__ */ jsx6(
365
403
  SocialButtons,
366
404
  {
367
405
  providers: socialProviders,
@@ -369,10 +407,10 @@ function LoginForm({
369
407
  disabled: isPending
370
408
  }
371
409
  ),
372
- /* @__PURE__ */ jsxs4("p", { className: "text-center text-sm text-muted-foreground", children: [
410
+ /* @__PURE__ */ jsxs5("p", { className: "text-center text-sm text-muted-foreground", children: [
373
411
  t.noAccount,
374
412
  " ",
375
- /* @__PURE__ */ jsx4(
413
+ /* @__PURE__ */ jsx6(
376
414
  "a",
377
415
  {
378
416
  href: registerUrl,
@@ -386,7 +424,7 @@ function LoginForm({
386
424
 
387
425
  // src/components/register-form.tsx
388
426
  import { useState as useState3 } from "react";
389
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
427
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
390
428
  var MIN_PASSWORD_LENGTH = 8;
391
429
  var DEFAULTS2 = {
392
430
  title: "Cr\xE9er un compte",
@@ -411,14 +449,18 @@ function RegisterForm({
411
449
  socialCallbackUrl = "/",
412
450
  socialProviders = [],
413
451
  labels,
452
+ titleClassName,
453
+ error: externalError,
414
454
  authClient
415
455
  }) {
416
456
  const t = { ...DEFAULTS2, ...labels };
417
457
  const [name, setName] = useState3("");
418
458
  const [email, setEmail] = useState3("");
419
459
  const [password, setPassword] = useState3("");
420
- const [error, setError] = useState3();
460
+ const [ownError, setOwnError] = useState3();
421
461
  const [isPending, setIsPending] = useState3(false);
462
+ const error = ownError ?? externalError;
463
+ const setError = setOwnError;
422
464
  const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH;
423
465
  const handleSubmit = async (e) => {
424
466
  e.preventDefault();
@@ -464,21 +506,18 @@ function RegisterForm({
464
506
  setError(err instanceof Error ? err.message : t.signUpFailed);
465
507
  }
466
508
  };
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",
509
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-7", children: [
510
+ /* @__PURE__ */ jsx7(
511
+ AuthHeading,
474
512
  {
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
513
+ title: t.title,
514
+ subtitle: t.subtitle,
515
+ titleClassName
478
516
  }
479
517
  ),
480
- /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
481
- /* @__PURE__ */ jsx5(
518
+ /* @__PURE__ */ jsx7(AuthAlert, { children: error }),
519
+ /* @__PURE__ */ jsxs6("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
520
+ /* @__PURE__ */ jsx7(
482
521
  AuthField,
483
522
  {
484
523
  label: t.namePlaceholder,
@@ -490,7 +529,7 @@ function RegisterForm({
490
529
  autoComplete: "name"
491
530
  }
492
531
  ),
493
- /* @__PURE__ */ jsx5(
532
+ /* @__PURE__ */ jsx7(
494
533
  AuthField,
495
534
  {
496
535
  label: t.emailPlaceholder,
@@ -505,7 +544,7 @@ function RegisterForm({
505
544
  spellCheck: false
506
545
  }
507
546
  ),
508
- /* @__PURE__ */ jsx5(
547
+ /* @__PURE__ */ jsx7(
509
548
  AuthField,
510
549
  {
511
550
  label: t.passwordPlaceholder,
@@ -519,10 +558,10 @@ function RegisterForm({
519
558
  invalid: tooShort
520
559
  }
521
560
  ),
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 })
561
+ /* @__PURE__ */ jsx7(AuthSubmit, { pending: isPending, pendingLabel: t.submitPending, children: t.submit }),
562
+ legal && /* @__PURE__ */ jsx7("p", { className: "text-center text-xs leading-relaxed text-muted-foreground", children: legal })
524
563
  ] }),
525
- /* @__PURE__ */ jsx5(
564
+ /* @__PURE__ */ jsx7(
526
565
  SocialButtons,
527
566
  {
528
567
  providers: socialProviders,
@@ -530,10 +569,10 @@ function RegisterForm({
530
569
  disabled: isPending
531
570
  }
532
571
  ),
533
- /* @__PURE__ */ jsxs5("p", { className: "text-center text-sm text-muted-foreground", children: [
572
+ /* @__PURE__ */ jsxs6("p", { className: "text-center text-sm text-muted-foreground", children: [
534
573
  t.haveAccount,
535
574
  " ",
536
- /* @__PURE__ */ jsx5(
575
+ /* @__PURE__ */ jsx7(
537
576
  "a",
538
577
  {
539
578
  href: loginUrl,
@@ -547,12 +586,83 @@ function RegisterForm({
547
586
 
548
587
  // src/components/verify-email-form.tsx
549
588
  import { useState as useState4 } from "react";
550
- import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
589
+
590
+ // src/components/auth-otp-field.tsx
591
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
592
+ var OTP_LENGTH = 6;
593
+ function AuthOtpField({
594
+ label,
595
+ value,
596
+ onChange,
597
+ disabled = false,
598
+ invalid = false,
599
+ id
600
+ }) {
601
+ return /* @__PURE__ */ jsxs7("div", { className: "space-y-2", children: [
602
+ /* @__PURE__ */ jsx8(
603
+ "label",
604
+ {
605
+ htmlFor: id,
606
+ className: "block text-sm font-medium leading-none text-foreground",
607
+ children: label
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsx8(
611
+ "input",
612
+ {
613
+ id,
614
+ type: "text",
615
+ inputMode: "numeric",
616
+ pattern: "[0-9]*",
617
+ maxLength: OTP_LENGTH,
618
+ value,
619
+ onChange: (e) => onChange(e.target.value.replace(/\D/g, "")),
620
+ required: true,
621
+ disabled,
622
+ autoComplete: "one-time-code",
623
+ "aria-invalid": invalid || void 0,
624
+ className: [
625
+ "h-[52px] w-full rounded-xl border bg-background px-4 sm:h-11",
626
+ "text-center font-mono text-lg tracking-[0.4em]",
627
+ "text-foreground",
628
+ "transition-[border-color,box-shadow] duration-150 ease-out",
629
+ "focus-visible:outline-none focus-visible:ring-[3px]",
630
+ invalid ? "border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20" : "border-input focus-visible:border-ring focus-visible:ring-ring/15",
631
+ "disabled:cursor-not-allowed disabled:opacity-60"
632
+ ].join(" ")
633
+ }
634
+ )
635
+ ] });
636
+ }
637
+
638
+ // src/components/verify-email-form.tsx
639
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
640
+ var DEFAULTS3 = {
641
+ title: "V\xE9rifie ton adresse",
642
+ subtitle: "Entre le code \xE0 6 chiffres envoy\xE9 \xE0",
643
+ subtitleNoEmail: "Entre le code \xE0 6 chiffres re\xE7u par e-mail.",
644
+ codePlaceholder: "Code de v\xE9rification",
645
+ submit: "V\xE9rifier",
646
+ submitPending: "V\xE9rification\u2026",
647
+ resend: "Renvoyer le code",
648
+ resendPending: "Envoi\u2026",
649
+ resent: "Un nouveau code vient de t'\xEAtre envoy\xE9.",
650
+ alreadyVerified: "Adresse d\xE9j\xE0 v\xE9rifi\xE9e ?",
651
+ login: "Se connecter",
652
+ codeRequired: "Entre le code \xE0 6 chiffres",
653
+ invalidCode: "Code invalide. R\xE9essaie.",
654
+ resendFailed: "L'envoi du code a \xE9chou\xE9",
655
+ missingEmail: "Adresse e-mail introuvable. Recommence l'inscription."
656
+ };
551
657
  function VerifyEmailForm({
552
658
  email,
553
659
  onSuccess,
660
+ loginUrl = "/login",
661
+ labels,
662
+ titleClassName,
554
663
  authClient
555
664
  }) {
665
+ const t = { ...DEFAULTS3, ...labels };
556
666
  const [otp, setOtp] = useState4("");
557
667
  const [isVerifying, setIsVerifying] = useState4(false);
558
668
  const [isResending, setIsResending] = useState4(false);
@@ -560,29 +670,29 @@ function VerifyEmailForm({
560
670
  const [error, setError] = useState4();
561
671
  const handleVerify = async (e) => {
562
672
  e.preventDefault();
563
- if (!otp.trim() || otp.length < 6) {
564
- setError("Please enter the 6-digit code");
673
+ if (otp.length < OTP_LENGTH) {
674
+ setError(t.codeRequired);
565
675
  return;
566
676
  }
567
677
  setError(void 0);
678
+ setResendMessage(void 0);
568
679
  setIsVerifying(true);
569
680
  try {
570
681
  const res = await authClient.emailOtp.verifyEmail({ email, otp });
571
- console.log("[verify-email] response:", JSON.stringify(res?.data), "error:", JSON.stringify(res?.error));
572
682
  if (res?.error) {
573
- setError(res.error.message ?? "Invalid code. Please try again.");
683
+ setError(res.error.message ?? t.invalidCode);
574
684
  return;
575
685
  }
576
686
  onSuccess?.();
577
687
  } catch (err) {
578
- setError(err instanceof Error ? err.message : "Invalid code. Please try again.");
688
+ setError(err instanceof Error ? err.message : t.invalidCode);
579
689
  } finally {
580
690
  setIsVerifying(false);
581
691
  }
582
692
  };
583
693
  const handleResend = async () => {
584
694
  if (!email) {
585
- setError("Email address is not available. Please register again.");
695
+ setError(t.missingEmail);
586
696
  return;
587
697
  }
588
698
  setError(void 0);
@@ -593,150 +703,167 @@ function VerifyEmailForm({
593
703
  email,
594
704
  type: "email-verification"
595
705
  });
596
- setResendMessage("A new code has been sent to your inbox.");
706
+ setResendMessage(t.resent);
597
707
  } catch (err) {
598
- setError(err instanceof Error ? err.message : "Failed to resend code.");
708
+ setError(err instanceof Error ? err.message : t.resendFailed);
599
709
  } finally {
600
710
  setIsResending(false);
601
711
  }
602
712
  };
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",
713
+ return /* @__PURE__ */ jsxs8("div", { className: "space-y-7", children: [
714
+ /* @__PURE__ */ jsx9(
715
+ AuthHeading,
716
+ {
717
+ title: t.title,
718
+ subtitle: email ? `${t.subtitle} ${email}.` : t.subtitleNoEmail,
719
+ titleClassName
720
+ }
721
+ ),
722
+ /* @__PURE__ */ jsx9(AuthAlert, { children: error }),
723
+ /* @__PURE__ */ jsx9(AuthAlert, { tone: "success", children: resendMessage }),
724
+ /* @__PURE__ */ jsxs8("form", { onSubmit: handleVerify, className: "space-y-5", noValidate: true, children: [
725
+ /* @__PURE__ */ jsx9(
726
+ AuthOtpField,
617
727
  {
618
- type: "text",
619
- inputMode: "numeric",
620
- pattern: "[0-9]*",
621
- maxLength: 6,
728
+ id: "verify-email-otp",
729
+ label: t.codePlaceholder,
622
730
  value: otp,
623
- onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
624
- placeholder: "000000",
625
- required: true,
626
- 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"
731
+ onChange: setOtp,
732
+ disabled: isVerifying
629
733
  }
630
734
  ),
631
- /* @__PURE__ */ jsx6(
632
- "button",
735
+ /* @__PURE__ */ jsx9(
736
+ AuthSubmit,
633
737
  {
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"
738
+ pending: isVerifying,
739
+ disabled: otp.length < OTP_LENGTH,
740
+ pendingLabel: t.submitPending,
741
+ children: t.submit
638
742
  }
639
- ),
640
- /* @__PURE__ */ jsx6(
743
+ )
744
+ ] }),
745
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
746
+ /* @__PURE__ */ jsx9(
641
747
  "button",
642
748
  {
643
749
  type: "button",
644
750
  onClick: handleResend,
645
751
  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"
752
+ 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",
753
+ children: isResending ? t.resendPending : t.resend
648
754
  }
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
- )
755
+ ),
756
+ /* @__PURE__ */ jsxs8("p", { children: [
757
+ t.alreadyVerified,
758
+ " ",
759
+ /* @__PURE__ */ jsx9(
760
+ "a",
761
+ {
762
+ href: loginUrl,
763
+ 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",
764
+ children: t.login
765
+ }
766
+ )
767
+ ] })
662
768
  ] })
663
769
  ] });
664
770
  }
665
771
 
666
772
  // src/components/forgot-password-form.tsx
667
773
  import { useState as useState5 } from "react";
668
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
774
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
775
+ var DEFAULTS4 = {
776
+ title: "Mot de passe oubli\xE9 ?",
777
+ subtitle: "Entre ton adresse e-mail et nous t'enverrons un code pour le r\xE9initialiser.",
778
+ emailPlaceholder: "Adresse e-mail",
779
+ submit: "Envoyer le code",
780
+ submitPending: "Envoi\u2026",
781
+ rememberPassword: "Tu t'en souviens finalement ?",
782
+ login: "Se connecter",
783
+ emailRequired: "Renseigne ton adresse e-mail",
784
+ sendFailed: "L'envoi du code a \xE9chou\xE9"
785
+ };
669
786
  function ForgotPasswordForm({
670
787
  onSuccess,
671
788
  loginUrl = "/login",
789
+ labels,
790
+ titleClassName,
672
791
  authClient
673
792
  }) {
793
+ const t = { ...DEFAULTS4, ...labels };
674
794
  const [email, setEmail] = useState5("");
675
795
  const [error, setError] = useState5();
676
796
  const [isPending, setIsPending] = useState5(false);
677
797
  const handleSubmit = async (e) => {
678
798
  e.preventDefault();
679
799
  if (!email.trim()) {
680
- setError("Please enter your email address");
800
+ setError(t.emailRequired);
681
801
  return;
682
802
  }
683
803
  setError(void 0);
684
804
  setIsPending(true);
685
805
  try {
686
806
  const res = await authClient.emailOtp.sendVerificationOtp({
687
- email,
807
+ email: email.trim(),
688
808
  type: "forget-password"
689
809
  });
690
810
  if (res?.error) {
691
- setError(res.error.message ?? "Failed to send reset code");
811
+ setError(res.error.message ?? t.sendFailed);
692
812
  return;
693
813
  }
694
- onSuccess?.(email);
814
+ onSuccess?.(email.trim());
695
815
  } catch (err) {
696
- setError(err instanceof Error ? err.message : "Failed to send reset code");
816
+ setError(err instanceof Error ? err.message : t.sendFailed);
697
817
  } finally {
698
818
  setIsPending(false);
699
819
  }
700
820
  };
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",
821
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-7", children: [
822
+ /* @__PURE__ */ jsx10(
823
+ AuthHeading,
824
+ {
825
+ title: t.title,
826
+ subtitle: t.subtitle,
827
+ titleClassName
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx10(AuthAlert, { children: error }),
831
+ /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
832
+ /* @__PURE__ */ jsx10(
833
+ AuthField,
710
834
  {
835
+ label: t.emailPlaceholder,
711
836
  type: "email",
837
+ inputMode: "email",
712
838
  value: email,
713
839
  onChange: (e) => setEmail(e.target.value),
714
- placeholder: "Email address",
715
840
  required: true,
716
841
  disabled: isPending,
717
842
  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"
843
+ autoCapitalize: "none",
844
+ spellCheck: false,
845
+ invalid: !!error
719
846
  }
720
847
  ),
721
- /* @__PURE__ */ jsx7(
722
- "button",
848
+ /* @__PURE__ */ jsx10(
849
+ AuthSubmit,
723
850
  {
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"
851
+ pending: isPending,
852
+ disabled: !email.trim(),
853
+ pendingLabel: t.submitPending,
854
+ children: t.submit
728
855
  }
729
856
  )
730
857
  ] }),
731
- /* @__PURE__ */ jsxs7("p", { className: "text-center text-sm text-muted-foreground", children: [
732
- "Remember your password?",
858
+ /* @__PURE__ */ jsxs9("p", { className: "text-center text-sm text-muted-foreground", children: [
859
+ t.rememberPassword,
733
860
  " ",
734
- /* @__PURE__ */ jsx7(
861
+ /* @__PURE__ */ jsx10(
735
862
  "a",
736
863
  {
737
864
  href: loginUrl,
738
- className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
739
- children: "Sign in"
865
+ 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",
866
+ children: t.login
740
867
  }
741
868
  )
742
869
  ] })
@@ -745,13 +872,37 @@ function ForgotPasswordForm({
745
872
 
746
873
  // src/components/reset-password-form.tsx
747
874
  import { useState as useState6 } from "react";
748
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
875
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
876
+ var MIN_PASSWORD_LENGTH2 = 8;
877
+ var DEFAULTS5 = {
878
+ title: "Nouveau mot de passe",
879
+ subtitle: "Entre le code \xE0 6 chiffres re\xE7u par e-mail et ton nouveau mot de passe.",
880
+ codePlaceholder: "Code de v\xE9rification",
881
+ passwordPlaceholder: "Nouveau mot de passe",
882
+ passwordHint: `Au moins ${MIN_PASSWORD_LENGTH2} caract\xE8res.`,
883
+ confirmPlaceholder: "Confirme le mot de passe",
884
+ submit: "R\xE9initialiser",
885
+ submitPending: "R\xE9initialisation\u2026",
886
+ resend: "Renvoyer le code",
887
+ resendPending: "Envoi\u2026",
888
+ resent: "Un nouveau code vient de t'\xEAtre envoy\xE9.",
889
+ rememberPassword: "Tu t'en souviens finalement ?",
890
+ login: "Se connecter",
891
+ codeRequired: "Entre le code \xE0 6 chiffres",
892
+ passwordTooShort: `Le mot de passe doit faire au moins ${MIN_PASSWORD_LENGTH2} caract\xE8res`,
893
+ passwordMismatch: "Les deux mots de passe ne correspondent pas",
894
+ resetFailed: "La r\xE9initialisation a \xE9chou\xE9",
895
+ resendFailed: "L'envoi du code a \xE9chou\xE9"
896
+ };
749
897
  function ResetPasswordForm({
750
898
  email,
751
899
  onSuccess,
752
900
  loginUrl = "/login",
901
+ labels,
902
+ titleClassName,
753
903
  authClient
754
904
  }) {
905
+ const t = { ...DEFAULTS5, ...labels };
755
906
  const [otp, setOtp] = useState6("");
756
907
  const [password, setPassword] = useState6("");
757
908
  const [confirmPassword, setConfirmPassword] = useState6("");
@@ -759,21 +910,24 @@ function ResetPasswordForm({
759
910
  const [isResetting, setIsResetting] = useState6(false);
760
911
  const [isResending, setIsResending] = useState6(false);
761
912
  const [resendMessage, setResendMessage] = useState6();
913
+ const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH2;
914
+ const mismatch = confirmPassword.length > 0 && confirmPassword !== password;
762
915
  const handleSubmit = async (e) => {
763
916
  e.preventDefault();
764
- if (!otp.trim() || otp.length < 6) {
765
- setError("Please enter the 6-digit code");
917
+ if (otp.length < OTP_LENGTH) {
918
+ setError(t.codeRequired);
766
919
  return;
767
920
  }
768
- if (password.length < 8) {
769
- setError("Password must be at least 8 characters");
921
+ if (password.length < MIN_PASSWORD_LENGTH2) {
922
+ setError(t.passwordTooShort);
770
923
  return;
771
924
  }
772
925
  if (password !== confirmPassword) {
773
- setError("Passwords do not match");
926
+ setError(t.passwordMismatch);
774
927
  return;
775
928
  }
776
929
  setError(void 0);
930
+ setResendMessage(void 0);
777
931
  setIsResetting(true);
778
932
  try {
779
933
  const res = await fetch("/api/auth/email-otp/reset-password", {
@@ -783,14 +937,12 @@ function ResetPasswordForm({
783
937
  });
784
938
  if (!res.ok) {
785
939
  const body = await res.json().catch(() => null);
786
- setError(body?.message ?? "Failed to reset password");
940
+ setError(body?.message ?? t.resetFailed);
787
941
  return;
788
942
  }
789
943
  onSuccess?.();
790
944
  } catch (err) {
791
- setError(
792
- err instanceof Error ? err.message : "Failed to reset password"
793
- );
945
+ setError(err instanceof Error ? err.message : t.resetFailed);
794
946
  } finally {
795
947
  setIsResetting(false);
796
948
  }
@@ -804,125 +956,145 @@ function ResetPasswordForm({
804
956
  email,
805
957
  type: "forget-password"
806
958
  });
807
- setResendMessage("A new code has been sent to your inbox.");
959
+ setResendMessage(t.resent);
808
960
  } catch (err) {
809
- setError(err instanceof Error ? err.message : "Failed to resend code.");
961
+ setError(err instanceof Error ? err.message : t.resendFailed);
810
962
  } finally {
811
963
  setIsResending(false);
812
964
  }
813
965
  };
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",
966
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-7", children: [
967
+ /* @__PURE__ */ jsx11(
968
+ AuthHeading,
969
+ {
970
+ title: t.title,
971
+ subtitle: t.subtitle,
972
+ titleClassName
973
+ }
974
+ ),
975
+ /* @__PURE__ */ jsx11(AuthAlert, { children: error }),
976
+ /* @__PURE__ */ jsx11(AuthAlert, { tone: "success", children: resendMessage }),
977
+ /* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-5", noValidate: true, children: [
978
+ /* @__PURE__ */ jsx11(
979
+ AuthOtpField,
829
980
  {
830
- type: "text",
831
- inputMode: "numeric",
832
- pattern: "[0-9]*",
833
- maxLength: 6,
981
+ id: "reset-password-otp",
982
+ label: t.codePlaceholder,
834
983
  value: otp,
835
- onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
836
- placeholder: "000000",
837
- required: true,
838
- 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"
984
+ onChange: setOtp,
985
+ disabled: isResetting
841
986
  }
842
987
  ),
843
- /* @__PURE__ */ jsx8(
844
- "input",
988
+ /* @__PURE__ */ jsx11(
989
+ AuthField,
845
990
  {
991
+ label: t.passwordPlaceholder,
846
992
  type: "password",
847
993
  value: password,
848
994
  onChange: (e) => setPassword(e.target.value),
849
- placeholder: "New password",
850
995
  required: true,
851
996
  disabled: isResetting,
852
997
  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"
998
+ description: t.passwordHint,
999
+ invalid: tooShort
854
1000
  }
855
1001
  ),
856
- /* @__PURE__ */ jsx8(
857
- "input",
1002
+ /* @__PURE__ */ jsx11(
1003
+ AuthField,
858
1004
  {
1005
+ label: t.confirmPlaceholder,
859
1006
  type: "password",
860
1007
  value: confirmPassword,
861
1008
  onChange: (e) => setConfirmPassword(e.target.value),
862
- placeholder: "Confirm new password",
863
1009
  required: true,
864
1010
  disabled: isResetting,
865
1011
  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"
1012
+ invalid: mismatch
867
1013
  }
868
1014
  ),
869
- /* @__PURE__ */ jsx8(
870
- "button",
1015
+ /* @__PURE__ */ jsx11(
1016
+ AuthSubmit,
871
1017
  {
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"
1018
+ pending: isResetting,
1019
+ disabled: otp.length < OTP_LENGTH || !password || !confirmPassword,
1020
+ pendingLabel: t.submitPending,
1021
+ children: t.submit
876
1022
  }
877
- ),
878
- /* @__PURE__ */ jsx8(
1023
+ )
1024
+ ] }),
1025
+ /* @__PURE__ */ jsxs10("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
1026
+ /* @__PURE__ */ jsx11(
879
1027
  "button",
880
1028
  {
881
1029
  type: "button",
882
1030
  onClick: handleResend,
883
1031
  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"
1032
+ 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",
1033
+ children: isResending ? t.resendPending : t.resend
886
1034
  }
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
- )
1035
+ ),
1036
+ /* @__PURE__ */ jsxs10("p", { children: [
1037
+ t.rememberPassword,
1038
+ " ",
1039
+ /* @__PURE__ */ jsx11(
1040
+ "a",
1041
+ {
1042
+ href: loginUrl,
1043
+ 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",
1044
+ children: t.login
1045
+ }
1046
+ )
1047
+ ] })
900
1048
  ] })
901
1049
  ] });
902
1050
  }
903
1051
 
904
1052
  // src/components/auth-layout.tsx
905
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1053
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
906
1054
  function AuthLayout({
907
1055
  logo,
908
- title,
909
- subtitle,
1056
+ panel,
910
1057
  children,
911
1058
  footer
912
1059
  }) {
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 })
918
- ] }),
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 })
921
- ] }) });
1060
+ 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(
1061
+ "div",
1062
+ {
1063
+ className: `mx-auto w-full ${panel ? "max-w-4xl" : "max-w-[420px]"}`,
1064
+ children: [
1065
+ /* @__PURE__ */ jsxs11(
1066
+ "div",
1067
+ {
1068
+ className: [
1069
+ "sm:overflow-hidden sm:rounded-2xl sm:border sm:border-border/70 sm:bg-card sm:shadow-lg sm:shadow-black/5",
1070
+ panel ? "md:grid md:grid-cols-2" : ""
1071
+ ].filter(Boolean).join(" "),
1072
+ children: [
1073
+ /* @__PURE__ */ jsxs11("div", { className: "sm:p-8", children: [
1074
+ logo && /* @__PURE__ */ jsx12("div", { className: "mb-7 flex justify-center", children: logo }),
1075
+ children
1076
+ ] }),
1077
+ panel && // Decorative: it must never carry information the form does not,
1078
+ // so it is hidden from assistive tech rather than described.
1079
+ /* @__PURE__ */ jsx12(
1080
+ "div",
1081
+ {
1082
+ "aria-hidden": "true",
1083
+ className: "hidden bg-muted md:block [&>*]:h-full [&>*]:w-full [&>img]:object-cover",
1084
+ children: panel
1085
+ }
1086
+ )
1087
+ ]
1088
+ }
1089
+ ),
1090
+ footer && /* @__PURE__ */ jsx12("div", { className: "mt-6 text-center text-xs leading-relaxed text-muted-foreground", children: footer })
1091
+ ]
1092
+ }
1093
+ ) });
922
1094
  }
923
1095
 
924
1096
  // src/components/invitation-notice.tsx
925
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1097
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
926
1098
  function defaultSupportEmail() {
927
1099
  if (typeof window === "undefined") return void 0;
928
1100
  const host = window.location.hostname;
@@ -942,15 +1114,15 @@ function InvitationNotice({
942
1114
  action
943
1115
  }) {
944
1116
  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 })
1117
+ return /* @__PURE__ */ jsxs12("div", { className: "space-y-8", children: [
1118
+ /* @__PURE__ */ jsxs12("div", { children: [
1119
+ /* @__PURE__ */ jsx13("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
1120
+ /* @__PURE__ */ jsx13("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
949
1121
  ] }),
950
- contact ? /* @__PURE__ */ jsxs10("p", { className: "text-sm text-muted-foreground", children: [
1122
+ contact ? /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
951
1123
  "\xC9crivez-nous \xE0",
952
1124
  " ",
953
- /* @__PURE__ */ jsx10(
1125
+ /* @__PURE__ */ jsx13(
954
1126
  "a",
955
1127
  {
956
1128
  href: `mailto:${contact}`,