@lalternative/auth 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -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 +46 -4
- package/dist/index.js +203 -46
- package/dist/index.js.map +1 -1
- package/dist/{invitation-DuoHHBNA.d.ts → invitation-DcALM_sl.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-BbTfkT5F.d.ts} +84 -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,62 @@ 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
|
+
`initialMagicLinkError` ignores OAuth's codes, and `initialOAuthError` is
|
|
90
|
+
unchanged, so a screen offering both flows reads the one `?error=` against each
|
|
91
|
+
vocabulary without either claiming the other's failures. `INVALID_TOKEN` covers
|
|
92
|
+
expiry and reuse alike: the token is consumed atomically on first use, so a link
|
|
93
|
+
followed twice is indistinguishable from one that timed out.
|
|
94
|
+
|
|
39
95
|
### Invitations
|
|
40
96
|
|
|
41
97
|
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-BbTfkT5F.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-BbTfkT5F.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-BbTfkT5F.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-DcALM_sl.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, 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,40 @@ declare function initialOAuthError(labels: OAuthErrorLabels, param?: string): st
|
|
|
249
257
|
*/
|
|
250
258
|
declare function clearOAuthError(param?: string): void;
|
|
251
259
|
|
|
260
|
+
type MagicLinkErrorLabels = {
|
|
261
|
+
invalidToken: string;
|
|
262
|
+
signUpDisabled: string;
|
|
263
|
+
failed: string;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Turns the failure a followed magic link redirected back with into something
|
|
267
|
+
* the person can act on.
|
|
268
|
+
*
|
|
269
|
+
* Every way the flow can fail lands here rather than on the send: Better Auth
|
|
270
|
+
* consumes the token at /magic-link/verify, and any refusal there is thrown as
|
|
271
|
+
* a redirect to the errorCallbackURL. `INVALID_TOKEN` covers expiry and reuse
|
|
272
|
+
* alike — the token is consumed atomically on first use, so a link followed
|
|
273
|
+
* twice is indistinguishable from one that timed out, and the copy names both.
|
|
274
|
+
*/
|
|
275
|
+
declare function magicLinkErrorMessage(code: string | null | undefined, labels?: Partial<MagicLinkErrorLabels>): string;
|
|
276
|
+
/**
|
|
277
|
+
* Whether a `?error=` came from a magic link rather than from OAuth.
|
|
278
|
+
*
|
|
279
|
+
* Both flows return to the same screens through the same parameter, so a page
|
|
280
|
+
* offering the two needs to know which vocabulary to read the code against —
|
|
281
|
+
* otherwise an expired link is reported as a failed social sign-in.
|
|
282
|
+
*/
|
|
283
|
+
declare function isMagicLinkError(code: string | null | undefined): boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Reads the failure a followed link redirected back with.
|
|
286
|
+
*
|
|
287
|
+
* Following a link leaves the app entirely, so no component state survives it;
|
|
288
|
+
* the address bar is the only carrier left. Mirrors initialOAuthError, down to
|
|
289
|
+
* reading `window.location` rather than a typed route search — the auth routes
|
|
290
|
+
* declare none on purpose.
|
|
291
|
+
*/
|
|
292
|
+
declare function initialMagicLinkError(labels?: Partial<MagicLinkErrorLabels>, param?: string): string | undefined;
|
|
293
|
+
|
|
252
294
|
interface AuthLinkProps {
|
|
253
295
|
to: string;
|
|
254
296
|
as?: LinkComponent;
|
|
@@ -264,4 +306,4 @@ interface AuthLinkProps {
|
|
|
264
306
|
*/
|
|
265
307
|
declare function AuthLink({ to, as: Link, className, children }: AuthLinkProps): react.JSX.Element;
|
|
266
308
|
|
|
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 };
|
|
309
|
+
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, magicLinkErrorMessage, normalizeInviteToken, oauthErrorMessage, useLogout, useSession, withInviteToken };
|
package/dist/index.js
CHANGED
|
@@ -967,11 +967,131 @@ 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
972
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
973
|
-
var MIN_PASSWORD_LENGTH2 = 8;
|
|
974
973
|
var DEFAULTS5 = {
|
|
974
|
+
title: "Connexion par lien",
|
|
975
|
+
subtitle: "Entre ton adresse e-mail et nous t'enverrons un lien pour te connecter, sans mot de passe.",
|
|
976
|
+
emailPlaceholder: "Adresse e-mail",
|
|
977
|
+
submit: "Envoyer le lien",
|
|
978
|
+
submitPending: "Envoi\u2026",
|
|
979
|
+
sent: "Lien envoy\xE9. Ouvre ta bo\xEEte de r\xE9ception pour te connecter \u2014 il expire dans 5 minutes.",
|
|
980
|
+
resend: "Renvoyer le lien",
|
|
981
|
+
usePassword: "Tu pr\xE9f\xE8res ton mot de passe ?",
|
|
982
|
+
login: "Se connecter",
|
|
983
|
+
emailRequired: "Renseigne ton adresse e-mail",
|
|
984
|
+
sendFailed: "L'envoi du lien a \xE9chou\xE9"
|
|
985
|
+
};
|
|
986
|
+
function MagicLinkForm({
|
|
987
|
+
onSuccess,
|
|
988
|
+
loginUrl = "/login",
|
|
989
|
+
callbackUrl = "/",
|
|
990
|
+
newUserCallbackUrl,
|
|
991
|
+
labels,
|
|
992
|
+
submitClassName,
|
|
993
|
+
fieldClassName,
|
|
994
|
+
error: externalError,
|
|
995
|
+
linkComponent,
|
|
996
|
+
invite,
|
|
997
|
+
authClient
|
|
998
|
+
}) {
|
|
999
|
+
const t = { ...DEFAULTS5, ...labels };
|
|
1000
|
+
const [email, setEmail] = useState6("");
|
|
1001
|
+
const [ownError, setOwnError] = useState6();
|
|
1002
|
+
const [isPending, setIsPending] = useState6(false);
|
|
1003
|
+
const [isSent, setIsSent] = useState6(false);
|
|
1004
|
+
const error = ownError ?? externalError;
|
|
1005
|
+
const handleSubmit = async (e) => {
|
|
1006
|
+
e.preventDefault();
|
|
1007
|
+
if (!email.trim()) {
|
|
1008
|
+
setOwnError(t.emailRequired);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
setOwnError(void 0);
|
|
1012
|
+
setIsPending(true);
|
|
1013
|
+
try {
|
|
1014
|
+
const res = await authClient.signIn.magicLink({
|
|
1015
|
+
email: email.trim(),
|
|
1016
|
+
// The invitation rides the callback: following the link leaves the
|
|
1017
|
+
// browser on the mail client, so the auth handler redeems the token on
|
|
1018
|
+
// the way back, as it does for OAuth.
|
|
1019
|
+
callbackURL: withInviteToken(callbackUrl, invite),
|
|
1020
|
+
...newUserCallbackUrl ? { newUserCallbackURL: withInviteToken(newUserCallbackUrl, invite) } : {}
|
|
1021
|
+
});
|
|
1022
|
+
if (res?.error) {
|
|
1023
|
+
setOwnError(res.error.message ?? t.sendFailed);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
setIsSent(true);
|
|
1027
|
+
onSuccess?.(email.trim());
|
|
1028
|
+
} catch (err) {
|
|
1029
|
+
setOwnError(err instanceof Error ? err.message : t.sendFailed);
|
|
1030
|
+
} finally {
|
|
1031
|
+
setIsPending(false);
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-7", children: [
|
|
1035
|
+
/* @__PURE__ */ jsx11(AuthAlert, { children: error }),
|
|
1036
|
+
/* @__PURE__ */ jsx11(AuthAlert, { tone: "success", children: !error && isSent ? t.sent : void 0 }),
|
|
1037
|
+
/* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, className: "space-y-[1.125rem]", noValidate: true, children: [
|
|
1038
|
+
/* @__PURE__ */ jsx11(
|
|
1039
|
+
AuthField,
|
|
1040
|
+
{
|
|
1041
|
+
label: t.emailPlaceholder,
|
|
1042
|
+
type: "email",
|
|
1043
|
+
inputMode: "email",
|
|
1044
|
+
value: email,
|
|
1045
|
+
onChange: (e) => {
|
|
1046
|
+
setEmail(e.target.value);
|
|
1047
|
+
setIsSent(false);
|
|
1048
|
+
},
|
|
1049
|
+
required: true,
|
|
1050
|
+
disabled: isPending,
|
|
1051
|
+
autoComplete: "email",
|
|
1052
|
+
autoCapitalize: "none",
|
|
1053
|
+
spellCheck: false,
|
|
1054
|
+
invalid: !!error,
|
|
1055
|
+
fieldClassName
|
|
1056
|
+
}
|
|
1057
|
+
),
|
|
1058
|
+
/* @__PURE__ */ jsx11(
|
|
1059
|
+
AuthSubmit,
|
|
1060
|
+
{
|
|
1061
|
+
spacedAbove: true,
|
|
1062
|
+
pending: isPending,
|
|
1063
|
+
disabled: !email.trim(),
|
|
1064
|
+
pendingLabel: t.submitPending,
|
|
1065
|
+
className: submitClassName,
|
|
1066
|
+
children: isSent ? t.resend : t.submit
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
1071
|
+
t.usePassword,
|
|
1072
|
+
" ",
|
|
1073
|
+
/* @__PURE__ */ jsx11(
|
|
1074
|
+
AuthLink,
|
|
1075
|
+
{
|
|
1076
|
+
to: withInviteToken(loginUrl, invite),
|
|
1077
|
+
as: linkComponent,
|
|
1078
|
+
className: AUTH_LINK_CLASS,
|
|
1079
|
+
children: t.login
|
|
1080
|
+
}
|
|
1081
|
+
)
|
|
1082
|
+
] })
|
|
1083
|
+
] });
|
|
1084
|
+
}
|
|
1085
|
+
MagicLinkForm.defaults = {
|
|
1086
|
+
title: DEFAULTS5.title,
|
|
1087
|
+
subtitle: DEFAULTS5.subtitle
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// src/components/reset-password-form.tsx
|
|
1091
|
+
import { useState as useState7 } from "react";
|
|
1092
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1093
|
+
var MIN_PASSWORD_LENGTH2 = 8;
|
|
1094
|
+
var DEFAULTS6 = {
|
|
975
1095
|
title: "Nouveau mot de passe",
|
|
976
1096
|
subtitle: "Entre le code \xE0 6 chiffres re\xE7u par e-mail et ton nouveau mot de passe.",
|
|
977
1097
|
codePlaceholder: "Code de v\xE9rification",
|
|
@@ -1001,14 +1121,14 @@ function ResetPasswordForm({
|
|
|
1001
1121
|
linkComponent,
|
|
1002
1122
|
authClient
|
|
1003
1123
|
}) {
|
|
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] =
|
|
1124
|
+
const t = { ...DEFAULTS6, ...labels };
|
|
1125
|
+
const [otp, setOtp] = useState7("");
|
|
1126
|
+
const [password, setPassword] = useState7("");
|
|
1127
|
+
const [confirmPassword, setConfirmPassword] = useState7("");
|
|
1128
|
+
const [error, setError] = useState7();
|
|
1129
|
+
const [isResetting, setIsResetting] = useState7(false);
|
|
1130
|
+
const [isResending, setIsResending] = useState7(false);
|
|
1131
|
+
const [resendMessage, setResendMessage] = useState7();
|
|
1012
1132
|
const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH2;
|
|
1013
1133
|
const mismatch = confirmPassword.length > 0 && confirmPassword !== password;
|
|
1014
1134
|
const handleSubmit = async (e) => {
|
|
@@ -1062,11 +1182,11 @@ function ResetPasswordForm({
|
|
|
1062
1182
|
setIsResending(false);
|
|
1063
1183
|
}
|
|
1064
1184
|
};
|
|
1065
|
-
return /* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
1068
|
-
/* @__PURE__ */
|
|
1069
|
-
/* @__PURE__ */
|
|
1185
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-7", children: [
|
|
1186
|
+
/* @__PURE__ */ jsx12(AuthAlert, { children: error }),
|
|
1187
|
+
/* @__PURE__ */ jsx12(AuthAlert, { tone: "success", children: resendMessage }),
|
|
1188
|
+
/* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-[1.125rem]", noValidate: true, children: [
|
|
1189
|
+
/* @__PURE__ */ jsx12(
|
|
1070
1190
|
AuthOtpField,
|
|
1071
1191
|
{
|
|
1072
1192
|
id: "reset-password-otp",
|
|
@@ -1077,7 +1197,7 @@ function ResetPasswordForm({
|
|
|
1077
1197
|
fieldClassName
|
|
1078
1198
|
}
|
|
1079
1199
|
),
|
|
1080
|
-
/* @__PURE__ */
|
|
1200
|
+
/* @__PURE__ */ jsx12(
|
|
1081
1201
|
AuthField,
|
|
1082
1202
|
{
|
|
1083
1203
|
label: t.passwordPlaceholder,
|
|
@@ -1091,7 +1211,7 @@ function ResetPasswordForm({
|
|
|
1091
1211
|
invalid: tooShort
|
|
1092
1212
|
}
|
|
1093
1213
|
),
|
|
1094
|
-
/* @__PURE__ */
|
|
1214
|
+
/* @__PURE__ */ jsx12(
|
|
1095
1215
|
AuthField,
|
|
1096
1216
|
{
|
|
1097
1217
|
label: t.confirmPlaceholder,
|
|
@@ -1104,7 +1224,7 @@ function ResetPasswordForm({
|
|
|
1104
1224
|
invalid: mismatch
|
|
1105
1225
|
}
|
|
1106
1226
|
),
|
|
1107
|
-
/* @__PURE__ */
|
|
1227
|
+
/* @__PURE__ */ jsx12(
|
|
1108
1228
|
AuthSubmit,
|
|
1109
1229
|
{
|
|
1110
1230
|
spacedAbove: true,
|
|
@@ -1116,8 +1236,8 @@ function ResetPasswordForm({
|
|
|
1116
1236
|
}
|
|
1117
1237
|
)
|
|
1118
1238
|
] }),
|
|
1119
|
-
/* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1239
|
+
/* @__PURE__ */ jsxs10("div", { className: "space-y-4 text-center text-sm text-muted-foreground", children: [
|
|
1240
|
+
/* @__PURE__ */ jsx12(
|
|
1121
1241
|
"button",
|
|
1122
1242
|
{
|
|
1123
1243
|
type: "button",
|
|
@@ -1127,10 +1247,10 @@ function ResetPasswordForm({
|
|
|
1127
1247
|
children: isResending ? t.resendPending : t.resend
|
|
1128
1248
|
}
|
|
1129
1249
|
),
|
|
1130
|
-
/* @__PURE__ */
|
|
1250
|
+
/* @__PURE__ */ jsxs10("p", { children: [
|
|
1131
1251
|
t.rememberPassword,
|
|
1132
1252
|
" ",
|
|
1133
|
-
/* @__PURE__ */
|
|
1253
|
+
/* @__PURE__ */ jsx12(
|
|
1134
1254
|
AuthLink,
|
|
1135
1255
|
{
|
|
1136
1256
|
to: loginUrl,
|
|
@@ -1144,25 +1264,25 @@ function ResetPasswordForm({
|
|
|
1144
1264
|
] });
|
|
1145
1265
|
}
|
|
1146
1266
|
ResetPasswordForm.defaults = {
|
|
1147
|
-
title:
|
|
1148
|
-
subtitle:
|
|
1267
|
+
title: DEFAULTS6.title,
|
|
1268
|
+
subtitle: DEFAULTS6.subtitle
|
|
1149
1269
|
};
|
|
1150
1270
|
|
|
1151
1271
|
// src/components/auth-heading.tsx
|
|
1152
|
-
import { jsx as
|
|
1272
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1153
1273
|
function AuthHeading({
|
|
1154
1274
|
title,
|
|
1155
1275
|
subtitle,
|
|
1156
1276
|
titleClassName = "text-[2rem] font-semibold leading-[1.1] tracking-[-0.03em] sm:text-[2.25rem]"
|
|
1157
1277
|
}) {
|
|
1158
|
-
return /* @__PURE__ */
|
|
1159
|
-
/* @__PURE__ */
|
|
1160
|
-
subtitle && /* @__PURE__ */
|
|
1278
|
+
return /* @__PURE__ */ jsxs11("header", { className: "space-y-2.5 text-center", children: [
|
|
1279
|
+
/* @__PURE__ */ jsx13("h1", { className: titleClassName, children: title }),
|
|
1280
|
+
subtitle && /* @__PURE__ */ jsx13("p", { className: "text-balance text-[0.9375rem] leading-relaxed text-muted-foreground", children: subtitle })
|
|
1161
1281
|
] });
|
|
1162
1282
|
}
|
|
1163
1283
|
|
|
1164
1284
|
// src/components/auth-layout.tsx
|
|
1165
|
-
import { jsx as
|
|
1285
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1166
1286
|
function AuthLayout({
|
|
1167
1287
|
logo,
|
|
1168
1288
|
panel,
|
|
@@ -1172,7 +1292,7 @@ function AuthLayout({
|
|
|
1172
1292
|
children,
|
|
1173
1293
|
footer
|
|
1174
1294
|
}) {
|
|
1175
|
-
return /* @__PURE__ */
|
|
1295
|
+
return /* @__PURE__ */ jsxs12(
|
|
1176
1296
|
"div",
|
|
1177
1297
|
{
|
|
1178
1298
|
className: [
|
|
@@ -1186,7 +1306,7 @@ function AuthLayout({
|
|
|
1186
1306
|
// screen, so the colour only has to sit behind the heading. Covering
|
|
1187
1307
|
// the whole height would leave the panel's own copy showing under the
|
|
1188
1308
|
// card, which reads as a second, half-hidden screen.
|
|
1189
|
-
/* @__PURE__ */
|
|
1309
|
+
/* @__PURE__ */ jsx14(
|
|
1190
1310
|
"div",
|
|
1191
1311
|
{
|
|
1192
1312
|
"aria-hidden": "true",
|
|
@@ -1194,12 +1314,12 @@ function AuthLayout({
|
|
|
1194
1314
|
children: panel
|
|
1195
1315
|
}
|
|
1196
1316
|
),
|
|
1197
|
-
/* @__PURE__ */
|
|
1317
|
+
/* @__PURE__ */ jsxs12("div", { className: `mx-auto w-full ${panel ? "max-w-4xl" : "max-w-[440px]"}`, children: [
|
|
1198
1318
|
(logo || title) && // The mark sits tight above the title so the two read as one block
|
|
1199
1319
|
// rather than as a stray label; the gap down to the card is the
|
|
1200
1320
|
// largest on the screen — that step is what separates "who this is"
|
|
1201
1321
|
// from "what you do here".
|
|
1202
|
-
/* @__PURE__ */
|
|
1322
|
+
/* @__PURE__ */ jsxs12(
|
|
1203
1323
|
"div",
|
|
1204
1324
|
{
|
|
1205
1325
|
className: [
|
|
@@ -1210,8 +1330,8 @@ function AuthLayout({
|
|
|
1210
1330
|
panel ? "text-white [&_p]:text-white/75 md:text-foreground md:[&_p]:text-muted-foreground" : ""
|
|
1211
1331
|
].filter(Boolean).join(" "),
|
|
1212
1332
|
children: [
|
|
1213
|
-
logo && /* @__PURE__ */
|
|
1214
|
-
title && /* @__PURE__ */
|
|
1333
|
+
logo && /* @__PURE__ */ jsx14("div", { className: "flex justify-center", children: logo }),
|
|
1334
|
+
title && /* @__PURE__ */ jsx14(
|
|
1215
1335
|
AuthHeading,
|
|
1216
1336
|
{
|
|
1217
1337
|
title,
|
|
@@ -1222,7 +1342,7 @@ function AuthLayout({
|
|
|
1222
1342
|
]
|
|
1223
1343
|
}
|
|
1224
1344
|
),
|
|
1225
|
-
/* @__PURE__ */
|
|
1345
|
+
/* @__PURE__ */ jsxs12(
|
|
1226
1346
|
"div",
|
|
1227
1347
|
{
|
|
1228
1348
|
className: [
|
|
@@ -1236,7 +1356,7 @@ function AuthLayout({
|
|
|
1236
1356
|
children: [
|
|
1237
1357
|
panel && // Decorative: it must never carry information the form does not,
|
|
1238
1358
|
// so it is hidden from assistive tech rather than described.
|
|
1239
|
-
/* @__PURE__ */
|
|
1359
|
+
/* @__PURE__ */ jsx14(
|
|
1240
1360
|
"div",
|
|
1241
1361
|
{
|
|
1242
1362
|
"aria-hidden": "true",
|
|
@@ -1244,11 +1364,11 @@ function AuthLayout({
|
|
|
1244
1364
|
children: panel
|
|
1245
1365
|
}
|
|
1246
1366
|
),
|
|
1247
|
-
/* @__PURE__ */
|
|
1367
|
+
/* @__PURE__ */ jsx14("div", { className: panel ? "px-5 pb-12 pt-7 sm:p-7" : "sm:p-7", children })
|
|
1248
1368
|
]
|
|
1249
1369
|
}
|
|
1250
1370
|
),
|
|
1251
|
-
footer && /* @__PURE__ */
|
|
1371
|
+
footer && /* @__PURE__ */ jsx14("div", { className: "mt-8 text-center text-xs leading-relaxed text-muted-foreground", children: footer })
|
|
1252
1372
|
] })
|
|
1253
1373
|
]
|
|
1254
1374
|
}
|
|
@@ -1256,7 +1376,7 @@ function AuthLayout({
|
|
|
1256
1376
|
}
|
|
1257
1377
|
|
|
1258
1378
|
// src/components/invitation-notice.tsx
|
|
1259
|
-
import { jsx as
|
|
1379
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1260
1380
|
function defaultSupportEmail() {
|
|
1261
1381
|
if (typeof window === "undefined") return void 0;
|
|
1262
1382
|
const host = window.location.hostname;
|
|
@@ -1276,15 +1396,15 @@ function InvitationNotice({
|
|
|
1276
1396
|
action
|
|
1277
1397
|
}) {
|
|
1278
1398
|
const contact = supportEmail ?? defaultSupportEmail();
|
|
1279
|
-
return /* @__PURE__ */
|
|
1280
|
-
/* @__PURE__ */
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
/* @__PURE__ */
|
|
1399
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-8", children: [
|
|
1400
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1401
|
+
/* @__PURE__ */ jsx15("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
|
|
1402
|
+
/* @__PURE__ */ jsx15("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
|
|
1283
1403
|
] }),
|
|
1284
|
-
contact ? /* @__PURE__ */
|
|
1404
|
+
contact ? /* @__PURE__ */ jsxs13("p", { className: "text-sm text-muted-foreground", children: [
|
|
1285
1405
|
"\xC9crivez-nous \xE0",
|
|
1286
1406
|
" ",
|
|
1287
|
-
/* @__PURE__ */
|
|
1407
|
+
/* @__PURE__ */ jsx15(
|
|
1288
1408
|
"a",
|
|
1289
1409
|
{
|
|
1290
1410
|
href: `mailto:${contact}`,
|
|
@@ -1323,6 +1443,39 @@ function clearOAuthError(param = "error") {
|
|
|
1323
1443
|
url.searchParams.delete(param);
|
|
1324
1444
|
window.history.replaceState({}, "", url.toString());
|
|
1325
1445
|
}
|
|
1446
|
+
|
|
1447
|
+
// src/magic-link-error.ts
|
|
1448
|
+
var MAGIC_LINK_ERROR_DEFAULTS = {
|
|
1449
|
+
invalidToken: "Ce lien n'est plus valide. Il expire apr\xE8s 5 minutes et ne fonctionne qu'une fois \u2014 demandes-en un nouveau.",
|
|
1450
|
+
signUpDisabled: "Aucun compte n'existe pour cette adresse. Cr\xE9e-en un d'abord.",
|
|
1451
|
+
failed: "La connexion par lien a \xE9chou\xE9. R\xE9essaie."
|
|
1452
|
+
};
|
|
1453
|
+
function magicLinkErrorMessage(code, labels = {}) {
|
|
1454
|
+
const t = { ...MAGIC_LINK_ERROR_DEFAULTS, ...labels };
|
|
1455
|
+
switch (code) {
|
|
1456
|
+
case "INVALID_TOKEN":
|
|
1457
|
+
return t.invalidToken;
|
|
1458
|
+
case "new_user_signup_disabled":
|
|
1459
|
+
return t.signUpDisabled;
|
|
1460
|
+
default:
|
|
1461
|
+
return t.failed;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
var MAGIC_LINK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1465
|
+
"INVALID_TOKEN",
|
|
1466
|
+
"new_user_signup_disabled",
|
|
1467
|
+
"failed_to_create_user",
|
|
1468
|
+
"failed_to_create_session"
|
|
1469
|
+
]);
|
|
1470
|
+
function isMagicLinkError(code) {
|
|
1471
|
+
return !!code && MAGIC_LINK_ERROR_CODES.has(code);
|
|
1472
|
+
}
|
|
1473
|
+
function initialMagicLinkError(labels = {}, param = "error") {
|
|
1474
|
+
if (typeof window === "undefined") return void 0;
|
|
1475
|
+
const code = new URLSearchParams(window.location.search).get(param);
|
|
1476
|
+
if (!isMagicLinkError(code)) return void 0;
|
|
1477
|
+
return magicLinkErrorMessage(code, labels);
|
|
1478
|
+
}
|
|
1326
1479
|
export {
|
|
1327
1480
|
AuthField,
|
|
1328
1481
|
AuthLayout,
|
|
@@ -1331,14 +1484,18 @@ export {
|
|
|
1331
1484
|
ForgotPasswordForm,
|
|
1332
1485
|
InvitationNotice,
|
|
1333
1486
|
LoginForm,
|
|
1487
|
+
MagicLinkForm,
|
|
1334
1488
|
RegisterForm,
|
|
1335
1489
|
ResetPasswordForm,
|
|
1336
1490
|
SocialButtons,
|
|
1337
1491
|
VerifyEmailForm,
|
|
1338
1492
|
clearOAuthError,
|
|
1493
|
+
initialMagicLinkError,
|
|
1339
1494
|
initialOAuthError,
|
|
1340
1495
|
isEmailNotVerified,
|
|
1341
1496
|
isInvitationFailure,
|
|
1497
|
+
isMagicLinkError,
|
|
1498
|
+
magicLinkErrorMessage,
|
|
1342
1499
|
normalizeInviteToken,
|
|
1343
1500
|
oauthErrorMessage,
|
|
1344
1501
|
useLogout,
|