betterstart-cli 0.0.98 → 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.
Files changed (66) hide show
  1. package/dist/assets/adapters/next/templates/init/api/upload-route.ts +2 -1
  2. package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +2 -2
  3. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sign-out.tsx +27 -8
  4. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +3 -1
  5. package/dist/assets/adapters/next/templates/init/components/shared/delete-dialog.tsx +3 -2
  6. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-version-restore-dialog.tsx +4 -3
  7. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-button.tsx +5 -2
  8. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +11 -5
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +6 -4
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +12 -8
  11. package/dist/assets/adapters/next/templates/init/components/shared/media/media-empty-state.tsx +6 -5
  12. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +1 -1
  13. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +5 -3
  14. package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +2 -0
  15. package/dist/assets/adapters/next/templates/init/components/shared/sort-order-dialog.tsx +12 -7
  16. package/dist/assets/adapters/next/templates/init/hooks/use-entity-versions.ts +2 -1
  17. package/dist/assets/adapters/next/templates/init/lib/actions/audit/cleanup-audit-logs.ts +2 -1
  18. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +2 -1
  19. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +2 -1
  20. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +2 -1
  21. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +2 -1
  22. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +2 -1
  23. package/dist/assets/adapters/next/templates/init/lib/actions/profile/change-own-password.ts +2 -1
  24. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +2 -1
  25. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +2 -2
  26. package/dist/assets/adapters/next/templates/init/lib/actions/users/change-user-password.ts +2 -1
  27. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +2 -1
  28. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +2 -1
  29. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +2 -1
  30. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +2 -1
  31. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +2 -1
  32. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +2 -1
  33. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +2 -1
  34. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +2 -1
  35. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +2 -1
  36. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +7 -7
  37. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +7 -7
  38. package/dist/assets/adapters/next/templates/init/pages/login-page.tsx +1 -1
  39. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +5 -4
  40. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +13 -10
  41. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +5 -5
  42. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +3 -1
  43. package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +10 -5
  44. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +19 -10
  45. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +9 -4
  46. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -20
  47. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +4 -4
  48. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +3 -1
  49. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +3 -1
  50. package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +6 -5
  51. package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +5 -5
  52. package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +6 -4
  53. package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +9 -5
  54. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +1 -1
  55. package/dist/assets/adapters/next/templates/init/utils/error/action-error.ts +35 -0
  56. package/dist/assets/shared-assets/react-admin/schema.json +8 -0
  57. package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +3 -8
  58. package/dist/assets/shared-assets/react-admin/ui/spinner.tsx +1 -1
  59. package/dist/{chunk-AIQWGMU7.js → chunk-45Z6XDG5.js} +419 -83
  60. package/dist/chunk-45Z6XDG5.js.map +1 -0
  61. package/dist/cli.js +2027 -2391
  62. package/dist/cli.js.map +1 -1
  63. package/dist/{template-reader-F2VFSDKV.js → template-reader-J6G7N5V6.js} +2 -2
  64. package/package.json +1 -1
  65. package/dist/chunk-AIQWGMU7.js.map +0 -1
  66. /package/dist/{template-reader-F2VFSDKV.js.map → template-reader-J6G7N5V6.js.map} +0 -0
@@ -7,6 +7,7 @@ import { requireRole, UserRole } from '@admin/auth/middleware'
7
7
  import db from '@admin/db'
8
8
  import { user } from '@admin/db/schema'
9
9
  import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
10
+ import { toActionError } from '@admin/utils/error/action-error'
10
11
  import { eq } from 'drizzle-orm'
11
12
  import { updateTag } from 'next/cache'
12
13
  import { headers } from 'next/headers'
@@ -71,7 +72,7 @@ export async function changeUserPassword(
71
72
  } catch (error) {
72
73
  return {
73
74
  success: false,
74
- error: error instanceof Error ? error.message : 'Failed to change user password'
75
+ error: toActionError(error, 'Failed to change user password')
75
76
  }
76
77
  }
77
78
  }
@@ -8,6 +8,7 @@ import db from '@admin/db'
8
8
  import { user } from '@admin/db/schema'
9
9
  import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
10
10
  import { getTouchedFieldNames } from '@admin/utils/audit/audit'
