betterstart-cli 0.0.28 → 0.0.30

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 (82) hide show
  1. package/dist/assets/adapters/next/integrations/mailchimp/actions/mailchimp.ts +0 -1
  2. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -1
  3. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -1
  4. package/dist/assets/adapters/next/templates/init/admin-globals.css +1 -1
  5. package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +12 -2
  6. package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +75 -0
  7. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -2
  8. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -8
  9. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +14 -5
  10. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +111 -112
  11. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +4 -0
  12. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  13. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +1 -1
  14. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +45 -47
  15. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +1 -1
  16. package/dist/assets/adapters/next/templates/init/data/webhook-events.ts +5 -0
  17. package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +27 -7
  18. package/dist/assets/adapters/next/templates/init/hooks/use-webhooks.ts +211 -0
  19. package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +2 -0
  20. package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +0 -1
  21. package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +0 -4
  22. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +0 -2
  23. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +55 -0
  24. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +26 -0
  25. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/dispatch.ts +135 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-deliveries.ts +124 -0
  27. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +28 -0
  28. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-subscriptions.ts +32 -0
  29. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhooks.ts +154 -0
  30. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +30 -0
  31. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/internal-get-active-endpoints.ts +20 -0
  32. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +35 -0
  33. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/register.ts +70 -0
  34. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +81 -0
  35. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/types.ts +104 -0
  36. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +72 -0
  37. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +71 -2
  38. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +2 -0
  39. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -1
  40. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +6 -1
  41. package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +1 -1
  42. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +1 -1
  43. package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +1 -1
  44. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-columns.tsx +150 -0
  45. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page-content.tsx +137 -0
  46. package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page-skeleton.tsx +1 -1
  47. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +295 -0
  48. package/dist/assets/adapters/next/templates/init/pages/settings/forms/edit-form-notifications-dialog.tsx +128 -0
  49. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-content.tsx +111 -0
  50. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-skeleton.tsx +7 -0
  51. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +14 -0
  52. package/dist/assets/adapters/next/templates/init/pages/settings/settings-layout.tsx +10 -0
  53. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +82 -0
  54. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +53 -0
  55. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-enabled-switch.tsx +18 -0
  56. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +370 -0
  57. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-columns.tsx +258 -0
  58. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-columns.tsx +200 -0
  59. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +117 -0
  60. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +14 -0
  61. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +253 -0
  62. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-content.tsx +161 -0
  63. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-skeleton.tsx +7 -0
  64. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +14 -0
  65. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +264 -0
  66. package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +1 -1
  67. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +103 -109
  68. package/dist/assets/adapters/next/templates/init/types/webhooks.ts +7 -0
  69. package/dist/assets/adapters/next/templates/init/utils/webhook/signature.ts +13 -0
  70. package/dist/assets/shared-assets/react-admin/ui/card.tsx +1 -1
  71. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
  72. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +3 -3
  73. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +1 -1
  74. package/dist/cli.js +947 -620
  75. package/dist/cli.js.map +1 -1
  76. package/package.json +1 -1
  77. package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +0 -40
  78. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +0 -550
  79. package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +0 -28
  80. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-drawer.tsx +0 -0
  81. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-row.tsx +0 -0
  82. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page.tsx +0 -0
