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,161 @@
1
+ 'use client'
2
+
3
+ import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
4
+ import { EntityFiltersBar } from '@admin/components/shared/entity-filters-bar'
5
+ import { PageHeader } from '@admin/components/shared/page-header'
6
+ import { Button } from '@admin/components/ui/button'
7
+ import { useIsFetching } from '@tanstack/react-query'
8
+ import { Plus } from 'lucide-react'
9
+ import { parseAsInteger, parseAsString, useQueryState, useQueryStates } from 'nuqs'
10
+ import * as React from 'react'
11
+ import { DeleteWebhookDialog } from './delete-webhook-dialog'
12
+ import { WebhookEndpointDialog } from './webhook-endpoint-dialog'
13
+ import { createWebhooksColumns } from './webhooks-columns'
14
+ import { WebhooksTable } from './webhooks-table'
15
+
16
+ const filterParsers = {
17
+ enabled: parseAsString.withDefault(''),
18
+ lastDelivery: parseAsString.withDefault(''),
19
+ createdAtFrom: parseAsString.withDefault(''),
20
+ createdAtTo: parseAsString.withDefault('')
21
+ } as const
22
+
23
+ export function WebhooksPageContent() {
24
+ const isSearchFetching = useIsFetching({ queryKey: ['webhooks'] }) > 0
25
+ const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''))
26
+ const [, setPageIndex] = useQueryState('page', parseAsInteger.withDefault(0))
27
+
28
+ const [filterValues, setFilters] = useQueryStates(filterParsers)
29
+ const enabled = filterValues.enabled
30
+ const lastDelivery = filterValues.lastDelivery
31
+ const createdAtFrom = filterValues.createdAtFrom
32
+ const createdAtTo = filterValues.createdAtTo
33
+
34
+ const [dialogOpen, setDialogOpen] = React.useState(false)
35
+ const [editingWebhook, setEditingWebhook] = React.useState<WebhookEndpointListItem | null>(null)
36
+ const [deletingWebhook, setDeletingWebhook] = React.useState<WebhookEndpointListItem | null>(null)
37
+
38
+ function openCreateDialog() {
39
+ setEditingWebhook(null)
40
+ setDialogOpen(true)
41
+ }
42
+
43
+ function openEditDialog(webhook: WebhookEndpointListItem) {
44
+ setEditingWebhook(webhook)
45
+ setDialogOpen(true)
46
+ }
47
+
48
+ const columns = createWebhooksColumns({
49
+ onEdit: openEditDialog,
50
+ onDelete: setDeletingWebhook
51
+ })
52
+
53
+ const activeFilterCount = [search, enabled, lastDelivery, createdAtFrom || createdAtTo].filter(
54
+ Boolean
55
+ ).length
56
+
57
+ function handleSearch(value: string | null) {
58
+ React.startTransition(() => {
59
+ void setPageIndex(0)
60
+ void setSearch(value)
61
+ })
62
+ }
63
+
64
+ function handleClearAll() {
65
+ React.startTransition(() => {
66
+ void setPageIndex(0)
67
+ void setSearch(null)
68
+ void setFilters({
69
+ enabled: null,
70
+ lastDelivery: null,
71
+ createdAtFrom: null,
72
+ createdAtTo: null
73
+ })
74
+ })
75
+ }
76
+
77
+ const filters = [
78
+ {
79
+ key: 'enabled',
80
+ label: 'Status',
81
+ type: 'select' as const,
82
+ value: enabled,
83
+ options: [
84
+ { label: 'Enabled', value: 'enabled' },
85
+ { label: 'Disabled', value: 'disabled' }
86
+ ]
87
+ },
88
+ {
89
+ key: 'lastDelivery',
90
+ label: 'Deliveries',
91
+ type: 'select' as const,
92
+ value: lastDelivery,
93
+ options: [
94
+ { label: 'Delivered', value: 'delivered' },
95
+ { label: 'Failed', value: 'failed' },
96
+ { label: 'Never delivered', value: 'never' }
97
+ ]
98
+ },
99
+ {
100
+ key: 'createdAt',
101
+ label: 'Created',
102
+ type: 'date-range' as const,
103
+ from: createdAtFrom,
104
+ to: createdAtTo
105
+ }
106
+ ]
107
+
108
+ return (
109
+ <React.Fragment>
110
+ <PageHeader
111
+ title="Webhooks"
112
+ actions={
113
+ <Button onClick={openCreateDialog}>
114
+ <Plus className="size-3.5" />
115
+ Add endpoint
116
+ </Button>
117
+ }
118
+ />
119
+
120
+ <EntityFiltersBar
121
+ filters={filters}
122
+ activeFilterCount={activeFilterCount}
123
+ onFilterChange={(updates) => {
124
+ React.startTransition(() => {
125
+ void setPageIndex(0)
126
+ void setFilters(updates)
127
+ })
128
+ }}
129
+ onClearAll={handleClearAll}
130
+ search={search}
131
+ searchPlaceholder="Search webhooks..."
132
+ isPending={isSearchFetching}
133
+ onSearch={handleSearch}
134
+ />
135
+ <main className="space-y-4 px-4 pb-4 flex-1">
136
+ <WebhooksTable
137
+ columns={columns}
138
+ search={search}
139
+ enabled={enabled}
140
+ lastDelivery={lastDelivery}
141
+ createdAtFrom={createdAtFrom}
142
+ createdAtTo={createdAtTo}
143
+ onRowClick={openEditDialog}
144
+ />
145
+ </main>
146
+ <WebhookEndpointDialog
147
+ key={editingWebhook?.id ?? 'create'}
148
+ webhook={editingWebhook}
149
+ open={dialogOpen}
150
+ onOpenChange={setDialogOpen}
151
+ />
152
+ <DeleteWebhookDialog
153
+ webhook={deletingWebhook}
154
+ open={deletingWebhook !== null}
155
+ onOpenChange={(nextOpen) => {
156
+ if (!nextOpen) setDeletingWebhook(null)
157
+ }}
158
+ />
159
+ </React.Fragment>
160
+ )
161
+ }
@@ -0,0 +1,7 @@
1
+ export function WebhooksPageSkeleton() {
2
+ return (
3
+ <div className="flex items-center justify-center h-48">
4
+ <div className="text-muted-foreground">Loading Webhooks…</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 { WebhooksPageContent } from './webhooks-page-content'
4
+ import { WebhooksPageSkeleton } from './webhooks-page-skeleton'
5
+
6
+ export default async function WebhooksPage() {
7
+ await requireRole([UserRole.ADMIN])
8
+
9
+ return (
10
+ <React.Suspense fallback={<WebhooksPageSkeleton />}>
11
+ <WebhooksPageContent />
12
+ </React.Suspense>
13
+ )
14
+ }
@@ -0,0 +1,264 @@
1
+ 'use client'
2
+
3
+ import type { WebhookEndpointListItem } from '@admin/actions/webhooks'
4
+ import {
5
+ DataGrid,
6
+ DataGridBody,
7
+ DataGridCell,
8
+ DataGridHead,
9
+ DataGridHeader,
10
+ DataGridRow
11
+ } from '@admin/components/shared/data-table/data-grid'
12
+ import { DataTablePagination } from '@admin/components/shared/data-table/data-table-pagination'
13
+ import { useTableUtils } from '@admin/hooks/use-table-utils'
14
+ import { useWebhooks } from '@admin/hooks/use-webhooks'
15
+ import { cn } from '@admin/utils/shared/cn'
16
+ import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
17
+ import { GripVertical } from 'lucide-react'
18
+ import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
19
+
20
+ interface WebhooksTableProps<TValue> extends React.HTMLAttributes<HTMLDivElement> {
21
+ columns: ColumnDef<WebhookEndpointListItem, TValue>[]
22
+ search?: string
23
+ enabled?: string
24
+ lastDelivery?: string
25
+ createdAtFrom?: string
26
+ createdAtTo?: string
27
+ onRowClick: (webhook: WebhookEndpointListItem) => void
28
+ }
29
+
30
+ export function WebhooksTable<TValue>({
31
+ columns,
32
+ search,
33
+ enabled,
34
+ lastDelivery,
35
+ createdAtFrom,
36
+ createdAtTo,
37
+ onRowClick,
38
+ className
39
+ }: WebhooksTableProps<TValue>) {
40
+ const defaultPageSize = 20
41
+ const [{ page: pageIndex, size: pageSize, sort: sortField, dir: sortDir }, setTableQuery] =
42
+ useQueryStates({
43
+ page: parseAsInteger.withDefault(0),
44
+ size: parseAsInteger.withDefault(defaultPageSize),
45
+ sort: parseAsString.withDefault(''),
46
+ dir: parseAsString.withDefault('')
47
+ })
48
+
49
+ const safePageIndex = Number.isFinite(pageIndex) && pageIndex > 0 ? pageIndex : 0
50
+ const safePageSize =
51
+ pageSize === -1 || (Number.isFinite(pageSize) && pageSize > 0) ? pageSize : defaultPageSize
52
+ const { data, error, isPending } = useWebhooks(
53
+ search,
54
+ enabled,
55
+ lastDelivery,
56
+ createdAtFrom,
57
+ createdAtTo,
58
+ safePageIndex,
59
+ safePageSize,
60
+ sortField || undefined,
61
+ (sortDir as 'asc' | 'desc') || undefined
62
+ )
63
+
64
+ const tableData = data?.webhooks ?? []
65
+ const totalRows = data?.total ?? 0
66
+ const effectivePageSize = safePageSize === -1 ? totalRows : safePageSize
67
+ const pageCount = effectivePageSize > 0 ? Math.ceil(totalRows / effectivePageSize) : 1
68
+ const shouldShowPagination = totalRows > defaultPageSize
69
+
70
+ const {
71
+ columnSizing,
72
+ handleColumnSizingChange,
73
+ handlePaginationChange,
74
+ handleSortingChange,
75
+ hoveredColumnId,
76
+ registerTable,
77
+ resetColumnSizingToContainer,
78
+ setHoveredColumnId,
79
+ sorting,
80
+ tableContainerRef
81
+ } = useTableUtils<WebhookEndpointListItem>({
82
+ data: tableData,
83
+ pageIndex: safePageIndex,
84
+ pageSize: effectivePageSize,
85
+ setPageIndex: (nextPageIndex) => setTableQuery({ page: nextPageIndex }),
86
+ setSortingParams: ({
87
+ pageIndex: nextPageIndex,
88
+ sortDir: nextSortDir,
89
+ sortField: nextSortField
90
+ }) =>
91
+ setTableQuery({
92
+ page: nextPageIndex,
93
+ sort: nextSortField,
94
+ dir: nextSortDir
95
+ }),
96
+ sortField,
97
+ sortDir
98
+ })
99
+
100
+ const table = useReactTable({
101
+ data: tableData,
102
+ columns,
103
+ columnResizeMode: 'onChange',
104
+ defaultColumn: {
105
+ minSize: 48,
106
+ size: 220
107
+ },
108
+ getCoreRowModel: getCoreRowModel(),
109
+ manualPagination: true,
110
+ manualSorting: true,
111
+ pageCount,
112
+ rowCount: totalRows,
113
+ onSortingChange: handleSortingChange,
114
+ onColumnSizingChange: handleColumnSizingChange,
115
+ onPaginationChange: handlePaginationChange,
116
+ state: {
117
+ sorting,
118
+ columnSizing,
119
+ pagination: {
120
+ pageIndex: safePageIndex,
121
+ pageSize: effectivePageSize
122
+ }
123
+ }
124
+ })
125
+ registerTable(table)
126
+ const gridTemplateColumns = table
127
+ .getVisibleLeafColumns()
128
+ .map((column) => `${column.getSize()}px`)
129
+ .join(' ')
130
+
131
+ return (
132
+ <div className={cn('flex flex-col gap-6 pt-px pb-2 h-full', className)}>
133
+ <DataGrid
134
+ containerClassName="min-h-0 flex-1"
135
+ containerRef={tableContainerRef}
136
+ gridTemplateColumns={gridTemplateColumns}
137
+ style={{ width: table.getTotalSize() }}
138
+ >
139
+ <DataGridHeader>
140
+ {table.getHeaderGroups().map((headerGroup) => (
141
+ <DataGridRow key={headerGroup.id}>
142
+ {headerGroup.headers.map((header, headerIndex) => {
143
+ const resizeLabel =
144
+ typeof header.column.columnDef.header === 'string'
145
+ ? header.column.columnDef.header
146
+ : header.column.id
147
+ const nextHeader = headerGroup.headers[headerIndex + 1]
148
+ const canResizeColumn =
149
+ header.column.getCanResize() && nextHeader?.column.getCanResize() === true
150
+ return (
151
+ <DataGridHead
152
+ key={header.id}
153
+ className="group/resize-header relative overflow-hidden"
154
+ data-column-id={header.column.id}
155
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
156
+ onMouseLeave={() =>
157
+ setHoveredColumnId((current) =>
158
+ current === header.column.id ? null : current
159
+ )
160
+ }
161
+ >
162
+ {header.isPlaceholder
163
+ ? null
164
+ : flexRender(header.column.columnDef.header, header.getContext())}
165
+ {canResizeColumn ? (
166
+ <button
167
+ type="button"
168
+ aria-label={`Resize ${resizeLabel} column`}
169
+ className="group/resize-handle absolute right-0 top-0 z-10 flex h-full w-5 cursor-col-resize touch-none select-none items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:text-foreground focus-visible:opacity-100 focus-visible:outline-none group-focus-within/resize-header:opacity-100 data-[column-hovered=true]:opacity-100 data-[resizing=true]:opacity-100"
170
+ data-resize-handle
171
+ data-column-hovered={
172
+ hoveredColumnId === header.column.id ? 'true' : 'false'
173
+ }
174
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
175
+ onDoubleClick={resetColumnSizingToContainer}
176
+ onMouseDown={header.getResizeHandler()}
177
+ onTouchStart={header.getResizeHandler()}
178
+ >
179
+ <GripVertical
180
+ aria-hidden="true"
181
+ className={cn('size-3.5', {
182
+ 'text-ring': header.column.getIsResizing()
183
+ })}
184
+ />
185
+ </button>
186
+ ) : null}
187
+ </DataGridHead>
188
+ )
189
+ })}
190
+ </DataGridRow>
191
+ ))}
192
+ </DataGridHeader>
193
+ <DataGridBody>
194
+ {isPending ? (
195
+ <DataGridRow>
196
+ <DataGridCell
197
+ className="flex h-24 items-center justify-center text-center"
198
+ style={{ gridColumn: '1 / -1' }}
199
+ >
200
+ <div className="text-muted-foreground">Loading Webhooks…</div>
201
+ </DataGridCell>
202
+ </DataGridRow>
203
+ ) : error ? (
204
+ <DataGridRow>
205
+ <DataGridCell
206
+ className="flex h-24 items-center justify-center text-center"
207
+ style={{ gridColumn: '1 / -1' }}
208
+ >
209
+ <div className="text-destructive">Error loading Webhooks: {error.message}</div>
210
+ </DataGridCell>
211
+ </DataGridRow>
212
+ ) : table.getRowModel().rows?.length ? (
213
+ table.getRowModel().rows.map((row) => (
214
+ <DataGridRow
215
+ key={row.id}
216
+ className="cursor-pointer hover:bg-muted/50"
217
+ onClick={(e) => {
218
+ const target = e.target as HTMLElement
219
+ if (!e.currentTarget.contains(target)) return
220
+ if (target.closest('button, a, input, [role="checkbox"], [role="switch"]')) return
221
+ onRowClick(row.original)
222
+ }}
223
+ >
224
+ {row.getVisibleCells().map((cell) => (
225
+ <DataGridCell
226
+ key={cell.id}
227
+ data-column-id={cell.column.id}
228
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
229
+ onMouseLeave={() =>
230
+ setHoveredColumnId((current) => (current === cell.column.id ? null : current))
231
+ }
232
+ >
233
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
234
+ </DataGridCell>
235
+ ))}
236
+ </DataGridRow>
237
+ ))
238
+ ) : (
239
+ <DataGridRow>
240
+ <DataGridCell
241
+ className="flex h-24 items-center justify-center text-center"
242
+ style={{ gridColumn: '1 / -1' }}
243
+ >
244
+ No Webhooks found.
245
+ </DataGridCell>
246
+ </DataGridRow>
247
+ )}
248
+ </DataGridBody>
249
+ </DataGrid>
250
+
251
+ {shouldShowPagination ? (
252
+ <DataTablePagination
253
+ table={table}
254
+ paginationMode="controlled"
255
+ pageSize={safePageSize}
256
+ pageIndex={safePageIndex}
257
+ pageCount={pageCount}
258
+ setPageIndex={(nextPageIndex) => setTableQuery({ page: nextPageIndex })}
259
+ setPageSize={(nextPageSize) => setTableQuery({ page: 0, size: nextPageSize })}
260
+ />
261
+ ) : null}
262
+ </div>
263
+ )
264
+ }
@@ -1,7 +1,7 @@
1
1
  export function UsersPageSkeleton() {
2
2
  return (
3
3
  <div className="flex items-center justify-center h-48">
4
- <div className="text-muted-foreground">Loading Users...</div>
4
+ <div className="text-muted-foreground">Loading Users…</div>
5
5
  </div>
6
6
  )
7
7
  }