11
+ import { toActionError } from '@admin/utils/error/action-error'
11
12
  import { eq } from 'drizzle-orm'
12
13
  import { updateTag } from 'next/cache'
13
14
  import { usersCacheTags } from './types'
@@ -67,7 +68,7 @@ export async function createUser(input: CreateUserInput): Promise<CreateUserResu
67
68
  } catch (error) {
68
69
  return {
69
70
  success: false,
70
- error: error instanceof Error ? error.message : 'Failed to create user'
71
+ error: toActionError(error, 'Failed to create user')
71
72
  }
72
73
  }
73
74
  }
@@ -6,6 +6,7 @@ import { requireRole, UserRole } from '@admin/auth/middleware'
6
6
  import db from '@admin/db'
7
7
  import { user } from '@admin/db/schema'
8
8
  import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
9
+ import { toActionError } from '@admin/utils/error/action-error'
9
10
  import { eq } from 'drizzle-orm'
10
11
  import { updateTag } from 'next/cache'
11
12
  import { usersCacheTags } from './types'
@@ -40,7 +41,7 @@ export async function deleteUser(userId: string): Promise<DeleteUserResult> {
40
41
  } catch (error) {
41
42
  return {
42
43
  success: false,
43
- error: error instanceof Error ? error.message : 'Failed to delete user'
44
+ error: toActionError(error, 'Failed to delete user')
44
45
  }
45
46
  }
46
47
  }
@@ -6,6 +6,7 @@ import { requireRole, UserRole } from '@admin/auth/middleware'
6
6
  import db from '@admin/db'
7
7
  import { user } from '@admin/db/schema'
8
8
  import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
9
+ import { toActionError } from '@admin/utils/error/action-error'
9
10
  import { eq } from 'drizzle-orm'
10
11
  import { updateTag } from 'next/cache'
11
12
  import { usersCacheTags } from './types'
@@ -44,7 +45,7 @@ export async function updateUserRole(
44
45
  } catch (error) {
45
46
  return {
46
47
  success: false,
47
- error: error instanceof Error ? error.message : 'Failed to update user role'
48
+ error: toActionError(error, 'Failed to update user role')
48
49
  }
49
50
  }
50
51
  }
@@ -4,6 +4,7 @@ import type { CreateWebhookInput, WebhookResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhookSubscriptions, webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { generateWebhookSecret } from '@admin/utils/webhook/signature'
8
9
  import { updateTag } from 'next/cache'
9
10
  import { webhooksCacheTags } from './types'
@@ -49,7 +50,7 @@ export async function createWebhook(input: CreateWebhookInput): Promise<WebhookR
49
50
  console.error('Error creating webhook:', error)
50
51
  return {
51
52
  success: false,
52
- error: error instanceof Error ? error.message : 'Failed to create webhook'
53
+ error: toActionError(error, 'Failed to create webhook')
53
54
  }
54
55
  }
55
56
  }
@@ -4,6 +4,7 @@ import type { WebhookResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { eq } from 'drizzle-orm'
8
9
  import { updateTag } from 'next/cache'
9
10
  import { webhooksCacheTags } from './types'
@@ -20,7 +21,7 @@ export async function deleteWebhook(id: string): Promise<WebhookResult> {
20
21
  console.error('Error deleting webhook:', error)
21
22
  return {
22
23
  success: false,
23
- error: error instanceof Error ? error.message : 'Failed to delete webhook'
24
+ error: toActionError(error, 'Failed to delete webhook')
24
25
  }
25
26
  }
26
27
  }
@@ -4,6 +4,7 @@ import type { WebhookSecretResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { eq } from 'drizzle-orm'
8
9
 
9
10
  export async function getWebhookSecret(id: string): Promise<WebhookSecretResult> {
@@ -22,7 +23,7 @@ export async function getWebhookSecret(id: string): Promise<WebhookSecretResult>
22
23
  console.error('Error fetching webhook secret:', error)
23
24
  return {
24
25
  success: false,
25
- error: error instanceof Error ? error.message : 'Failed to fetch webhook secret'
26
+ error: toActionError(error, 'Failed to fetch webhook secret')
26
27
  }
27
28
  }
28
29
  }
