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.
Files changed (34) hide show
  1. package/dist/assets/adapters/next/templates/init/admin-globals.css +1 -1
  2. package/dist/assets/adapters/next/templates/init/components/layouts/{admin-sidebar-branding-rsc.tsx → admin-sidebar-branding.tsx} +1 -1
  3. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -2
  4. package/dist/assets/adapters/next/templates/init/components/layouts/role-gate.tsx +14 -0
  5. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +1 -0
  6. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +89 -78
  7. package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +5 -1
  8. package/dist/assets/adapters/next/templates/init/pages/account-layout.tsx +2 -2
  9. package/dist/assets/adapters/next/templates/init/pages/{account-shell-rsc.tsx → account-shell.tsx} +1 -1
  10. package/dist/assets/adapters/next/templates/init/pages/{auth-gate-rsc.tsx → auth-gate.tsx} +1 -1
  11. package/dist/assets/adapters/next/templates/init/pages/authenticated-layout.tsx +2 -2
  12. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +41 -36
  13. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +94 -70
  14. package/dist/assets/adapters/next/templates/init/pages/{login-page-rsc.tsx → login-page-content.tsx} +1 -1
  15. package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +2 -2
  16. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +7 -7
  17. package/dist/assets/adapters/next/templates/init/pages/profile/profile-page-content.tsx +31 -0
  18. package/dist/assets/adapters/next/templates/init/pages/profile/profile-page-skeleton.tsx +9 -0
  19. package/dist/assets/adapters/next/templates/init/pages/profile/profile-page.tsx +7 -27
  20. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +86 -67
  21. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page.tsx +6 -5
  22. package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +1 -0
  23. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +6 -5
  24. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -8
  25. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +6 -5
  26. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +6 -5
  27. package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +8 -10
  28. package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +8 -5
  29. package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +1 -1
  30. package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +76 -33
  31. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +6 -0
  32. package/dist/cli.js +337 -202
  33. package/dist/cli.js.map +1 -1
  34. 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
- const handleSubmit = (e: React.FormEvent) => {
27
- e.preventDefault()
28
- setError(null)
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
- setError(result.error.message || 'Invalid email or password')
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
- setError('Failed to sign you in. Try again.')
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
- <form onSubmit={handleSubmit} className="p-6 md:p-20">
67
- <FieldGroup className="gap-10 pb-12">
68
- <div className="flex flex-col items-start">
69
- <h1 className="text-xl font-medium">Welcome back</h1>
70
- <p className="text-balance text-muted-foreground">Sign in to your account</p>
71
- </div>
72
- {resetSuccess ? (
73
- <div className="rounded-md bg-emerald-500/10 p-3 text-sm text-emerald-700">
74
- Password reset. Sign in with your new password.
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
- ) : null}
77
- {error ? (
78
- <div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
79
- {error}
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
- ) : null}
82
- <div className="flex flex-col gap-4">
83
- <Field>
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>
@@ -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 LoginPageRsc() {
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 { LoginPageRsc } from './login-page-rsc'
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
- <LoginPageRsc />
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: 'onBlur',
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="current-password"
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="current-password"
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="new-password"
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="new-password"
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
+ }
@@ -0,0 +1,9 @@
1
+ export function ProfilePageSkeleton() {
2
+ return (
3
+ <main className="container mx-auto w-full max-w-5xl p-4">
4
+ <div className="flex h-48 items-center justify-center">
5
+ <div className="text-muted-foreground">Loading Profile…</div>
6
+ </div>
7
+ </main>
8
+ )
9
+ }
@@ -1,31 +1,11 @@
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 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
- {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
- </>
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 = (e: React.FormEvent) => {
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
- setError(error.message || 'Failed to reset password')
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
- setError('Failed to reset your password. Try again.')
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
- <form onSubmit={handleSubmit} className="p-6 md:p-20">
111
- <FieldGroup className="gap-10 pb-12">
112
- <div className="flex flex-col items-start">
113
- <h1 className="text-xl font-medium">Reset password</h1>
114
- <p className="text-balance text-muted-foreground">Enter your new password below</p>
115
- </div>
116
- {error ? (
117
- <div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
118
- {error}
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
- ) : null}
121
- <div className="flex flex-col gap-4">
122
- <Field>
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>
@@ -1,14 +1,15 @@
1
1
  import * as React from 'react'
2
- import { requireRole, UserRole } from '@admin/auth/middleware'
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 async function AuditLogPage() {
7
- await requireRole([UserRole.ADMIN])
8
-
7
+ export default function AuditLogPage() {
9
8
  return (
10
9
  <React.Suspense fallback={<AuditLogPageSkeleton />}>
11
- <AuditLogPageContent />
10
+ <RoleGate roles={[UserRole.ADMIN]}>
11
+ <AuditLogPageContent />
12
+ </RoleGate>
12
13
  </React.Suspense>
13
14
  )
14
15
  }
@@ -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
  >
@@ -1,14 +1,15 @@
1
1
  import * as React from 'react'
2
- import { requireRole, UserRole } from '@admin/auth/middleware'
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 async function FormsSettingsPage() {
7
- await requireRole([UserRole.ADMIN, UserRole.EDITOR])
8
-
7
+ export default function FormsSettingsPage() {
9
8
  return (
10
9
  <React.Suspense fallback={<FormsSettingsPageSkeleton />}>
11
- <FormsSettingsPageContent />
10
+ <RoleGate roles={[UserRole.ADMIN, UserRole.EDITOR]}>
11
+ <FormsSettingsPageContent />
12
+ </RoleGate>
12
13
  </React.Suspense>
13
14
  )
14
15
  }