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
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
--warning-foreground: oklch(0.625 0.165 57.4);
|
|
99
99
|
--border: oklch(0.93 0 0);
|
|
100
100
|
--highlight: oklch(0.9 0.004 286.32);
|
|
101
|
-
--input: oklch(0.
|
|
101
|
+
--input: oklch(0.93 0 0);
|
|
102
102
|
--ring: oklch(0.708 0 0);
|
|
103
103
|
--chart-1: oklch(0.845 0.143 164.978);
|
|
104
104
|
--chart-2: oklch(0.696 0.17 162.48);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getSetting } from '@admin/actions/settings'
|
|
2
2
|
import { SidebarBranding } from './sidebar-branding'
|
|
3
3
|
|
|
4
|
-
export async function
|
|
4
|
+
export async function AdminSidebarBranding() {
|
|
5
5
|
const settings = await getSetting()
|
|
6
6
|
|
|
7
7
|
return <SidebarBranding initialData={settings} />
|
|
@@ -14,7 +14,7 @@ import { adminNavigation } from '@admin/data/navigation'
|
|
|
14
14
|
import { groupAdminNavigationItems } from '@admin/utils/navigation/sidebar'
|
|
15
15
|
import { Settings, Users } from 'lucide-react'
|
|
16
16
|
import { Fragment, Suspense } from 'react'
|
|
17
|
-
import {
|
|
17
|
+
import { AdminSidebarBranding } from './admin-sidebar-branding'
|
|
18
18
|
import { BrandingSkeleton } from './admin-sidebar-branding-skeleton'
|
|
19
19
|
import { SidebarNavLink } from './admin-sidebar-nav-link'
|
|
20
20
|
import { SidebarUserMenu } from './admin-sidebar-user-menu'
|
|
@@ -27,7 +27,7 @@ export function AdminSidebar(props: ComponentProps<typeof Sidebar>) {
|
|
|
27
27
|
<Sidebar collapsible="icon" {...props}>
|
|
28
28
|
<SidebarHeader className="flex h-14 w-full items-center border-t border-b border-border/80">
|
|
29
29
|
<Suspense fallback={<BrandingSkeleton />}>
|
|
30
|
-
<
|
|
30
|
+
<AdminSidebarBranding />
|
|
31
31
|
</Suspense>
|
|
32
32
|
</SidebarHeader>
|
|
33
33
|
<SidebarContent className="pt-4 pb-1">
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserRole } from '@admin/types/auth'
|
|
2
|
+
import { requireRole } from '@admin/auth/middleware'
|
|
3
|
+
|
|
4
|
+
export async function RoleGate({
|
|
5
|
+
roles,
|
|
6
|
+
children
|
|
7
|
+
}: {
|
|
8
|
+
roles: readonly UserRole[]
|
|
9
|
+
children: React.ReactNode
|
|
10
|
+
}) {
|
|
11
|
+
await requireRole(roles)
|
|
12
|
+
|
|
13
|
+
return <>{children}</>
|
|
14
|
+
}
|
package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { UploadFileResult } from '@admin/types'
|
|
5
|
+
import type { FieldErrors } from 'react-hook-form'
|
|
5
6
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
7
|
+
import { Form } from '@admin/components/ui/form'
|
|
6
8
|
import {
|
|
7
9
|
InputGroup,
|
|
8
10
|
InputGroupAddon,
|
|
@@ -15,19 +17,29 @@ import { getMediaUrlPlaceholder } from '@admin/utils/media/get-media-url-placeho
|
|
|
15
17
|
import { cn } from '@admin/utils/shared/cn'
|
|
16
18
|
import { isValidUrl } from '@admin/utils/validation/is-valid-url'
|
|
17
19
|
import { Upload } from 'lucide-react'
|
|
20
|
+
import { useForm } from 'react-hook-form'
|
|
21
|
+
import { toast } from 'sonner'
|
|
18
22
|
|
|
19
23
|
interface MediaUrlImporterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
24
|
onImportFromUrl: (url: string) => Promise<UploadFileResult>
|
|
21
25
|
accept?: string
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
interface MediaUrlFormValues {
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUrlImporterProps) {
|
|
25
|
-
const [urlValue, setUrlValue] = React.useState('')
|
|
26
|
-
const [urlError, setUrlError] = React.useState<string | null>(null)
|
|
27
33
|
const [isImporting, startImportTransition] = React.useTransition()
|
|
28
34
|
const mountedRef = React.useRef(true)
|
|
29
35
|
const importInFlightRef = React.useRef(false)
|
|
30
36
|
const importRequestIdRef = React.useRef(0)
|
|
37
|
+
const form = useForm<MediaUrlFormValues>({
|
|
38
|
+
defaultValues: { url: '' },
|
|
39
|
+
mode: 'onSubmit',
|
|
40
|
+
reValidateMode: 'onChange'
|
|
41
|
+
})
|
|
42
|
+
const urlField = form.register('url', { required: 'Enter a direct media URL to import.' })
|
|
31
43
|
|
|
32
44
|
React.useEffect(() => {
|
|
33
45
|
mountedRef.current = true
|
|
@@ -42,93 +54,92 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
|
|
|
42
54
|
const urlHelpText = getMediaUrlHelpText(accept)
|
|
43
55
|
const urlPlaceholder = getMediaUrlPlaceholder(accept)
|
|
44
56
|
|
|
45
|
-
const handleImport = React.useCallback(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const nextUrl = urlValue.trim()
|
|
49
|
-
|
|
50
|
-
if (!nextUrl) {
|
|
51
|
-
setUrlError('Enter a direct media URL to import.')
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!isValidUrl(nextUrl)) {
|
|
56
|
-
setUrlError('Enter a valid HTTP or HTTPS URL.')
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
setUrlError(null)
|
|
61
|
-
const importRequestId = importRequestIdRef.current + 1
|
|
62
|
-
importRequestIdRef.current = importRequestId
|
|
63
|
-
importInFlightRef.current = true
|
|
57
|
+
const handleImport = React.useCallback(
|
|
58
|
+
(values: MediaUrlFormValues) => {
|
|
59
|
+
if (isImporting || importInFlightRef.current) return
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const result = await onImportFromUrl(nextUrl)
|
|
61
|
+
const nextUrl = values.url.trim()
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!result.success) {
|
|
74
|
-
setUrlError(result.error ?? 'Failed to import media from URL.')
|
|
75
|
-
return
|
|
76
|
-
}
|
|
63
|
+
if (!isValidUrl(nextUrl)) {
|
|
64
|
+
toast.error('Enter a valid HTTP or HTTPS URL.')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
const importRequestId = importRequestIdRef.current + 1
|
|
69
|
+
importRequestIdRef.current = importRequestId
|
|
70
|
+
importInFlightRef.current = true
|
|
71
|
+
|
|
72
|
+
startImportTransition(async () => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await onImportFromUrl(nextUrl)
|
|
75
|
+
|
|
76
|
+
if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
toast.error(result.error ?? 'Failed to import media from URL.')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
form.reset()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
toast.error(error instanceof Error ? error.message : 'Failed to import media from URL.')
|
|
92
|
+
} finally {
|
|
93
|
+
if (importRequestIdRef.current === importRequestId) {
|
|
94
|
+
importInFlightRef.current = false
|
|
95
|
+
}
|
|
82
96
|
}
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
[form, isImporting, onImportFromUrl]
|
|
100
|
+
)
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}, [isImporting, onImportFromUrl, urlValue])
|
|
102
|
+
const handleInvalid = (errors: FieldErrors<MediaUrlFormValues>) => {
|
|
103
|
+
const message = errors.url?.message
|
|
104
|
+
toast.error(typeof message === 'string' ? message : 'Enter a direct media URL to import.')
|
|
105
|
+
}
|
|
92
106
|
|
|
93
107
|
return (
|
|
94
108
|
<Card className={cn('flex items-center justify-center', className)}>
|
|
95
109
|
<CardContent className="h-full w-full">
|
|
96
110
|
<div className="flex h-full w-full flex-col items-center justify-center gap-3 text-center">
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
type="url"
|
|
100
|
-
name="url"
|
|
101
|
-
aria-label="Media URL"
|
|
102
|
-
value={urlValue}
|
|
103
|
-
placeholder={urlPlaceholder}
|
|
111
|
+
<Form {...form}>
|
|
112
|
+
<form
|
|
104
113
|
autoComplete="off"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
noValidate
|
|
115
|
+
onSubmit={form.handleSubmit(handleImport, handleInvalid)}
|
|
116
|
+
className="w-full"
|
|
117
|
+
>
|
|
118
|
+
<InputGroup className="mx-auto max-w-2xl bg-background">
|
|
119
|
+
<InputGroupInput
|
|
120
|
+
type="url"
|
|
121
|
+
aria-label="Media URL"
|
|
122
|
+
placeholder={urlPlaceholder}
|
|
123
|
+
autoComplete="off"
|
|
124
|
+
spellCheck={false}
|
|
125
|
+
{...urlField}
|
|
126
|
+
disabled={isImporting}
|
|
127
|
+
/>
|
|
128
|
+
<InputGroupAddon align="inline-end">
|
|
129
|
+
<InputGroupButton
|
|
130
|
+
type="submit"
|
|
131
|
+
variant="outline"
|
|
132
|
+
disabled={isImporting}
|
|
133
|
+
aria-busy={isImporting}
|
|
134
|
+
className="h-full px-5"
|
|
135
|
+
>
|
|
136
|
+
{isImporting ? <Spinner /> : <Upload />}
|
|
137
|
+
{isImporting ? 'Importing…' : 'Import'}
|
|
138
|
+
</InputGroupButton>
|
|
139
|
+
</InputGroupAddon>
|
|
140
|
+
</InputGroup>
|
|
141
|
+
</form>
|
|
142
|
+
</Form>
|
|
132
143
|
|
|
133
144
|
<div className="flex w-full flex-col items-center text-center">
|
|
134
145
|
<p className="text-sm text-muted-foreground">{urlHelpText}</p>
|
|
@@ -40,7 +40,11 @@ export function SearchInput({
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<Form {...form}>
|
|
43
|
-
<form
|
|
43
|
+
<form
|
|
44
|
+
autoComplete="off"
|
|
45
|
+
onSubmit={form.handleSubmit(onSubmit)}
|
|
46
|
+
className="flex w-full items-center"
|
|
47
|
+
>
|
|
44
48
|
<FormField
|
|
45
49
|
control={form.control}
|
|
46
50
|
name="search"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ContentSkeleton } from '@admin/components/layouts/content-skeleton'
|
|
2
2
|
import { Suspense } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { AccountShell } from './account-shell'
|
|
4
4
|
|
|
5
5
|
export default function AccountLayout({ children }: { children: React.ReactNode }) {
|
|
6
6
|
return (
|
|
7
7
|
<Suspense fallback={<ContentSkeleton />}>
|
|
8
|
-
<
|
|
8
|
+
<AccountShell>{children}</AccountShell>
|
|
9
9
|
</Suspense>
|
|
10
10
|
)
|
|
11
11
|
}
|
package/dist/assets/adapters/next/templates/init/pages/{account-shell-rsc.tsx → account-shell.tsx}
RENAMED
|
@@ -6,7 +6,7 @@ import { canAccessAdmin } from '@admin/utils/auth/can-access-admin'
|
|
|
6
6
|
import { redirect } from 'next/navigation'
|
|
7
7
|
import { MinimalAccountShell } from './minimal-account-shell'
|
|
8
8
|
|
|
9
|
-
export async function
|
|
9
|
+
export async function AccountShell({ children }: { children: React.ReactNode }) {
|
|
10
10
|
const session = await getSession({ disableCookieCache: true })
|
|
11
11
|
|
|
12
12
|
if (!session?.user) {
|
|
@@ -2,7 +2,7 @@ import { requireRole, UserRole } from '@admin/auth/middleware'
|
|
|
2
2
|
import { AdminSidebar } from '@admin/components/layouts/admin-sidebar'
|
|
3
3
|
import { SidebarProvider } from '@admin/components/ui/sidebar'
|
|
4
4
|
|
|
5
|
-
export async function
|
|
5
|
+
export async function AuthGate({ children }: { children: React.ReactNode }) {
|
|
6
6
|
await requireRole([UserRole.ADMIN, UserRole.EDITOR])
|
|
7
7
|
|
|
8
8
|
return (
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ContentSkeleton } from '@admin/components/layouts/content-skeleton'
|
|
2
2
|
import { Suspense } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { AuthGate } from './auth-gate'
|
|
4
4
|
|
|
5
5
|
export default function AdminAuthLayout({ children }: { children: React.ReactNode }) {
|
|
6
6
|
return (
|
|
7
7
|
<Suspense fallback={<ContentSkeleton />}>
|
|
8
|
-
<
|
|
8
|
+
<AuthGate>{children}</AuthGate>
|
|
9
9
|
</Suspense>
|
|
10
10
|
)
|
|
11
11
|
}
|
|
@@ -1,49 +1,68 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import type { FieldErrors } from 'react-hook-form'
|
|
4
5
|
import { isEmailConfigured } from '@admin/actions/profile'
|
|
5
6
|
import { authClient } from '@admin/auth/client'
|
|
6
7
|
import { Button } from '@admin/components/ui/button'
|
|
7
8
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
8
9
|
import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
|
|
10
|
+
import { Form } from '@admin/components/ui/form'
|
|
9
11
|
import { Input } from '@admin/components/ui/input'
|
|
10
12
|
import { Spinner } from '@admin/components/ui/spinner'
|
|
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'
|
|
17
|
+
import { useForm } from 'react-hook-form'
|
|
18
|
+
import { toast } from 'sonner'
|
|
19
|
+
import { z } from 'zod/v3'
|
|
20
|
+
|
|
21
|
+
const forgotPasswordSchema = z.object({
|
|
22
|
+
email: z.string().trim().min(1, 'Email is required').email('Enter a valid email address')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>
|
|
14
26
|
|
|
15
27
|
export function ForgotPasswordForm({ className, ...props }: React.ComponentProps<'div'>) {
|
|
16
|
-
const [email, setEmail] = React.useState('')
|
|
17
|
-
const [error, setError] = React.useState<string | null>(null)
|
|
18
|
-
const [success, setSuccess] = React.useState(false)
|
|
19
28
|
const [isPending, startTransition] = React.useTransition()
|
|
29
|
+
const form = useForm<ForgotPasswordFormValues>({
|
|
30
|
+
resolver: standardSchemaResolver(forgotPasswordSchema),
|
|
31
|
+
defaultValues: { email: '' },
|
|
32
|
+
mode: 'onChange',
|
|
33
|
+
reValidateMode: 'onChange'
|
|
34
|
+
})
|
|
20
35
|
|
|
21
|
-
const handleSubmit = (
|
|
22
|
-
e.preventDefault()
|
|
23
|
-
setError(null)
|
|
24
|
-
|
|
36
|
+
const handleSubmit = (values: ForgotPasswordFormValues) => {
|
|
25
37
|
startTransition(async () => {
|
|
26
38
|
try {
|
|
27
39
|
const emailReady = await isEmailConfigured()
|
|
28
40
|
if (!emailReady) {
|
|
29
|
-
|
|
41
|
+
toast.error('Email delivery is not configured for this project.')
|
|
30
42
|
return
|
|
31
43
|
}
|
|
32
44
|
const { error } = await authClient.requestPasswordReset({
|
|
33
|
-
email,
|
|
45
|
+
email: values.email,
|
|
34
46
|
redirectTo: '/admin/reset-password'
|
|
35
47
|
})
|
|
36
48
|
if (error) {
|
|
37
|
-
|
|
49
|
+
toast.error(error.message || 'Failed to send reset link')
|
|
38
50
|
return
|
|
39
51
|
}
|
|
40
|
-
|
|
52
|
+
toast.success(
|
|
53
|
+
'If an account exists, we sent a password reset link. Check your inbox and spam folder.'
|
|
54
|
+
)
|
|
41
55
|
} catch {
|
|
42
|
-
|
|
56
|
+
toast.error('Failed to send the reset link. Try again.')
|
|
43
57
|
}
|
|
44
58
|
})
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
const handleInvalid = (errors: FieldErrors<ForgotPasswordFormValues>) => {
|
|
62
|
+
const message = errors.email?.message
|
|
63
|
+
toast.error(typeof message === 'string' ? message : 'Enter a valid email address.')
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
return (
|
|
48
67
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
49
68
|
<Card className="overflow-hidden p-0">
|
|
@@ -58,21 +77,12 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
|
|
|
58
77
|
/>
|
|
59
78
|
</div>
|
|
60
79
|
<div className="p-6 md:p-20">
|
|
61
|
-
{
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
inbox and spam folder.
|
|
68
|
-
</p>
|
|
69
|
-
</div>
|
|
70
|
-
<Link href="/admin/login" className="text-sm underline-offset-4 hover:underline">
|
|
71
|
-
Back to sign in
|
|
72
|
-
</Link>
|
|
73
|
-
</FieldGroup>
|
|
74
|
-
) : (
|
|
75
|
-
<form onSubmit={handleSubmit}>
|
|
80
|
+
<Form {...form}>
|
|
81
|
+
<form
|
|
82
|
+
autoComplete="off"
|
|
83
|
+
noValidate
|
|
84
|
+
onSubmit={form.handleSubmit(handleSubmit, handleInvalid)}
|
|
85
|
+
>
|
|
76
86
|
<FieldGroup className="gap-10 pb-12">
|
|
77
87
|
<div className="flex flex-col items-start">
|
|
78
88
|
<h1 className="text-xl font-medium">Forgot password</h1>
|
|
@@ -81,21 +91,16 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
|
|
|
81
91
|
enabled
|
|
82
92
|
</p>
|
|
83
93
|
</div>
|
|
84
|
-
{error ? (
|
|
85
|
-
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
|
|
86
|
-
{error}
|
|
87
|
-
</div>
|
|
88
|
-
) : null}
|
|
89
94
|
<div className="flex flex-col gap-4">
|
|
90
95
|
<Field>
|
|
91
96
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
92
97
|
<Input
|
|
93
98
|
id="email"
|
|
94
99
|
type="email"
|
|
95
|
-
autoComplete="
|
|
100
|
+
autoComplete="off"
|
|
96
101
|
placeholder="m@example.com"
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
aria-invalid={Boolean(form.formState.errors.email)}
|
|
103
|
+
{...form.register('email')}
|
|
99
104
|
required
|
|
100
105
|
disabled={isPending}
|
|
101
106
|
/>
|
|
@@ -115,7 +120,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
|
|
|
115
120
|
</div>
|
|
116
121
|
</FieldGroup>
|
|
117
122
|
</form>
|
|
118
|
-
|
|
123
|
+
</Form>
|
|
119
124
|
</div>
|
|
120
125
|
</CardContent>
|
|
121
126
|
</Card>
|