@@ -4,6 +4,7 @@ import type { WebhookSecretResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { generateWebhookSecret } from '@admin/utils/webhook/signature'
8
9
  import { eq } from 'drizzle-orm'
9
10
  import { updateTag } from 'next/cache'
@@ -29,7 +30,7 @@ export async function regenerateWebhookSecret(id: string): Promise<WebhookSecret
29
30
  console.error('Error regenerating webhook secret:', error)
30
31
  return {
31
32
  success: false,
32
- error: error instanceof Error ? error.message : 'Failed to regenerate webhook secret'
33
+ error: toActionError(error, 'Failed to regenerate webhook secret')
33
34
  }
34
35
  }
35
36
  }
@@ -4,6 +4,7 @@ import type { SendTestWebhookResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { createWebhookEventId, signWebhookPayload } from '@admin/utils/webhook/signature'
8
9
  import { eq } from 'drizzle-orm'
9
10
  import { recordWebhookDelivery } from './dispatch'
@@ -75,7 +76,7 @@ export async function sendTestWebhook(id: string): Promise<SendTestWebhookResult
75
76
  console.error('Error sending test webhook:', error)
76
77
  return {
77
78
  success: false,
78
- error: error instanceof Error ? error.message : 'Failed to send test webhook'
79
+ error: toActionError(error, 'Failed to send test webhook')
79
80
  }
80
81
  }
81
82
  }
@@ -4,6 +4,7 @@ import type { UpdateWebhookInput, WebhookResult } from './types'
4
4
  import { requireRole, UserRole } from '@admin/auth/middleware'
5
5
  import db from '@admin/db'
6
6
  import { webhookSubscriptions, webhooks } from '@admin/db/schema'
7
+ import { toActionError } from '@admin/utils/error/action-error'
7
8
  import { and, eq, notInArray } from 'drizzle-orm'
8
9
  import { updateTag } from 'next/cache'
9
10
  import { webhooksCacheTags } from './types'
@@ -66,7 +67,7 @@ export async function updateWebhook(id: string, input: UpdateWebhookInput): Prom
66
67
  console.error('Error updating webhook:', error)
67
68
  return {
68
69
  success: false,
69
- error: error instanceof Error ? error.message : 'Failed to update webhook'
70
+ error: toActionError(error, 'Failed to update webhook')
70
71
  }
71
72
  }
72
73
  }
@@ -7,8 +7,8 @@ import { Button } from '@admin/components/ui/button'
7
7
  import { Card, CardContent } from '@admin/components/ui/card'
8
8
  import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
9
9
  import { Input } from '@admin/components/ui/input'
10
+ import { Spinner } from '@admin/components/ui/spinner'
10
11
  import { cn } from '@admin/utils/shared/cn'
11
- import { LoaderCircle } from 'lucide-react'
12
12
  import Image from 'next/image'
13
13
  import Link from 'next/link'
14
14
 
@@ -39,7 +39,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
39
39
  }
40
40
  setSuccess(true)
41
41
  } catch {
42
- setError('An error occurred. Please try again.')
42
+ setError('Failed to send the reset link. Try again.')
43
43
  }
44
44
  })
45
45
  }
@@ -68,7 +68,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
68
68
  </p>
69
69
  </div>
70
70
  <Link href="/admin/login" className="text-sm underline-offset-4 hover:underline">
71
- Back to login
71
+ Back to sign in
72
72
  </Link>
73
73
  </FieldGroup>
