@lalternative/auth 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -3
- package/dist/client.d.ts +9 -3
- package/dist/client.js +11 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +56 -4
- package/dist/index.js +220 -46
- package/dist/index.js.map +1 -1
- package/dist/{invitation-DuoHHBNA.d.ts → invitation-p9JYNUiB.d.ts} +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +40 -2
- package/dist/server.js.map +1 -1
- package/dist/{types-B7Ranu_a.d.ts → types-D6eIAL25.d.ts} +94 -4
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Shared [Better Auth](https://better-auth.com) wrapper for L'Alternative apps.
|
|
4
4
|
|
|
5
|
-
Provides platform auth defaults (email-OTP + admin plugins
|
|
6
|
-
and the auth UI forms (login, register, verify-email,
|
|
7
|
-
auth layout).
|
|
5
|
+
Provides platform auth defaults (email-OTP + admin plugins, opt-in magic link),
|
|
6
|
+
a React client, and the auth UI forms (login, register, verify-email,
|
|
7
|
+
forgot/reset password, magic link, auth layout).
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -36,6 +36,68 @@ export const authClient = createPlatformAuthClient({ baseURL })
|
|
|
36
36
|
import { LoginForm, RegisterForm, SocialButtons, VerifyEmailForm, ForgotPasswordForm, ResetPasswordForm, AuthLayout, useSession, useLogout } from "@lalternative/auth"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Magic link
|
|
40
|
+
|
|
41
|
+
Passwordless sign-in by emailed link. Off unless `magicLink` is passed — the
|
|
42
|
+
`/sign-in/magic-link` route is only mounted when it is, so an app that does not
|
|
43
|
+
render the form does not expose the endpoint either.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export const auth = createPlatformAuth({
|
|
47
|
+
// …
|
|
48
|
+
magicLink: {
|
|
49
|
+
expiresIn: 300, // default
|
|
50
|
+
allowSignUp: false, // default
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`allowSignUp` is off on purpose. `createPlatformAuth` requires a verified email
|
|
56
|
+
and can be put behind an invite-only beta, and both of those gates gate
|
|
57
|
+
`/sign-up/email` — a magic link that creates the account walks past them. Turn
|
|
58
|
+
it on only where sign-up is open anyway.
|
|
59
|
+
|
|
60
|
+
The mailer receives the ready-made URL rather than an OTP:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const mailer: PlatformAuthMailer = async ({ to, subject, html, type, url }) => {
|
|
64
|
+
// type === "magic-link", url is signed and points at the app's callback
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { MagicLinkForm } from "@lalternative/auth"
|
|
70
|
+
|
|
71
|
+
<MagicLinkForm authClient={authClient} callbackUrl="/dashboard" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The form confirms that a link was sent, never that the account exists: Better
|
|
75
|
+
Auth answers the send identically either way, and only refuses at
|
|
76
|
+
`/magic-link/verify`. Distinguishing the two in the form would tell an
|
|
77
|
+
anonymous caller which addresses are registered.
|
|
78
|
+
|
|
79
|
+
That means **every** failure past the send comes back on the callback as
|
|
80
|
+
`?error=`, with no component mounted to have caught it — the same shape as an
|
|
81
|
+
OAuth round-trip, and read the same way:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { initialMagicLinkError, isMagicLinkError } from "@lalternative/auth"
|
|
85
|
+
|
|
86
|
+
const error = initialMagicLinkError() // undefined unless the code is a magic-link one
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That `?error=` lands on `errorCallbackUrl`, which defaults to the page the form
|
|
90
|
+
is on — the one place asking for another link is possible. Better Auth would
|
|
91
|
+
otherwise fall back to `callbackUrl`, typically a signed-in destination, where
|
|
92
|
+
an auth guard bounces the visitor and drops the error on the way, leaving an
|
|
93
|
+
expired link looking like nothing happened at all.
|
|
94
|
+
|
|
95
|
+
`initialMagicLinkError` ignores OAuth's codes, and `initialOAuthError` is
|
|
96
|
+
unchanged, so a screen offering both flows reads the one `?error=` against each
|
|
97
|
+
vocabulary without either claiming the other's failures. `INVALID_TOKEN` covers
|
|
98
|
+
expiry and reuse alike: the token is consumed atomically on first use, so a link
|
|
99
|
+
followed twice is indistinguishable from one that timed out.
|
|
100
|
+
|
|
39
101
|
### Invitations
|
|
40
102
|
|
|
41
103
|
An invitation link lands on the app's own sign-up page
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAuthClient } from 'better-auth/react';
|
|
2
|
-
import { d as AuthClientSurface, P as PlatformAuthClientConfig } from './types-
|
|
2
|
+
import { d as AuthClientSurface, j as MagicLinkClientSurface, P as PlatformAuthClientConfig } from './types-D6eIAL25.js';
|
|
3
3
|
import 'better-auth';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,11 +9,17 @@ import 'better-auth';
|
|
|
9
9
|
* reaches into zod's internals), so the surface the auth screens call is
|
|
10
10
|
* declared by hand in AuthClientSurface and intersected with the rest of the
|
|
11
11
|
* client. Keep it in sync with the plugins enabled below.
|
|
12
|
+
*
|
|
13
|
+
* signIn carries both halves: the client always mounts magicLinkClient, since
|
|
14
|
+
* which methods exist client-side costs nothing — whether the route answers is
|
|
15
|
+
* decided server-side by passing `magicLink` to createPlatformAuth.
|
|
12
16
|
*/
|
|
13
|
-
type PlatformAuthClient =
|
|
17
|
+
type PlatformAuthClient = Omit<AuthClientSurface, "signIn"> & {
|
|
18
|
+
signIn: AuthClientSurface["signIn"] & MagicLinkClientSurface["signIn"];
|
|
19
|
+
} & Omit<ReturnType<typeof createAuthClient>, keyof AuthClientSurface | keyof MagicLinkClientSurface>;
|
|
14
20
|
/**
|
|
15
21
|
* Creates a Better Auth client for React usage.
|
|
16
|
-
* Provides useSession() and the email-OTP / admin plugin methods.
|
|
22
|
+
* Provides useSession() and the email-OTP / magic-link / admin plugin methods.
|
|
17
23
|
*/
|
|
18
24
|
declare function createPlatformAuthClient(config?: PlatformAuthClientConfig): PlatformAuthClient;
|
|
19
25
|
|
package/dist/client.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import { createAuthClient } from "better-auth/react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
emailOTPClient,
|
|
5
|
+
adminClient,
|
|
6
|
+
magicLinkClient
|
|
7
|
+
} from "better-auth/client/plugins";
|
|
4
8
|
function createPlatformAuthClient(config) {
|
|
5
9
|
return createAuthClient({
|
|
6
10
|
baseURL: config?.baseURL ?? (typeof window !== "undefined" ? window.location.origin : "http://localhost:3000"),
|
|
7
|
-
plugins: [
|
|
11
|
+
plugins: [
|
|
12
|
+
emailOTPClient(),
|
|
13
|
+
magicLinkClient(),
|
|
14
|
+
adminClient(),
|
|
15
|
+
...config?.plugins ?? []
|
|
16
|
+
]
|
|
8
17
|
});
|
|
9
18
|
}
|
|
10
19
|
export {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createAuthClient } from \"better-auth/react\"\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createAuthClient } from \"better-auth/react\"\nimport {\n emailOTPClient,\n adminClient,\n magicLinkClient,\n} from \"better-auth/client/plugins\"\nimport type {\n AuthClientSurface,\n MagicLinkClientSurface,\n PlatformAuthClientConfig,\n} from \"./types\"\n\n/**\n * A Better Auth React client carrying the platform plugins.\n *\n * The concrete inferred type cannot be named in a published .d.ts (TS2742 — it\n * reaches into zod's internals), so the surface the auth screens call is\n * declared by hand in AuthClientSurface and intersected with the rest of the\n * client. Keep it in sync with the plugins enabled below.\n *\n * signIn carries both halves: the client always mounts magicLinkClient, since\n * which methods exist client-side costs nothing — whether the route answers is\n * decided server-side by passing `magicLink` to createPlatformAuth.\n */\nexport type PlatformAuthClient = Omit<AuthClientSurface, \"signIn\"> & {\n signIn: AuthClientSurface[\"signIn\"] & MagicLinkClientSurface[\"signIn\"]\n} & Omit<\n ReturnType<typeof createAuthClient>,\n keyof AuthClientSurface | keyof MagicLinkClientSurface\n >\n\n/**\n * Creates a Better Auth client for React usage.\n * Provides useSession() and the email-OTP / magic-link / admin plugin methods.\n */\nexport function createPlatformAuthClient(\n config?: PlatformAuthClientConfig,\n): PlatformAuthClient {\n return createAuthClient({\n baseURL:\n config?.baseURL ??\n (typeof window !== \"undefined\"\n ? window.location.origin\n : \"http://localhost:3000\"),\n plugins: [\n emailOTPClient(),\n magicLinkClient(),\n adminClient(),\n ...(config?.plugins ?? []),\n ],\n }) as unknown as PlatformAuthClient\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8BA,SAAS,yBACd,QACoB;AACpB,SAAO,iBAAiB;AAAA,IACtB,SACE,QAAQ,YACP,OAAO,WAAW,cACf,OAAO,SAAS,SAChB;AAAA,IACN,SAAS;AAAA,MACP,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,GAAI,QAAQ,WAAW,CAAC;AAAA,IAC1B;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-
|
|
2
|
-
export { d as AuthClientSurface, e as AuthInviteProps, f as AuthNavProps, g as AuthThemeProps, h as InvitationFailure, i as LoginFormLabels, P as PlatformAuthClientConfig,
|
|
1
|
+
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, M as MagicLinkFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-D6eIAL25.js';
|
|
2
|
+
export { d as AuthClientSurface, e as AuthInviteProps, f as AuthNavProps, g as AuthThemeProps, h as InvitationFailure, i as LoginFormLabels, j as MagicLinkClientSurface, k as MagicLinkConfig, l as MagicLinkFormLabels, P as PlatformAuthClientConfig, m as PlatformAuthConfig, n as PlatformAuthMailer, o as PlatformAuthMailerArgs, p as PlatformAuthMailerType, q as PlatformSession, r as PlatformSessionData, s as PlatformUser, t as RegisterFormLabels } from './types-D6eIAL25.js';
|
|
3
3
|
import * as better_auth_react from 'better-auth/react';
|
|
4
4
|
import * as better_auth from 'better-auth';
|
|
5
5
|
import { PlatformAuthClient } from './client.js';
|
|
6
6
|
import * as react from 'react';
|
|
7
7
|
import { InputHTMLAttributes, ReactNode } from 'react';
|
|
8
|
-
export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-
|
|
8
|
+
export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-p9JYNUiB.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Returns a useSession hook bound to the given auth client.
|
|
@@ -144,6 +144,14 @@ declare namespace ForgotPasswordForm {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
declare function MagicLinkForm({ onSuccess, loginUrl, callbackUrl, newUserCallbackUrl, errorCallbackUrl, labels, submitClassName, fieldClassName, error: externalError, linkComponent, invite, authClient, }: MagicLinkFormProps): react.JSX.Element;
|
|
148
|
+
declare namespace MagicLinkForm {
|
|
149
|
+
var defaults: {
|
|
150
|
+
title: string;
|
|
151
|
+
subtitle: string;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
declare function ResetPasswordForm({ email, onSuccess, loginUrl, labels, submitClassName, fieldClassName, linkComponent, authClient, }: ResetPasswordFormProps): react.JSX.Element;
|
|
148
156
|
declare namespace ResetPasswordForm {
|
|
149
157
|
var defaults: {
|
|
@@ -249,6 +257,50 @@ declare function initialOAuthError(labels: OAuthErrorLabels, param?: string): st
|
|
|
249
257
|
*/
|
|
250
258
|
declare function clearOAuthError(param?: string): void;
|
|
251
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Where a link that did not work should send the browser back to.
|
|
262
|
+
*
|
|
263
|
+
* Falls back to the current page rather than to Better Auth's own default,
|
|
264
|
+
* which is the success callback: that is a signed-in destination, so an auth
|
|
265
|
+
* guard bounces the visitor and drops the `?error=` on the way, and an expired
|
|
266
|
+
* link ends up looking like nothing happened. `fallback` covers the server
|
|
267
|
+
* render, where there is no current page to name.
|
|
268
|
+
*/
|
|
269
|
+
declare function magicLinkErrorCallback(explicit: string | undefined, fallback: string, currentPath: string | undefined): string;
|
|
270
|
+
type MagicLinkErrorLabels = {
|
|
271
|
+
invalidToken: string;
|
|
272
|
+
signUpDisabled: string;
|
|
273
|
+
failed: string;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Turns the failure a followed magic link redirected back with into something
|
|
277
|
+
* the person can act on.
|
|
278
|
+
*
|
|
279
|
+
* Every way the flow can fail lands here rather than on the send: Better Auth
|
|
280
|
+
* consumes the token at /magic-link/verify, and any refusal there is thrown as
|
|
281
|
+
* a redirect to the errorCallbackURL. `INVALID_TOKEN` covers expiry and reuse
|
|
282
|
+
* alike — the token is consumed atomically on first use, so a link followed
|
|
283
|
+
* twice is indistinguishable from one that timed out, and the copy names both.
|
|
284
|
+
*/
|
|
285
|
+
declare function magicLinkErrorMessage(code: string | null | undefined, labels?: Partial<MagicLinkErrorLabels>): string;
|
|
286
|
+
/**
|
|
287
|
+
* Whether a `?error=` came from a magic link rather than from OAuth.
|
|
288
|
+
*
|
|
289
|
+
* Both flows return to the same screens through the same parameter, so a page
|
|
290
|
+
* offering the two needs to know which vocabulary to read the code against —
|
|
291
|
+
* otherwise an expired link is reported as a failed social sign-in.
|
|
292
|
+
*/
|
|
293
|
+
declare function isMagicLinkError(code: string | null | undefined): boolean;
|
|
294
|
+
/**
|
|
295
|
+
* Reads the failure a followed link redirected back with.
|
|
296
|
+
*
|
|
297
|
+
* Following a link leaves the app entirely, so no component state survives it;
|
|
298
|
+
* the address bar is the only carrier left. Mirrors initialOAuthError, down to
|
|
299
|
+
* reading `window.location` rather than a typed route search — the auth routes
|
|
300
|
+
* declare none on purpose.
|
|
301
|
+
*/
|
|
302
|
+
declare function initialMagicLinkError(labels?: Partial<MagicLinkErrorLabels>, param?: string): string | undefined;
|
|
303
|
+
|
|
252
304
|
interface AuthLinkProps {
|
|
253
305
|
to: string;
|
|
254
306
|
as?: LinkComponent;
|
|
@@ -264,4 +316,4 @@ interface AuthLinkProps {
|
|
|
264
316
|
*/
|
|
265
317
|
declare function AuthLink({ to, as: Link, className, children }: AuthLinkProps): react.JSX.Element;
|
|
266
318
|
|
|
267
|
-
export { AuthClientResult, AuthField, type AuthFieldProps, AuthLayout, AuthLayoutProps, AuthLink, AuthSubmit, ForgotPasswordForm, ForgotPasswordFormProps, InvitationNotice, InvitationNoticeProps, LinkComponent, LoginForm, LoginFormProps, type OAuthErrorLabels, RegisterForm, RegisterFormProps, ResetPasswordForm, ResetPasswordFormProps, SocialButtons, VerifyEmailForm, VerifyEmailFormProps, clearOAuthError, initialOAuthError, isEmailNotVerified, normalizeInviteToken, oauthErrorMessage, useLogout, useSession, withInviteToken };
|
|
319
|
+
export { AuthClientResult, AuthField, type AuthFieldProps, AuthLayout, AuthLayoutProps, AuthLink, AuthSubmit, ForgotPasswordForm, ForgotPasswordFormProps, InvitationNotice, InvitationNoticeProps, LinkComponent, LoginForm, LoginFormProps, type MagicLinkErrorLabels, MagicLinkForm, MagicLinkFormProps, type OAuthErrorLabels, RegisterForm, RegisterFormProps, ResetPasswordForm, ResetPasswordFormProps, SocialButtons, VerifyEmailForm, VerifyEmailFormProps, clearOAuthError, initialMagicLinkError, initialOAuthError, isEmailNotVerified, isMagicLinkError, magicLinkErrorCallback, magicLinkErrorMessage, normalizeInviteToken, oauthErrorMessage, useLogout, useSession, withInviteToken };
|
package/dist/index.js
CHANGED
|
@@ -967,11 +967,180 @@ ForgotPasswordForm.defaults = {
|
|
|
967
967
|
subtitle: DEFAULTS4.subtitle
|
|
968
968
|
};
|
|
969
969
|
|
|
970
|
-
// src/components/
|
|
970
|
+
// src/components/magic-link-form.tsx
|
|
971
971
|
import { useState as useState6 } from "react";
|
|
972
|
+
|
|
973
|
+
// src/magic-link-error.ts
|
|
974
|
+
function magicLinkErrorCallback(explicit, fallback, currentPath) {
|
|
975
|
+
return explicit ?? currentPath ?? fallback;
|
|
976
|
+
}
|
|
977
|
+
var MAGIC_LINK_ERROR_DEFAULTS = {
|
|
978
|
+
invalidToken: "Ce lien n'est plus valide. Il expire apr\xE8s 5 minutes et ne fonctionne qu'une fois \u2014 demandes-en un nouveau.",
|
|
979
|
+
signUpDisabled: "Aucun compte n'existe pour cette adresse. Cr\xE9e-en un d'abord.",
|
|
980
|
+
failed: "La connexion par lien a \xE9chou\xE9. R\xE9essaie."
|
|
981
|
+
};
|
|
982
|
+
function magicLinkErrorMessage(code, labels = {}) {
|
|
983
|
+
const t = { ...MAGIC_LINK_ERROR_DEFAULTS, ...labels };
|
|
984
|
+
switch (code) {
|
|
985
|
+
case "INVALID_TOKEN":
|
|
986
|
+
return t.invalidToken;
|
|
987
|
+
case "new_user_signup_disabled":
|
|
988
|
+
return t.signUpDisabled;
|
|
989
|
+
default:
|
|
990
|
+
return t.failed;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
var MAGIC_LINK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
994
|
+
"INVALID_TOKEN",
|
|
995
|
+
"new_user_signup_disabled",
|
|
996
|
+
"failed_to_create_user",
|
|
997
|
+
"failed_to_create_session"
|
|
998
|
+
]);
|
|
999
|
+
function isMagicLinkError(code) {
|
|
1000
|
+
return !!code && MAGIC_LINK_ERROR_CODES.has(code);
|
|
1001
|
+
}
|
|
1002
|
+
function initialMagicLinkError(labels = {}, param = "error") {
|
|
1003
|
+
if (typeof window === "undefined") return void 0;
|
|
1004
|
+
const code = new URLSearchParams(window.location.search).get(param);
|
|
1005
|
+
if (!isMagicLinkError(code)) return void 0;
|
|
1006
|
+
return magicLinkErrorMessage(code, labels);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/components/magic-link-form.tsx
|
|
972
1010
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
973
|
-
var MIN_PASSWORD_LENGTH2 = 8;
|
|
974
1011
|
var DEFAULTS5 = {
|
|
1012
|
+
title: "Connexion par lien",
|
|
1013
|
+
subtitle: "Entre ton adresse e-mail et nous t'enverrons un lien pour te connecter, sans mot de passe.",
|
|
1014
|
+
emailPlaceholder: "Adresse e-mail",
|
|
1015
|
+
submit: "Envoyer le lien",
|
|
1016
|
+
submitPending: "Envoi\u2026",
|
|
1017
|
+
sent: "Lien envoy\xE9. Ouvre ta bo\xEEte de r\xE9ception pour te connecter \u2014 il expire dans 5 minutes.",
|
|
1018
|
+
resend: "Renvoyer le lien",
|
|
1019
|
+
usePassword: "Tu pr\xE9f\xE8res ton mot de passe ?",
|
|
1020
|
+
login: "Se connecter",
|
|
1021
|
+
emailRequired: "Renseigne ton adresse e-mail",
|
|
1022
|
+
sendFailed: "L'envoi du lien a \xE9chou\xE9"
|
|
1023
|
+
};
|
|
1024
|
+
function MagicLinkForm({
|
|
1025
|
+
onSuccess,
|
|
1026
|
+
loginUrl = "/login",
|
|
1027
|
+
callbackUrl = "/",
|
|
1028
|
+
newUserCallbackUrl,
|
|
1029
|
+
errorCallbackUrl,
|
|
1030
|
+
labels,
|
|
1031
|
+
submitClassName,
|
|
1032
|
+
fieldClassName,
|
|
1033
|
+
error: externalError,
|
|
1034
|
+
linkComponent,
|
|
1035
|
+
invite,
|
|
1036
|
+
authClient
|
|
1037
|
+
}) {
|
|
1038
|
+
const t = { ...DEFAULTS5, ...labels };
|
|
1039
|
+
const [email, setEmail] = useState6("");
|
|
1040
|
+
const [ownError, setOwnError] = useState6();
|
|
1041
|
+
const [isPending, setIsPending] = useState6(false);
|
|
1042
|
+
const [isSent, setIsSent] = useState6(false);
|
|
1043
|
+
const error = ownError ?? externalError;
|
|
1044
|
+
const handleSubmit = async (e) => {
|
|
1045
|
+
e.preventDefault();
|
|
1046
|
+
if (!email.trim()) {
|
|
1047
|
+
setOwnError(t.emailRequired);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
setOwnError(void 0);
|
|
1051
|
+
setIsPending(true);
|
|
1052
|
+
try {
|
|
1053
|
+
const res = await authClient.signIn.magicLink({
|
|
1054
|
+
email: email.trim(),
|
|
1055
|
+
// The invitation rides the callback: following the link leaves the
|
|
1056
|
+
// browser on the mail client, so the auth handler redeems the token on
|
|
1057
|
+
// the way back, as it does for OAuth.
|
|
1058
|
+
callbackURL: withInviteToken(callbackUrl, invite),
|
|
1059
|
+
...newUserCallbackUrl ? { newUserCallbackURL: withInviteToken(newUserCallbackUrl, invite) } : {},
|
|
1060
|
+
// Resolved here rather than at render: the default is the current page,
|
|
1061
|
+
// and this runs in the browser, where there is one.
|
|
1062
|
+
errorCallbackURL: withInviteToken(
|
|
1063
|
+
magicLinkErrorCallback(
|
|
1064
|
+
errorCallbackUrl,
|
|
1065
|
+
loginUrl,
|
|
1066
|
+
typeof window !== "undefined" ? window.location.pathname : void 0
|
|
1067
|
+
),
|
|
1068
|
+
invite
|
|
1069
|
+
)
|
|
1070
|
+
});
|
|
1071
|
+
if (res?.error) {
|
|
1072
|
+
setOwnError(res.error.message ?? t.sendFailed);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
setIsSent(true);
|
|
1076
|
+
onSuccess?.(email.trim());
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
setOwnError(err instanceof Error ? err.message : t.sendFailed);
|
|
1079
|
+
} finally {
|
|
1080
|
+
setIsPending(false);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-7", children: [
|
|
1084
|
+
/* @__PURE__ */ jsx11(AuthAlert, { children: error }),
|
|
1085
|
+
/* @__PURE__ */ jsx11(AuthAlert, { tone: "success", children: !error && isSent ? t.sent : void 0 }),
|
|
1086
|
+
/* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, className: "space-y-[1.125rem]", noValidate: true, children: [
|
|
1087
|
+
/* @__PURE__ */ jsx11(
|
|
1088
|
+
AuthField,
|
|
1089
|
+
{
|
|
1090
|
+
label: t.emailPlaceholder,
|
|
1091
|
+
type: "email",
|
|
1092
|
+
inputMode: "email",
|
|
1093
|
+
value: email,
|
|
1094
|
+
onChange: (e) => {
|
|
1095
|
+
setEmail(e.target.value);
|
|
1096
|
+
setIsSent(false);
|
|
1097
|
+
},
|
|
1098
|
+
required: true,
|
|
1099
|
+
disabled: isPending,
|
|
1100
|
+
autoComplete: "email",
|
|
1101
|
+
autoCapitalize: "none",
|
|
1102
|
+
spellCheck: false,
|
|
1103
|
+
invalid: !!error,
|
|
1104
|
+
fieldClassName
|
|
1105
|
+
}
|
|
1106
|
+
),
|
|
1107
|
+
/* @__PURE__ */ jsx11(
|
|
1108
|
+
AuthSubmit,
|
|
1109
|
+
{
|
|
1110
|
+
spacedAbove: true,
|
|
1111
|
+
pending: isPending,
|
|
1112
|
+
disabled: !email.trim(),
|
|
1113
|
+
pendingLabel: t.submitPending,
|
|
1114
|
+
className: submitClassName,
|
|
1115
|
+
children: isSent ? t.resend : t.submit
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
] }),
|
|
1119
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
1120
|
+
t.usePassword,
|
|
1121
|
+
" ",
|
|
1122
|
+
/* @__PURE__ */ jsx11(
|
|
1123
|
+
AuthLink,
|
|
1124
|
+
{
|
|
1125
|
+
to: withInviteToken(loginUrl, invite),
|
|
1126
|
+
as: linkComponent,
|
|
1127
|
+
className: AUTH_LINK_CLASS,
|
|
1128
|
+
children: t.login
|
|
1129
|
+
}
|
|
1130
|
+
)
|
|
1131
|
+
] })
|
|
1132
|
+
] });
|
|
1133
|
+
}
|
|
1134
|
+
MagicLinkForm.defaults = {
|
|
1135
|
+
title: DEFAULTS5.title,
|
|
1136
|
+
subtitle: DEFAULTS5.subtitle
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// src/components/reset-password-form.tsx
|
|
1140
|
+
import { useState as useState7 } from "react";
|
|
1141
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1142
|
+
var MIN_PASSWORD_LENGTH2 = 8;
|
|
1143
|
+
var DEFAULTS6 = {
|
|
975
1144
|
title: "Nouveau mot de passe",
|
|
976
1145
|
subtitle: "Entre le code \xE0 6 chiffres re\xE7u par e-mail et ton nouveau mot de passe.",
|
|
977
1146
|
codePlaceholder: "Code de v\xE9rification",
|
|
@@ -1001,14 +1170,14 @@ function ResetPasswordForm({
|
|
|
1001
1170
|
linkComponent,
|
|
1002
1171
|
authClient
|
|
1003
1172
|
}) {
|
|
1004
|
-
const t = { ...
|
|
1005
|
-
const [otp, setOtp] =
|
|
1006
|
-
const [password, setPassword] =
|
|
1007
|
-
const [confirmPassword, setConfirmPassword] =
|
|
1008
|
-
const [error, setError] =
|
|
1009
|
-
const [isResetting, setIsResetting] =
|
|
1010
|
-
const [isResending, setIsResending] =
|
|
1011
|
-
const [resendMessage, setResendMessage] =
|
|
1173
|
+
const t = { ...DEFAULTS6, ...labels };
|
|
1174
|
+
const [otp, setOtp] = useState7("");
|
|
1175
|
+
const [password, setPassword] = useState7("");
|
|
1176
|
+
const [confirmPassword, setConfirmPassword] = useState7("");
|
|
1177
|
+
const [error, setError] = useState7();
|
|
1178
|
+
const [isResetting, setIsResetting] = useState7(false);
|
|
1179
|
+
const [isResending, setIsResending] = useState7(false);
|
|
1180
|
+
const [resendMessage, setResendMessage] = useState7();
|
|
1012
1181
|
const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH2;
|
|
1013
1182
|
const mismatch = confirmPassword.length > 0 && confirmPassword !== password;
|
|
1014
1183
|
const handleSubmit = async (e) => {
|
|
@@ -1062,11 +1231,11 @@ function ResetPasswordForm({
|
|
|
1062
1231
|
setIsResending(false);
|
|
1063
1232
|
}
|
|
1064
1233
|
};
|
|
1065
|
-
return /* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
1068
|
-
/* @__PURE__ */
|
|
1069
|
-
/* @__PURE__ */
|
|
1234
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-7", children: [
|
|
1235
|
+
/* @__PURE__ */ jsx12(AuthAlert, { children: error }),
|
|
1236
|
+
/* @__PURE__ */ jsx12(AuthAlert, { tone: "success", children: resendMessage }),
|
|
1237
|
+
/* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-[1.125rem]", noValidate: true, children: [
|
|
1238
|
+
/* @__PURE__ */ jsx12(
|
|
1070
1239
|
AuthOtpField,
|
|
1071
1240
|
{
|
|
1072
1241
|
id: "reset-password-otp",
|
|
@@ -1077,7 +1246,7 @@ function ResetPasswordForm({
|
|
|
1077
1246
|
fieldClassName
|
|
1078
1247
|
}
|
|
1079
1248
|
),
|
|
1080
|
-
/* @__PURE__ */
|
|
1249
|
+
/* @__PURE__ */ jsx12(
|
|
1081
1250
|
AuthField,
|
|
1082
1251
|
{
|
|
1083
1252
|
label: t.passwordPlaceholder,
|
|
@@ -1091,7 +1260,7 @@ function ResetPasswordForm({
|
|
|
1091
1260
|
invalid: tooShort
|
|
1092
1261
|
}
|
|
1093
1262
|
),
|
|
1094
|
-
/* @__PURE__ */
|
|
1263
|
+
/* @__PURE__ */ jsx12(
|
|
1095
1264
|
AuthField,
|
|
1096
1265
|
{
|
|
1097
1266
|
label: t.confirmPlaceholder,
|
|
@@ -1104,7 +1273,7 @@ function ResetPasswordForm({
|
|
|
1104
1273
|
invalid: mismatch
|
|
1105
1274
|
}
|
|
1106
1275
|
),
|
|
1107
|
-
/* @__PURE__ */
|
|
1276
|
+
/* @__PURE__ */ jsx12(
|
|
1108
1277
|
AuthSubmit,
|
|
1109
1278
|
{
|
|
1110
1279
|
spacedAbove: true,
|
|
@@ -1116,8 +1285,8 @@ function ResetPasswordForm({
|
|
|
1116
1285
|
}
|
|
1117
1286
|
)
|
|
1118
1287
|
] }),
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1288
|
+
/* @__PURE__ */ jsxs10("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
|
|
1289
|
+
/* @__PURE__ */ jsx12(
|
|
1121
1290
|
"button",
|
|
1122
1291
|
{
|
|
1123
1292
|
type: "button",
|
|
@@ -1127,10 +1296,10 @@ function ResetPasswordForm({
|
|
|
1127
1296
|
children: isResending ? t.resendPending : t.resend
|
|
1128
1297
|
}
|
|
1129
1298
|
),
|
|
1130
|
-
/* @__PURE__ */
|
|
1299
|
+
/* @__PURE__ */ jsxs10("p", { children: [
|
|
1131
1300
|
t.rememberPassword,
|
|
1132
1301
|
" ",
|
|
1133
|
-
/* @__PURE__ */
|
|
1302
|
+
/* @__PURE__ */ jsx12(
|
|
1134
1303
|
AuthLink,
|
|
1135
1304
|
{
|
|
1136
1305
|
to: loginUrl,
|
|
@@ -1144,25 +1313,25 @@ function ResetPasswordForm({
|
|
|
1144
1313
|
] });
|
|
1145
1314
|
}
|
|
1146
1315
|
ResetPasswordForm.defaults = {
|
|
1147
|
-
title:
|
|
1148
|
-
subtitle:
|
|
1316
|
+
title: DEFAULTS6.title,
|
|
1317
|
+
subtitle: DEFAULTS6.subtitle
|
|
1149
1318
|
};
|
|
1150
1319
|
|
|
1151
1320
|
// src/components/auth-heading.tsx
|
|
1152
|
-
import { jsx as
|
|
1321
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1153
1322
|
function AuthHeading({
|
|
1154
1323
|
title,
|
|
1155
1324
|
subtitle,
|
|
1156
1325
|
titleClassName = "text-[2rem] font-semibold leading-[1.1] tracking-[-0.03em] sm:text-[2.25rem]"
|
|
1157
1326
|
}) {
|
|
1158
|
-
return /* @__PURE__ */
|
|
1159
|
-
/* @__PURE__ */
|
|
1160
|
-
subtitle && /* @__PURE__ */
|
|
1327
|
+
return /* @__PURE__ */ jsxs11("header", { className: "space-y-2.5 text-center", children: [
|
|
1328
|
+
/* @__PURE__ */ jsx13("h1", { className: titleClassName, children: title }),
|
|
1329
|
+
subtitle && /* @__PURE__ */ jsx13("p", { className: "text-balance text-[0.9375rem] leading-relaxed text-muted-foreground", children: subtitle })
|
|
1161
1330
|
] });
|
|
1162
1331
|
}
|
|
1163
1332
|
|
|
1164
1333
|
// src/components/auth-layout.tsx
|
|
1165
|
-
import { jsx as
|
|
1334
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1166
1335
|
function AuthLayout({
|
|
1167
1336
|
logo,
|
|
1168
1337
|
panel,
|
|
@@ -1172,7 +1341,7 @@ function AuthLayout({
|
|
|
1172
1341
|
children,
|
|
1173
1342
|
footer
|
|
1174
1343
|
}) {
|
|
1175
|
-
return /* @__PURE__ */
|
|
1344
|
+
return /* @__PURE__ */ jsxs12(
|
|
1176
1345
|
"div",
|
|
1177
1346
|
{
|
|
1178
1347
|
className: [
|
|
@@ -1186,7 +1355,7 @@ function AuthLayout({
|
|
|
1186
1355
|
// screen, so the colour only has to sit behind the heading. Covering
|
|
1187
1356
|
// the whole height would leave the panel's own copy showing under the
|
|
1188
1357
|
// card, which reads as a second, half-hidden screen.
|
|
1189
|
-
/* @__PURE__ */
|
|
1358
|
+
/* @__PURE__ */ jsx14(
|
|
1190
1359
|
"div",
|
|
1191
1360
|
{
|
|
1192
1361
|
"aria-hidden": "true",
|
|
@@ -1194,12 +1363,12 @@ function AuthLayout({
|
|
|
1194
1363
|
children: panel
|
|
1195
1364
|
}
|
|
1196
1365
|
),
|
|
1197
|
-
/* @__PURE__ */
|
|
1366
|
+
/* @__PURE__ */ jsxs12("div", { className: `mx-auto w-full ${panel ? "max-w-4xl" : "max-w-[440px]"}`, children: [
|
|
1198
1367
|
(logo || title) && // The mark sits tight above the title so the two read as one block
|
|
1199
1368
|
// rather than as a stray label; the gap down to the card is the
|
|
1200
1369
|
// largest on the screen — that step is what separates "who this is"
|
|
1201
1370
|
// from "what you do here".
|
|
1202
|
-
/* @__PURE__ */
|
|
1371
|
+
/* @__PURE__ */ jsxs12(
|
|
1203
1372
|
"div",
|
|
1204
1373
|
{
|
|
1205
1374
|
className: [
|
|
@@ -1210,8 +1379,8 @@ function AuthLayout({
|
|
|
1210
1379
|
panel ? "text-white [&_p]:text-white/75 md:text-foreground md:[&_p]:text-muted-foreground" : ""
|
|
1211
1380
|
].filter(Boolean).join(" "),
|
|
1212
1381
|
children: [
|
|
1213
|
-
logo && /* @__PURE__ */
|
|
1214
|
-
title && /* @__PURE__ */
|
|
1382
|
+
logo && /* @__PURE__ */ jsx14("div", { className: "flex justify-center", children: logo }),
|
|
1383
|
+
title && /* @__PURE__ */ jsx14(
|
|
1215
1384
|
AuthHeading,
|
|
1216
1385
|
{
|
|
1217
1386
|
title,
|
|
@@ -1222,7 +1391,7 @@ function AuthLayout({
|
|
|
1222
1391
|
]
|
|
1223
1392
|
}
|
|
1224
1393
|
),
|
|
1225
|
-
/* @__PURE__ */
|
|
1394
|
+
/* @__PURE__ */ jsxs12(
|
|
1226
1395
|
"div",
|
|
1227
1396
|
{
|
|
1228
1397
|
className: [
|
|
@@ -1236,7 +1405,7 @@ function AuthLayout({
|
|
|
1236
1405
|
children: [
|
|
1237
1406
|
panel && // Decorative: it must never carry information the form does not,
|
|
1238
1407
|
// so it is hidden from assistive tech rather than described.
|
|
1239
|
-
/* @__PURE__ */
|
|
1408
|
+
/* @__PURE__ */ jsx14(
|
|
1240
1409
|
"div",
|
|
1241
1410
|
{
|
|
1242
1411
|
"aria-hidden": "true",
|
|
@@ -1244,11 +1413,11 @@ function AuthLayout({
|
|
|
1244
1413
|
children: panel
|
|
1245
1414
|
}
|
|
1246
1415
|
),
|
|
1247
|
-
/* @__PURE__ */
|
|
1416
|
+
/* @__PURE__ */ jsx14("div", { className: panel ? "px-5 pb-12 pt-7 sm:p-7" : "sm:p-7", children })
|
|
1248
1417
|
]
|
|
1249
1418
|
}
|
|
1250
1419
|
),
|
|
1251
|
-
footer && /* @__PURE__ */
|
|
1420
|
+
footer && /* @__PURE__ */ jsx14("div", { className: "mt-8 text-center text-xs leading-relaxed text-muted-foreground", children: footer })
|
|
1252
1421
|
] })
|
|
1253
1422
|
]
|
|
1254
1423
|
}
|
|
@@ -1256,7 +1425,7 @@ function AuthLayout({
|
|
|
1256
1425
|
}
|
|
1257
1426
|
|
|
1258
1427
|
// src/components/invitation-notice.tsx
|
|
1259
|
-
import { jsx as
|
|
1428
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1260
1429
|
function defaultSupportEmail() {
|
|
1261
1430
|
if (typeof window === "undefined") return void 0;
|
|
1262
1431
|
const host = window.location.hostname;
|
|
@@ -1276,15 +1445,15 @@ function InvitationNotice({
|
|
|
1276
1445
|
action
|
|
1277
1446
|
}) {
|
|
1278
1447
|
const contact = supportEmail ?? defaultSupportEmail();
|
|
1279
|
-
return /* @__PURE__ */
|
|
1280
|
-
/* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1448
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-8", children: [
|
|
1449
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1450
|
+
/* @__PURE__ */ jsx15("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
|
|
1451
|
+
/* @__PURE__ */ jsx15("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
|
|
1283
1452
|
] }),
|
|
1284
|
-
contact ? /* @__PURE__ */
|
|
1453
|
+
contact ? /* @__PURE__ */ jsxs13("p", { className: "text-sm text-muted-foreground", children: [
|
|
1285
1454
|
"\xC9crivez-nous \xE0",
|
|
1286
1455
|
" ",
|
|
1287
|
-
/* @__PURE__ */
|
|
1456
|
+
/* @__PURE__ */ jsx15(
|
|
1288
1457
|
"a",
|
|
1289
1458
|
{
|
|
1290
1459
|
href: `mailto:${contact}`,
|
|
@@ -1331,14 +1500,19 @@ export {
|
|
|
1331
1500
|
ForgotPasswordForm,
|
|
1332
1501
|
InvitationNotice,
|
|
1333
1502
|
LoginForm,
|
|
1503
|
+
MagicLinkForm,
|
|
1334
1504
|
RegisterForm,
|
|
1335
1505
|
ResetPasswordForm,
|
|
1336
1506
|
SocialButtons,
|
|
1337
1507
|
VerifyEmailForm,
|
|
1338
1508
|
clearOAuthError,
|
|
1509
|
+
initialMagicLinkError,
|
|
1339
1510
|
initialOAuthError,
|
|
1340
1511
|
isEmailNotVerified,
|
|
1341
1512
|
isInvitationFailure,
|
|
1513
|
+
isMagicLinkError,
|
|
1514
|
+
magicLinkErrorCallback,
|
|
1515
|
+
magicLinkErrorMessage,
|
|
1342
1516
|
normalizeInviteToken,
|
|
1343
1517
|
oauthErrorMessage,
|
|
1344
1518
|
useLogout,
|