betterstart-cli 0.0.116 → 0.0.118

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 (85) hide show
  1. package/dist/assets/adapters/next/templates/init/admin-globals.css +20 -0
  2. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid-row.tsx +33 -1
  3. package/dist/assets/adapters/next/templates/init/components/shared/media/media-gallery-dialog.tsx +3 -3
  4. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +3 -1
  5. package/dist/assets/adapters/next/templates/init/hooks/use-copy-to-clipboard.ts +1 -3
  6. package/dist/assets/adapters/next/templates/init/hooks/use-field-error-visible.ts +25 -0
  7. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +1 -1
  8. package/dist/assets/adapters/next/templates/init/hooks/use-upload.ts +11 -5
  9. package/dist/assets/adapters/next/templates/init/lib/actions/audit/cleanup-audit-logs.ts +1 -0
  10. package/dist/assets/adapters/next/templates/init/lib/actions/audit/query-audit-logs.ts +2 -2
  11. package/dist/assets/adapters/next/templates/init/lib/actions/audit/record-audit-event.ts +1 -2
  12. package/dist/assets/adapters/next/templates/init/lib/actions/auth/client.ts +4 -1
  13. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/get-entity-versions.ts +5 -1
  14. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-all-form-settings.ts +1 -1
  15. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +2 -1
  16. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +1 -0
  17. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +1 -0
  18. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +2 -4
  19. package/dist/assets/adapters/next/templates/init/lib/actions/profile/change-own-password.ts +1 -0
  20. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +1 -0
  21. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-files.ts +1 -0
  22. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +16 -6
  23. package/dist/assets/adapters/next/templates/init/lib/actions/users/change-user-password.ts +1 -0
  24. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +2 -1
  25. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +1 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/users/types.ts +1 -1
  27. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +1 -0
  28. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +12 -4
  29. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/registry.ts +4 -4
  30. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -7
  31. package/dist/assets/adapters/next/templates/init/pages/container-layout.tsx +3 -0
  32. package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +78 -76
  33. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +5 -8
  34. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +2 -2
  35. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +7 -9
  36. package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +2 -2
  37. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +8 -16
  38. package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +2 -2
  39. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-detail-row.tsx +1 -1
  40. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +1 -6
  41. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-table.tsx +2 -7
  42. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +1 -6
  43. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +1 -1
  44. package/dist/assets/adapters/next/templates/init/public/placeholder.png +0 -0
  45. package/dist/assets/adapters/next/templates/init/types/index.ts +2 -0
  46. package/dist/assets/adapters/next/templates/init/utils/audit/normalize-comparable-value.ts +4 -2
  47. package/dist/assets/adapters/next/templates/init/utils/date/format-action-date.ts +12 -0
  48. package/dist/assets/adapters/next/templates/init/utils/dev-mode/dev-mode-json-schemas.ts +66 -0
  49. package/dist/assets/adapters/next/templates/init/utils/dev-mode/parse-dev-mode-lifecycle-hooks.ts +11 -0
  50. package/dist/assets/adapters/next/templates/init/utils/dev-mode/parse-dev-mode-snippets.ts +9 -0
  51. package/dist/assets/adapters/next/templates/init/utils/editor/set-task-item-checked.ts +13 -0
  52. package/dist/assets/adapters/next/templates/init/utils/editor/update-nodes-attr.ts +2 -1
  53. package/dist/assets/adapters/next/templates/init/utils/email/email-theme.ts +13 -0
  54. package/dist/assets/adapters/next/templates/init/utils/email/render-form-submission-rows.ts +3 -2
  55. package/dist/assets/adapters/next/templates/init/utils/email/render-notification-email.ts +7 -6
  56. package/dist/assets/adapters/next/templates/init/utils/email/render-thank-you-email.ts +5 -4
  57. package/dist/assets/adapters/next/templates/init/utils/error/read-string-property.ts +4 -2
  58. package/dist/assets/adapters/next/templates/init/utils/error/recognize.ts +2 -4
  59. package/dist/assets/adapters/next/templates/init/utils/error/to-action-error.ts +0 -1
  60. package/dist/assets/adapters/next/templates/init/utils/form/clone-default-value.ts +4 -2
  61. package/dist/assets/adapters/next/templates/init/utils/shared/is-record.ts +3 -0
  62. package/dist/assets/adapters/next/templates/init/utils/table/default-get-id.ts +3 -2
  63. package/dist/assets/shared-assets/react-admin/custom/content-editor/editor-toolbar.tsx +1 -1
  64. package/dist/assets/shared-assets/react-admin/custom/content-editor/slash-command-menu.tsx +5 -3
  65. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +1 -1
  66. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node.tsx +3 -1
  67. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/task-item-node/task-item-node-extension.tsx +6 -15
  68. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +1 -6
  69. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +1 -6
  70. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +9 -2
  71. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +3 -3
  72. package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +2 -2
  73. package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +3 -1
  74. package/dist/assets/shared-assets/react-admin/custom/progressive-blur.tsx +3 -1
  75. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +20 -23
  76. package/dist/assets/shared-assets/react-admin/ui/form.tsx +6 -3
  77. package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +1 -1
  78. package/dist/{chunk-SG4U6EQY.js → chunk-BNHFSHPE.js} +14 -1
  79. package/dist/chunk-BNHFSHPE.js.map +1 -0
  80. package/dist/cli.js +962 -633
  81. package/dist/cli.js.map +1 -1
  82. package/dist/{read-namespaced-template-D2CD4Q7U.js → read-namespaced-template-WYIGUH7S.js} +2 -2
  83. package/package.json +1 -1
  84. package/dist/chunk-SG4U6EQY.js.map +0 -1
  85. /package/dist/{read-namespaced-template-D2CD4Q7U.js.map → read-namespaced-template-WYIGUH7S.js.map} +0 -0