74
74
  ) : (
@@ -101,16 +101,16 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
101
101
  />
102
102
  </Field>
103
103
  <Field className="pt-4">
104
- <Button type="submit" disabled={isPending}>
105
- {isPending && <LoaderCircle className="animate-spin" />}
106
- {isPending ? 'Sending...' : 'Send Reset Link'}
104
+ <Button type="submit" disabled={isPending} aria-busy={isPending}>
105
+ {isPending && <Spinner />}
106
+ {isPending ? 'Sending' : 'Send Reset Link'}
107
107
  </Button>
108
108
  </Field>
109
109
  <Link
110
110
  href="/admin/login"
111
111
  className="text-center text-sm underline-offset-4 hover:underline"
112
112
  >
113
- Back to login
113
+ Back to sign in
114
114
  </Link>
115
115
  </div>
116
116
  </FieldGroup>
@@ -6,9 +6,9 @@ import { Button } from '@admin/components/ui/button'
6
6
  import { Card, CardContent } from '@admin/components/ui/card'
7
7
  import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
8
8
  import { Input } from '@admin/components/ui/input'
9
+ import { Spinner } from '@admin/components/ui/spinner'
9
10
  import { getAdminPostLoginPath } from '@admin/utils/auth/roles'
10
11
  import { cn } from '@admin/utils/shared/cn'
11
- import { LoaderCircle } from 'lucide-react'
12
12
  import Image from 'next/image'
13
13
  import Link from 'next/link'
14
14
  import { useRouter } from 'next/navigation'
@@ -45,7 +45,7 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
45
45
  router.replace(getAdminPostLoginPath(role))
46
46
  router.refresh()
47
47
  } catch {
48
- setError('An error occurred. Please try again.')
48
+ setError('Failed to sign you in. Try again.')
49
49
  }
50
50
  })
51
51
  }
@@ -67,11 +67,11 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
67
67
  <FieldGroup className="gap-10 pb-12">
68
68
  <div className="flex flex-col items-start">
69
69
  <h1 className="text-xl font-medium">Welcome back</h1>
70
- <p className="text-balance text-muted-foreground">Login to your account</p>
70
+ <p className="text-balance text-muted-foreground">Sign in to your account</p>
71
71
  </div>
72
72
  {resetSuccess ? (
73
73
  <div className="rounded-md bg-emerald-500/10 p-3 text-sm text-emerald-700 dark:text-emerald-400">
74
- Password reset successfully. Please log in with your new password.
74
+ Password reset. Sign in with your new password.
75
75
  </div>
76
76
  ) : null}
77
77
  {error ? (
@@ -116,9 +116,9 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
116
116
  />
117
117
  </Field>
118
118
  <Field className="pt-4">
119
- <Button type="submit" disabled={isPending}>
120
- {isPending && <LoaderCircle className="animate-spin" />}
121
- {isPending ? 'Signing in...' : 'Login'}
119
+ <Button type="submit" disabled={isPending} aria-busy={isPending}>
120
+ {isPending && <Spinner />}
121
+ {isPending ? 'Signing in' : 'Sign in'}
122
122
  </Button>
123
123
  </Field>
124
124
  </div>
@@ -4,7 +4,7 @@ import { LoginPageRsc } from './login-page-rsc'
4
4
  import { LoginPageSkeleton } from './login-page-skeleton'
5
5
 
6
6
  export const metadata: Metadata = {
7
- title: 'Admin Login',
7
+ title: 'Admin sign in',
8
8
  robots: { index: false, follow: false }
9
9
  }
10
10
 
@@ -156,12 +156,13 @@ export function MediaPageContent() {
156
156
  mutationFn: (ids: string[]) => deleteMediaBulk(ids),
157
157
  onSuccess: (result, ids) => {
158
158
  if (result.success) {
159
- const suffix = ids.length === 1 ? '' : 's'
160
- toast.success(`Deleted ${ids.length} file${suffix}`)
159
+ toast.success(
160
+ ids.length === 1 ? '1 media item deleted' : `${ids.length} media items deleted`
161
+ )
161
162
  void setSelectedIds([])
162
163
  queryClient.invalidateQueries({ queryKey: ['media'] })
163
164
  } else {
164
- toast.error(result.error ?? 'Failed to delete files')
165
+ toast.error(result.error ?? 'Failed to delete the selected media')
165
166
  }
166
167
  }
167
168
  })
@@ -169,7 +170,7 @@ export function MediaPageContent() {
169
170
  return (
170
171
  <React.Fragment>
171
172
  <PageHeader
172
- title="Media Gallery"
173
+ title="Media"
173
174
  actions={
174
175
  <Tabs defaultValue="upload">
175
176
  <TabsList>
@@ -29,8 +29,8 @@ 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 { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
33
- import { LoaderCircle } from 'lucide-react'
34
34
  import { useRouter } from 'next/navigation'
35
35
  import { useForm } from 'react-hook-form'
36
36
  import { toast } from 'sonner'
@@ -139,7 +139,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
139
139
  })
140
140
  router.refresh()
141
141
  } catch {
142
- toast.error('An error occurred while updating your profile')
142
+ toast.error('Failed to update your profile. Try again.')
143
143
  }
144
144
  })
