betterstart-cli 0.0.99 → 0.0.100
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/api/upload-route.ts +2 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +2 -2
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sign-out.tsx +27 -8
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/components/shared/delete-dialog.tsx +3 -2
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-restore-dialog.tsx +4 -3
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-button.tsx +5 -2
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +11 -5
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +6 -4
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +12 -8
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-empty-state.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +5 -3
- package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +2 -0
- package/dist/assets/adapters/next/templates/init/components/shared/sort-order-dialog.tsx +12 -7
- package/dist/assets/adapters/next/templates/init/hooks/use-entity-versions.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/cleanup-audit-logs.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/profile/change-own-password.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +2 -2
- package/dist/assets/adapters/next/templates/init/lib/actions/users/change-user-password.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +2 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +2 -1
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +7 -7
- package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +7 -7
- package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +5 -4
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +13 -10
- package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +5 -5
- package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +10 -5
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +19 -10
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +9 -4
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -20
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +4 -4
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +6 -5
- package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +5 -5
- package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +6 -4
- package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +9 -5
- package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/utils/error/action-error.ts +35 -0
- package/dist/assets/shared-assets/react-admin/schema.json +8 -0
- package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +3 -8
- package/dist/assets/shared-assets/react-admin/ui/spinner.tsx +1 -1
- package/dist/{chunk-SO2CAJFC.js → chunk-45Z6XDG5.js} +418 -79
- package/dist/chunk-45Z6XDG5.js.map +1 -0
- package/dist/cli.js +2027 -2391
- package/dist/cli.js.map +1 -1
- package/dist/{template-reader-XMWPFYIK.js → template-reader-J6G7N5V6.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-SO2CAJFC.js.map +0 -1
- /package/dist/{template-reader-XMWPFYIK.js.map → template-reader-J6G7N5V6.js.map} +0 -0
package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx
CHANGED
|
@@ -29,12 +29,12 @@ import {
|
|
|
29
29
|
FormMessage
|
|
30
30
|
} from '@admin/components/ui/form'
|
|
31
31
|
import { Input } from '@admin/components/ui/input'
|
|
32
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
32
33
|
import { Switch } from '@admin/components/ui/switch'
|
|
33
34
|
import { webhookEventSources } from '@admin/data/webhook-events'
|
|
34
35
|
import { useWebhookSubscriptions } from '@admin/hooks/use-webhooks'
|
|
35
36
|
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
36
37
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
37
|
-
import { LoaderCircle } from 'lucide-react'
|
|
38
38
|
import { useForm } from 'react-hook-form'
|
|
39
39
|
import { toast } from 'sonner'
|
|
40
40
|
import { z } from 'zod/v3'
|
|
@@ -95,13 +95,13 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
95
95
|
? await updateWebhook(webhook.id, { ...endpointValues, events })
|
|
96
96
|
: await createWebhook({ ...endpointValues, events })
|
|
97
97
|
if (!result.success) {
|
|
98
|
-
throw new Error(result.error || 'Failed to save
|
|
98
|
+
throw new Error(result.error || 'Failed to save this webhook')
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
return result
|
|
102
102
|
},
|
|
103
103
|
onSuccess: async () => {
|
|
104
|
-
toast.success(isEdit ? '
|
|
104
|
+
toast.success(isEdit ? 'Webhook saved' : 'Webhook created')
|
|
105
105
|
await queryClient.invalidateQueries({ queryKey: ['webhooks'] })
|
|
106
106
|
await queryClient.invalidateQueries({
|
|
107
107
|
queryKey: ['webhook-subscriptions']
|
|
@@ -109,13 +109,13 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
109
109
|
onOpenChange(false)
|
|
110
110
|
},
|
|
111
111
|
onError: (error: Error) => {
|
|
112
|
-
toast.error(error.message || '
|
|
112
|
+
toast.error(error.message || 'Failed to save this webhook')
|
|
113
113
|
}
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
const revealSecretMutation = useMutation({
|
|
117
117
|
mutationFn: async () => {
|
|
118
|
-
if (!webhook) throw new Error('Save
|
|
118
|
+
if (!webhook) throw new Error('Save this webhook first.')
|
|
119
119
|
const result = await getWebhookSecret(webhook.id)
|
|
120
120
|
if (!result.success || !result.secret) {
|
|
121
121
|
throw new Error(result.error || 'Failed to fetch signing secret')
|
|
@@ -131,7 +131,7 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
131
131
|
|
|
132
132
|
const regenerateSecretMutation = useMutation({
|
|
133
133
|
mutationFn: async () => {
|
|
134
|
-
if (!webhook) throw new Error('Save
|
|
134
|
+
if (!webhook) throw new Error('Save this webhook first.')
|
|
135
135
|
const result = await regenerateWebhookSecret(webhook.id)
|
|
136
136
|
if (!result.success || !result.secret) {
|
|
137
137
|
throw new Error(result.error || 'Failed to regenerate signing secret')
|
|
@@ -150,7 +150,7 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
150
150
|
|
|
151
151
|
const testMutation = useMutation({
|
|
152
152
|
mutationFn: async () => {
|
|
153
|
-
if (!webhook) throw new Error('Save
|
|
153
|
+
if (!webhook) throw new Error('Save this webhook first.')
|
|
154
154
|
const result = await sendTestWebhook(webhook.id)
|
|
155
155
|
if (!result.success) {
|
|
156
156
|
throw new Error(result.error || 'Test webhook failed')
|
|
@@ -183,9 +183,7 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
183
183
|
<Form {...form}>
|
|
184
184
|
<form onSubmit={form.handleSubmit((values) => saveMutation.mutate(values))}>
|
|
185
185
|
<DialogHeader>
|
|
186
|
-
<DialogTitle>
|
|
187
|
-
{isEdit ? `Edit endpoint — ${webhook.name}` : 'Add endpoint'}
|
|
188
|
-
</DialogTitle>
|
|
186
|
+
<DialogTitle>{isEdit ? `Edit webhook — ${webhook.name}` : 'Add webhook'}</DialogTitle>
|
|
189
187
|
<DialogDescription>
|
|
190
188
|
Events are delivered as signed JSON POST requests
|
|
191
189
|
</DialogDescription>
|
|
@@ -252,21 +250,22 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
252
250
|
variant="outline"
|
|
253
251
|
size="sm"
|
|
254
252
|
disabled={revealSecretMutation.isPending || secret !== null}
|
|
253
|
+
aria-busy={revealSecretMutation.isPending}
|
|
255
254
|
onClick={() => revealSecretMutation.mutate()}
|
|
256
255
|
>
|
|
257
|
-
|
|
256
|
+
{revealSecretMutation.isPending && <Spinner />}
|
|
257
|
+
{revealSecretMutation.isPending ? 'Revealing…' : 'Reveal'}
|
|
258
258
|
</Button>
|
|
259
259
|
<Button
|
|
260
260
|
type="button"
|
|
261
261
|
variant="outline"
|
|
262
262
|
size="sm"
|
|
263
263
|
disabled={regenerateSecretMutation.isPending}
|
|
264
|
+
aria-busy={regenerateSecretMutation.isPending}
|
|
264
265
|
onClick={() => regenerateSecretMutation.mutate()}
|
|
265
266
|
>
|
|
266
|
-
{regenerateSecretMutation.isPending &&
|
|
267
|
-
|
|
268
|
-
)}
|
|
269
|
-
Regenerate
|
|
267
|
+
{regenerateSecretMutation.isPending && <Spinner />}
|
|
268
|
+
{regenerateSecretMutation.isPending ? 'Regenerating…' : 'Regenerate'}
|
|
270
269
|
</Button>
|
|
271
270
|
</div>
|
|
272
271
|
<p className="text-xs text-muted-foreground">
|
|
@@ -351,15 +350,22 @@ export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEn
|
|
|
351
350
|
variant="outline"
|
|
352
351
|
size="lg"
|
|
353
352
|
disabled={testMutation.isPending}
|
|
353
|
+
aria-busy={testMutation.isPending}
|
|
354
354
|
onClick={() => testMutation.mutate()}
|
|
355
355
|
>
|
|
356
|
-
{testMutation.isPending && <
|
|
357
|
-
Send test
|
|
356
|
+
{testMutation.isPending && <Spinner />}
|
|
357
|
+
{testMutation.isPending ? 'Sending test…' : 'Send test'}
|
|
358
358
|
</Button>
|
|
359
359
|
)}
|
|
360
|
-
<Button type="submit" disabled={isPending} size="lg">
|
|
361
|
-
{isPending && <
|
|
362
|
-
{
|
|
360
|
+
<Button type="submit" disabled={isPending} aria-busy={isPending} size="lg">
|
|
361
|
+
{isPending && <Spinner />}
|
|
362
|
+
{isPending
|
|
363
|
+
? isEdit
|
|
364
|
+
? 'Saving webhook…'
|
|
365
|
+
: 'Creating webhook…'
|
|
366
|
+
: isEdit
|
|
367
|
+
? 'Save webhook'
|
|
368
|
+
: 'Create webhook'}
|
|
363
369
|
</Button>
|
|
364
370
|
</DialogFooter>
|
|
365
371
|
</form>
|
|
@@ -56,17 +56,17 @@ export function WebhooksLogsPageContent() {
|
|
|
56
56
|
const filters = [
|
|
57
57
|
{
|
|
58
58
|
key: 'status',
|
|
59
|
-
label: '
|
|
59
|
+
label: 'Status',
|
|
60
60
|
type: 'select' as const,
|
|
61
61
|
value: status,
|
|
62
62
|
options: [
|
|
63
|
-
{ label: '
|
|
63
|
+
{ label: 'Delivered', value: 'success' },
|
|
64
64
|
{ label: 'Failed', value: 'failed' }
|
|
65
65
|
]
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
key: 'endpoint',
|
|
69
|
-
label: '
|
|
69
|
+
label: 'Webhooks',
|
|
70
70
|
type: 'select' as const,
|
|
71
71
|
value: endpoint,
|
|
72
72
|
options: (endpointOptions?.webhooks ?? []).map((webhook) => ({
|
|
@@ -85,7 +85,7 @@ export function WebhooksLogsPageContent() {
|
|
|
85
85
|
|
|
86
86
|
return (
|
|
87
87
|
<React.Fragment>
|
|
88
|
-
<PageHeader title="
|
|
88
|
+
<PageHeader title="Deliveries" />
|
|
89
89
|
|
|
90
90
|
<EntityFiltersBar
|
|
91
91
|
filters={filters}
|
package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx
CHANGED
|
@@ -205,7 +205,9 @@ export function WebhooksLogsTable<TValue>({
|
|
|
205
205
|
className="flex h-24 items-center justify-center text-center"
|
|
206
206
|
style={{ gridColumn: '1 / -1' }}
|
|
207
207
|
>
|
|
208
|
-
<div className="text-destructive">
|
|
208
|
+
<div className="text-destructive">
|
|
209
|
+
Failed to load deliveries. Refresh to try again.
|
|
210
|
+
</div>
|
|
209
211
|
</DataGridCell>
|
|
210
212
|
</DataGridRow>
|
|
211
213
|
) : table.getRowModel().rows?.length ? (
|
|
@@ -207,7 +207,9 @@ export function WebhooksTable<TValue>({
|
|
|
207
207
|
className="flex h-24 items-center justify-center text-center"
|
|
208
208
|
style={{ gridColumn: '1 / -1' }}
|
|
209
209
|
>
|
|
210
|
-
<div className="text-destructive">
|
|
210
|
+
<div className="text-destructive">
|
|
211
|
+
Failed to load webhooks. Refresh to try again.
|
|
212
|
+
</div>
|
|
211
213
|
</DataGridCell>
|
|
212
214
|
</DataGridRow>
|
|
213
215
|
) : table.getRowModel().rows?.length ? (
|
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
FormMessage
|
|
26
26
|
} from '@admin/components/ui/form'
|
|
27
27
|
import { Input } from '@admin/components/ui/input'
|
|
28
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
28
29
|
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
29
30
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
30
|
-
import { KeySquare
|
|
31
|
+
import { KeySquare } from 'lucide-react'
|
|
31
32
|
import { useForm } from 'react-hook-form'
|
|
32
33
|
import { toast } from 'sonner'
|
|
33
34
|
import { z } from 'zod/v3'
|
|
@@ -112,7 +113,7 @@ export function ChangePasswordDialog({
|
|
|
112
113
|
setOpen(false)
|
|
113
114
|
},
|
|
114
115
|
onError: (error: Error) => {
|
|
115
|
-
toast.error(error.message || '
|
|
116
|
+
toast.error(error.message || 'Failed to change this password')
|
|
116
117
|
}
|
|
117
118
|
})
|
|
118
119
|
|
|
@@ -216,9 +217,9 @@ export function ChangePasswordDialog({
|
|
|
216
217
|
</DialogBody>
|
|
217
218
|
|
|
218
219
|
<DialogFooter showCloseButton={true}>
|
|
219
|
-
<Button type="submit" disabled={isPending} size="lg">
|
|
220
|
-
{isPending ? <
|
|
221
|
-
{isPending ? 'Changing
|
|
220
|
+
<Button type="submit" disabled={isPending} aria-busy={isPending} size="lg">
|
|
221
|
+
{isPending ? <Spinner /> : null}
|
|
222
|
+
{isPending ? 'Changing…' : 'Change Password'}
|
|
222
223
|
</Button>
|
|
223
224
|
</DialogFooter>
|
|
224
225
|
</form>
|
|
@@ -30,10 +30,10 @@ import {
|
|
|
30
30
|
SelectTrigger,
|
|
31
31
|
SelectValue
|
|
32
32
|
} from '@admin/components/ui/select'
|
|
33
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
33
34
|
import { UserRole } from '@admin/types/auth'
|
|
34
35
|
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
35
36
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
36
|
-
import { LoaderCircle } from 'lucide-react'
|
|
37
37
|
import { useForm } from 'react-hook-form'
|
|
38
38
|
import { toast } from 'sonner'
|
|
39
39
|
import { z } from 'zod/v3'
|
|
@@ -87,7 +87,7 @@ export function CreateUserDialog() {
|
|
|
87
87
|
setOpen(false)
|
|
88
88
|
},
|
|
89
89
|
onError: (error: Error) => {
|
|
90
|
-
toast.error(error.message || '
|
|
90
|
+
toast.error(error.message || 'Failed to create this user')
|
|
91
91
|
}
|
|
92
92
|
})
|
|
93
93
|
|
|
@@ -214,9 +214,9 @@ export function CreateUserDialog() {
|
|
|
214
214
|
</DialogBody>
|
|
215
215
|
|
|
216
216
|
<DialogFooter showCloseButton={true}>
|
|
217
|
-
<Button type="submit" disabled={isPending} size="lg">
|
|
218
|
-
{isPending && <
|
|
219
|
-
Create
|
|
217
|
+
<Button type="submit" disabled={isPending} aria-busy={isPending} size="lg">
|
|
218
|
+
{isPending && <Spinner />}
|
|
219
|
+
{isPending ? 'Creating…' : 'Create'}
|
|
220
220
|
</Button>
|
|
221
221
|
</DialogFooter>
|
|
222
222
|
</form>
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
FormMessage
|
|
25
25
|
} from '@admin/components/ui/form'
|
|
26
26
|
import { Input } from '@admin/components/ui/input'
|
|
27
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
27
28
|
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
28
29
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
29
|
-
import {
|
|
30
|
+
import { Trash } from 'lucide-react'
|
|
30
31
|
import { useForm } from 'react-hook-form'
|
|
31
32
|
import { toast } from 'sonner'
|
|
32
33
|
import { z } from 'zod/v3'
|
|
@@ -80,7 +81,7 @@ export function DeleteUserDialog({ userId, userName, isCurrentUser }: DeleteUser
|
|
|
80
81
|
setOpen(false)
|
|
81
82
|
},
|
|
82
83
|
onError: (error: Error) => {
|
|
83
|
-
toast.error(error.message || '
|
|
84
|
+
toast.error(error.message || 'Failed to delete this account')
|
|
84
85
|
}
|
|
85
86
|
})
|
|
86
87
|
|
|
@@ -149,11 +150,12 @@ export function DeleteUserDialog({ userId, userName, isCurrentUser }: DeleteUser
|
|
|
149
150
|
<Button
|
|
150
151
|
type="submit"
|
|
151
152
|
disabled={isPending || !form.formState.isValid}
|
|
153
|
+
aria-busy={isPending}
|
|
152
154
|
size="lg"
|
|
153
155
|
variant="destructive"
|
|
154
156
|
>
|
|
155
|
-
{isPending ? <
|
|
156
|
-
{isPending ? 'Deleting
|
|
157
|
+
{isPending ? <Spinner /> : null}
|
|
158
|
+
{isPending ? 'Deleting…' : 'Delete account'}
|
|
157
159
|
</Button>
|
|
158
160
|
</AlertDialogFooter>
|
|
159
161
|
</form>
|
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
SelectTrigger,
|
|
20
20
|
SelectValue
|
|
21
21
|
} from '@admin/components/ui/select'
|
|
22
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
22
23
|
import { UserRole } from '@admin/types/auth'
|
|
23
24
|
import { useQueryClient } from '@tanstack/react-query'
|
|
24
|
-
import { LoaderCircle } from 'lucide-react'
|
|
25
25
|
import { toast } from 'sonner'
|
|
26
26
|
|
|
27
27
|
interface EditRoleDialogProps {
|
|
@@ -49,7 +49,7 @@ export function EditRoleDialog({ userId, currentRole, userName, children }: Edit
|
|
|
49
49
|
toast.error(result.error || 'Failed to update role')
|
|
50
50
|
}
|
|
51
51
|
} catch {
|
|
52
|
-
toast.error('
|
|
52
|
+
toast.error('Failed to change this role. Try again.')
|
|
53
53
|
}
|
|
54
54
|
})
|
|
55
55
|
}
|
|
@@ -80,9 +80,13 @@ export function EditRoleDialog({ userId, currentRole, userName, children }: Edit
|
|
|
80
80
|
<Button variant="outline" onClick={() => setOpen(false)} disabled={isPending}>
|
|
81
81
|
Cancel
|
|
82
82
|
</Button>
|
|
83
|
-
<Button
|
|
84
|
-
{
|
|
85
|
-
|
|
83
|
+
<Button
|
|
84
|
+
onClick={handleSave}
|
|
85
|
+
disabled={isPending || role === currentRole}
|
|
86
|
+
aria-busy={isPending}
|
|
87
|
+
>
|
|
88
|
+
{isPending && <Spinner />}
|
|
89
|
+
{isPending ? 'Saving Role…' : 'Save Role'}
|
|
86
90
|
</Button>
|
|
87
91
|
</DialogFooter>
|
|
88
92
|
</DialogContent>
|
|
@@ -215,7 +215,7 @@ export function UsersTable<TValue>({
|
|
|
215
215
|
className="flex h-24 items-center justify-center px-4 text-center"
|
|
216
216
|
style={{ gridColumn: '1 / -1' }}
|
|
217
217
|
>
|
|
218
|
-
<div className="text-destructive">
|
|
218
|
+
<div className="text-destructive">Failed to load users. Refresh to try again.</div>
|
|
219
219
|
</DataGridCell>
|
|
220
220
|
</DataGridRow>
|
|
221
221
|
) : table.getRowModel().rows?.length ? (
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const POSTGRES_MESSAGES: Record<string, string> = {
|
|
2
|
+
'23502': 'A required value is missing.',
|
|
3
|
+
'23503': 'This is still in use somewhere else.',
|
|
4
|
+
'23505': 'That value is already in use.'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const AUTH_MESSAGES: Record<string, string> = {
|
|
8
|
+
INVALID_EMAIL_OR_PASSWORD: 'That email or password is incorrect.',
|
|
9
|
+
PASSWORD_TOO_SHORT: 'That password is too short.',
|
|
10
|
+
USER_ALREADY_EXISTS: 'That email is already in use.'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readStringProperty(value: unknown, key: string): string | undefined {
|
|
14
|
+
if (typeof value !== 'object' || value === null) return undefined
|
|
15
|
+
const property = (value as Record<string, unknown>)[key]
|
|
16
|
+
return typeof property === 'string' ? property : undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function recognize(error: unknown): string | undefined {
|
|
20
|
+
const authCode = readStringProperty(
|
|
21
|
+
(error as { body?: unknown } | null)?.body ?? undefined,
|
|
22
|
+
'code'
|
|
23
|
+
)
|
|
24
|
+
if (authCode && AUTH_MESSAGES[authCode]) return AUTH_MESSAGES[authCode]
|
|
25
|
+
|
|
26
|
+
const sqlState = readStringProperty(error, 'code')
|
|
27
|
+
if (sqlState && POSTGRES_MESSAGES[sqlState]) return POSTGRES_MESSAGES[sqlState]
|
|
28
|
+
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toActionError(error: unknown, fallback: string): string {
|
|
33
|
+
console.error(error)
|
|
34
|
+
return recognize(error) ?? fallback
|
|
35
|
+
}
|
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
"type": "string",
|
|
21
21
|
"description": "Human-readable display name for the schema"
|
|
22
22
|
},
|
|
23
|
+
"labelSingular": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Display noun for one record, used inside sentences (e.g., 'Job application' in 'Delete this job application?'). Derived from 'label' when omitted."
|
|
26
|
+
},
|
|
27
|
+
"labelPlural": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Display noun for many records, used inside sentences (e.g., 'job applications' in '3 job applications deleted'). Falls back to 'label' when omitted."
|
|
30
|
+
},
|
|
23
31
|
"description": {
|
|
24
32
|
"type": "string",
|
|
25
33
|
"description": "Brief description of what this schema represents"
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import type { ToasterProps } from 'sonner'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
InfoIcon,
|
|
7
|
-
LoaderCircle,
|
|
8
|
-
OctagonXIcon,
|
|
9
|
-
TriangleAlertIcon
|
|
10
|
-
} from 'lucide-react'
|
|
4
|
+
import { Spinner } from '@admin/components/ui/spinner'
|
|
5
|
+
import { CircleCheckIcon, InfoIcon, OctagonXIcon, TriangleAlertIcon } from 'lucide-react'
|
|
11
6
|
import { useTheme } from 'next-themes'
|
|
12
7
|
import { Toaster as Sonner } from 'sonner'
|
|
13
8
|
|
|
@@ -23,7 +18,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|
|
23
18
|
info: <InfoIcon className="size-4" />,
|
|
24
19
|
warning: <TriangleAlertIcon className="size-4" />,
|
|
25
20
|
error: <OctagonXIcon className="size-4" />,
|
|
26
|
-
loading: <
|
|
21
|
+
loading: <Spinner className="size-4" />
|
|
27
22
|
}}
|
|
28
23
|
style={
|
|
29
24
|
{
|