@@ -35,86 +35,88 @@ export default function DashboardPage() {
35
35
  <PageHeader title="Dashboard" hasBackButton={false} />
36
36
 
37
37
  <div className="w-full space-y-6 p-6">
38
- <Card>
39
- <CardHeader>
40
- <div className="flex items-center gap-2">
41
- <CircleGauge className="size-4 text-muted-foreground" />
42
- <CardTitle className="font-medium">Setup your dashboard</CardTitle>
43
- </div>
44
- </CardHeader>
45
- <CardContent className="p-10!">
46
- <div className="grid grid-cols-2 gap-10">
47
- <div className="flex w-full flex-col gap-1">
48
- <p className="text-base font-medium">Hey, let's get started</p>
49
- <p className="text-muted-foreground">
50
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
51
- </p>
38
+ <div className="container mx-auto">
39
+ <Card>
40
+ <CardHeader>
41
+ <div className="flex items-center gap-2">
42
+ <CircleGauge className="size-4 text-muted-foreground" />
43
+ <CardTitle className="font-medium">Setup your dashboard</CardTitle>
52
44
  </div>
53
- <div className="flex w-full flex-col">
54
- <Accordion defaultValue={['item-1']}>
55
- <AccordionItem value="item-1">
56
- <AccordionTrigger>
57
- <div className="flex items-center gap-3">
58
- <ShieldEllipsis className="size-4 text-muted-foreground" />
59
- <span>Setup ENV Variables</span>
60
- </div>
61
- </AccordionTrigger>
62
- <AccordionContent className="h-auto pl-7">
63
- <div className="flex w-full flex-col gap-1">
64
- {envVariables.map((env) => (
65
- <div
66
- className="flex w-full flex-col rounded-sm border border-border bg-card py-2 pr-2 pl-4 shadow-card"
67
- key={env.name}
68
- >
69
- <div className="flex items-center justify-between">
70
- <div className="flex items-center gap-2">
71
- <span className="">{env.label}</span>
72
- <Badge variant={env.value ? 'success' : 'destructive'}>
73
- {env.value ? 'Connected' : 'Not configured'}
74
- </Badge>
75
- </div>
45
+ </CardHeader>
46
+ <CardContent className="p-10!">
47
+ <div className="grid grid-cols-2 gap-10">
48
+ <div className="flex w-full flex-col gap-1">
49
+ <p className="text-base font-medium">Hey, let's get started</p>
50
+ <p className="text-muted-foreground">
51
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
52
+ </p>
53
+ </div>
54
+ <div className="flex w-full flex-col">
55
+ <Accordion defaultValue={['item-1']}>
56
+ <AccordionItem value="item-1">
57
+ <AccordionTrigger>
58
+ <div className="flex items-center gap-3">
59
+ <ShieldEllipsis className="size-4 text-muted-foreground" />
60
+ <span>Setup ENV Variables</span>
61
+ </div>
62
+ </AccordionTrigger>
63
+ <AccordionContent className="h-auto pl-7">
64
+ <div className="flex w-full flex-col gap-1">
65
+ {envVariables.map((env) => (
66
+ <div
67
+ className="flex w-full flex-col rounded-sm border border-border bg-card py-2 pr-2 pl-4 shadow-card"
68
+ key={env.name}
69
+ >
70
+ <div className="flex items-center justify-between">
71
+ <div className="flex items-center gap-2">
72
+ <span className="">{env.label}</span>
73
+ <Badge variant={env.value ? 'success' : 'destructive'}>
74
+ {env.value ? 'Connected' : 'Not configured'}
75
+ </Badge>
76
+ </div>
76
77
 
77
- <Button variant="ghost" className="size-8">
78
- <FileQuestion className="size-4" />
79
- </Button>
78
+ <Button variant="ghost" className="size-8">
79
+ <FileQuestion className="size-4" />
80
+ </Button>
81
+ </div>
80
82
  </div>
81
- </div>
82
- ))}
83
- </div>
84
- </AccordionContent>
85
- </AccordionItem>
86
- <AccordionItem value="item-2">
87
- <AccordionTrigger>
88
- <div className="flex items-center gap-3">
89
- <Blocks className="size-4 text-muted-foreground" />
90
- <span>Install Pre-built Modules</span>
91
- </div>
92
- </AccordionTrigger>
93
- <AccordionContent className="pl-7">Lorem</AccordionContent>
94
- </AccordionItem>
95
- <AccordionItem value="item-3">
96
- <AccordionTrigger>
97
- <div className="flex items-center gap-3">
98
- <PlugZap className="size-4 text-muted-foreground" />
99
- <span>Setup Provider Integrations</span>
100
- </div>
101
- </AccordionTrigger>
102
- <AccordionContent className="pl-7">Lorem</AccordionContent>
103
- </AccordionItem>
104
- <AccordionItem value="item-4">
105
- <AccordionTrigger>
106
- <div className="flex items-center gap-3">
107
- <Globe className="size-4 text-muted-foreground" />
108
- <span>Deploy</span>
109
- </div>
110
- </AccordionTrigger>
111
- <AccordionContent className="pl-7">Lorem</AccordionContent>
112
- </AccordionItem>
113
- </Accordion>
83
+ ))}
84
+ </div>
85
+ </AccordionContent>
86
+ </AccordionItem>
87
+ <AccordionItem value="item-2">
88
+ <AccordionTrigger>
89
+ <div className="flex items-center gap-3">
90
+ <Blocks className="size-4 text-muted-foreground" />
91
+ <span>Install Pre-built Modules</span>
92
+ </div>
93
+ </AccordionTrigger>
94
+ <AccordionContent className="pl-7">Lorem</AccordionContent>
95
+ </AccordionItem>
96
+ <AccordionItem value="item-3">
97
+ <AccordionTrigger>
98
+ <div className="flex items-center gap-3">
99
+ <PlugZap className="size-4 text-muted-foreground" />
100
+ <span>Setup Provider Integrations</span>
101
+ </div>
102
+ </AccordionTrigger>
103
+ <AccordionContent className="pl-7">Lorem</AccordionContent>
104
+ </AccordionItem>
105
+ <AccordionItem value="item-4">
106
+ <AccordionTrigger>
107
+ <div className="flex items-center gap-3">
108
+ <Globe className="size-4 text-muted-foreground" />
109
+ <span>Deploy</span>
110
+ </div>
111
+ </AccordionTrigger>
112
+ <AccordionContent className="pl-7">Lorem</AccordionContent>
113
+ </AccordionItem>
114
+ </Accordion>
115
+ </div>
114
116
  </div>
115
- </div>
116
- </CardContent>
117
- </Card>
117
+ </CardContent>
118
+ </Card>
119
+ </div>
118
120
  </div>
119
121
  </React.Fragment>
120
122
  )