145
145
  }
@@ -164,7 +164,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
164
164
  confirmPassword: ''
165
165
  })
166
166
  } catch {
167
- toast.error('An error occurred while changing your password')
167
+ toast.error('Failed to change your password. Try again.')
168
168
  }
169
169
  })
170
170
  }
@@ -187,7 +187,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
187
187
  }
188
188
  toast.success('Check your email for a password reset link')
189
189
  } catch {
190
- toast.error('An error occurred while sending the reset link')
190
+ toast.error('Failed to send the reset link. Try again.')
191
191
  }
192
192
  })
193
193
  }
@@ -297,10 +297,11 @@ export function ProfileForm({ user }: ProfileFormProps) {
297
297
  <Button
298
298
  type="submit"
299
299
  disabled={profilePending || !profileForm.formState.isDirty}
300
+ aria-busy={profilePending}
300
301
  className="ml-auto min-w-25"
301
302
  >
302
- {profilePending && <LoaderCircle className="animate-spin" />}
303
- {profilePending ? 'Saving...' : 'Save'}
303
+ {profilePending && <Spinner />}
304
+ {profilePending ? 'Saving' : 'Save'}
304
305
  </Button>
305
306
  </CardFooter>
306
307
  </Card>
@@ -375,10 +376,11 @@ export function ProfileForm({ user }: ProfileFormProps) {
375
376
  <Button
376
377
  type="submit"
377
378
  disabled={passwordPending || !passwordForm.formState.isDirty}
379
+ aria-busy={passwordPending}
378
380
  className="ml-auto min-w-25"
379
381
  >
380
- {passwordPending && <LoaderCircle className="animate-spin" />}
381
- {passwordPending ? 'Changing...' : 'Change Password'}
382
+ {passwordPending && <Spinner />}
383
+ {passwordPending ? 'Changing' : 'Change Password'}
382
384
  </Button>
383
385
  </CardFooter>
384
386
  </Card>
@@ -401,10 +403,11 @@ export function ProfileForm({ user }: ProfileFormProps) {
401
403
  type="button"
402
404
  className="ml-auto min-w-25"
403
405
  disabled={resetPending}
406
+ aria-busy={resetPending}
404
407
  onClick={handleResetPassword}
405
408
  >
406
- {resetPending && <LoaderCircle className="animate-spin" />}
407
- {resetPending ? 'Sending...' : 'Reset Password'}
409
+ {resetPending && <Spinner />}
410
+ {resetPending ? 'Sending' : 'Reset Password'}
408
411
  </Button>
409
412
  </CardFooter>
410
413
  </Card>
@@ -6,8 +6,8 @@ import { Button } from '@admin/components/ui/button'
6
6
  import { Card, CardContent } from '@admin/components/ui/card'
7
7
  import { Field, FieldGroup, FieldLabel } from '@admin/components/ui/field'
8
8
  import { Input } from '@admin/components/ui/input'
9
+ import { Spinner } from '@admin/components/ui/spinner'
9
10
  import { cn } from '@admin/utils/shared/cn'
10
- import { LoaderCircle } from 'lucide-react'
11
11
  import Image from 'next/image'
12
12
  import Link from 'next/link'
13
13
  import { useRouter } from 'next/navigation'
@@ -89,7 +89,7 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
89
89
  await authClient.getSession({ query: { disableCookieCache: true } })
90
90
  router.push(serializeLoginUrl('/admin/login', { reset: 'success' }))
91
91
  } catch {
92
- setError('An error occurred. Please try again.')
92
+ setError('Failed to reset your password. Try again.')
93
93
  }
94
94
  })
95
95
  }
