@better-auth-ui/heroui 1.6.27 → 1.6.28
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/components/auth/forgot-password.d.ts +3 -1
- package/dist/components/auth/magic-link/magic-link-sent.d.ts +20 -0
- package/dist/components/auth/reset-link-sent.d.ts +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +271 -230
- package/dist/lib/auth/magic-link-plugin.d.ts +4 -0
- package/dist/plugins.d.ts +1 -0
- package/dist/plugins.js +423 -380
- package/dist/user-view-CxSDufx_.js +126 -0
- package/package.json +1 -1
- package/src/components/auth/auth.tsx +8 -1
- package/src/components/auth/forgot-password.tsx +9 -6
- package/src/components/auth/magic-link/magic-link-sent.tsx +84 -0
- package/src/components/auth/magic-link/magic-link.tsx +10 -6
- package/src/components/auth/reset-link-sent.tsx +80 -0
- package/src/components/auth/settings/security/change-password.tsx +32 -14
- package/src/index.tsx +1 -0
- package/src/lib/auth/magic-link-plugin.ts +2 -1
- package/src/plugins.ts +1 -0
- package/dist/user-view--47tVtJ3.js +0 -113
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { authMutationKeys as e, getEmailProviderLink as t, getProviderName as n } from "@better-auth-ui/core";
|
|
2
|
+
import { providerIcons as r, useAuth as i, useSession as a, useSignInSocial as o } from "@better-auth-ui/react";
|
|
3
|
+
import { ArrowUpRightFromSquare as s, Person as c } from "@gravity-ui/icons";
|
|
4
|
+
import { Avatar as l, Button as u, Link as d, Separator as f, Skeleton as p, Spinner as m, cn as h } from "@heroui/react";
|
|
5
|
+
import { useMemo as g } from "react";
|
|
6
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
7
|
+
import { buttonVariants as y } from "@heroui/styles";
|
|
8
|
+
import { useIsMutating as b } from "@tanstack/react-query";
|
|
9
|
+
//#region src/components/auth/open-email-button.tsx
|
|
10
|
+
function x({ email: e, className: n }) {
|
|
11
|
+
let { localization: r } = i(), a = t(e);
|
|
12
|
+
return a ? /* @__PURE__ */ v(d, {
|
|
13
|
+
href: a.loginUrl,
|
|
14
|
+
target: "_blank",
|
|
15
|
+
rel: "noopener noreferrer",
|
|
16
|
+
className: h(y({ variant: "primary" }), "w-full gap-2", n),
|
|
17
|
+
children: [r.auth.openEmailProvider.replace("{{provider}}", a.companyProvider), /* @__PURE__ */ _(s, { className: "size-3" })]
|
|
18
|
+
}) : null;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/components/auth/field-separator.tsx
|
|
22
|
+
function S({ children: e }) {
|
|
23
|
+
return /* @__PURE__ */ v("div", {
|
|
24
|
+
className: "flex items-center gap-4",
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ _(f, { className: "flex-1" }),
|
|
27
|
+
/* @__PURE__ */ _("p", {
|
|
28
|
+
className: "text-xs text-muted shrink-0",
|
|
29
|
+
children: e
|
|
30
|
+
}),
|
|
31
|
+
/* @__PURE__ */ _(f, { className: "flex-1" })
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/components/auth/provider-button.tsx
|
|
37
|
+
function C({ provider: t, display: a = "full", variant: s = "tertiary", ...c }) {
|
|
38
|
+
let { authClient: l, baseURL: d, localization: f, redirectTo: p } = i(), h = `${d}${p}`, { mutate: g, isPending: y } = o(l), x = r[t];
|
|
39
|
+
return /* @__PURE__ */ v(u, {
|
|
40
|
+
variant: s,
|
|
41
|
+
isPending: b({ mutationKey: e.signIn.all }) + b({ mutationKey: e.signUp.all }) > 0,
|
|
42
|
+
onPress: () => g({
|
|
43
|
+
provider: t,
|
|
44
|
+
callbackURL: h
|
|
45
|
+
}),
|
|
46
|
+
...c,
|
|
47
|
+
"aria-label": n(t),
|
|
48
|
+
children: [y ? /* @__PURE__ */ _(m, {
|
|
49
|
+
color: "current",
|
|
50
|
+
size: "sm"
|
|
51
|
+
}) : /* @__PURE__ */ _(x, {}), a === "full" ? f.auth.continueWith.replace("{{provider}}", n(t)) : a === "name" ? n(t) : null]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/components/auth/provider-buttons.tsx
|
|
56
|
+
function w({ socialLayout: e = "auto" }) {
|
|
57
|
+
let { socialProviders: t } = i(), n = g(() => e === "auto" ? t?.length && t.length >= 4 ? "horizontal" : "vertical" : e, [e, t?.length]);
|
|
58
|
+
return /* @__PURE__ */ _("div", {
|
|
59
|
+
className: h("gap-3", n === "grid" && "grid grid-cols-2", n === "vertical" && "flex flex-col", n === "horizontal" && "flex flex-wrap"),
|
|
60
|
+
children: t?.map((e) => /* @__PURE__ */ _(C, {
|
|
61
|
+
provider: e,
|
|
62
|
+
display: n === "vertical" ? "full" : n === "grid" ? "name" : "icon",
|
|
63
|
+
className: h("w-full", n === "horizontal" && "flex-1")
|
|
64
|
+
}, e))
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/components/auth/user/user-avatar.tsx
|
|
69
|
+
function T({ className: e, fallback: t, isPending: n, user: r, size: o = "sm", style: s, ...u }) {
|
|
70
|
+
let { authClient: d } = i(), { data: f, isPending: m } = a(d, { enabled: !r && !n });
|
|
71
|
+
if ((n || m) && !r) return /* @__PURE__ */ _(p, {
|
|
72
|
+
className: h("rounded-full", o === "sm" ? "size-8" : o === "md" ? "size-10" : "size-12", e),
|
|
73
|
+
style: s
|
|
74
|
+
});
|
|
75
|
+
let g = r ?? f?.user, y = (g?.username || g?.name || g?.email)?.slice(0, 2).toUpperCase();
|
|
76
|
+
return /* @__PURE__ */ v(l, {
|
|
77
|
+
size: o,
|
|
78
|
+
className: h("rounded-full", e),
|
|
79
|
+
style: s,
|
|
80
|
+
...u,
|
|
81
|
+
children: [/* @__PURE__ */ _(l.Image, {
|
|
82
|
+
alt: g?.displayUsername || g?.name || g?.email,
|
|
83
|
+
src: g?.image ?? void 0
|
|
84
|
+
}), /* @__PURE__ */ _(l.Fallback, {
|
|
85
|
+
className: h(o === "lg" ? "text-xl" : o === "md" ? "text-base" : "text-sm"),
|
|
86
|
+
delayMs: g?.image ? 600 : void 0,
|
|
87
|
+
children: t || y || /* @__PURE__ */ _(c, { className: "size-4" })
|
|
88
|
+
})]
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/components/auth/user/user-view.tsx
|
|
93
|
+
function E({ className: e, isPending: t, size: n = "md", hideSubtitle: r = !1, user: o, ...s }) {
|
|
94
|
+
let { authClient: c } = i(), { data: l, isPending: u } = a(c, { enabled: !o && !t }), d = o ?? l?.user;
|
|
95
|
+
return (t || u) && !o ? /* @__PURE__ */ v("div", {
|
|
96
|
+
className: h("flex items-center gap-2 min-w-0", e),
|
|
97
|
+
...s,
|
|
98
|
+
children: [/* @__PURE__ */ _(T, {
|
|
99
|
+
isPending: !0,
|
|
100
|
+
className: n === "sm" ? "size-5 [&>span]:text-xs" : void 0,
|
|
101
|
+
size: n === "lg" ? "md" : "sm"
|
|
102
|
+
}), /* @__PURE__ */ v("div", {
|
|
103
|
+
className: "flex flex-col gap-1 min-w-0",
|
|
104
|
+
children: [/* @__PURE__ */ _(p, { className: "h-3.5 w-24 rounded-lg" }), r ? null : /* @__PURE__ */ _(p, { className: "h-3 w-32 rounded-lg" })]
|
|
105
|
+
})]
|
|
106
|
+
}) : /* @__PURE__ */ v("div", {
|
|
107
|
+
className: h("flex items-center gap-2 min-w-0", e),
|
|
108
|
+
...s,
|
|
109
|
+
children: [/* @__PURE__ */ _(T, {
|
|
110
|
+
className: n === "sm" ? "size-5 [&>span]:text-xs" : void 0,
|
|
111
|
+
user: d,
|
|
112
|
+
size: n === "lg" ? "md" : "sm"
|
|
113
|
+
}), /* @__PURE__ */ v("div", {
|
|
114
|
+
className: "flex flex-col min-w-0",
|
|
115
|
+
children: [/* @__PURE__ */ _("p", {
|
|
116
|
+
className: "text-foreground text-sm font-medium leading-tight truncate",
|
|
117
|
+
children: d?.displayUsername || d?.name || d?.email
|
|
118
|
+
}), !r && (d?.displayUsername || d?.name) ? /* @__PURE__ */ _("p", {
|
|
119
|
+
className: "text-muted text-xs leading-tight truncate overflow-x-hidden",
|
|
120
|
+
children: d?.email
|
|
121
|
+
}) : null]
|
|
122
|
+
})]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { S as a, C as i, T as n, x as o, w as r, E as t };
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import type { CardProps } from "@heroui/react"
|
|
|
4
4
|
import { type ComponentType, useEffect } from "react"
|
|
5
5
|
import { ForgotPassword } from "./forgot-password"
|
|
6
6
|
import type { SocialLayout } from "./provider-buttons"
|
|
7
|
+
import { ResetLinkSent } from "./reset-link-sent"
|
|
7
8
|
import { ResetPassword } from "./reset-password"
|
|
8
9
|
import { SignIn } from "./sign-in"
|
|
9
10
|
import { SignOut } from "./sign-out"
|
|
@@ -25,7 +26,12 @@ export type AuthProps = {
|
|
|
25
26
|
* When it's disabled, the `<Auth>` router redirects these to `signIn` so a
|
|
26
27
|
* plugin's `fallbackViews.auth.signIn` (e.g. magic link) takes over.
|
|
27
28
|
*/
|
|
28
|
-
const PASSWORD_ONLY_VIEWS = [
|
|
29
|
+
const PASSWORD_ONLY_VIEWS = [
|
|
30
|
+
"signUp",
|
|
31
|
+
"forgotPassword",
|
|
32
|
+
"resetPassword",
|
|
33
|
+
"resetLinkSent"
|
|
34
|
+
]
|
|
29
35
|
|
|
30
36
|
const AUTH_VIEWS: Partial<Record<AuthView, ComponentType<AuthProps>>> = {
|
|
31
37
|
signIn: SignIn,
|
|
@@ -33,6 +39,7 @@ const AUTH_VIEWS: Partial<Record<AuthView, ComponentType<AuthProps>>> = {
|
|
|
33
39
|
signUp: SignUp,
|
|
34
40
|
forgotPassword: ForgotPassword,
|
|
35
41
|
resetPassword: ResetPassword,
|
|
42
|
+
resetLinkSent: ResetLinkSent,
|
|
36
43
|
verifyEmail: VerifyEmail
|
|
37
44
|
}
|
|
38
45
|
|
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
Label,
|
|
16
16
|
Link,
|
|
17
17
|
Spinner,
|
|
18
|
-
TextField
|
|
19
|
-
toast
|
|
18
|
+
TextField
|
|
20
19
|
} from "@heroui/react"
|
|
21
20
|
import type { SyntheticEvent } from "react"
|
|
22
21
|
|
|
22
|
+
import { RESET_LINK_SENT_STORAGE_KEY } from "./reset-link-sent"
|
|
23
|
+
|
|
23
24
|
export type ForgotPasswordProps = {
|
|
24
25
|
className?: string
|
|
25
26
|
variant?: CardProps["variant"]
|
|
@@ -29,7 +30,9 @@ export type ForgotPasswordProps = {
|
|
|
29
30
|
* Render a card-based "Forgot Password" form that sends a password-reset email.
|
|
30
31
|
*
|
|
31
32
|
* The form displays an email input, submit button, and a link back to sign-in.
|
|
32
|
-
*
|
|
33
|
+
* After a successful request the submitted email is stored in `sessionStorage`
|
|
34
|
+
* and the user is redirected to the reset-link-sent view, which offers to open
|
|
35
|
+
* their email provider. Errors are handled globally by `ErrorToaster`.
|
|
33
36
|
*
|
|
34
37
|
* @param className - Optional additional CSS class names applied to the card
|
|
35
38
|
* @returns The forgot-password form UI as a JSX element
|
|
@@ -53,9 +56,9 @@ export function ForgotPassword({ className, variant }: ForgotPasswordProps) {
|
|
|
53
56
|
onError: () => {
|
|
54
57
|
resetFetchOptions()
|
|
55
58
|
},
|
|
56
|
-
onSuccess: () => {
|
|
57
|
-
|
|
58
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.
|
|
59
|
+
onSuccess: (_data, { email }) => {
|
|
60
|
+
sessionStorage.setItem(RESET_LINK_SENT_STORAGE_KEY, email)
|
|
61
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.resetLinkSent}` })
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
type CardProps,
|
|
5
|
+
cn,
|
|
6
|
+
Description,
|
|
7
|
+
Link,
|
|
8
|
+
useIsHydrated
|
|
9
|
+
} from "@heroui/react"
|
|
10
|
+
import { useEffect, useState } from "react"
|
|
11
|
+
|
|
12
|
+
import { magicLinkPlugin } from "../../../lib/auth/magic-link-plugin"
|
|
13
|
+
import { OpenEmailButton } from "../open-email-button"
|
|
14
|
+
|
|
15
|
+
/** `sessionStorage` key the magic-link form stores the submitted email under. */
|
|
16
|
+
export const MAGIC_LINK_SENT_STORAGE_KEY = "better-auth-ui.magic-link-sent"
|
|
17
|
+
|
|
18
|
+
export type MagicLinkSentProps = {
|
|
19
|
+
className?: string
|
|
20
|
+
variant?: CardProps["variant"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Render a card confirming that a magic-link email was sent, with a button
|
|
25
|
+
* to open the user's email provider.
|
|
26
|
+
*
|
|
27
|
+
* The target email is read from `sessionStorage` (set when the magic-link
|
|
28
|
+
* form redirects here); the OpenEmail button is only shown when an email is
|
|
29
|
+
* stored and resolves to a known provider.
|
|
30
|
+
*
|
|
31
|
+
* @param className - Additional CSS classes applied to the outer card container
|
|
32
|
+
* @param variant - Variant to apply to the card
|
|
33
|
+
* @returns The magic-link-sent card React element
|
|
34
|
+
*/
|
|
35
|
+
export function MagicLinkSent({ className, variant }: MagicLinkSentProps) {
|
|
36
|
+
const { basePaths, emailAndPassword, localization, viewPaths } = useAuth()
|
|
37
|
+
const { localization: magicLinkLocalization } = useAuthPlugin(magicLinkPlugin)
|
|
38
|
+
|
|
39
|
+
const isHydrated = useIsHydrated()
|
|
40
|
+
const [email, setEmail] = useState(
|
|
41
|
+
(isHydrated && sessionStorage.getItem(MAGIC_LINK_SENT_STORAGE_KEY)) || ""
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
setEmail(sessionStorage.getItem(MAGIC_LINK_SENT_STORAGE_KEY) ?? "")
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Card
|
|
50
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
51
|
+
variant={variant}
|
|
52
|
+
>
|
|
53
|
+
<Card.Header>
|
|
54
|
+
<Card.Title className="text-xl font-semibold mb-1">
|
|
55
|
+
{localization.auth.checkYourEmailTitle}
|
|
56
|
+
</Card.Title>
|
|
57
|
+
</Card.Header>
|
|
58
|
+
|
|
59
|
+
<Card.Content className="gap-4">
|
|
60
|
+
<Description className="text-sm">
|
|
61
|
+
{email
|
|
62
|
+
? magicLinkLocalization.magicLinkSentTo.replace("{{email}}", email)
|
|
63
|
+
: magicLinkLocalization.magicLinkSent}
|
|
64
|
+
</Description>
|
|
65
|
+
|
|
66
|
+
{email && <OpenEmailButton email={email} />}
|
|
67
|
+
</Card.Content>
|
|
68
|
+
|
|
69
|
+
{emailAndPassword?.enabled && (
|
|
70
|
+
<Card.Footer className="flex-col gap-3">
|
|
71
|
+
<Description className="text-sm">
|
|
72
|
+
{localization.auth.needToCreateAnAccount}{" "}
|
|
73
|
+
<Link
|
|
74
|
+
href={`${basePaths.auth}/${viewPaths.auth.signUp}`}
|
|
75
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
76
|
+
>
|
|
77
|
+
{localization.auth.signUp}
|
|
78
|
+
</Link>
|
|
79
|
+
</Description>
|
|
80
|
+
</Card.Footer>
|
|
81
|
+
)}
|
|
82
|
+
</Card>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -17,8 +17,7 @@ import {
|
|
|
17
17
|
Label,
|
|
18
18
|
Link,
|
|
19
19
|
Spinner,
|
|
20
|
-
TextField
|
|
21
|
-
toast
|
|
20
|
+
TextField
|
|
22
21
|
} from "@heroui/react"
|
|
23
22
|
import { useIsMutating } from "@tanstack/react-query"
|
|
24
23
|
import { type SyntheticEvent, useState } from "react"
|
|
@@ -26,6 +25,7 @@ import { type SyntheticEvent, useState } from "react"
|
|
|
26
25
|
import { magicLinkPlugin } from "../../../lib/auth/magic-link-plugin"
|
|
27
26
|
import { FieldSeparator } from "../field-separator"
|
|
28
27
|
import { ProviderButtons, type SocialLayout } from "../provider-buttons"
|
|
28
|
+
import { MAGIC_LINK_SENT_STORAGE_KEY } from "./magic-link-sent"
|
|
29
29
|
|
|
30
30
|
export type MagicLinkProps = {
|
|
31
31
|
className?: string
|
|
@@ -53,20 +53,24 @@ export function MagicLink({
|
|
|
53
53
|
baseURL,
|
|
54
54
|
emailAndPassword,
|
|
55
55
|
localization,
|
|
56
|
+
navigate,
|
|
56
57
|
plugins,
|
|
57
58
|
redirectTo,
|
|
58
59
|
socialProviders,
|
|
59
60
|
viewPaths
|
|
60
61
|
} = useAuth()
|
|
61
|
-
const { localization: magicLinkLocalization } =
|
|
62
|
+
const { localization: magicLinkLocalization, viewPaths: magicLinkViewPaths } =
|
|
63
|
+
useAuthPlugin(magicLinkPlugin)
|
|
62
64
|
|
|
63
65
|
const [email, setEmail] = useState("")
|
|
64
66
|
|
|
65
67
|
const { mutate: signInMagicLink, isPending: signInMagicLinkPending } =
|
|
66
68
|
useSignInMagicLink(authClient as MagicLinkAuthClient, {
|
|
67
|
-
onSuccess: () => {
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
onSuccess: (_data, variables) => {
|
|
70
|
+
sessionStorage.setItem(MAGIC_LINK_SENT_STORAGE_KEY, variables.email)
|
|
71
|
+
navigate({
|
|
72
|
+
to: `${basePaths.auth}/${magicLinkViewPaths.auth.magicLinkSent}`
|
|
73
|
+
})
|
|
70
74
|
}
|
|
71
75
|
})
|
|
72
76
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useAuth } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
type CardProps,
|
|
5
|
+
cn,
|
|
6
|
+
Description,
|
|
7
|
+
Link,
|
|
8
|
+
useIsHydrated
|
|
9
|
+
} from "@heroui/react"
|
|
10
|
+
import { useEffect, useState } from "react"
|
|
11
|
+
|
|
12
|
+
import { OpenEmailButton } from "./open-email-button"
|
|
13
|
+
|
|
14
|
+
/** `sessionStorage` key the forgot-password form stores the submitted email under. */
|
|
15
|
+
export const RESET_LINK_SENT_STORAGE_KEY = "better-auth-ui.reset-link-sent"
|
|
16
|
+
|
|
17
|
+
export type ResetLinkSentProps = {
|
|
18
|
+
className?: string
|
|
19
|
+
variant?: CardProps["variant"]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Render a card confirming that a password-reset email was sent, with a
|
|
24
|
+
* button to open the user's email provider.
|
|
25
|
+
*
|
|
26
|
+
* The target email is read from `sessionStorage` (set when the forgot-password
|
|
27
|
+
* form redirects here); the OpenEmail button is only shown when an email is
|
|
28
|
+
* stored and resolves to a known provider.
|
|
29
|
+
*
|
|
30
|
+
* @param className - Additional CSS classes applied to the outer card container
|
|
31
|
+
* @param variant - Variant to apply to the card
|
|
32
|
+
* @returns The reset-link-sent card React element
|
|
33
|
+
*/
|
|
34
|
+
export function ResetLinkSent({ className, variant }: ResetLinkSentProps) {
|
|
35
|
+
const { basePaths, localization, viewPaths } = useAuth()
|
|
36
|
+
|
|
37
|
+
const isHydrated = useIsHydrated()
|
|
38
|
+
const [email, setEmail] = useState(
|
|
39
|
+
(isHydrated && sessionStorage.getItem(RESET_LINK_SENT_STORAGE_KEY)) || ""
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setEmail(sessionStorage.getItem(RESET_LINK_SENT_STORAGE_KEY) ?? "")
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Card
|
|
48
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
49
|
+
variant={variant}
|
|
50
|
+
>
|
|
51
|
+
<Card.Header>
|
|
52
|
+
<Card.Title className="text-xl font-semibold mb-1">
|
|
53
|
+
{localization.auth.checkYourEmailTitle}
|
|
54
|
+
</Card.Title>
|
|
55
|
+
</Card.Header>
|
|
56
|
+
|
|
57
|
+
<Card.Content className="gap-4">
|
|
58
|
+
<Description className="text-sm">
|
|
59
|
+
{email
|
|
60
|
+
? localization.auth.resetLinkSentTo.replace("{{email}}", email)
|
|
61
|
+
: localization.auth.passwordResetEmailSent}
|
|
62
|
+
</Description>
|
|
63
|
+
|
|
64
|
+
{email && <OpenEmailButton email={email} />}
|
|
65
|
+
</Card.Content>
|
|
66
|
+
|
|
67
|
+
<Card.Footer className="flex-col gap-3">
|
|
68
|
+
<Description className="text-sm">
|
|
69
|
+
{localization.auth.rememberYourPassword}{" "}
|
|
70
|
+
<Link
|
|
71
|
+
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
72
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
73
|
+
>
|
|
74
|
+
{localization.auth.signIn}
|
|
75
|
+
</Link>
|
|
76
|
+
</Description>
|
|
77
|
+
</Card.Footer>
|
|
78
|
+
</Card>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
} from "@heroui/react"
|
|
26
26
|
import { type SyntheticEvent, useState } from "react"
|
|
27
27
|
|
|
28
|
+
import { OpenEmailButton } from "../../open-email-button"
|
|
29
|
+
|
|
28
30
|
export type ChangePasswordProps = {
|
|
29
31
|
className?: string
|
|
30
32
|
variant?: CardProps["variant"]
|
|
@@ -77,6 +79,7 @@ function SetPassword({
|
|
|
77
79
|
const { authClient, localization, plugins } = useAuth()
|
|
78
80
|
const { data: session } = useSession(authClient)
|
|
79
81
|
const { fetchOptions, resetFetchOptions } = useFetchOptions()
|
|
82
|
+
const [sentEmail, setSentEmail] = useState("")
|
|
80
83
|
|
|
81
84
|
const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(
|
|
82
85
|
authClient,
|
|
@@ -84,7 +87,9 @@ function SetPassword({
|
|
|
84
87
|
onError: () => {
|
|
85
88
|
resetFetchOptions()
|
|
86
89
|
},
|
|
87
|
-
onSuccess: () =>
|
|
90
|
+
onSuccess: (_data, { email }) => {
|
|
91
|
+
setSentEmail(email)
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
94
|
)
|
|
90
95
|
|
|
@@ -115,19 +120,32 @@ function SetPassword({
|
|
|
115
120
|
</p>
|
|
116
121
|
</div>
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
{sentEmail ? (
|
|
124
|
+
<div className="flex flex-col gap-3 items-start sm:items-end">
|
|
125
|
+
<p className="text-sm" role="status">
|
|
126
|
+
{localization.auth.resetLinkSentTo.replace(
|
|
127
|
+
"{{email}}",
|
|
128
|
+
sentEmail
|
|
129
|
+
)}
|
|
130
|
+
</p>
|
|
131
|
+
|
|
132
|
+
<OpenEmailButton email={sentEmail} className="w-auto" />
|
|
133
|
+
</div>
|
|
134
|
+
) : (
|
|
135
|
+
<div className="flex flex-col gap-3 items-start sm:items-end">
|
|
136
|
+
{Captcha && <div>{Captcha}</div>}
|
|
137
|
+
|
|
138
|
+
<Button
|
|
139
|
+
size="sm"
|
|
140
|
+
isPending={isPending}
|
|
141
|
+
isDisabled={!session?.user.email}
|
|
142
|
+
onPress={handleSetPassword}
|
|
143
|
+
>
|
|
144
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
145
|
+
{localization.auth.sendResetLink}
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
131
149
|
</Card.Content>
|
|
132
150
|
</Card>
|
|
133
151
|
</div>
|
package/src/index.tsx
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./components/auth/forgot-password"
|
|
|
7
7
|
export * from "./components/auth/open-email-button"
|
|
8
8
|
export * from "./components/auth/provider-button"
|
|
9
9
|
export * from "./components/auth/provider-buttons"
|
|
10
|
+
export * from "./components/auth/reset-link-sent"
|
|
10
11
|
export * from "./components/auth/reset-password"
|
|
11
12
|
export * from "./components/auth/settings/account/account-settings"
|
|
12
13
|
export * from "./components/auth/settings/account/change-email"
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
|
|
7
7
|
import { MagicLink } from "../../components/auth/magic-link/magic-link"
|
|
8
8
|
import { MagicLinkButton } from "../../components/auth/magic-link/magic-link-button"
|
|
9
|
+
import { MagicLinkSent } from "../../components/auth/magic-link/magic-link-sent"
|
|
9
10
|
|
|
10
11
|
export const magicLinkPlugin = createAuthPlugin(
|
|
11
12
|
coreMagicLinkPlugin.id,
|
|
@@ -13,7 +14,7 @@ export const magicLinkPlugin = createAuthPlugin(
|
|
|
13
14
|
...coreMagicLinkPlugin(options),
|
|
14
15
|
authButtons: [MagicLinkButton],
|
|
15
16
|
views: {
|
|
16
|
-
auth: { magicLink: MagicLink }
|
|
17
|
+
auth: { magicLink: MagicLink, magicLinkSent: MagicLinkSent }
|
|
17
18
|
},
|
|
18
19
|
// Conditional, not an override: when `emailAndPassword.enabled === false`
|
|
19
20
|
// the `<Auth>` router renders this at `/auth/sign-in` instead of the
|
package/src/plugins.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./components/auth/delete-user/danger-zone"
|
|
|
6
6
|
export * from "./components/auth/delete-user/delete-account"
|
|
7
7
|
// Plugin-contributed components that may be used standalone
|
|
8
8
|
export * from "./components/auth/magic-link/magic-link"
|
|
9
|
+
export * from "./components/auth/magic-link/magic-link-sent"
|
|
9
10
|
export * from "./components/auth/multi-session/manage-account"
|
|
10
11
|
export * from "./components/auth/multi-session/manage-accounts"
|
|
11
12
|
export * from "./components/auth/multi-session/switch-account-submenu"
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { authMutationKeys as e, getProviderName as t } from "@better-auth-ui/core";
|
|
2
|
-
import { providerIcons as n, useAuth as r, useSession as i, useSignInSocial as a } from "@better-auth-ui/react";
|
|
3
|
-
import { Person as o } from "@gravity-ui/icons";
|
|
4
|
-
import { Avatar as s, Button as c, Separator as l, Skeleton as u, Spinner as d, cn as f } from "@heroui/react";
|
|
5
|
-
import { useMemo as p } from "react";
|
|
6
|
-
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
7
|
-
import { useIsMutating as g } from "@tanstack/react-query";
|
|
8
|
-
//#region src/components/auth/field-separator.tsx
|
|
9
|
-
function _({ children: e }) {
|
|
10
|
-
return /* @__PURE__ */ h("div", {
|
|
11
|
-
className: "flex items-center gap-4",
|
|
12
|
-
children: [
|
|
13
|
-
/* @__PURE__ */ m(l, { className: "flex-1" }),
|
|
14
|
-
/* @__PURE__ */ m("p", {
|
|
15
|
-
className: "text-xs text-muted shrink-0",
|
|
16
|
-
children: e
|
|
17
|
-
}),
|
|
18
|
-
/* @__PURE__ */ m(l, { className: "flex-1" })
|
|
19
|
-
]
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region src/components/auth/provider-button.tsx
|
|
24
|
-
function v({ provider: i, display: o = "full", variant: s = "tertiary", ...l }) {
|
|
25
|
-
let { authClient: u, baseURL: f, localization: p, redirectTo: _ } = r(), v = `${f}${_}`, { mutate: y, isPending: b } = a(u), x = n[i];
|
|
26
|
-
return /* @__PURE__ */ h(c, {
|
|
27
|
-
variant: s,
|
|
28
|
-
isPending: g({ mutationKey: e.signIn.all }) + g({ mutationKey: e.signUp.all }) > 0,
|
|
29
|
-
onPress: () => y({
|
|
30
|
-
provider: i,
|
|
31
|
-
callbackURL: v
|
|
32
|
-
}),
|
|
33
|
-
...l,
|
|
34
|
-
"aria-label": t(i),
|
|
35
|
-
children: [b ? /* @__PURE__ */ m(d, {
|
|
36
|
-
color: "current",
|
|
37
|
-
size: "sm"
|
|
38
|
-
}) : /* @__PURE__ */ m(x, {}), o === "full" ? p.auth.continueWith.replace("{{provider}}", t(i)) : o === "name" ? t(i) : null]
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/components/auth/provider-buttons.tsx
|
|
43
|
-
function y({ socialLayout: e = "auto" }) {
|
|
44
|
-
let { socialProviders: t } = r(), n = p(() => e === "auto" ? t?.length && t.length >= 4 ? "horizontal" : "vertical" : e, [e, t?.length]);
|
|
45
|
-
return /* @__PURE__ */ m("div", {
|
|
46
|
-
className: f("gap-3", n === "grid" && "grid grid-cols-2", n === "vertical" && "flex flex-col", n === "horizontal" && "flex flex-wrap"),
|
|
47
|
-
children: t?.map((e) => /* @__PURE__ */ m(v, {
|
|
48
|
-
provider: e,
|
|
49
|
-
display: n === "vertical" ? "full" : n === "grid" ? "name" : "icon",
|
|
50
|
-
className: f("w-full", n === "horizontal" && "flex-1")
|
|
51
|
-
}, e))
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
//#endregion
|
|
55
|
-
//#region src/components/auth/user/user-avatar.tsx
|
|
56
|
-
function b({ className: e, fallback: t, isPending: n, user: a, size: c = "sm", style: l, ...d }) {
|
|
57
|
-
let { authClient: p } = r(), { data: g, isPending: _ } = i(p, { enabled: !a && !n });
|
|
58
|
-
if ((n || _) && !a) return /* @__PURE__ */ m(u, {
|
|
59
|
-
className: f("rounded-full", c === "sm" ? "size-8" : c === "md" ? "size-10" : "size-12", e),
|
|
60
|
-
style: l
|
|
61
|
-
});
|
|
62
|
-
let v = a ?? g?.user, y = (v?.username || v?.name || v?.email)?.slice(0, 2).toUpperCase();
|
|
63
|
-
return /* @__PURE__ */ h(s, {
|
|
64
|
-
size: c,
|
|
65
|
-
className: f("rounded-full", e),
|
|
66
|
-
style: l,
|
|
67
|
-
...d,
|
|
68
|
-
children: [/* @__PURE__ */ m(s.Image, {
|
|
69
|
-
alt: v?.displayUsername || v?.name || v?.email,
|
|
70
|
-
src: v?.image ?? void 0
|
|
71
|
-
}), /* @__PURE__ */ m(s.Fallback, {
|
|
72
|
-
className: f(c === "lg" ? "text-xl" : c === "md" ? "text-base" : "text-sm"),
|
|
73
|
-
delayMs: v?.image ? 600 : void 0,
|
|
74
|
-
children: t || y || /* @__PURE__ */ m(o, { className: "size-4" })
|
|
75
|
-
})]
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/components/auth/user/user-view.tsx
|
|
80
|
-
function x({ className: e, isPending: t, size: n = "md", hideSubtitle: a = !1, user: o, ...s }) {
|
|
81
|
-
let { authClient: c } = r(), { data: l, isPending: d } = i(c, { enabled: !o && !t }), p = o ?? l?.user;
|
|
82
|
-
return (t || d) && !o ? /* @__PURE__ */ h("div", {
|
|
83
|
-
className: f("flex items-center gap-2 min-w-0", e),
|
|
84
|
-
...s,
|
|
85
|
-
children: [/* @__PURE__ */ m(b, {
|
|
86
|
-
isPending: !0,
|
|
87
|
-
className: n === "sm" ? "size-5 [&>span]:text-xs" : void 0,
|
|
88
|
-
size: n === "lg" ? "md" : "sm"
|
|
89
|
-
}), /* @__PURE__ */ h("div", {
|
|
90
|
-
className: "flex flex-col gap-1 min-w-0",
|
|
91
|
-
children: [/* @__PURE__ */ m(u, { className: "h-3.5 w-24 rounded-lg" }), a ? null : /* @__PURE__ */ m(u, { className: "h-3 w-32 rounded-lg" })]
|
|
92
|
-
})]
|
|
93
|
-
}) : /* @__PURE__ */ h("div", {
|
|
94
|
-
className: f("flex items-center gap-2 min-w-0", e),
|
|
95
|
-
...s,
|
|
96
|
-
children: [/* @__PURE__ */ m(b, {
|
|
97
|
-
className: n === "sm" ? "size-5 [&>span]:text-xs" : void 0,
|
|
98
|
-
user: p,
|
|
99
|
-
size: n === "lg" ? "md" : "sm"
|
|
100
|
-
}), /* @__PURE__ */ h("div", {
|
|
101
|
-
className: "flex flex-col min-w-0",
|
|
102
|
-
children: [/* @__PURE__ */ m("p", {
|
|
103
|
-
className: "text-foreground text-sm font-medium leading-tight truncate",
|
|
104
|
-
children: p?.displayUsername || p?.name || p?.email
|
|
105
|
-
}), !a && (p?.displayUsername || p?.name) ? /* @__PURE__ */ m("p", {
|
|
106
|
-
className: "text-muted text-xs leading-tight truncate overflow-x-hidden",
|
|
107
|
-
children: p?.email
|
|
108
|
-
}) : null]
|
|
109
|
-
})]
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
113
|
-
export { _ as a, v as i, b as n, y as r, x as t };
|