@@ -10,6 +10,7 @@ import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
10
10
  import { Form } from '@admin/components/ui/form'
11
11
  import { Input } from '@admin/components/ui/input'
12
12
  import { Spinner } from '@admin/components/ui/spinner'
13
+ import { useFieldErrorVisible } from '@admin/hooks/use-field-error-visible'
13
14
  import { cn } from '@admin/utils/shared/cn'
14
15
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
15
16
  import Image from 'next/image'
@@ -33,6 +34,8 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
33
34
  reValidateMode: 'onChange'
34
35
  })
35
36
 
37
+ const isEmailErrorVisible = useFieldErrorVisible(form, 'email')
38
+
36
39
  const handleSubmit = (values: ForgotPasswordFormValues) => {
37
40
  startTransition(async () => {
38
41
  try {
@@ -68,13 +71,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
68
71
  <Card className="overflow-hidden p-0">
69
72
  <CardContent className="grid grid-cols-[5fr_7fr] p-0">
70
73
  <div className="relative block bg-muted">
71
- <Image
72
- src="https://assets.betterstart.dev/assets/placeholder.png"
73
- alt=""
74
- fill
75
- sizes="42vw"
76
- className="object-cover"
77
- />
74
+ <Image src="/placeholder.png" alt="" fill sizes="42vw" className="object-cover" />
78
75
  </div>
79
76
  <div className="p-20">
80
77
  <Form {...form}>
@@ -99,7 +96,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
99
96
  type="email"
100
97
  autoComplete="off"
101
98
  placeholder="m@example.com"
102
- aria-invalid={Boolean(form.formState.errors.email)}
99
+ aria-invalid={isEmailErrorVisible}
103
100
  {...form.register('email')}
104
101
  required
105
102
  disabled={isPending}
@@ -2,8 +2,8 @@ export function ForgotPasswordPageSkeleton() {
2
2
  return (
3
3
  <div className="overflow-hidden rounded-sm border bg-card">
4
4
  <div className="grid grid-cols-[5fr_7fr]">
5
- <div className="block min-h-[460px] bg-muted" />
6
- <div className="flex min-h-[460px] flex-col gap-10 p-20">
5
+ <div className="block min-h-auth-forgot-panel bg-muted" />
6
+ <div className="flex min-h-auth-forgot-panel flex-col gap-10 p-20">
7
7
  <div className="space-y-3">
8
8
  <div className="h-6 w-40 animate-pulse rounded-md bg-muted" />
9
9
  <div className="h-4 w-full max-w-72 animate-pulse rounded-md bg-muted" />
@@ -9,6 +9,7 @@ import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
9
9
  import { Form } from '@admin/components/ui/form'
10
10
  import { Input } from '@admin/components/ui/input'
11
11
  import { Spinner } from '@admin/components/ui/spinner'
12
+ import { useFieldErrorVisible } from '@admin/hooks/use-field-error-visible'
12
13
  import { getAdminPostLoginPath } from '@admin/utils/auth/get-admin-post-login-path'
13
14
  import { cn } from '@admin/utils/shared/cn'
14
15
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
@@ -41,6 +42,9 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
41
42
  reValidateMode: 'onChange'
42
43
  })
43
44
 
45
+ const isEmailErrorVisible = useFieldErrorVisible(form, 'email')
46
+ const isPasswordErrorVisible = useFieldErrorVisible(form, 'password')
47
+
44
48
  React.useEffect(() => {
45
49
  if (reset === 'success') {
46
50
  toast.success('Password reset. Sign in with your new password.', {
@@ -83,13 +87,7 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
83
87
  <Card className="overflow-hidden p-0">
84
88
  <CardContent className="grid grid-cols-[5fr_7fr] p-0">
85
89
  <div className="relative block bg-muted">
86
- <Image
87
- src="https://assets.betterstart.dev/assets/placeholder.png"
88
- alt=""
89
- fill
90
- sizes="42vw"
91
- className="object-cover"
92
- />
90
+ <Image src="/placeholder.png" alt="" fill sizes="42vw" className="object-cover" />
93
91
  </div>
94
92
  <Form {...form}>
95
93
  <form
@@ -110,7 +108,7 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
110
108
  type="email"
111
109
  autoComplete="off"
112
110
  placeholder="m@example.com"
113
- aria-invalid={Boolean(form.formState.errors.email)}
111
+ aria-invalid={isEmailErrorVisible}
114
112
  {...form.register('email')}
115
113
  required
116
114
  disabled={isPending}
@@ -132,7 +130,7 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
132
130
  type="password"
133
131
  autoComplete="off"
134
132
  placeholder="Enter your password"
135
- aria-invalid={Boolean(form.formState.errors.password)}
133
+ aria-invalid={isPasswordErrorVisible}
136
134
  {...form.register('password')}
137
135
  required
138
136
  disabled={isPending}
@@ -2,8 +2,8 @@ export function LoginPageSkeleton() {
2
2
  return (
3
3
  <div className="overflow-hidden rounded-sm border bg-card">
4
4
  <div className="grid grid-cols-[5fr_7fr]">
5
- <div className="block min-h-[540px] bg-muted" />
6
- <div className="flex min-h-[540px] flex-col gap-10 p-20">
5
+ <div className="block min-h-auth-login-panel bg-muted" />
6
+ <div className="flex min-h-auth-login-panel flex-col gap-10 p-20">
7
7
  <div className="space-y-3">
8
8
  <div className="h-6 w-32 animate-pulse rounded-md bg-muted" />
9
9
  <div className="h-4 w-40 animate-pulse rounded-md bg-muted" />
@@ -9,6 +9,7 @@ import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
9
9
  import { Form } from '@admin/components/ui/form'
10
10
  import { Input } from '@admin/components/ui/input'
11
11
  import { Spinner } from '@admin/components/ui/spinner'
12
+ import { useFieldErrorVisible } from '@admin/hooks/use-field-error-visible'
12
13
  import { cn } from '@admin/utils/shared/cn'
13
14
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
14
15
  import Image from 'next/image'
@@ -49,19 +50,16 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
49
50
  reValidateMode: 'onChange'
50
51
  })
51
52
 
53
+ const isNewPasswordErrorVisible = useFieldErrorVisible(form, 'newPassword')
54
+ const isConfirmPasswordErrorVisible = useFieldErrorVisible(form, 'confirmPassword')
55
+
52
56
  if (!token) {
53
57
  return (
54
58
  <div className={cn('flex flex-col gap-6', className)} {...props}>
55
59
  <Card className="overflow-hidden p-0">
56
60
  <CardContent className="grid grid-cols-[5fr_7fr] p-0">
57
61
  <div className="relative block bg-muted">
58
- <Image
59
- src="https://assets.betterstart.dev/assets/placeholder.png"
60
- alt=""
61
- fill
62
- sizes="42vw"
63
- className="object-cover"
64
- />
62
+ <Image src="/placeholder.png" alt="" fill sizes="42vw" className="object-cover" />
65
63
  </div>
66
64
  <div className="p-20">
67
65
  <FieldGroup className="gap-10 pb-12">
@@ -114,13 +112,7 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
114
112
  <Card className="overflow-hidden p-0">
115
113
  <CardContent className="grid grid-cols-[5fr_7fr] p-0">
116
114
  <div className="relative block bg-muted">
117
- <Image
118
- src="https://assets.betterstart.dev/assets/placeholder.png"
119
- alt=""
120
- fill
121
- sizes="42vw"
122
- className="object-cover"
123
- />
115
+ <Image src="/placeholder.png" alt="" fill sizes="42vw" className="object-cover" />
124
116
  </div>
125
117
  <Form {...form}>
126
118
  <form
@@ -144,7 +136,7 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
144
136
  type="password"
145
137
  autoComplete="off"
146
138
  placeholder="Enter new password"
147
- aria-invalid={Boolean(form.formState.errors.newPassword)}
139
+ aria-invalid={isNewPasswordErrorVisible}
148
140
  {...form.register('newPassword')}
149
141
  required
150
142
  disabled={isPending}
@@ -157,7 +149,7 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
157
149
  type="password"
158
150
  autoComplete="off"
159
151
  placeholder="Confirm new password"
160
- aria-invalid={Boolean(form.formState.errors.confirmPassword)}
152
+ aria-invalid={isConfirmPasswordErrorVisible}
161
153
  {...form.register('confirmPassword')}
162
154
  required
163
155
  disabled={isPending}
@@ -2,8 +2,8 @@ export function ResetPasswordPageSkeleton() {
2
2
  return (
3
3
  <div className="overflow-hidden rounded-sm border bg-card">
4
4
  <div className="grid grid-cols-[5fr_7fr]">
5
- <div className="block min-h-[520px] bg-muted" />
6
- <div className="flex min-h-[520px] flex-col gap-10 p-20">
5
+ <div className="block min-h-auth-reset-panel bg-muted" />
6
+ <div className="flex min-h-auth-reset-panel flex-col gap-10 p-20">
7
7
  <div className="space-y-3">
8
8
  <div className="h-6 w-36 animate-pulse rounded-md bg-muted" />
9
9
  <div className="h-4 w-56 animate-pulse rounded-md bg-muted" />
@@ -7,7 +7,7 @@ interface AuditLogDetailRowProps {
7
7
 
8
8
  export function AuditLogDetailRow({ label, value }: AuditLogDetailRowProps) {
9
9
  return (
10
- <div className="grid grid-cols-[120px_1fr] gap-3 border-b border-border px-4 py-2 last:border-b-0">
10
+ <div className="grid grid-cols-audit-detail gap-3 border-b border-border px-4 py-2 last:border-b-0">
11
11
  <dt className="text-sm font-medium text-muted-foreground">{label}</dt>
12
12
  <dd className="min-w-0 text-sm break-words text-foreground">{value || 'Unknown'}</dd>
13
13
  </div>
@@ -248,12 +248,7 @@ export function AuditLogTable<TValue>({
248
248
  <DataGridRow
249
249
  key={row.id}
250
250
  className="cursor-pointer hover:bg-muted/50"
251
- onClick={(event) => {
252
- const target = event.target as HTMLElement
253
- if (!event.currentTarget.contains(target)) return
254
- if (target.closest('button, a, input, [role="checkbox"]')) return
255
- onRowClick(row.original)
256
- }}
251
+ onActivate={() => onRowClick(row.original)}
257
252
  >
258
253
  {row.getVisibleCells().map((cell) => (
259
254
  <DataGridCell
@@ -36,7 +36,7 @@ export function FormsSettingsTable({
36
36
  <div className={cn('flex h-full flex-col gap-6 pt-px pb-2', className)}>
37
37
  <DataGrid
38
38
  containerClassName="min-h-0 flex-1"
39
- gridTemplateColumns="minmax(200px, 1fr) minmax(240px, 2fr)"
39
+ gridTemplateColumns="minmax(var(--spacing-form-settings-name), 1fr) minmax(var(--spacing-form-settings-recipients), 2fr)"
40
40
  >
41
41
  <DataGridHeader>
42
42
  {table.getHeaderGroups().map((headerGroup) => (
@@ -57,12 +57,7 @@ export function FormsSettingsTable({
57
57
  <DataGridRow
58
58
  key={row.id}
59
59
  className="cursor-pointer hover:bg-muted/50"
60
- onClick={(event) => {
61
- const target = event.target as HTMLElement
62
- if (!event.currentTarget.contains(target)) return
63
- if (target.closest('button, a, input, [role="checkbox"]')) return
64
- onRowClick(row.original)
65
- }}
60
+ onActivate={() => onRowClick(row.original)}
66
61
  >
67
62
  {row.getVisibleCells().map((cell) => (
68
63
  <DataGridCell key={cell.id} data-column-id={cell.column.id}>
@@ -217,12 +217,7 @@ export function WebhooksTable<TValue>({
217
217
  <DataGridRow
218
218
  key={row.id}
219
219
  className="cursor-pointer hover:bg-muted/50"
220
- onClick={(e) => {
221
- const target = e.target as HTMLElement
222
- if (!e.currentTarget.contains(target)) return
223
- if (target.closest('button, a, input, [role="checkbox"], [role="switch"]')) return
224
- onRowClick(row.original)
225
- }}
220
+ onActivate={() => onRowClick(row.original)}
226
221
  >
227
222
  {row.getVisibleCells().map((cell) => (
228
223
  <DataGridCell
@@ -118,7 +118,7 @@ export function UsersTable<TValue>({
118
118
  email: session.user.email,
119
119
  name: session.user.name,
120
120
  image: session.user.image,
121
- role: (session.user as { role?: string }).role || 'member'
121
+ role: session.user.role || 'member'
122
122
  }
123
123
  : null
124
124
  },
@@ -23,6 +23,8 @@ export interface AdminMedia {
23
23
  updatedBy: string | null
24
24
  createdByUser?: AdminUserSummary | null
25
25
  updatedByUser?: AdminUserSummary | null
26
+ createdAtFormatted?: string
27
+ updatedAtFormatted?: string
26
28
  }
27
29
 
28
30
  export interface MediaListParams {
@@ -1,10 +1,12 @@
1
+ import { isRecord } from '@admin/utils/shared/is-record'
2
+
1
3
  export function normalizeComparableValue(value: unknown): string {
2
4
  if (value instanceof Date) return value.toISOString()
3
5
  if (Array.isArray(value)) {
4
6
  return JSON.stringify(value.map((item) => normalizeComparableValue(item)))
5
7
  }
6
- if (value && typeof value === 'object') {
7
- const entries = Object.entries(value as Record<string, unknown>)
8
+ if (isRecord(value)) {
9
+ const entries = Object.entries(value)
8
10
  .sort(([a], [b]) => a.localeCompare(b))
9
11
  .map(([key, item]) => [key, normalizeComparableValue(item)])
10
12
  return JSON.stringify(entries)
@@ -0,0 +1,12 @@
1
+ export function formatActionDate(value: unknown): string {
2
+ if (typeof value !== 'string') return ''
3
+ const date = new Date(value)
4
+ if (Number.isNaN(date.getTime())) return ''
5
+
6
+ return date.toLocaleDateString('en-US', {
7
+ month: 'short',
8
+ day: '2-digit',
9
+ year: 'numeric',
10
+ timeZone: 'UTC'
11
+ })
12
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod/v3'
2
+
3
+ export const devModeCodeLanguageSchema = z.enum(['tsx', 'ts', 'json'])
4
+
5
+ export const devModeSnippetIconSchema = z.enum([
6
+ 'eye',
7
+ 'file-code',
8
+ 'inbox',
9
+ 'list',
10
+ 'pencil',
11
+ 'plus',
12
+ 'save',
13
+ 'send',
14
+ 'trash'
15
+ ])
16
+
17
+ export const devModeSnippetExampleSchema = z.object({
18
+ label: z.string(),
19
+ language: devModeCodeLanguageSchema,
20
+ code: z.string()
21
+ })
22
+
23
+ export const devModeSnippetCodeTabSchema = z.object({
24
+ id: z.string(),
25
+ label: z.string(),
26
+ language: devModeCodeLanguageSchema,
27
+ code: z.string()
28
+ })
29
+
30
+ export const devModeSnippetSchema = z.object({
31
+ id: z.string(),
32
+ label: z.string(),
33
+ kind: z.enum(['jsx', 'query', 'mutation']),
34
+ icon: devModeSnippetIconSchema,
35
+ language: devModeCodeLanguageSchema,
36
+ code: z.string(),
37
+ codeTabs: z.array(devModeSnippetCodeTabSchema).optional(),
38
+ payloadExample: devModeSnippetExampleSchema.optional(),
39
+ responseExample: devModeSnippetExampleSchema.optional()
40
+ })
41
+
42
+ export const devModeLifecycleHookExampleSchema = z.object({
43
+ id: z.string(),
44
+ label: z.string(),
45
+ event: z.enum([
46
+ 'beforeCreate',
47
+ 'afterCreate',
48
+ 'beforeUpdate',
49
+ 'afterUpdate',
50
+ 'beforeDelete',
51
+ 'afterDelete',
52
+ 'beforePublish',
53
+ 'afterPublish',
54
+ 'beforeUnpublish',
55
+ 'afterUnpublish',
56
+ 'beforeRestore',
57
+ 'afterRestore',
58
+ 'beforeReorder',
59
+ 'afterReorder'
60
+ ]),
61
+ phase: z.enum(['before', 'after']),
62
+ icon: devModeSnippetIconSchema,
63
+ language: devModeCodeLanguageSchema,
64
+ code: z.string(),
65
+ contextExample: devModeSnippetExampleSchema
66
+ })
@@ -0,0 +1,11 @@
1
+ import type { DevModeLifecycleHookExample } from '@admin/components/shared/dev-mode/dev-mode-types'
2
+ import { devModeLifecycleHookExampleSchema } from '@admin/utils/dev-mode/dev-mode-json-schemas'
3
+ import { z } from 'zod/v3'
4
+
5
+ const lifecycleHooksSchema = z.array(devModeLifecycleHookExampleSchema)
6
+
7
+ export function parseDevModeLifecycleHooks<Raw>(
8
+ values: readonly Raw[]
9
+ ): DevModeLifecycleHookExample[] {
10
+ return lifecycleHooksSchema.parse(values)
11
+ }
@@ -0,0 +1,9 @@
1
+ import type { DevModeSnippet } from '@admin/components/shared/dev-mode/dev-mode-types'
2
+ import { devModeSnippetSchema } from '@admin/utils/dev-mode/dev-mode-json-schemas'
3
+ import { z } from 'zod/v3'
4
+
5
+ const snippetsSchema = z.array(devModeSnippetSchema)
6
+
7
+ export function parseDevModeSnippets<Raw>(values: readonly Raw[]): DevModeSnippet[] {
8
+ return snippetsSchema.parse(values)
9
+ }
@@ -0,0 +1,13 @@
1
+ import type { Transaction } from '@tiptap/pm/state'
2
+
3
+ export function setTaskItemChecked(
4
+ tr: Transaction,
5
+ position: number | undefined,
6
+ checked: boolean
7
+ ): boolean {
8
+ if (typeof position !== 'number') return false
9
+
10
+ const taskItem = tr.doc.nodeAt(position)
11
+ tr.setNodeMarkup(position, undefined, { ...taskItem?.attrs, checked })
12
+ return true
13
+ }
@@ -15,7 +15,8 @@ export function updateNodesAttr<A extends string = string, V = unknown>(
15
15
  const currentNode = tr.doc.nodeAt(pos)
16
16
  if (!currentNode) continue
17
17
 
18
- const prevValue = (currentNode.attrs as Record<string, unknown>)[attrName] as V | undefined
18
+ const attrs: Record<string, unknown> = currentNode.attrs
19
+ const prevValue = attrs[attrName] as V | undefined
19
20
  const resolvedNext =
20
21
  typeof next === 'function' ? (next as (p: V | undefined) => V | undefined)(prevValue) : next
21
22
 
@@ -0,0 +1,13 @@
1
+ export const emailTheme = {
2
+ pageBackground: '#f8fafc',
3
+ surface: '#ffffff',
4
+ border: '#e2e8f0',
5
+ headerBackground: '#0f172a',
6
+ text: '#0f172a',
7
+ bodyText: '#334155',
8
+ mutedText: '#475569',
9
+ inverseText: '#ffffff',
10
+ mutedInverseText: '#cbd5e1',
11
+ compactWidth: '560px',
12
+ contentWidth: '640px'
13
+ } as const
@@ -1,3 +1,4 @@
1
+ import { emailTheme } from '@admin/utils/email/email-theme'
1
2
  import { escapeHtml } from '@admin/utils/email/escape-html'
2
3
  import { formatEmailValue } from '@admin/utils/email/format-email-value'
3
4
 
@@ -8,8 +9,8 @@ export function renderFormSubmissionRows(submissionData: Record<string, unknown>
8
9
  const displayValue = escapeHtml(formatEmailValue(value))
9
10
 
10
11
  return `<tr>
11
- <th style="border-bottom:1px solid #e2e8f0;color:#475569;font-size:12px;font-weight:600;padding:12px;text-align:left;text-transform:uppercase;vertical-align:top;width:36%;">${label}</th>
12
- <td style="border-bottom:1px solid #e2e8f0;color:#0f172a;font-size:14px;line-height:20px;padding:12px;vertical-align:top;">${displayValue}</td>
12
+ <th style="border-bottom:1px solid ${emailTheme.border};color:${emailTheme.mutedText};font-size:12px;font-weight:600;padding:12px;text-align:left;text-transform:uppercase;vertical-align:top;width:36%;">${label}</th>
13
+ <td style="border-bottom:1px solid ${emailTheme.border};color:${emailTheme.text};font-size:14px;line-height:20px;padding:12px;vertical-align:top;">${displayValue}</td>
13
14
  </tr>`
14
15
  })
15
16
  .join('\n')