@@ -146,9 +146,9 @@ export function ResetPasswordForm({ className, ...props }: React.ComponentProps<
146
146
  />
147
147
  </Field>
148
148
  <Field className="pt-4">
149
- <Button type="submit" disabled={isPending}>
150
- {isPending && <LoaderCircle className="animate-spin" />}
151
- {isPending ? 'Resetting...' : 'Reset Password'}
149
+ <Button type="submit" disabled={isPending} aria-busy={isPending}>
150
+ {isPending && <Spinner />}
151
+ {isPending ? 'Resetting' : 'Reset Password'}
152
152
  </Button>
153
153
  </Field>
154
154
  </div>
@@ -238,7 +238,9 @@ export function AuditLogTable<TValue>({
238
238
  className="flex h-24 items-center justify-center text-center"
239
239
  style={{ gridColumn: '1 / -1' }}
240
240
  >
241
- <div className="text-destructive">Error loading Audit Logs: {error.message}</div>
241
+ <div className="text-destructive">
242
+ Failed to load audit logs. Refresh to try again.
243
+ </div>
242
244
  </DataGridCell>
243
245
  </DataGridRow>
244
246
  ) : table.getRowModel().rows.length ? (
@@ -22,10 +22,10 @@ import {
22
22
  FormMessage
23
23
  } from '@admin/components/ui/form'
24
24
  import { ScrollArea } from '@admin/components/ui/scroll-area'
25
+ import { Spinner } from '@admin/components/ui/spinner'
25
26
  import { Textarea } from '@admin/components/ui/textarea'
26
27
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
27
28
  import { useMutation, useQueryClient } from '@tanstack/react-query'
28
- import { LoaderCircle } from 'lucide-react'
29
29
  import { useForm } from 'react-hook-form'
30
30
  import { toast } from 'sonner'
31
31
  import { z } from 'zod/v3'
@@ -75,7 +75,7 @@ export function FormNotificationsDrawer({
75
75
  onOpenChange(false)
76
76
  },
77
77
  onError: (error: Error) => {
78
- toast.error(error.message || 'An unexpected error occurred')
78
+ toast.error(error.message || 'Failed to save these notification settings')
79
79
  }
80
80
  })
81
81
 
@@ -133,9 +133,14 @@ export function FormNotificationsDrawer({
133
133
  Cancel
134
134
  </Button>
135
135
  </DrawerClose>
136
- <Button type="submit" disabled={isPending || !form.formState.isDirty} size="lg">
137
- {isPending && <LoaderCircle className="animate-spin" />}
138
- Save
136
+ <Button
137
+ type="submit"
138
+ disabled={isPending || !form.formState.isDirty}
139
+ aria-busy={isPending}
140
+ size="lg"
141
+ >
142
+ {isPending && <Spinner />}
143
+ {isPending ? 'Saving…' : 'Save'}
139
144
  </Button>
140
145
  </DrawerFooter>
141
146
  </form>
@@ -12,8 +12,8 @@ import {
12
12
  DialogHeader,
13
13
  DialogTitle
14
14
  } from '@admin/components/ui/dialog'
15
+ import { Spinner } from '@admin/components/ui/spinner'
15
16
  import { useMutation, useQueryClient } from '@tanstack/react-query'
16
- import { LoaderCircle } from 'lucide-react'
17
17
  import { toast } from 'sonner'
18
18
 
19
19
  interface DeleteWebhookDialogProps {
@@ -27,16 +27,16 @@ export function DeleteWebhookDialog({ webhook, open, onOpenChange }: DeleteWebho
27
27
 
28
28
  const mutation = useMutation({
29
29
  mutationFn: async () => {
30
- if (!webhook) throw new Error('No endpoint selected')
30
+ if (!webhook) throw new Error('No webhook selected')
31
31
  const result = await deleteWebhook(webhook.id)
32
32
  if (!result.success) {
33
- throw new Error(result.error || 'Failed to delete endpoint')
33
+ throw new Error(result.error || 'Failed to delete this webhook')
34
34
  }
35
35
 
36
36
  return result
37
37
  },
38
38
  onSuccess: async () => {
39
- toast.success('Endpoint deleted')
39
+ toast.success('Webhook deleted')
40
40
  await queryClient.invalidateQueries({ queryKey: ['webhooks'] })
41
41
  await queryClient.invalidateQueries({
42
42
  queryKey: ['webhook-subscriptions']
@@ -45,7 +45,7 @@ export function DeleteWebhookDialog({ webhook, open, onOpenChange }: DeleteWebho
45
45
  onOpenChange(false)
46
46
  },
47
47
  onError: (error: Error) => {
48
- toast.error(error.message || 'An unexpected error occurred')
48
+ toast.error(error.message || 'Failed to delete this webhook')
49
49
  }
50
50
  })
51
51
 
@@ -55,25 +55,34 @@ export function DeleteWebhookDialog({ webhook, open, onOpenChange }: DeleteWebho
55
55
  <Dialog open={open} onOpenChange={onOpenChange}>
56
56
  <DialogContent className="sm:max-w-105">
57
57
  <DialogHeader>
58
- <DialogTitle>Delete endpoint</DialogTitle>
58
+ <DialogTitle>Delete webhook?</DialogTitle>
59
59
  <DialogDescription>
60
60
  {webhook
61
- ? `"${webhook.name}" will stop receiving events. Its subscriptions and delivery history are removed too.`
62
- : 'This endpoint will stop receiving events.'}
61
+ ? `"${webhook.name}" stops receiving events. Its subscriptions and delivery history are deleted too.`
62
+ : 'This webhook stops receiving events.'}
63
63
  </DialogDescription>
64
64
  </DialogHeader>
65
65
  <DialogBody>
66
66
  <p className="text-sm text-muted-foreground">This action cannot be undone.</p>
67
67
  </DialogBody>
68
68
  <DialogFooter showCloseButton={true}>
69
+ <Button
70
+ variant="secondary"
71
+ size="lg"
72
+ disabled={isPending}
73
+ onClick={() => onOpenChange(false)}
74
+ >
75
+ Cancel
76
+ </Button>
69
77
  <Button
70
78
  variant="destructive"
71
79
  size="lg"
72
80
  disabled={isPending}
81
+ aria-busy={isPending}
73
82
  onClick={() => mutation.mutate()}
74
83
  >
75
- {isPending && <LoaderCircle className="animate-spin" />}
76
- Delete
84
+ {isPending && <Spinner />}
85
+ {isPending ? 'Deleting…' : 'Delete webhook'}
77
86
  </Button>
78
87
  </DialogFooter>
79
88
  </DialogContent>
@@ -8,8 +8,9 @@ import {
8
8
  DropdownMenuItem,
9
9
  DropdownMenuTrigger
10
10
  } from '@admin/components/ui/dropdown-menu'
11
+ import { Spinner } from '@admin/components/ui/spinner'
11
12
  import { useSendTestWebhook } from '@admin/hooks/use-webhooks'
12
- import { LoaderCircle, MoreHorizontal } from 'lucide-react'
13
+ import { MoreHorizontal } from 'lucide-react'
13
14
 
14
15
  interface WebhookActionsProps {
15
16
  webhook: WebhookEndpointListItem
@@ -30,7 +31,7 @@ export function WebhookActions({ webhook, onEdit, onDelete }: WebhookActionsProp
30
31
  >
31
32
  <span className="sr-only">Open webhook actions</span>
32
33
  {sendTest.isPending ? (
33
- <LoaderCircle className="size-4 animate-spin" />
34
+ <Spinner className="size-4" />
34
35
  ) : (
35
36
  <MoreHorizontal className="size-4" />
36
37
  )}
@@ -39,9 +40,13 @@ export function WebhookActions({ webhook, onEdit, onDelete }: WebhookActionsProp
39
40
  <DropdownMenuContent align="end">
40
41
  <DropdownMenuItem
41
42
  disabled={sendTest.isPending}
42
- onSelect={() => sendTest.mutate(webhook.id)}
43
+ aria-busy={sendTest.isPending}
44
+ onSelect={(event) => {
45
+ event.preventDefault()
46
+ sendTest.mutate(webhook.id)
47
+ }}
43
48
  >
44
- Test
49
+ {sendTest.isPending ? 'Testing…' : 'Test'}
45
50
  </DropdownMenuItem>
46
51
  <DropdownMenuItem onSelect={() => onEdit(webhook)}>Edit</DropdownMenuItem>
47
52
  <DropdownMenuItem variant="destructive" onSelect={() => onDelete(webhook)}>