betterstart-cli 0.0.108 → 0.0.110
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/assets/adapters/next/templates/init/admin-globals.css +1 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/{admin-sidebar-branding-rsc.tsx → admin-sidebar-branding.tsx} +1 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/layouts/role-gate.tsx +14 -0
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +89 -78
- package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +5 -1
- package/dist/assets/adapters/next/templates/init/pages/account-layout.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/pages/{account-shell-rsc.tsx → account-shell.tsx} +1 -1
- package/dist/assets/adapters/next/templates/init/pages/{auth-gate-rsc.tsx → auth-gate.tsx} +1 -1
- package/dist/assets/adapters/next/templates/init/pages/authenticated-layout.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +41 -36
- package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +94 -70
- package/dist/assets/adapters/next/templates/init/pages/{login-page-rsc.tsx → login-page-content.tsx} +1 -1
- package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +7 -7
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-page-content.tsx +31 -0
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-page-skeleton.tsx +9 -0
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-page.tsx +7 -27
- package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +86 -67
- package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -8
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +8 -10
- package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +8 -5
- package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +76 -33
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +6 -0
- package/dist/cli.js +337 -202
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,41 +1,64 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import type { FieldErrors } from 'react-hook-form'
|
|
4
5
|
import { authClient } from '@admin/auth/client'
|
|
5
6
|
import { Button } from '@admin/components/ui/button'
|
|
6
7
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
7
8
|
import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
|
|
9
|
+
import { Form } from '@admin/components/ui/form'
|
|
8
10
|
import { Input } from '@admin/components/ui/input'
|
|
9
11
|
import { Spinner } from '@admin/components/ui/spinner'
|
|
10
12
|
import { getAdminPostLoginPath } from '@admin/utils/auth/get-admin-post-login-path'
|
|
11
13
|
import { cn } from '@admin/utils/shared/cn'
|
|
14
|
+
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
12
15
|
import Image from 'next/image'
|
|
13
16
|
import Link from 'next/link'
|
|
14
17
|
import { useRouter } from 'next/navigation'
|
|
15
18
|
import { parseAsStringLiteral, useQueryState } from 'nuqs'
|
|
19
|
+
import { useForm } from 'react-hook-form'
|
|
20
|
+
import { toast } from 'sonner'
|
|
21
|
+
import { z } from 'zod/v3'
|
|
22
|
+
|
|
23
|
+
const loginSchema = z.object({
|
|
24
|
+
email: z.string().trim().min(1, 'Email is required').email('Enter a valid email address'),
|
|
25
|
+
password: z.string().min(1, 'Password is required')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
type LoginFormValues = z.infer<typeof loginSchema>
|
|
16
29
|
|
|
17
30
|
export function LoginForm({ className, ...props }: React.ComponentProps<'div'>) {
|
|
18
31
|
const router = useRouter()
|
|
19
32
|
const [reset] = useQueryState('reset', parseAsStringLiteral(['success']))
|
|
20
|
-
const resetSuccess = reset === 'success'
|
|
21
|
-
const [email, setEmail] = React.useState('')
|
|
22
|
-
const [password, setPassword] = React.useState('')
|
|
23
|
-
const [error, setError] = React.useState<string | null>(null)
|
|
24
33
|
const [isPending, startTransition] = React.useTransition()
|
|
34
|
+
const form = useForm<LoginFormValues>({
|
|
35
|
+
resolver: standardSchemaResolver(loginSchema),
|
|
36
|
+
defaultValues: {
|
|
37
|
+
email: '',
|
|
38
|
+
password: ''
|
|
39
|
+
},
|
|
40
|
+
mode: 'onChange',
|
|
41
|
+
reValidateMode: 'onChange'
|
|
42
|
+
})
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (reset === 'success') {
|
|
46
|
+
toast.success('Password reset. Sign in with your new password.', {
|
|
47
|
+
id: 'admin-login-password-reset-success'
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}, [reset])
|
|
29
51
|
|
|
52
|
+
const handleSubmit = (values: LoginFormValues) => {
|
|
30
53
|
startTransition(async () => {
|
|
31
54
|
try {
|
|
32
55
|
const result = await authClient.signIn.email({
|
|
33
|
-
email,
|
|
34
|
-
password
|
|
56
|
+
email: values.email,
|
|
57
|
+
password: values.password
|
|
35
58
|
})
|
|
36
59
|
|
|
37
60
|
if (result.error) {
|
|
38
|
-
|
|
61
|
+
toast.error(result.error.message || 'Invalid email or password')
|
|
39
62
|
return
|
|
40
63
|
}
|
|
41
64
|
|
|
@@ -45,11 +68,16 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
|
|
|
45
68
|
router.replace(getAdminPostLoginPath(role))
|
|
46
69
|
router.refresh()
|
|
47
70
|
} catch {
|
|
48
|
-
|
|
71
|
+
toast.error('Failed to sign you in. Try again.')
|
|
49
72
|
}
|
|
50
73
|
})
|
|
51
74
|
}
|
|
52
75
|
|
|
76
|
+
const handleInvalid = (errors: FieldErrors<LoginFormValues>) => {
|
|
77
|
+
const message = errors.email?.message ?? errors.password?.message
|
|
78
|
+
toast.error(typeof message === 'string' ? message : 'Please check your sign-in details.')
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
return (
|
|
54
82
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
55
83
|
<Card className="overflow-hidden p-0">
|
|
@@ -63,67 +91,63 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
|
|
|
63
91
|
className="object-cover"
|
|
64
92
|
/>
|
|
65
93
|
</div>
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
<Form {...form}>
|
|
95
|
+
<form
|
|
96
|
+
autoComplete="off"
|
|
97
|
+
noValidate
|
|
98
|
+
onSubmit={form.handleSubmit(handleSubmit, handleInvalid)}
|
|
99
|
+
className="p-6 md:p-20"
|
|
100
|
+
>
|
|
101
|
+
<FieldGroup className="gap-10 pb-12">
|
|
102
|
+
<div className="flex flex-col items-start">
|
|
103
|
+
<h1 className="text-xl font-medium">Sign In</h1>
|
|
75
104
|
</div>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
<div className="flex flex-col gap-4">
|
|
106
|
+
<Field>
|
|
107
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
108
|
+
<Input
|
|
109
|
+
id="email"
|
|
110
|
+
type="email"
|
|
111
|
+
autoComplete="off"
|
|
112
|
+
placeholder="m@example.com"
|
|
113
|
+
aria-invalid={Boolean(form.formState.errors.email)}
|
|
114
|
+
{...form.register('email')}
|
|
115
|
+
required
|
|
116
|
+
disabled={isPending}
|
|
117
|
+
/>
|
|
118
|
+
</Field>
|
|
119
|
+
<Field>
|
|
120
|
+
<div className="flex items-center">
|
|
121
|
+
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
122
|
+
<Link
|
|
123
|
+
href="/admin/forgot-password"
|
|
124
|
+
className="ml-auto text-sm underline-offset-4 hover:underline"
|
|
125
|
+
tabIndex={-1}
|
|
126
|
+
>
|
|
127
|
+
Forgot password?
|
|
128
|
+
</Link>
|
|
129
|
+
</div>
|
|
130
|
+
<Input
|
|
131
|
+
id="password"
|
|
132
|
+
type="password"
|
|
133
|
+
autoComplete="off"
|
|
134
|
+
placeholder="Enter your password"
|
|
135
|
+
aria-invalid={Boolean(form.formState.errors.password)}
|
|
136
|
+
{...form.register('password')}
|
|
137
|
+
required
|
|
138
|
+
disabled={isPending}
|
|
139
|
+
/>
|
|
140
|
+
</Field>
|
|
141
|
+
<Field className="pt-4">
|
|
142
|
+
<Button type="submit" disabled={isPending} aria-busy={isPending}>
|
|
143
|
+
{isPending && <Spinner />}
|
|
144
|
+
{isPending ? 'Signing in…' : 'Sign in'}
|
|
145
|
+
</Button>
|
|
146
|
+
</Field>
|
|
80
147
|
</div>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
85
|
-
<Input
|
|
86
|
-
id="email"
|
|
87
|
-
type="email"
|
|
88
|
-
autoComplete="email"
|
|
89
|
-
placeholder="m@example.com"
|
|
90
|
-
value={email}
|
|
91
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
92
|
-
required
|
|
93
|
-
disabled={isPending}
|
|
94
|
-
/>
|
|
95
|
-
</Field>
|
|
96
|
-
<Field>
|
|
97
|
-
<div className="flex items-center">
|
|
98
|
-
<FieldLabel htmlFor="password">Password</FieldLabel>
|
|
99
|
-
<Link
|
|
100
|
-
href="/admin/forgot-password"
|
|
101
|
-
className="ml-auto text-sm underline-offset-4 hover:underline"
|
|
102
|
-
tabIndex={-1}
|
|
103
|
-
>
|
|
104
|
-
Forgot password?
|
|
105
|
-
</Link>
|
|
106
|
-
</div>
|
|
107
|
-
<Input
|
|
108
|
-
id="password"
|
|
109
|
-
type="password"
|
|
110
|
-
autoComplete="current-password"
|
|
111
|
-
placeholder="Enter your password"
|
|
112
|
-
value={password}
|
|
113
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
114
|
-
required
|
|
115
|
-
disabled={isPending}
|
|
116
|
-
/>
|
|
117
|
-
</Field>
|
|
118
|
-
<Field className="pt-4">
|
|
119
|
-
<Button type="submit" disabled={isPending} aria-busy={isPending}>
|
|
120
|
-
{isPending && <Spinner />}
|
|
121
|
-
{isPending ? 'Signing in…' : 'Sign in'}
|
|
122
|
-
</Button>
|
|
123
|
-
</Field>
|
|
124
|
-
</div>
|
|
125
|
-
</FieldGroup>
|
|
126
|
-
</form>
|
|
148
|
+
</FieldGroup>
|
|
149
|
+
</form>
|
|
150
|
+
</Form>
|
|
127
151
|
</CardContent>
|
|
128
152
|
</Card>
|
|
129
153
|
</div>
|
package/dist/assets/adapters/next/templates/init/pages/{login-page-rsc.tsx → login-page-content.tsx}
RENAMED
|
@@ -3,7 +3,7 @@ import { getAdminPostLoginPath } from '@admin/utils/auth/get-admin-post-login-pa
|
|
|
3
3
|
import { redirect } from 'next/navigation'
|
|
4
4
|
import { LoginForm } from './login-form'
|
|
5
5
|
|
|
6
|
-
export async function
|
|
6
|
+
export async function LoginPageContent() {
|
|
7
7
|
const session = await getSession({ disableCookieCache: true })
|
|
8
8
|
|
|
9
9
|
if (session?.user) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from 'next'
|
|
2
2
|
import { Suspense } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { LoginPageContent } from './login-page-content'
|
|
4
4
|
import { LoginPageSkeleton } from './login-page-skeleton'
|
|
5
5
|
|
|
6
6
|
export const metadata: Metadata = {
|
|
@@ -13,7 +13,7 @@ export default function LoginPage() {
|
|
|
13
13
|
<div className="flex min-h-svh flex-col items-center justify-center bg-background p-6 md:p-10">
|
|
14
14
|
<div className="w-full max-w-sm md:max-w-4xl">
|
|
15
15
|
<Suspense fallback={<LoginPageSkeleton />}>
|
|
16
|
-
<
|
|
16
|
+
<LoginPageContent />
|
|
17
17
|
</Suspense>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -90,7 +90,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
90
90
|
newPassword: '',
|
|
91
91
|
confirmPassword: ''
|
|
92
92
|
},
|
|
93
|
-
mode: '
|
|
93
|
+
mode: 'onChange',
|
|
94
94
|
reValidateMode: 'onChange'
|
|
95
95
|
})
|
|
96
96
|
|
|
@@ -195,7 +195,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
195
195
|
return (
|
|
196
196
|
<div className="space-y-6">
|
|
197
197
|
<Form {...profileForm}>
|
|
198
|
-
<form onSubmit={profileForm.handleSubmit(onProfileSubmit)}>
|
|
198
|
+
<form autoComplete="off" onSubmit={profileForm.handleSubmit(onProfileSubmit)}>
|
|
199
199
|
<Card>
|
|
200
200
|
<CardHeader>
|
|
201
201
|
<div className="flex items-center gap-4 pl-1">
|
|
@@ -282,7 +282,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
282
282
|
<Input
|
|
283
283
|
type="password"
|
|
284
284
|
placeholder="Enter current password to change email"
|
|
285
|
-
autoComplete="
|
|
285
|
+
autoComplete="off"
|
|
286
286
|
disabled={profilePending}
|
|
287
287
|
{...formField}
|
|
288
288
|
/>
|
|
@@ -309,7 +309,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
309
309
|
</Form>
|
|
310
310
|
|
|
311
311
|
<Form {...passwordForm}>
|
|
312
|
-
<form onSubmit={passwordForm.handleSubmit(onPasswordSubmit)}>
|
|
312
|
+
<form autoComplete="off" onSubmit={passwordForm.handleSubmit(onPasswordSubmit)}>
|
|
313
313
|
<Card>
|
|
314
314
|
<CardHeader>
|
|
315
315
|
<CardTitle>Change Password</CardTitle>
|
|
@@ -325,7 +325,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
325
325
|
<FormControl>
|
|
326
326
|
<Input
|
|
327
327
|
type="password"
|
|
328
|
-
autoComplete="
|
|
328
|
+
autoComplete="off"
|
|
329
329
|
disabled={passwordPending}
|
|
330
330
|
{...field}
|
|
331
331
|
/>
|
|
@@ -343,7 +343,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
343
343
|
<FormControl>
|
|
344
344
|
<Input
|
|
345
345
|
type="password"
|
|
346
|
-
autoComplete="
|
|
346
|
+
autoComplete="off"
|
|
347
347
|
disabled={passwordPending}
|
|
348
348
|
{...field}
|
|
349
349
|
/>
|
|
@@ -362,7 +362,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
|
|
|
362
362
|
<FormControl>
|
|
363
363
|
<Input
|
|
364
364
|
type="password"
|
|
365
|
-
autoComplete="
|
|
365
|
+
autoComplete="off"
|
|
366
366
|
disabled={passwordPending}
|
|
367
367
|
{...field}
|
|
368
368
|
/>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getSession } from '@admin/auth/middleware'
|
|
2
|
+
import { PageHeader } from '@admin/components/shared/page-header'
|
|
3
|
+
import { UserRole } from '@admin/types/auth'
|
|
4
|
+
import { redirect } from 'next/navigation'
|
|
5
|
+
import { ProfileForm } from './profile-form'
|
|
6
|
+
|
|
7
|
+
export async function ProfilePageContent() {
|
|
8
|
+
const session = await getSession()
|
|
9
|
+
|
|
10
|
+
if (!session?.user) {
|
|
11
|
+
redirect('/admin/login')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const showPageHeader =
|
|
15
|
+
session.user.role === UserRole.ADMIN || session.user.role === UserRole.EDITOR
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{showPageHeader && <PageHeader title="Profile" />}
|
|
20
|
+
<main className="container mx-auto w-full max-w-5xl p-4">
|
|
21
|
+
<ProfileForm
|
|
22
|
+
user={{
|
|
23
|
+
name: session.user.name,
|
|
24
|
+
email: session.user.email,
|
|
25
|
+
image: session.user.image ?? null
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
</main>
|
|
29
|
+
</>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -1,31 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { redirect } from 'next/navigation'
|
|
5
|
-
import { ProfileForm } from './profile-form'
|
|
6
|
-
|
|
7
|
-
export default async function ProfilePage() {
|
|
8
|
-
const session = await getSession()
|
|
9
|
-
|
|
10
|
-
if (!session?.user) {
|
|
11
|
-
redirect('/admin/login')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const showPageHeader =
|
|
15
|
-
session.user.role === UserRole.ADMIN || session.user.role === UserRole.EDITOR
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { ProfilePageContent } from './profile-page-content'
|
|
3
|
+
import { ProfilePageSkeleton } from './profile-page-skeleton'
|
|
16
4
|
|
|
5
|
+
export default function ProfilePage() {
|
|
17
6
|
return (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<ProfileForm
|
|
22
|
-
user={{
|
|
23
|
-
name: session.user.name,
|
|
24
|
-
email: session.user.email,
|
|
25
|
-
image: session.user.image ?? null
|
|
26
|
-
}}
|
|
27
|
-
/>
|
|
28
|
-
</main>
|
|
29
|
-
</>
|
|
7
|
+
<React.Suspense fallback={<ProfilePageSkeleton />}>
|
|
8
|
+
<ProfilePageContent />
|
|
9
|
+
</React.Suspense>
|
|
30
10
|
)
|
|
31
11
|
}
|
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import type { FieldErrors } from 'react-hook-form'
|
|
4
5
|
import { authClient } from '@admin/auth/client'
|
|
5
6
|
import { Button } from '@admin/components/ui/button'
|
|
6
7
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
7
8
|
import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
|
|
9
|
+
import { Form } from '@admin/components/ui/form'
|
|
8
10
|
import { Input } from '@admin/components/ui/input'
|
|
9
11
|
import { Spinner } from '@admin/components/ui/spinner'
|
|
10
12
|
import { cn } from '@admin/utils/shared/cn'
|
|
13
|
+
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
11
14
|
import Image from 'next/image'
|
|
12
15
|
import Link from 'next/link'
|
|
13
16
|
import { useRouter } from 'next/navigation'
|
|
14
17
|
import { createSerializer, parseAsString, parseAsStringLiteral, useQueryState } from 'nuqs'
|
|
18
|
+
import { useForm } from 'react-hook-form'
|
|
19
|
+
import { toast } from 'sonner'
|
|
20
|
+
import { z } from 'zod/v3'
|
|
15
21
|
|
|
16
22
|
const serializeLoginUrl = createSerializer({
|
|
17
23
|
reset: parseAsStringLiteral(['success'])
|
|
18
24
|
})
|
|
19
25
|
|
|
26
|
+
const resetPasswordSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
|
29
|
+
confirmPassword: z.string().min(1, 'Confirm the new password')
|
|
30
|
+
})
|
|
31
|
+
.refine((values) => values.newPassword === values.confirmPassword, {
|
|
32
|
+
message: 'Passwords do not match',
|
|
33
|
+
path: ['confirmPassword']
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
type ResetPasswordFormValues = z.infer<typeof resetPasswordSchema>
|
|
37
|
+
|
|
20
38
|
export function ResetPasswordForm({ className, ...props }: React.ComponentProps<'div'>) {
|
|
21
39
|
const router = useRouter()
|
|
22
40
|
const [token] = useQueryState('token', parseAsString)
|
|
23
|
-
|
|
24
|
-
const [newPassword, setNewPassword] = React.useState('')
|
|
25
|
-
const [confirmPassword, setConfirmPassword] = React.useState('')
|
|
26
|
-
const [error, setError] = React.useState<string | null>(null)
|
|
27
41
|
const [isPending, startTransition] = React.useTransition()
|
|
42
|
+
const form = useForm<ResetPasswordFormValues>({
|
|
43
|
+
resolver: standardSchemaResolver(resetPasswordSchema),
|
|
44
|
+
defaultValues: {
|
|
45
|
+
newPassword: '',
|
|
46
|
+
confirmPassword: ''
|
|
47
|
+
},
|
|
48
|
+
mode: 'onChange',
|
|
49
|
+
reValidateMode: 'onChange'
|
|
50
|
+
})
|
|
28
51
|
|
|
29
52
|
if (!token) {
|
|
30
53
|
return (
|
|
@@ -62,38 +85,30 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
|
|
|
62
85
|
)
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
const handleSubmit = (
|
|
66
|
-
e.preventDefault()
|
|
67
|
-
setError(null)
|
|
68
|
-
|
|
69
|
-
if (newPassword !== confirmPassword) {
|
|
70
|
-
setError('Passwords do not match')
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (newPassword.length < 8) {
|
|
75
|
-
setError('Password must be at least 8 characters')
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
88
|
+
const handleSubmit = (values: ResetPasswordFormValues) => {
|
|
79
89
|
startTransition(async () => {
|
|
80
90
|
try {
|
|
81
91
|
const { error } = await authClient.resetPassword({
|
|
82
|
-
newPassword,
|
|
92
|
+
newPassword: values.newPassword,
|
|
83
93
|
token
|
|
84
94
|
})
|
|
85
95
|
if (error) {
|
|
86
|
-
|
|
96
|
+
toast.error(error.message || 'Failed to reset password')
|
|
87
97
|
return
|
|
88
98
|
}
|
|
89
99
|
await authClient.getSession({ query: { disableCookieCache: true } })
|
|
90
100
|
router.push(serializeLoginUrl('/admin/login', { reset: 'success' }))
|
|
91
101
|
} catch {
|
|
92
|
-
|
|
102
|
+
toast.error('Failed to reset your password. Try again.')
|
|
93
103
|
}
|
|
94
104
|
})
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
const handleInvalid = (errors: FieldErrors<ResetPasswordFormValues>) => {
|
|
108
|
+
const message = errors.newPassword?.message ?? errors.confirmPassword?.message
|
|
109
|
+
toast.error(typeof message === 'string' ? message : 'Please check your new password.')
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
return (
|
|
98
113
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
99
114
|
<Card className="overflow-hidden p-0">
|
|
@@ -107,53 +122,57 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
|
|
|
107
122
|
className="object-cover"
|
|
108
123
|
/>
|
|
109
124
|
</div>
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
<Form {...form}>
|
|
126
|
+
<form
|
|
127
|
+
autoComplete="off"
|
|
128
|
+
noValidate
|
|
129
|
+
onSubmit={form.handleSubmit(handleSubmit, handleInvalid)}
|
|
130
|
+
className="p-6 md:p-20"
|
|
131
|
+
>
|
|
132
|
+
<FieldGroup className="gap-10 pb-12">
|
|
133
|
+
<div className="flex flex-col items-start">
|
|
134
|
+
<h1 className="text-xl font-medium">Reset password</h1>
|
|
135
|
+
<p className="text-balance text-muted-foreground">
|
|
136
|
+
Enter your new password below
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div className="flex flex-col gap-4">
|
|
140
|
+
<Field>
|
|
141
|
+
<FieldLabel htmlFor="newPassword">New Password</FieldLabel>
|
|
142
|
+
<Input
|
|
143
|
+
id="newPassword"
|
|
144
|
+
type="password"
|
|
145
|
+
autoComplete="off"
|
|
146
|
+
placeholder="Enter new password"
|
|
147
|
+
aria-invalid={Boolean(form.formState.errors.newPassword)}
|
|
148
|
+
{...form.register('newPassword')}
|
|
149
|
+
required
|
|
150
|
+
disabled={isPending}
|
|
151
|
+
/>
|
|
152
|
+
</Field>
|
|
153
|
+
<Field>
|
|
154
|
+
<FieldLabel htmlFor="confirmPassword">Confirm Password</FieldLabel>
|
|
155
|
+
<Input
|
|
156
|
+
id="confirmPassword"
|
|
157
|
+
type="password"
|
|
158
|
+
autoComplete="off"
|
|
159
|
+
placeholder="Confirm new password"
|
|
160
|
+
aria-invalid={Boolean(form.formState.errors.confirmPassword)}
|
|
161
|
+
{...form.register('confirmPassword')}
|
|
162
|
+
required
|
|
163
|
+
disabled={isPending}
|
|
164
|
+
/>
|
|
165
|
+
</Field>
|
|
166
|
+
<Field className="pt-4">
|
|
167
|
+
<Button type="submit" disabled={isPending} aria-busy={isPending}>
|
|
168
|
+
{isPending && <Spinner />}
|
|
169
|
+
{isPending ? 'Resetting…' : 'Reset Password'}
|
|
170
|
+
</Button>
|
|
171
|
+
</Field>
|
|
119
172
|
</div>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<FieldLabel htmlFor="newPassword">New Password</FieldLabel>
|
|
124
|
-
<Input
|
|
125
|
-
id="newPassword"
|
|
126
|
-
type="password"
|
|
127
|
-
autoComplete="new-password"
|
|
128
|
-
placeholder="Enter new password"
|
|
129
|
-
value={newPassword}
|
|
130
|
-
onChange={(e) => setNewPassword(e.target.value)}
|
|
131
|
-
required
|
|
132
|
-
disabled={isPending}
|
|
133
|
-
/>
|
|
134
|
-
</Field>
|
|
135
|
-
<Field>
|
|
136
|
-
<FieldLabel htmlFor="confirmPassword">Confirm Password</FieldLabel>
|
|
137
|
-
<Input
|
|
138
|
-
id="confirmPassword"
|
|
139
|
-
type="password"
|
|
140
|
-
autoComplete="new-password"
|
|
141
|
-
placeholder="Confirm new password"
|
|
142
|
-
value={confirmPassword}
|
|
143
|
-
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
144
|
-
required
|
|
145
|
-
disabled={isPending}
|
|
146
|
-
/>
|
|
147
|
-
</Field>
|
|
148
|
-
<Field className="pt-4">
|
|
149
|
-
<Button type="submit" disabled={isPending} aria-busy={isPending}>
|
|
150
|
-
{isPending && <Spinner />}
|
|
151
|
-
{isPending ? 'Resetting…' : 'Reset Password'}
|
|
152
|
-
</Button>
|
|
153
|
-
</Field>
|
|
154
|
-
</div>
|
|
155
|
-
</FieldGroup>
|
|
156
|
-
</form>
|
|
173
|
+
</FieldGroup>
|
|
174
|
+
</form>
|
|
175
|
+
</Form>
|
|
157
176
|
</CardContent>
|
|
158
177
|
</Card>
|
|
159
178
|
</div>
|
package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { UserRole } from '@admin/auth/middleware'
|
|
3
|
+
import { RoleGate } from '@admin/components/layouts/role-gate'
|
|
3
4
|
import { AuditLogPageContent } from './audit-log-page-content'
|
|
4
5
|
import { AuditLogPageSkeleton } from './audit-log-page-skeleton'
|
|
5
6
|
|
|
6
|
-
export default
|
|
7
|
-
await requireRole([UserRole.ADMIN])
|
|
8
|
-
|
|
7
|
+
export default function AuditLogPage() {
|
|
9
8
|
return (
|
|
10
9
|
<React.Suspense fallback={<AuditLogPageSkeleton />}>
|
|
11
|
-
<
|
|
10
|
+
<RoleGate roles={[UserRole.ADMIN]}>
|
|
11
|
+
<AuditLogPageContent />
|
|
12
|
+
</RoleGate>
|
|
12
13
|
</React.Suspense>
|
|
13
14
|
)
|
|
14
15
|
}
|
package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx
CHANGED
|
@@ -86,6 +86,7 @@ export function FormNotificationsDrawer({
|
|
|
86
86
|
<DrawerContent className="h-full sm:max-w-xl">
|
|
87
87
|
<Form {...form}>
|
|
88
88
|
<form
|
|
89
|
+
autoComplete="off"
|
|
89
90
|
onSubmit={form.handleSubmit((values) => mutation.mutate(values))}
|
|
90
91
|
className="flex min-h-0 flex-1 flex-col gap-4"
|
|
91
92
|
>
|
package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { UserRole } from '@admin/auth/middleware'
|
|
3
|
+
import { RoleGate } from '@admin/components/layouts/role-gate'
|
|
3
4
|
import { FormsSettingsPageContent } from './forms-settings-page-content'
|
|
4
5
|
import { FormsSettingsPageSkeleton } from './forms-settings-page-skeleton'
|
|
5
6
|
|
|
6
|
-
export default
|
|
7
|
-
await requireRole([UserRole.ADMIN, UserRole.EDITOR])
|
|
8
|
-
|
|
7
|
+
export default function FormsSettingsPage() {
|
|
9
8
|
return (
|
|
10
9
|
<React.Suspense fallback={<FormsSettingsPageSkeleton />}>
|
|
11
|
-
<
|
|
10
|
+
<RoleGate roles={[UserRole.ADMIN, UserRole.EDITOR]}>
|
|
11
|
+
<FormsSettingsPageContent />
|
|
12
|
+
</RoleGate>
|
|
12
13
|
</React.Suspense>
|
|
13
14
|
)
|
|
14
15
|
}
|