betterstart-cli 0.0.27 → 0.0.29
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/integrations/mailchimp/actions/mailchimp.ts +0 -1
- package/dist/assets/adapters/next/integrations/vercel-blob/actions/vercel-blob.ts +39 -0
- package/dist/assets/adapters/next/integrations/vercel-blob/integration.ts +36 -0
- package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -1
- package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -1
- package/dist/assets/adapters/next/templates/init/admin-globals.css +1 -1
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +12 -2
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +75 -0
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -2
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -8
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +14 -5
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +111 -112
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +4 -0
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +45 -47
- package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/data/webhook-events.ts +5 -0
- package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +27 -7
- package/dist/assets/adapters/next/templates/init/hooks/use-webhooks.ts +211 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +2 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +0 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +0 -4
- package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +0 -2
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +55 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +26 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/dispatch.ts +135 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-deliveries.ts +124 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +28 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-subscriptions.ts +32 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhooks.ts +154 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +30 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/internal-get-active-endpoints.ts +20 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +35 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/register.ts +70 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +81 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/types.ts +104 -0
- package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +72 -0
- package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +71 -2
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +2 -0
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -1
- package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +6 -1
- package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-columns.tsx +150 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page-content.tsx +137 -0
- package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +295 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/edit-form-notifications-dialog.tsx +128 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-content.tsx +111 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-skeleton.tsx +7 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +14 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/settings-layout.tsx +10 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +82 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +53 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-enabled-switch.tsx +18 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +370 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-columns.tsx +258 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-columns.tsx +200 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +117 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +14 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +253 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-content.tsx +161 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-skeleton.tsx +7 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +14 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +264 -0
- package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +103 -109
- package/dist/assets/adapters/next/templates/init/types/webhooks.ts +7 -0
- package/dist/assets/adapters/next/templates/init/utils/webhook/signature.ts +13 -0
- package/dist/assets/shared-assets/react-admin/ui/card.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +1 -1
- package/dist/cli.js +2191 -1304
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +0 -40
- package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +0 -550
- package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +0 -28
- /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-drawer.tsx +0 -0
- /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-row.tsx +0 -0
- /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page.tsx +0 -0
|
@@ -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
|
+
}
|
package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-enabled-switch.tsx
ADDED
|
@@ -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
|
+
}
|
package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
|
|
4
|
+
import {
|
|
5
|
+
createWebhook,
|
|
6
|
+
getWebhookSecret,
|
|
7
|
+
regenerateWebhookSecret,
|
|
8
|
+
sendTestWebhook,
|
|
9
|
+
updateWebhook
|
|
10
|
+
} from '@admin/actions/webhooks'
|
|
11
|
+
import { Button } from '@admin/components/ui/button'
|
|
12
|
+
import { Checkbox } from '@admin/components/ui/checkbox'
|
|
13
|
+
import {
|
|
14
|
+
Dialog,
|
|
15
|
+
DialogBody,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogFooter,
|
|
19
|
+
DialogHeader,
|
|
20
|
+
DialogTitle
|
|
21
|
+
} from '@admin/components/ui/dialog'
|
|
22
|
+
import {
|
|
23
|
+
Form,
|
|
24
|
+
FormControl,
|
|
25
|
+
FormField,
|
|
26
|
+
FormItem,
|
|
27
|
+
FormLabel,
|
|
28
|
+
FormMessage
|
|
29
|
+
} from '@admin/components/ui/form'
|
|
30
|
+
import { Input } from '@admin/components/ui/input'
|
|
31
|
+
import { Switch } from '@admin/components/ui/switch'
|
|
32
|
+
import { webhookEventSources } from '@admin/data/webhook-events'
|
|
33
|
+
import { useWebhookSubscriptions } from '@admin/hooks/use-webhooks'
|
|
34
|
+
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
|
|
35
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
36
|
+
import { LoaderCircle } from 'lucide-react'
|
|
37
|
+
import * as React from 'react'
|
|
38
|
+
import { useForm } from 'react-hook-form'
|
|
39
|
+
import { toast } from 'sonner'
|
|
40
|
+
import { z } from 'zod/v3'
|
|
41
|
+
|
|
42
|
+
const endpointSchema = z.object({
|
|
43
|
+
name: z.string().trim().min(1, 'Name is required').max(200, 'Name is too long'),
|
|
44
|
+
url: z.string().trim().url('Enter a valid URL'),
|
|
45
|
+
enabled: z.boolean(),
|
|
46
|
+
events: z.array(z.string())
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
type EndpointFormValues = z.infer<typeof endpointSchema>
|
|
50
|
+
|
|
51
|
+
interface WebhookEndpointDialogProps {
|
|
52
|
+
webhook: WebhookEndpointListItem | null
|
|
53
|
+
open: boolean
|
|
54
|
+
onOpenChange: (open: boolean) => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function WebhookEndpointDialog({ webhook, open, onOpenChange }: WebhookEndpointDialogProps) {
|
|
58
|
+
const queryClient = useQueryClient()
|
|
59
|
+
const isEdit = webhook !== null
|
|
60
|
+
const { data: subscriptions } = useWebhookSubscriptions()
|
|
61
|
+
const subscribedEvents = React.useMemo(
|
|
62
|
+
() =>
|
|
63
|
+
webhook
|
|
64
|
+
? (subscriptions ?? [])
|
|
65
|
+
.filter((entry) => entry.webhookId === webhook.id)
|
|
66
|
+
.map((entry) => entry.event)
|
|
67
|
+
: [],
|
|
68
|
+
[subscriptions, webhook]
|
|
69
|
+
)
|
|
70
|
+
const [selectedEvents, setSelectedEvents] = React.useState<string[] | null>(null)
|
|
71
|
+
const events = selectedEvents ?? subscribedEvents
|
|
72
|
+
const [secret, setSecret] = React.useState<string | null>(null)
|
|
73
|
+
|
|
74
|
+
const formSources = webhookEventSources.filter((source) => source.kind === 'form')
|
|
75
|
+
const contentSources = webhookEventSources.filter((source) => source.kind !== 'form')
|
|
76
|
+
|
|
77
|
+
const form = useForm<EndpointFormValues>({
|
|
78
|
+
resolver: standardSchemaResolver(endpointSchema),
|
|
79
|
+
defaultValues: {
|
|
80
|
+
name: webhook?.name ?? '',
|
|
81
|
+
url: webhook?.url ?? '',
|
|
82
|
+
enabled: webhook?.enabled ?? true,
|
|
83
|
+
events: subscribedEvents
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const saveMutation = useMutation({
|
|
88
|
+
mutationFn: async (values: EndpointFormValues) => {
|
|
89
|
+
const endpointValues = {
|
|
90
|
+
name: values.name,
|
|
91
|
+
url: values.url,
|
|
92
|
+
enabled: values.enabled
|
|
93
|
+
}
|
|
94
|
+
const result = webhook
|
|
95
|
+
? await updateWebhook(webhook.id, { ...endpointValues, events })
|
|
96
|
+
: await createWebhook({ ...endpointValues, events })
|
|
97
|
+
if (!result.success) {
|
|
98
|
+
throw new Error(result.error || 'Failed to save endpoint')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result
|
|
102
|
+
},
|
|
103
|
+
onSuccess: async () => {
|
|
104
|
+
toast.success(isEdit ? 'Endpoint saved' : 'Endpoint created')
|
|
105
|
+
await queryClient.invalidateQueries({ queryKey: ['webhooks'] })
|
|
106
|
+
await queryClient.invalidateQueries({
|
|
107
|
+
queryKey: ['webhook-subscriptions']
|
|
108
|
+
})
|
|
109
|
+
onOpenChange(false)
|
|
110
|
+
},
|
|
111
|
+
onError: (error: Error) => {
|
|
112
|
+
toast.error(error.message || 'An unexpected error occurred')
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const revealSecretMutation = useMutation({
|
|
117
|
+
mutationFn: async () => {
|
|
118
|
+
if (!webhook) throw new Error('Save the endpoint first')
|
|
119
|
+
const result = await getWebhookSecret(webhook.id)
|
|
120
|
+
if (!result.success || !result.secret) {
|
|
121
|
+
throw new Error(result.error || 'Failed to fetch signing secret')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result.secret
|
|
125
|
+
},
|
|
126
|
+
onSuccess: (value) => setSecret(value),
|
|
127
|
+
onError: (error: Error) => {
|
|
128
|
+
toast.error(error.message || 'Failed to fetch signing secret')
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const regenerateSecretMutation = useMutation({
|
|
133
|
+
mutationFn: async () => {
|
|
134
|
+
if (!webhook) throw new Error('Save the endpoint first')
|
|
135
|
+
const result = await regenerateWebhookSecret(webhook.id)
|
|
136
|
+
if (!result.success || !result.secret) {
|
|
137
|
+
throw new Error(result.error || 'Failed to regenerate signing secret')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result.secret
|
|
141
|
+
},
|
|
142
|
+
onSuccess: (value) => {
|
|
143
|
+
setSecret(value)
|
|
144
|
+
toast.success('Signing secret regenerated')
|
|
145
|
+
},
|
|
146
|
+
onError: (error: Error) => {
|
|
147
|
+
toast.error(error.message || 'Failed to regenerate signing secret')
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const testMutation = useMutation({
|
|
152
|
+
mutationFn: async () => {
|
|
153
|
+
if (!webhook) throw new Error('Save the endpoint first')
|
|
154
|
+
const result = await sendTestWebhook(webhook.id)
|
|
155
|
+
if (!result.success) {
|
|
156
|
+
throw new Error(result.error || 'Test webhook failed')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
},
|
|
161
|
+
onSuccess: async (result) => {
|
|
162
|
+
toast.success(
|
|
163
|
+
result.statusCode ? `Test delivered (status ${result.statusCode})` : 'Test delivered'
|
|
164
|
+
)
|
|
165
|
+
await queryClient.invalidateQueries({ queryKey: ['webhook-deliveries'] })
|
|
166
|
+
},
|
|
167
|
+
onError: (error: Error) => {
|
|
168
|
+
toast.error(error.message || 'Test webhook failed')
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const isPending = saveMutation.isPending
|
|
173
|
+
|
|
174
|
+
function toggleEvent(event: string, checked: boolean) {
|
|
175
|
+
const nextEvents = checked ? [...events, event] : events.filter((entry) => entry !== event)
|
|
176
|
+
setSelectedEvents(nextEvents)
|
|
177
|
+
form.setValue('events', nextEvents)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
182
|
+
<DialogContent className="sm:max-w-135">
|
|
183
|
+
<Form {...form}>
|
|
184
|
+
<form onSubmit={form.handleSubmit((values) => saveMutation.mutate(values))}>
|
|
185
|
+
<DialogHeader>
|
|
186
|
+
<DialogTitle>
|
|
187
|
+
{isEdit ? `Edit endpoint — ${webhook.name}` : 'Add endpoint'}
|
|
188
|
+
</DialogTitle>
|
|
189
|
+
<DialogDescription>
|
|
190
|
+
Events are delivered as signed JSON POST requests
|
|
191
|
+
</DialogDescription>
|
|
192
|
+
</DialogHeader>
|
|
193
|
+
|
|
194
|
+
<DialogBody>
|
|
195
|
+
<div className="space-y-4">
|
|
196
|
+
<FormField
|
|
197
|
+
control={form.control}
|
|
198
|
+
name="name"
|
|
199
|
+
render={({ field }) => (
|
|
200
|
+
<FormItem>
|
|
201
|
+
<FormLabel>Name</FormLabel>
|
|
202
|
+
<FormControl>
|
|
203
|
+
<Input placeholder="Zapier" disabled={isPending} {...field} />
|
|
204
|
+
</FormControl>
|
|
205
|
+
<FormMessage />
|
|
206
|
+
</FormItem>
|
|
207
|
+
)}
|
|
208
|
+
/>
|
|
209
|
+
<FormField
|
|
210
|
+
control={form.control}
|
|
211
|
+
name="url"
|
|
212
|
+
render={({ field }) => (
|
|
213
|
+
<FormItem>
|
|
214
|
+
<FormLabel>URL</FormLabel>
|
|
215
|
+
<FormControl>
|
|
216
|
+
<Input
|
|
217
|
+
type="url"
|
|
218
|
+
placeholder="https://example.com/webhooks"
|
|
219
|
+
disabled={isPending}
|
|
220
|
+
{...field}
|
|
221
|
+
/>
|
|
222
|
+
</FormControl>
|
|
223
|
+
<FormMessage />
|
|
224
|
+
</FormItem>
|
|
225
|
+
)}
|
|
226
|
+
/>
|
|
227
|
+
<FormField
|
|
228
|
+
control={form.control}
|
|
229
|
+
name="enabled"
|
|
230
|
+
render={({ field }) => (
|
|
231
|
+
<FormItem className="flex items-center justify-between rounded-md border border-border px-3 py-2">
|
|
232
|
+
<FormLabel>Enabled</FormLabel>
|
|
233
|
+
<FormControl>
|
|
234
|
+
<Switch
|
|
235
|
+
checked={field.value}
|
|
236
|
+
onCheckedChange={field.onChange}
|
|
237
|
+
disabled={isPending}
|
|
238
|
+
/>
|
|
239
|
+
</FormControl>
|
|
240
|
+
</FormItem>
|
|
241
|
+
)}
|
|
242
|
+
/>
|
|
243
|
+
{isEdit && (
|
|
244
|
+
<div className="space-y-2 rounded-md border border-border p-3">
|
|
245
|
+
<p className="text-sm font-medium leading-none">Signing secret</p>
|
|
246
|
+
<div className="flex items-center gap-2">
|
|
247
|
+
<span className="min-w-0 flex-1 truncate font-mono text-xs">
|
|
248
|
+
{secret ?? 'whsec_••••••••••••••••'}
|
|
249
|
+
</span>
|
|
250
|
+
<Button
|
|
251
|
+
type="button"
|
|
252
|
+
variant="outline"
|
|
253
|
+
size="sm"
|
|
254
|
+
disabled={revealSecretMutation.isPending || secret !== null}
|
|
255
|
+
onClick={() => revealSecretMutation.mutate()}
|
|
256
|
+
>
|
|
257
|
+
Reveal
|
|
258
|
+
</Button>
|
|
259
|
+
<Button
|
|
260
|
+
type="button"
|
|
261
|
+
variant="outline"
|
|
262
|
+
size="sm"
|
|
263
|
+
disabled={regenerateSecretMutation.isPending}
|
|
264
|
+
onClick={() => regenerateSecretMutation.mutate()}
|
|
265
|
+
>
|
|
266
|
+
{regenerateSecretMutation.isPending && (
|
|
267
|
+
<LoaderCircle className="animate-spin" />
|
|
268
|
+
)}
|
|
269
|
+
Regenerate
|
|
270
|
+
</Button>
|
|
271
|
+
</div>
|
|
272
|
+
<p className="text-xs text-muted-foreground">
|
|
273
|
+
Requests carry x-webhook-id, x-webhook-timestamp, and an HMAC-SHA256
|
|
274
|
+
x-webhook-signature of the body.
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
{(formSources.length > 0 || contentSources.length > 0) && (
|
|
279
|
+
<FormField
|
|
280
|
+
control={form.control}
|
|
281
|
+
name="events"
|
|
282
|
+
render={() => (
|
|
283
|
+
<React.Fragment>
|
|
284
|
+
{formSources.length > 0 && (
|
|
285
|
+
<fieldset className="space-y-2 rounded-md border border-border p-3">
|
|
286
|
+
<legend className="px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
287
|
+
Events · Forms
|
|
288
|
+
</legend>
|
|
289
|
+
{formSources.flatMap((source) =>
|
|
290
|
+
source.events.map((event) => (
|
|
291
|
+
<FormItem key={event} className="flex items-center gap-2 space-y-0">
|
|
292
|
+
<FormControl>
|
|
293
|
+
<Checkbox
|
|
294
|
+
checked={events.includes(event)}
|
|
295
|
+
onCheckedChange={(checked) =>
|
|
296
|
+
toggleEvent(event, checked === true)
|
|
297
|
+
}
|
|
298
|
+
disabled={isPending}
|
|
299
|
+
/>
|
|
300
|
+
</FormControl>
|
|
301
|
+
<FormLabel className="font-mono text-xs font-normal">
|
|
302
|
+
{event}
|
|
303
|
+
</FormLabel>
|
|
304
|
+
</FormItem>
|
|
305
|
+
))
|
|
306
|
+
)}
|
|
307
|
+
</fieldset>
|
|
308
|
+
)}
|
|
309
|
+
{contentSources.length > 0 && (
|
|
310
|
+
<fieldset className="space-y-2 rounded-md border border-border p-3">
|
|
311
|
+
<legend className="px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
312
|
+
Events · Entities
|
|
313
|
+
</legend>
|
|
314
|
+
{contentSources.flatMap((source) =>
|
|
315
|
+
source.events.map((event) => (
|
|
316
|
+
<FormItem key={event} className="flex items-center gap-2 space-y-0">
|
|
317
|
+
<FormControl>
|
|
318
|
+
<Checkbox
|
|
319
|
+
checked={events.includes(event)}
|
|
320
|
+
onCheckedChange={(checked) =>
|
|
321
|
+
toggleEvent(event, checked === true)
|
|
322
|
+
}
|
|
323
|
+
disabled={isPending}
|
|
324
|
+
/>
|
|
325
|
+
</FormControl>
|
|
326
|
+
<FormLabel className="font-mono text-xs font-normal">
|
|
327
|
+
{event}
|
|
328
|
+
</FormLabel>
|
|
329
|
+
</FormItem>
|
|
330
|
+
))
|
|
331
|
+
)}
|
|
332
|
+
</fieldset>
|
|
333
|
+
)}
|
|
334
|
+
</React.Fragment>
|
|
335
|
+
)}
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
{formSources.length === 0 && contentSources.length === 0 && (
|
|
339
|
+
<p className="text-sm text-muted-foreground">
|
|
340
|
+
No schemas yet — generate an entity or form schema to subscribe this endpoint to
|
|
341
|
+
its events.
|
|
342
|
+
</p>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
</DialogBody>
|
|
346
|
+
|
|
347
|
+
<DialogFooter showCloseButton={true}>
|
|
348
|
+
{isEdit && (
|
|
349
|
+
<Button
|
|
350
|
+
type="button"
|
|
351
|
+
variant="outline"
|
|
352
|
+
size="lg"
|
|
353
|
+
disabled={testMutation.isPending}
|
|
354
|
+
onClick={() => testMutation.mutate()}
|
|
355
|
+
>
|
|
356
|
+
{testMutation.isPending && <LoaderCircle className="animate-spin" />}
|
|
357
|
+
Send test
|
|
358
|
+
</Button>
|
|
359
|
+
)}
|
|
360
|
+
<Button type="submit" disabled={isPending} size="lg">
|
|
361
|
+
{isPending && <LoaderCircle className="animate-spin" />}
|
|
362
|
+
{isEdit ? 'Save endpoint' : 'Create endpoint'}
|
|
363
|
+
</Button>
|
|
364
|
+
</DialogFooter>
|
|
365
|
+
</form>
|
|
366
|
+
</Form>
|
|
367
|
+
</DialogContent>
|
|
368
|
+
</Dialog>
|
|
369
|
+
)
|
|
370
|
+
}
|