@@ -0,0 +1,128 @@
1
+ 'use client'
2
+
3
+ import { upsertFormSettings } from '@admin/actions/forms'
4
+ import { Button } from '@admin/components/ui/button'
5
+ import {
6
+ Dialog,
7
+ DialogBody,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle
13
+ } from '@admin/components/ui/dialog'
14
+ import {
15
+ Form,
16
+ FormControl,
17
+ FormDescription,
18
+ FormField,
19
+ FormItem,
20
+ FormLabel,
21
+ FormMessage
22
+ } from '@admin/components/ui/form'
23
+ import { Textarea } from '@admin/components/ui/textarea'
24
+ import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
25
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
26
+ import { LoaderCircle } from 'lucide-react'
27
+ import { useForm } from 'react-hook-form'
28
+ import { toast } from 'sonner'
29
+ import { z } from 'zod/v3'
30
+
31
+ const notificationsSchema = z.object({
32
+ notificationEmails: z.string().trim()
33
+ })
34
+
35
+ type NotificationsFormValues = z.infer<typeof notificationsSchema>
36
+
37
+ interface EditFormNotificationsDialogProps {
38
+ formLabel: string
39
+ settingsKey: string
40
+ initialEmails: string
41
+ open: boolean
42
+ onOpenChange: (open: boolean) => void
43
+ }
44
+
45
+ export function EditFormNotificationsDialog({
46
+ formLabel,
47
+ settingsKey,
48
+ initialEmails,
49
+ open,
50
+ onOpenChange
51
+ }: EditFormNotificationsDialogProps) {
52
+ const queryClient = useQueryClient()
53
+
54
+ const form = useForm<NotificationsFormValues>({
55
+ resolver: standardSchemaResolver(notificationsSchema),
56
+ defaultValues: { notificationEmails: initialEmails }
57
+ })
58
+
59
+ const mutation = useMutation({
60
+ mutationFn: async (values: NotificationsFormValues) => {
61
+ const result = await upsertFormSettings(settingsKey, {
62
+ notificationEmails: values.notificationEmails || null
63
+ })
64
+ if (!result.success) {
65
+ throw new Error(result.error || 'Failed to save notification emails')
66
+ }
67
+
68
+ return result
69
+ },
70
+ onSuccess: async () => {
71
+ toast.success('Notification emails saved')
72
+ await queryClient.invalidateQueries({ queryKey: ['form-settings'] })
73
+ onOpenChange(false)
74
+ },
75
+ onError: (error: Error) => {
76
+ toast.error(error.message || 'An unexpected error occurred')
77
+ }
78
+ })
79
+
80
+ const isPending = mutation.isPending
81
+
82
+ return (
83
+ <Dialog open={open} onOpenChange={onOpenChange}>
84
+ <DialogContent className="sm:max-w-135">
85
+ <Form {...form}>
86
+ <form onSubmit={form.handleSubmit((values) => mutation.mutate(values))}>
87
+ <DialogHeader>
88
+ <DialogTitle>{formLabel} notifications</DialogTitle>
89
+ <DialogDescription>
90
+ Send an email to these addresses when a submission arrives
91
+ </DialogDescription>
92
+ </DialogHeader>
93
+
94
+ <DialogBody>
95
+ <FormField
96
+ control={form.control}
97
+ name="notificationEmails"
98
+ render={({ field }) => (
99
+ <FormItem>
100
+ <FormLabel>Notification emails</FormLabel>
101
+ <FormControl>
102
+ <Textarea
103
+ placeholder="sales@example.com, team@example.com"
104
+ disabled={isPending}
105
+ {...field}
106
+ />
107
+ </FormControl>
108
+ <FormDescription>
109
+ Comma-separated email addresses. Leave empty to disable notifications.
110
+ </FormDescription>
111
+ <FormMessage />
112
+ </FormItem>
113
+ )}
114
+ />
115
+ </DialogBody>
116
+
117
+ <DialogFooter showCloseButton={true}>
118
+ <Button type="submit" disabled={isPending || !form.formState.isDirty} size="lg">
119
+ {isPending && <LoaderCircle className="animate-spin" />}
120
+ Save
121
+ </Button>
122
+ </DialogFooter>
123
+ </form>
124
+ </Form>
125
+ </DialogContent>
126
+ </Dialog>
127
+ )
128
+ }
@@ -0,0 +1,111 @@
1
+ 'use client'
2
+
3
+ import { PageHeader } from '@admin/components/shared/page-header'
4
+ import { Button } from '@admin/components/ui/button'
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle
11
+ } from '@admin/components/ui/card'
12
+ import {
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow
19
+ } from '@admin/components/ui/table'
20
+ import { webhookEventSources } from '@admin/data/webhook-events'
21
+ import { useFormSettingsList } from '@admin/hooks/use-webhooks'
22
+ import * as React from 'react'
23
+ import { EditFormNotificationsDialog } from './edit-form-notifications-dialog'
24
+
25
+ export function FormsSettingsPageContent() {
26
+ const formSources = webhookEventSources.filter((source) => source.kind === 'form')
27
+ const { data: settings, isPending } = useFormSettingsList()
28
+ const [editingKey, setEditingKey] = React.useState<string | null>(null)
29
+
30
+ const editingSource = formSources.find((source) => source.settingsKey === editingKey)
31
+
32
+ return (
33
+ <React.Fragment>
34
+ <PageHeader title="Form Settings" />
35
+ <main className="mx-auto max-w-5xl w-full space-y-4 p-4">
36
+ <Card>
37
+ <CardHeader>
38
+ <CardTitle>Form notifications</CardTitle>
39
+ <CardDescription>
40
+ Emails notified when a submission arrives. Webhooks are managed on the Webhooks tab.
41
+ </CardDescription>
42
+ </CardHeader>
43
+ <CardContent>
44
+ {formSources.length === 0 ? (
45
+ <p className="text-sm text-muted-foreground">
46
+ No forms yet. Create a form schema to configure its notifications here.
47
+ </p>
48
+ ) : (
49
+ <Table>
50
+ <TableHeader>
51
+ <TableRow>
52
+ <TableHead>Form</TableHead>
53
+ <TableHead>Notification emails</TableHead>
54
+ <TableHead className="w-20" />
55
+ </TableRow>
56
+ </TableHeader>
57
+ <TableBody>
58
+ {formSources.map((source) => {
59
+ const formSettings = settings?.find(
60
+ (entry) => entry.formName === source.settingsKey
61
+ )
62
+ return (
63
+ <TableRow key={source.schema}>
64
+ <TableCell className="font-medium">{source.label}</TableCell>
65
+ <TableCell>
66
+ {isPending ? (
67
+ <span className="text-muted-foreground">Loading…</span>
68
+ ) : formSettings?.notificationEmails ? (
69
+ <span className="font-mono text-xs">
70
+ {formSettings.notificationEmails}
71
+ </span>
72
+ ) : (
73
+ <span className="text-muted-foreground">No emails configured</span>
74
+ )}
75
+ </TableCell>
76
+ <TableCell className="text-right">
77
+ <Button
78
+ variant="outline"
79
+ size="sm"
80
+ onClick={() => setEditingKey(source.settingsKey ?? source.schema)}
81
+ >
82
+ Edit
83
+ </Button>
84
+ </TableCell>
85
+ </TableRow>
86
+ )
87
+ })}
88
+ </TableBody>
89
+ </Table>
90
+ )}
91
+ </CardContent>
92
+ </Card>
93
+ </main>
94
+ {editingSource?.settingsKey && (
95
+ <EditFormNotificationsDialog
96
+ key={editingSource.settingsKey}
97
+ formLabel={editingSource.label}
98
+ settingsKey={editingSource.settingsKey}
99
+ initialEmails={
100
+ settings?.find((entry) => entry.formName === editingSource.settingsKey)
101
+ ?.notificationEmails ?? ''
102
+ }
103
+ open={editingKey !== null}
104
+ onOpenChange={(nextOpen) => {
105
+ if (!nextOpen) setEditingKey(null)
106
+ }}
107
+ />
108
+ )}
109
+ </React.Fragment>
110
+ )
111
+ }
@@ -0,0 +1,7 @@
1
+ export function FormsSettingsPageSkeleton() {
2
+ return (
3
+ <div className="flex items-center justify-center h-48">
4
+ <div className="text-muted-foreground">Loading Form Settings…</div>
5
+ </div>
6
+ )
7
+ }
@@ -0,0 +1,14 @@
1
+ import { requireRole, UserRole } from '@admin/auth/middleware'
2
+ import * as React from 'react'
3
+ import { FormsSettingsPageContent } from './forms-settings-page-content'
4
+ import { FormsSettingsPageSkeleton } from './forms-settings-page-skeleton'
5
+
6
+ export default async function FormsSettingsPage() {
7
+ await requireRole([UserRole.ADMIN, UserRole.EDITOR])
8
+
9
+ return (
10
+ <React.Suspense fallback={<FormsSettingsPageSkeleton />}>
11
+ <FormsSettingsPageContent />
12
+ </React.Suspense>
13
+ )
14
+ }
@@ -0,0 +1,10 @@
1
+ import { AdminSettingsSidebar } from '@admin/components/layouts/admin-settings-sidebar'
2
+
3
+ export default function SettingsLayout({ children }: { children: React.ReactNode }) {
4
+ return (
5
+ <div className="fixed inset-y-0 right-0 left-(--admin-shell-content-left) flex min-w-0 transition-[left] duration-200 ease-linear">
6
+ <AdminSettingsSidebar />
7
+ <div className="min-w-0 flex-1 overflow-auto">{children}</div>
8
+ </div>
9
+ )
10
+ }
@@ -0,0 +1,82 @@
1
+ 'use client'
2
+
3
+ import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
4
+ import { deleteWebhook } from '@admin/actions/webhooks'
5
+ import { Button } from '@admin/components/ui/button'
6
+ import {
7
+ Dialog,
8
+ DialogBody,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle
14
+ } from '@admin/components/ui/dialog'
15
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
16
+ import { LoaderCircle } from 'lucide-react'
17
+ import { toast } from 'sonner'
18
+
19
+ interface DeleteWebhookDialogProps {
20
+ webhook: WebhookEndpointListItem | null
21
+ open: boolean
22
+ onOpenChange: (open: boolean) => void
23
+ }
24
+
25
+ export function DeleteWebhookDialog({ webhook, open, onOpenChange }: DeleteWebhookDialogProps) {
26
+ const queryClient = useQueryClient()
27
+
28
+ const mutation = useMutation({
29
+ mutationFn: async () => {
30
+ if (!webhook) throw new Error('No endpoint selected')
31
+ const result = await deleteWebhook(webhook.id)
32
+ if (!result.success) {
33
+ throw new Error(result.error || 'Failed to delete endpoint')
34
+ }
35
+
36
+ return result
37
+ },
38
+ onSuccess: async () => {
39
+ toast.success('Endpoint deleted')
40
+ await queryClient.invalidateQueries({ queryKey: ['webhooks'] })
41
+ await queryClient.invalidateQueries({
42
+ queryKey: ['webhook-subscriptions']
43
+ })
44
+ await queryClient.invalidateQueries({ queryKey: ['webhook-deliveries'] })
45
+ onOpenChange(false)
46
+ },
47
+ onError: (error: Error) => {
48
+ toast.error(error.message || 'An unexpected error occurred')
49
+ }
50
+ })
51
+
52
+ const isPending = mutation.isPending
53
+
54
+ return (
55
+ <Dialog open={open} onOpenChange={onOpenChange}>
56
+ <DialogContent className="sm:max-w-105">
57
+ <DialogHeader>
58
+ <DialogTitle>Delete endpoint</DialogTitle>
59
+ <DialogDescription>
60
+ {webhook
61
+ ? `"${webhook.name}" will stop receiving events. Its subscriptions and delivery history are removed too.`
62
+ : 'This endpoint will stop receiving events.'}
63
+ </DialogDescription>
64
+ </DialogHeader>
65
+ <DialogBody>
66
+ <p className="text-sm text-muted-foreground">This action cannot be undone.</p>
67
+ </DialogBody>
68
+ <DialogFooter showCloseButton={true}>
69
+ <Button
70
+ variant="destructive"
71
+ size="lg"
72
+ disabled={isPending}
73
+ onClick={() => mutation.mutate()}
74
+ >
75
+ {isPending && <LoaderCircle className="animate-spin" />}
76
+ Delete
77
+ </Button>
78
+ </DialogFooter>
79
+ </DialogContent>
80
+ </Dialog>
81
+ )
82
+ }
@@ -0,0 +1,53 @@
1
+ 'use client'
2
+
3
+ import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
4
+ import { Button } from '@admin/components/ui/button'
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger
10
+ } from '@admin/components/ui/dropdown-menu'
11
+ import { useSendTestWebhook } from '@admin/hooks/use-webhooks'
12
+ import { LoaderCircle, MoreHorizontal } from 'lucide-react'
13
+
14
+ interface WebhookActionsProps {
15
+ webhook: WebhookEndpointListItem
16
+ onEdit: (webhook: WebhookEndpointListItem) => void
17
+ onDelete: (webhook: WebhookEndpointListItem) => void
18
+ }
19
+
20
+ export function WebhookActions({ webhook, onEdit, onDelete }: WebhookActionsProps) {
21
+ const sendTest = useSendTestWebhook()
22
+
23
+ return (
24
+ <DropdownMenu>
25
+ <DropdownMenuTrigger asChild>
26
+ <Button
27
+ variant="ghost"
28
+ className="size-8 p-0"
29
+ aria-label={`Open webhook actions for ${webhook.name}`}
30
+ >
31
+ <span className="sr-only">Open webhook actions</span>
32
+ {sendTest.isPending ? (
33
+ <LoaderCircle className="size-4 animate-spin" />
34
+ ) : (
35
+ <MoreHorizontal className="size-4" />
36
+ )}
37
+ </Button>
38
+ </DropdownMenuTrigger>
39
+ <DropdownMenuContent align="end">
40
+ <DropdownMenuItem
41
+ disabled={sendTest.isPending}
42
+ onSelect={() => sendTest.mutate(webhook.id)}
43
+ >
44
+ Test
45
+ </DropdownMenuItem>
46
+ <DropdownMenuItem onSelect={() => onEdit(webhook)}>Edit</DropdownMenuItem>
47
+ <DropdownMenuItem variant="destructive" onSelect={() => onDelete(webhook)}>
48
+ Delete
49
+ </DropdownMenuItem>
50
+ </DropdownMenuContent>
51
+ </DropdownMenu>
52
+ )
53
+ }
@@ -0,0 +1,18 @@
1
+ 'use client'
2
+
3
+ import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
4
+ import { Switch } from '@admin/components/ui/switch'
5
+ import { useToggleWebhookEnabled } from '@admin/hooks/use-webhooks'
6
+
7
+ export function WebhookEnabledSwitch({ webhook }: { webhook: WebhookEndpointListItem }) {
8
+ const toggleEnabled = useToggleWebhookEnabled()
9
+
10
+ return (
11
+ <Switch
12
+ checked={webhook.enabled}
13
+ disabled={toggleEnabled.isPending}
14
+ onCheckedChange={(checked) => toggleEnabled.mutate({ id: webhook.id, enabled: checked })}
15
+ aria-label={`Toggle ${webhook.name}`}
16
+ />
17
+ )
18
+ }