@lalternative/auth 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,778 @@
1
+ import {
2
+ isInvitationFailure
3
+ } from "./chunk-73BHM4PZ.js";
4
+
5
+ // src/hooks/use-session.ts
6
+ function useSession(authClient) {
7
+ return authClient.useSession();
8
+ }
9
+ function useLogout(authClient) {
10
+ const signOut = async () => {
11
+ await authClient.signOut();
12
+ };
13
+ return signOut;
14
+ }
15
+
16
+ // src/components/login-form.tsx
17
+ import { useState } from "react";
18
+
19
+ // src/components/social-buttons.tsx
20
+ import { jsx, jsxs } from "react/jsx-runtime";
21
+ var LABELS = {
22
+ google: "Continue with Google",
23
+ github: "Continue with GitHub"
24
+ };
25
+ function SocialButtons({
26
+ providers,
27
+ onSelect,
28
+ disabled = false
29
+ }) {
30
+ if (providers.length === 0) return null;
31
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
32
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
33
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-input" }) }),
34
+ /* @__PURE__ */ jsx("div", { className: "relative flex justify-center", children: /* @__PURE__ */ jsx("span", { className: "bg-background px-2 text-xs uppercase tracking-wider text-muted-foreground", children: "or" }) })
35
+ ] }),
36
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: providers.map((provider) => /* @__PURE__ */ jsx(
37
+ "button",
38
+ {
39
+ type: "button",
40
+ onClick: () => onSelect(provider),
41
+ disabled,
42
+ 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",
43
+ children: LABELS[provider] ?? provider
44
+ },
45
+ provider
46
+ )) })
47
+ ] });
48
+ }
49
+
50
+ // src/components/login-form.tsx
51
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
52
+ function oauthErrorMessage(code) {
53
+ switch (code) {
54
+ case "account_not_linked":
55
+ return "This email is already registered with a password. Sign in with your password instead.";
56
+ case "access_denied":
57
+ return "Sign-in was cancelled.";
58
+ default:
59
+ return "Sign-in failed. Please try again.";
60
+ }
61
+ }
62
+ function LoginForm({
63
+ onSuccess,
64
+ registerUrl = "/register",
65
+ forgotPasswordUrl = "/forgot-password",
66
+ socialCallbackUrl = "/",
67
+ socialProviders = [],
68
+ coreTokenUrl = "/api/auth/core-token",
69
+ authClient
70
+ }) {
71
+ const [email, setEmail] = useState("");
72
+ const [password, setPassword] = useState("");
73
+ const [error, setError] = useState();
74
+ const [isPending, setIsPending] = useState(false);
75
+ const handleSubmit = async (e) => {
76
+ e.preventDefault();
77
+ if (!email.trim()) {
78
+ setError("Please enter your email address");
79
+ return;
80
+ }
81
+ if (!password) {
82
+ setError("Please enter your password");
83
+ return;
84
+ }
85
+ setError(void 0);
86
+ setIsPending(true);
87
+ try {
88
+ const res = await authClient.signIn.email({
89
+ email: email.trim(),
90
+ password
91
+ });
92
+ if (res?.error) {
93
+ setError(res.error.message ?? "Invalid email or password");
94
+ return;
95
+ }
96
+ if (coreTokenUrl) {
97
+ await fetch(coreTokenUrl, { credentials: "include" });
98
+ }
99
+ onSuccess?.();
100
+ } catch (err) {
101
+ setError(err instanceof Error ? err.message : "Invalid email or password");
102
+ } finally {
103
+ setIsPending(false);
104
+ }
105
+ };
106
+ const handleSocial = async (provider) => {
107
+ setError(void 0);
108
+ try {
109
+ await authClient.signIn.social({
110
+ provider,
111
+ callbackURL: socialCallbackUrl
112
+ });
113
+ } catch (err) {
114
+ setError(
115
+ err instanceof Error ? oauthErrorMessage(err.message) : oauthErrorMessage("")
116
+ );
117
+ }
118
+ };
119
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-8", children: [
120
+ /* @__PURE__ */ jsxs2("div", { children: [
121
+ /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold tracking-tight", children: "Sign in" }),
122
+ /* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-muted-foreground", children: "Welcome back. Enter your credentials to continue." })
123
+ ] }),
124
+ error && /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
125
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
126
+ /* @__PURE__ */ jsx2(
127
+ "input",
128
+ {
129
+ type: "email",
130
+ value: email,
131
+ onChange: (e) => setEmail(e.target.value),
132
+ placeholder: "Email address",
133
+ required: true,
134
+ disabled: isPending,
135
+ autoComplete: "email",
136
+ 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"
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
140
+ /* @__PURE__ */ jsx2(
141
+ "input",
142
+ {
143
+ type: "password",
144
+ value: password,
145
+ onChange: (e) => setPassword(e.target.value),
146
+ placeholder: "Password",
147
+ required: true,
148
+ disabled: isPending,
149
+ autoComplete: "current-password",
150
+ 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"
151
+ }
152
+ ),
153
+ /* @__PURE__ */ jsx2("div", { className: "text-right", children: /* @__PURE__ */ jsx2(
154
+ "a",
155
+ {
156
+ href: forgotPasswordUrl,
157
+ className: "text-xs text-muted-foreground underline underline-offset-4 hover:text-foreground",
158
+ children: "Forgot your password?"
159
+ }
160
+ ) })
161
+ ] }),
162
+ /* @__PURE__ */ jsx2(
163
+ "button",
164
+ {
165
+ type: "submit",
166
+ disabled: isPending || !email.trim() || !password,
167
+ 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",
168
+ children: isPending ? "Signing in..." : "Sign in"
169
+ }
170
+ )
171
+ ] }),
172
+ /* @__PURE__ */ jsx2(
173
+ SocialButtons,
174
+ {
175
+ providers: socialProviders,
176
+ onSelect: handleSocial,
177
+ disabled: isPending
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsxs2("p", { className: "text-center text-sm text-muted-foreground", children: [
181
+ "Don't have an account?",
182
+ " ",
183
+ /* @__PURE__ */ jsx2(
184
+ "a",
185
+ {
186
+ href: registerUrl,
187
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
188
+ children: "Sign up"
189
+ }
190
+ )
191
+ ] })
192
+ ] });
193
+ }
194
+
195
+ // src/components/register-form.tsx
196
+ import { useState as useState2 } from "react";
197
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
198
+ var MIN_PASSWORD_LENGTH = 8;
199
+ function RegisterForm({
200
+ onSuccess,
201
+ loginUrl = "/login",
202
+ legal,
203
+ socialCallbackUrl = "/",
204
+ socialProviders = [],
205
+ authClient
206
+ }) {
207
+ const [name, setName] = useState2("");
208
+ const [email, setEmail] = useState2("");
209
+ const [password, setPassword] = useState2("");
210
+ const [error, setError] = useState2();
211
+ const [isPending, setIsPending] = useState2(false);
212
+ const handleSubmit = async (e) => {
213
+ e.preventDefault();
214
+ if (!name.trim()) {
215
+ setError("Please enter your name");
216
+ return;
217
+ }
218
+ if (!email.trim()) {
219
+ setError("Please enter your email address");
220
+ return;
221
+ }
222
+ if (password.length < MIN_PASSWORD_LENGTH) {
223
+ setError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
224
+ return;
225
+ }
226
+ setError(void 0);
227
+ setIsPending(true);
228
+ try {
229
+ const res = await authClient.signUp.email({
230
+ name: name.trim(),
231
+ email: email.trim(),
232
+ password
233
+ });
234
+ if (res?.error) {
235
+ setError(res.error.message ?? "Could not create your account");
236
+ return;
237
+ }
238
+ onSuccess?.(email.trim());
239
+ } catch (err) {
240
+ setError(
241
+ err instanceof Error ? err.message : "Could not create your account"
242
+ );
243
+ } finally {
244
+ setIsPending(false);
245
+ }
246
+ };
247
+ const handleSocial = async (provider) => {
248
+ setError(void 0);
249
+ try {
250
+ await authClient.signIn.social({
251
+ provider,
252
+ callbackURL: socialCallbackUrl
253
+ });
254
+ } catch (err) {
255
+ setError(err instanceof Error ? err.message : "Sign-up failed");
256
+ }
257
+ };
258
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-8", children: [
259
+ /* @__PURE__ */ jsxs3("div", { children: [
260
+ /* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold tracking-tight", children: "Create an account" }),
261
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-muted-foreground", children: "We'll send you a code to confirm your email address." })
262
+ ] }),
263
+ error && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
264
+ /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
265
+ /* @__PURE__ */ jsx3(
266
+ "input",
267
+ {
268
+ type: "text",
269
+ value: name,
270
+ onChange: (e) => setName(e.target.value),
271
+ placeholder: "Full name",
272
+ required: true,
273
+ disabled: isPending,
274
+ autoComplete: "name",
275
+ 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"
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsx3(
279
+ "input",
280
+ {
281
+ type: "email",
282
+ value: email,
283
+ onChange: (e) => setEmail(e.target.value),
284
+ placeholder: "Email address",
285
+ required: true,
286
+ disabled: isPending,
287
+ autoComplete: "email",
288
+ 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"
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
292
+ /* @__PURE__ */ jsx3(
293
+ "input",
294
+ {
295
+ type: "password",
296
+ value: password,
297
+ onChange: (e) => setPassword(e.target.value),
298
+ placeholder: "Password",
299
+ required: true,
300
+ disabled: isPending,
301
+ autoComplete: "new-password",
302
+ "aria-describedby": "register-password-hint",
303
+ 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"
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsxs3("p", { id: "register-password-hint", className: "text-xs text-muted-foreground", children: [
307
+ "At least ",
308
+ MIN_PASSWORD_LENGTH,
309
+ " characters."
310
+ ] })
311
+ ] }),
312
+ /* @__PURE__ */ jsx3(
313
+ "button",
314
+ {
315
+ type: "submit",
316
+ disabled: isPending,
317
+ 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",
318
+ children: isPending ? "Creating account..." : "Create account"
319
+ }
320
+ ),
321
+ legal && /* @__PURE__ */ jsx3("div", { className: "text-center text-xs text-muted-foreground", children: legal })
322
+ ] }),
323
+ /* @__PURE__ */ jsx3(
324
+ SocialButtons,
325
+ {
326
+ providers: socialProviders,
327
+ onSelect: handleSocial,
328
+ disabled: isPending
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsxs3("p", { className: "text-center text-sm text-muted-foreground", children: [
332
+ "Already have an account?",
333
+ " ",
334
+ /* @__PURE__ */ jsx3(
335
+ "a",
336
+ {
337
+ href: loginUrl,
338
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
339
+ children: "Sign in"
340
+ }
341
+ )
342
+ ] })
343
+ ] });
344
+ }
345
+
346
+ // src/components/verify-email-form.tsx
347
+ import { useState as useState3 } from "react";
348
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
349
+ function VerifyEmailForm({
350
+ email,
351
+ onSuccess,
352
+ authClient
353
+ }) {
354
+ const [otp, setOtp] = useState3("");
355
+ const [isVerifying, setIsVerifying] = useState3(false);
356
+ const [isResending, setIsResending] = useState3(false);
357
+ const [resendMessage, setResendMessage] = useState3();
358
+ const [error, setError] = useState3();
359
+ const handleVerify = async (e) => {
360
+ e.preventDefault();
361
+ if (!otp.trim() || otp.length < 6) {
362
+ setError("Please enter the 6-digit code");
363
+ return;
364
+ }
365
+ setError(void 0);
366
+ setIsVerifying(true);
367
+ try {
368
+ const res = await authClient.emailOtp.verifyEmail({ email, otp });
369
+ console.log("[verify-email] response:", JSON.stringify(res?.data), "error:", JSON.stringify(res?.error));
370
+ if (res?.error) {
371
+ setError(res.error.message ?? "Invalid code. Please try again.");
372
+ return;
373
+ }
374
+ onSuccess?.();
375
+ } catch (err) {
376
+ setError(err instanceof Error ? err.message : "Invalid code. Please try again.");
377
+ } finally {
378
+ setIsVerifying(false);
379
+ }
380
+ };
381
+ const handleResend = async () => {
382
+ if (!email) {
383
+ setError("Email address is not available. Please register again.");
384
+ return;
385
+ }
386
+ setError(void 0);
387
+ setResendMessage(void 0);
388
+ setIsResending(true);
389
+ try {
390
+ await authClient.emailOtp.sendVerificationOtp({
391
+ email,
392
+ type: "email-verification"
393
+ });
394
+ setResendMessage("A new code has been sent to your inbox.");
395
+ } catch (err) {
396
+ setError(err instanceof Error ? err.message : "Failed to resend code.");
397
+ } finally {
398
+ setIsResending(false);
399
+ }
400
+ };
401
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-8", children: [
402
+ /* @__PURE__ */ jsxs4("div", { children: [
403
+ /* @__PURE__ */ jsx4("h1", { className: "text-2xl font-bold tracking-tight", children: "Verify your email" }),
404
+ /* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-muted-foreground", children: email ? /* @__PURE__ */ jsxs4(Fragment, { children: [
405
+ "Enter the 6-digit code sent to",
406
+ " ",
407
+ /* @__PURE__ */ jsx4("span", { className: "font-medium text-foreground", children: email })
408
+ ] }) : "Enter the 6-digit code sent to your email" })
409
+ ] }),
410
+ error && /* @__PURE__ */ jsx4("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
411
+ resendMessage && /* @__PURE__ */ jsx4("div", { className: "rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700", children: resendMessage }),
412
+ /* @__PURE__ */ jsxs4("form", { onSubmit: handleVerify, className: "space-y-4", noValidate: true, children: [
413
+ /* @__PURE__ */ jsx4(
414
+ "input",
415
+ {
416
+ type: "text",
417
+ inputMode: "numeric",
418
+ pattern: "[0-9]*",
419
+ maxLength: 6,
420
+ value: otp,
421
+ onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
422
+ placeholder: "000000",
423
+ required: true,
424
+ disabled: isVerifying,
425
+ autoComplete: "one-time-code",
426
+ 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"
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx4(
430
+ "button",
431
+ {
432
+ type: "submit",
433
+ disabled: isVerifying || otp.length < 6,
434
+ 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",
435
+ children: isVerifying ? "Verifying..." : "Verify email"
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx4(
439
+ "button",
440
+ {
441
+ type: "button",
442
+ onClick: handleResend,
443
+ disabled: isResending,
444
+ 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",
445
+ children: isResending ? "Sending..." : "Resend code"
446
+ }
447
+ )
448
+ ] }),
449
+ /* @__PURE__ */ jsxs4("p", { className: "text-center text-sm text-muted-foreground", children: [
450
+ "Already verified?",
451
+ " ",
452
+ /* @__PURE__ */ jsx4(
453
+ "a",
454
+ {
455
+ href: "/login",
456
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
457
+ children: "Sign in"
458
+ }
459
+ )
460
+ ] })
461
+ ] });
462
+ }
463
+
464
+ // src/components/forgot-password-form.tsx
465
+ import { useState as useState4 } from "react";
466
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
467
+ function ForgotPasswordForm({
468
+ onSuccess,
469
+ loginUrl = "/login",
470
+ authClient
471
+ }) {
472
+ const [email, setEmail] = useState4("");
473
+ const [error, setError] = useState4();
474
+ const [isPending, setIsPending] = useState4(false);
475
+ const handleSubmit = async (e) => {
476
+ e.preventDefault();
477
+ if (!email.trim()) {
478
+ setError("Please enter your email address");
479
+ return;
480
+ }
481
+ setError(void 0);
482
+ setIsPending(true);
483
+ try {
484
+ const res = await authClient.emailOtp.sendVerificationOtp({
485
+ email,
486
+ type: "forget-password"
487
+ });
488
+ if (res?.error) {
489
+ setError(res.error.message ?? "Failed to send reset code");
490
+ return;
491
+ }
492
+ onSuccess?.(email);
493
+ } catch (err) {
494
+ setError(err instanceof Error ? err.message : "Failed to send reset code");
495
+ } finally {
496
+ setIsPending(false);
497
+ }
498
+ };
499
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-8", children: [
500
+ /* @__PURE__ */ jsxs5("div", { children: [
501
+ /* @__PURE__ */ jsx5("h1", { className: "text-2xl font-bold tracking-tight", children: "Forgot your password?" }),
502
+ /* @__PURE__ */ jsx5("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." })
503
+ ] }),
504
+ error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
505
+ /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
506
+ /* @__PURE__ */ jsx5(
507
+ "input",
508
+ {
509
+ type: "email",
510
+ value: email,
511
+ onChange: (e) => setEmail(e.target.value),
512
+ placeholder: "Email address",
513
+ required: true,
514
+ disabled: isPending,
515
+ autoComplete: "email",
516
+ 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"
517
+ }
518
+ ),
519
+ /* @__PURE__ */ jsx5(
520
+ "button",
521
+ {
522
+ type: "submit",
523
+ disabled: isPending || !email.trim(),
524
+ 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",
525
+ children: isPending ? "Sending..." : "Send reset code"
526
+ }
527
+ )
528
+ ] }),
529
+ /* @__PURE__ */ jsxs5("p", { className: "text-center text-sm text-muted-foreground", children: [
530
+ "Remember your password?",
531
+ " ",
532
+ /* @__PURE__ */ jsx5(
533
+ "a",
534
+ {
535
+ href: loginUrl,
536
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
537
+ children: "Sign in"
538
+ }
539
+ )
540
+ ] })
541
+ ] });
542
+ }
543
+
544
+ // src/components/reset-password-form.tsx
545
+ import { useState as useState5 } from "react";
546
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
547
+ function ResetPasswordForm({
548
+ email,
549
+ onSuccess,
550
+ loginUrl = "/login",
551
+ authClient
552
+ }) {
553
+ const [otp, setOtp] = useState5("");
554
+ const [password, setPassword] = useState5("");
555
+ const [confirmPassword, setConfirmPassword] = useState5("");
556
+ const [error, setError] = useState5();
557
+ const [isResetting, setIsResetting] = useState5(false);
558
+ const [isResending, setIsResending] = useState5(false);
559
+ const [resendMessage, setResendMessage] = useState5();
560
+ const handleSubmit = async (e) => {
561
+ e.preventDefault();
562
+ if (!otp.trim() || otp.length < 6) {
563
+ setError("Please enter the 6-digit code");
564
+ return;
565
+ }
566
+ if (password.length < 8) {
567
+ setError("Password must be at least 8 characters");
568
+ return;
569
+ }
570
+ if (password !== confirmPassword) {
571
+ setError("Passwords do not match");
572
+ return;
573
+ }
574
+ setError(void 0);
575
+ setIsResetting(true);
576
+ try {
577
+ const res = await fetch("/api/auth/email-otp/reset-password", {
578
+ method: "POST",
579
+ headers: { "Content-Type": "application/json" },
580
+ body: JSON.stringify({ email, otp, password })
581
+ });
582
+ if (!res.ok) {
583
+ const body = await res.json().catch(() => null);
584
+ setError(body?.message ?? "Failed to reset password");
585
+ return;
586
+ }
587
+ onSuccess?.();
588
+ } catch (err) {
589
+ setError(
590
+ err instanceof Error ? err.message : "Failed to reset password"
591
+ );
592
+ } finally {
593
+ setIsResetting(false);
594
+ }
595
+ };
596
+ const handleResend = async () => {
597
+ setError(void 0);
598
+ setResendMessage(void 0);
599
+ setIsResending(true);
600
+ try {
601
+ await authClient.emailOtp.sendVerificationOtp({
602
+ email,
603
+ type: "forget-password"
604
+ });
605
+ setResendMessage("A new code has been sent to your inbox.");
606
+ } catch (err) {
607
+ setError(err instanceof Error ? err.message : "Failed to resend code.");
608
+ } finally {
609
+ setIsResending(false);
610
+ }
611
+ };
612
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-8", children: [
613
+ /* @__PURE__ */ jsxs6("div", { children: [
614
+ /* @__PURE__ */ jsx6("h1", { className: "text-2xl font-bold tracking-tight", children: "Reset your password" }),
615
+ /* @__PURE__ */ jsxs6("p", { className: "mt-1 text-sm text-muted-foreground", children: [
616
+ "Enter the 6-digit code sent to",
617
+ " ",
618
+ /* @__PURE__ */ jsx6("span", { className: "font-medium text-foreground", children: email }),
619
+ " and your new password."
620
+ ] })
621
+ ] }),
622
+ error && /* @__PURE__ */ jsx6("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
623
+ 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 }),
624
+ /* @__PURE__ */ jsxs6("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
625
+ /* @__PURE__ */ jsx6(
626
+ "input",
627
+ {
628
+ type: "text",
629
+ inputMode: "numeric",
630
+ pattern: "[0-9]*",
631
+ maxLength: 6,
632
+ value: otp,
633
+ onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
634
+ placeholder: "000000",
635
+ required: true,
636
+ disabled: isResetting,
637
+ autoComplete: "one-time-code",
638
+ 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"
639
+ }
640
+ ),
641
+ /* @__PURE__ */ jsx6(
642
+ "input",
643
+ {
644
+ type: "password",
645
+ value: password,
646
+ onChange: (e) => setPassword(e.target.value),
647
+ placeholder: "New password",
648
+ required: true,
649
+ disabled: isResetting,
650
+ autoComplete: "new-password",
651
+ 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"
652
+ }
653
+ ),
654
+ /* @__PURE__ */ jsx6(
655
+ "input",
656
+ {
657
+ type: "password",
658
+ value: confirmPassword,
659
+ onChange: (e) => setConfirmPassword(e.target.value),
660
+ placeholder: "Confirm new password",
661
+ required: true,
662
+ disabled: isResetting,
663
+ autoComplete: "new-password",
664
+ 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"
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsx6(
668
+ "button",
669
+ {
670
+ type: "submit",
671
+ disabled: isResetting || otp.length < 6 || !password,
672
+ 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",
673
+ children: isResetting ? "Resetting..." : "Reset password"
674
+ }
675
+ ),
676
+ /* @__PURE__ */ jsx6(
677
+ "button",
678
+ {
679
+ type: "button",
680
+ onClick: handleResend,
681
+ disabled: isResending,
682
+ 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",
683
+ children: isResending ? "Sending..." : "Resend code"
684
+ }
685
+ )
686
+ ] }),
687
+ /* @__PURE__ */ jsxs6("p", { className: "text-center text-sm text-muted-foreground", children: [
688
+ "Remember your password?",
689
+ " ",
690
+ /* @__PURE__ */ jsx6(
691
+ "a",
692
+ {
693
+ href: loginUrl,
694
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
695
+ children: "Sign in"
696
+ }
697
+ )
698
+ ] })
699
+ ] });
700
+ }
701
+
702
+ // src/components/auth-layout.tsx
703
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
704
+ function AuthLayout({
705
+ logo,
706
+ title,
707
+ subtitle,
708
+ children,
709
+ footer
710
+ }) {
711
+ return /* @__PURE__ */ jsx7("div", { className: "flex min-h-screen items-center justify-center bg-background", children: /* @__PURE__ */ jsxs7("div", { className: "w-full max-w-md", children: [
712
+ /* @__PURE__ */ jsxs7("div", { className: "mb-10 text-center", children: [
713
+ logo && /* @__PURE__ */ jsx7("div", { className: "mb-6 flex justify-center", children: logo }),
714
+ /* @__PURE__ */ jsx7("h1", { className: "text-[32px] font-light tracking-tight", children: title }),
715
+ subtitle && /* @__PURE__ */ jsx7("p", { className: "mt-2 text-sm text-muted-foreground", children: subtitle })
716
+ ] }),
717
+ /* @__PURE__ */ jsx7("div", { className: "rounded-xl border bg-card p-8 shadow-sm", children }),
718
+ footer && /* @__PURE__ */ jsx7("div", { className: "mt-6 text-center text-xs text-muted-foreground", children: footer })
719
+ ] }) });
720
+ }
721
+
722
+ // src/components/invitation-notice.tsx
723
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
724
+ function defaultSupportEmail() {
725
+ if (typeof window === "undefined") return void 0;
726
+ const host = window.location.hostname;
727
+ if (!host || host === "localhost" || /^[\d.]+$/.test(host)) return void 0;
728
+ const apex = host.split(".").slice(-2).join(".");
729
+ return `contact@${apex}`;
730
+ }
731
+ var REASON_MESSAGE = {
732
+ expired: "Cette invitation a expir\xE9.",
733
+ claimed: "Cette invitation a d\xE9j\xE0 \xE9t\xE9 utilis\xE9e.",
734
+ unknown: "Ce lien d'invitation n'est pas valide."
735
+ };
736
+ function InvitationNotice({
737
+ reason = "unknown",
738
+ supportEmail,
739
+ title = "Invitation indisponible",
740
+ action
741
+ }) {
742
+ const contact = supportEmail ?? defaultSupportEmail();
743
+ return /* @__PURE__ */ jsxs8("div", { className: "space-y-8", children: [
744
+ /* @__PURE__ */ jsxs8("div", { children: [
745
+ /* @__PURE__ */ jsx8("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
746
+ /* @__PURE__ */ jsx8("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
747
+ ] }),
748
+ contact ? /* @__PURE__ */ jsxs8("p", { className: "text-sm text-muted-foreground", children: [
749
+ "\xC9crivez-nous \xE0",
750
+ " ",
751
+ /* @__PURE__ */ jsx8(
752
+ "a",
753
+ {
754
+ href: `mailto:${contact}`,
755
+ className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
756
+ children: contact
757
+ }
758
+ ),
759
+ " ",
760
+ "pour en recevoir une nouvelle."
761
+ ] }) : null,
762
+ action
763
+ ] });
764
+ }
765
+ export {
766
+ AuthLayout,
767
+ ForgotPasswordForm,
768
+ InvitationNotice,
769
+ LoginForm,
770
+ RegisterForm,
771
+ ResetPasswordForm,
772
+ SocialButtons,
773
+ VerifyEmailForm,
774
+ isInvitationFailure,
775
+ useLogout,
776
+ useSession
777
+ };
778
+ //# sourceMappingURL=index.js.map