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.
Files changed (84) hide show
  1. package/dist/assets/adapters/next/integrations/mailchimp/actions/mailchimp.ts +0 -1
  2. package/dist/assets/adapters/next/integrations/vercel-blob/actions/vercel-blob.ts +39 -0
  3. package/dist/assets/adapters/next/integrations/vercel-blob/integration.ts +36 -0
  4. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -1
  5. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -1
  6. package/dist/assets/adapters/next/templates/init/admin-globals.css +1 -1
  7. package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +12 -2
  8. package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +75 -0
  9. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -2
  10. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -8
  11. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +14 -5
  12. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +111 -112
  13. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +4 -0
  14. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  15. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +1 -1
  16. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +45 -47
  17. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +1 -1
  18. package/dist/assets/adapters/next/templates/init/data/webhook-events.ts +5 -0
  19. package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +27 -7
  20. package/dist/assets/adapters/next/templates/init/hooks/use-webhooks.ts +211 -0
  21. package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +2 -0
  22. package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +0 -1
  23. package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +0 -4
  24. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +0 -2
  25. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +55 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +26 -0
  27. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/dispatch.ts +135 -0
  28. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-deliveries.ts +124 -0
  29. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +28 -0
  30. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-subscriptions.ts +32 -0
  31. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhooks.ts +154 -0
  32. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +30 -0
  33. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/internal-get-active-endpoints.ts +20 -0
  34. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +35 -0
  35. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/register.ts +70 -0
  36. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +81 -0
  37. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/types.ts +104 -0
  38. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +72 -0
  39. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +71 -2
  40. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +2 -0
  41. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -1
  42. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +6 -1
  43. package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +1 -1
  44. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +1 -1
  45. package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +1 -1
  46. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-columns.tsx +150 -0
  47. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page-content.tsx +137 -0
  48. package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page-skeleton.tsx +1 -1
  49. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +295 -0
  50. package/dist/assets/adapters/next/templates/init/pages/settings/forms/edit-form-notifications-dialog.tsx +128 -0
  51. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-content.tsx +111 -0
  52. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-skeleton.tsx +7 -0
  53. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +14 -0
  54. package/dist/assets/adapters/next/templates/init/pages/settings/settings-layout.tsx +10 -0
  55. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +82 -0
  56. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +53 -0
  57. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-enabled-switch.tsx +18 -0
  58. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +370 -0
  59. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-columns.tsx +258 -0
  60. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-columns.tsx +200 -0
  61. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +117 -0
  62. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +14 -0
  63. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +253 -0
  64. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-content.tsx +161 -0
  65. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-skeleton.tsx +7 -0
  66. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +14 -0
  67. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +264 -0
  68. package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +1 -1
  69. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +103 -109
  70. package/dist/assets/adapters/next/templates/init/types/webhooks.ts +7 -0
  71. package/dist/assets/adapters/next/templates/init/utils/webhook/signature.ts +13 -0
  72. package/dist/assets/shared-assets/react-admin/ui/card.tsx +1 -1
  73. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
  74. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +3 -3
  75. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +1 -1
  76. package/dist/cli.js +2191 -1304
  77. package/dist/cli.js.map +1 -1
  78. package/package.json +1 -1
  79. package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +0 -40
  80. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +0 -550
  81. package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +0 -28
  82. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-drawer.tsx +0 -0
  83. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-row.tsx +0 -0
  84. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page.tsx +0 -0
@@ -9,7 +9,6 @@ if (process.env.BETTERSTART_MAILCHIMP_API_KEY && process.env.BETTERSTART_MAILCHI
9
9
 
10
10
  /**
11
11
  * Add an email address to the Mailchimp audience (fire-and-forget, non-blocking)
12
- * Follows the same pattern as sendWebhook() in utils/webhook/webhook.ts
13
12
  */
14
13
  export function addToMailchimpAudience(email: string): void {
15
14
  if (
@@ -0,0 +1,39 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import type { AdminStorageProvider, AdminStorageUploadInput } from '@admin/actions/storage/types'
3
+ import { put } from '@vercel/blob'
4
+
5
+ function getToken(): string {
6
+ return process.env.BLOB_READ_WRITE_TOKEN?.trim() ?? ''
7
+ }
8
+
9
+ function sanitizeFilename(filename: string): string {
10
+ return filename.replace(/[^a-zA-Z0-9.-]/g, '_')
11
+ }
12
+
13
+ function buildKey(filename: string, prefix = 'uploads'): string {
14
+ return `${prefix}/${Date.now()}-${randomUUID()}-${sanitizeFilename(filename)}`
15
+ }
16
+
17
+ export const vercelBlobStorageProvider: AdminStorageProvider = {
18
+ id: 'vercel-blob',
19
+ isConfigured() {
20
+ return Boolean(getToken())
21
+ },
22
+ async saveFile(input: AdminStorageUploadInput) {
23
+ if (!this.isConfigured()) {
24
+ throw new Error('Vercel Blob is not configured.')
25
+ }
26
+
27
+ const key = buildKey(input.filename, input.prefix)
28
+ const blob = await put(key, input.buffer, {
29
+ access: 'public',
30
+ contentType: input.contentType,
31
+ token: getToken()
32
+ })
33
+
34
+ return {
35
+ key,
36
+ url: blob.url
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,36 @@
1
+ import type { BetterstartIntegrationDefinition } from '@integration-engine/types.js'
2
+
3
+ export const vercelBlobIntegration: BetterstartIntegrationDefinition = {
4
+ id: 'vercel-blob',
5
+ title: 'Vercel Blob',
6
+ description: 'Remote media storage on Vercel Blob',
7
+ kind: 'storage',
8
+ version: '1.0.0',
9
+ dependencies: [],
10
+ conflicts: [],
11
+ files: [
12
+ {
13
+ outputPath: 'lib/actions/vercel-blob/provider.ts',
14
+ templatePath: 'actions/vercel-blob.ts'
15
+ }
16
+ ],
17
+ packageDependencies: ['@vercel/blob'],
18
+ devDependencies: [],
19
+ capabilities: {
20
+ storageProvider: {
21
+ provider: 'vercel-blob',
22
+ importPath: '@admin/actions/vercel-blob/provider',
23
+ exportName: 'vercelBlobStorageProvider'
24
+ }
25
+ },
26
+ envSections: [
27
+ {
28
+ // BLOB_READ_WRITE_TOKEN is deliberately unprefixed: it is the name the
29
+ // @vercel/blob SDK reads by default and the one Vercel injects into
30
+ // deployments when a Blob store is connected to the project.
31
+ header: 'Storage (Vercel Blob)',
32
+ vars: [{ key: 'BLOB_READ_WRITE_TOKEN', value: '' }]
33
+ }
34
+ ],
35
+ configure: 'vercel-blob'
36
+ }
@@ -22,7 +22,7 @@
22
22
  "name": "slug",
23
23
  "type": "string",
24
24
  "label": "Slug",
25
- "hint": "URL-friendly identifier (auto-generated from title)"
25
+ "hint": "URL-friendly identifier"
26
26
  },
27
27
  {
28
28
  "name": "items",
@@ -22,7 +22,7 @@
22
22
  "name": "slug",
23
23
  "type": "string",
24
24
  "label": "Slug",
25
- "hint": "URL-friendly identifier (auto-generated from title)"
25
+ "hint": "URL-friendly identifier"
26
26
  },
27
27
  {
28
28
  "name": "excerpt",
@@ -97,7 +97,7 @@
97
97
  --accent-foreground: oklch(0.15 0 0);
98
98
  --destructive: oklch(0.577 0.245 27.325);
99
99
  --warning: oklch(0.625 0.165 57.4);
100
- --border: oklch(0.94 0 0);
100
+ --border: oklch(0.93 0 0);
101
101
  --highlight: oklch(0.9 0.004 286.32);
102
102
  --input: oklch(0.994 0 0);
103
103
  --ring: oklch(0.708 0 0);
@@ -4,10 +4,20 @@ import { SidebarMenuButton } from '@admin/components/ui/sidebar'
4
4
  import Link from 'next/link'
5
5
  import { usePathname } from 'next/navigation'
6
6
 
7
- export function AdminNavLink({ href, children }: { href: string; children: React.ReactNode }) {
7
+ export function AdminNavLink({
8
+ href,
9
+ exact = false,
10
+ children
11
+ }: {
12
+ href: string
13
+ exact?: boolean
14
+ children: React.ReactNode
15
+ }) {
8
16
  const pathname = usePathname()
9
17
  const isActive =
10
- href === '/admin' ? pathname === '/admin' : pathname === href || pathname.startsWith(`${href}/`)
18
+ exact || href === '/admin'
19
+ ? pathname === href
20
+ : pathname === href || pathname.startsWith(`${href}/`)
11
21
 
12
22
  return (
13
23
  <SidebarMenuButton asChild>
@@ -0,0 +1,75 @@
1
+ 'use client'
2
+
3
+ import {
4
+ Sidebar,
5
+ SidebarContent,
6
+ SidebarGroup,
7
+ SidebarGroupContent,
8
+ SidebarGroupLabel,
9
+ SidebarMenu,
10
+ SidebarMenuItem
11
+ } from '@admin/components/ui/sidebar'
12
+ import { SidebarNavLink } from './admin-sidebar-nav-link'
13
+
14
+ const settingsNav = [
15
+ {
16
+ label: 'Global',
17
+ items: [
18
+ { label: 'General', href: '/admin/settings', exact: true },
19
+ { label: 'Audit logs', href: '/admin/settings/audit-log', exact: true }
20
+ ]
21
+ },
22
+ {
23
+ label: 'Forms',
24
+ items: [
25
+ {
26
+ label: 'Forms',
27
+ href: '/admin/settings/forms',
28
+ exact: true
29
+ }
30
+ ]
31
+ },
32
+ {
33
+ label: 'Webhooks',
34
+ items: [
35
+ {
36
+ label: 'Webhook endpoints',
37
+ href: '/admin/settings/webhooks',
38
+ exact: true
39
+ },
40
+ {
41
+ label: 'Webhook logs',
42
+ href: '/admin/settings/webhooks/logs',
43
+ exact: true
44
+ }
45
+ ]
46
+ }
47
+ ]
48
+
49
+ export function AdminSettingsSidebar() {
50
+ return (
51
+ <Sidebar
52
+ collapsible="none"
53
+ className="h-full w-(--sidebar-width) shrink-0 border-r border-border/80 pt-14"
54
+ >
55
+ <SidebarContent className="gap-0 py-2">
56
+ {settingsNav.map((item) => (
57
+ <SidebarGroup key={item.label} className="gap-1.5">
58
+ <SidebarGroupLabel>{item.label}</SidebarGroupLabel>
59
+ <SidebarGroupContent>
60
+ <SidebarMenu className="gap-px">
61
+ {item.items.map((item) => (
62
+ <SidebarMenuItem key={item.href}>
63
+ <SidebarNavLink href={item.href} exact={item.exact}>
64
+ <span>{item.label}</span>
65
+ </SidebarNavLink>
66
+ </SidebarMenuItem>
67
+ ))}
68
+ </SidebarMenu>
69
+ </SidebarGroupContent>
70
+ </SidebarGroup>
71
+ ))}
72
+ </SidebarContent>
73
+ </Sidebar>
74
+ )
75
+ }
@@ -3,10 +3,20 @@ import { Suspense } from 'react'
3
3
  import { AdminNavLink } from './admin-nav-link'
4
4
  import { SidebarNavLinkSkeleton } from './admin-sidebar-nav-link-skeleton'
5
5
 
6
- export function SidebarNavLink({ href, children }: { href: string; children: ReactNode }) {
6
+ export function SidebarNavLink({
7
+ href,
8
+ exact,
9
+ children
10
+ }: {
11
+ href: string
12
+ exact?: boolean
13
+ children: ReactNode
14
+ }) {
7
15
  return (
8
16
  <Suspense fallback={<SidebarNavLinkSkeleton href={href}>{children}</SidebarNavLinkSkeleton>}>
9
- <AdminNavLink href={href}>{children}</AdminNavLink>
17
+ <AdminNavLink href={href} exact={exact}>
18
+ {children}
19
+ </AdminNavLink>
10
20
  </Suspense>
11
21
  )
12
22
  }
@@ -11,7 +11,7 @@ import {
11
11
  } from '@admin/components/ui/sidebar'
12
12
  import { adminNavigation } from '@admin/data/navigation'
13
13
  import { groupAdminNavigationItems } from '@admin/utils/navigation/sidebar'
14
- import { History, Settings, Users } from 'lucide-react'
14
+ import { Settings, Users } from 'lucide-react'
15
15
  import type { ComponentProps } from 'react'
16
16
  import { Fragment, Suspense } from 'react'
17
17
  import { SidebarBrandingRsc } from './admin-sidebar-branding-rsc'
@@ -35,7 +35,7 @@ export function AdminSidebar(props: ComponentProps<typeof Sidebar>) {
35
35
  <Fragment key={group.label ?? '_ungrouped'}>
36
36
  <SidebarGroup className="py-0">
37
37
  {group.label && <SidebarGroupLabel>{group.label}</SidebarGroupLabel>}
38
- <SidebarMenu className="gap-0.5">
38
+ <SidebarMenu className="gap-px">
39
39
  {group.items.map((item) => (
40
40
  <SidebarMenuItem key={item.href}>
41
41
  <SidebarNavLink href={item.href}>
@@ -64,12 +64,6 @@ export function AdminSidebar(props: ComponentProps<typeof Sidebar>) {
64
64
  <span>Settings</span>
65
65
  </SidebarNavLink>
66
66
  </SidebarMenuItem>
67
- <SidebarMenuItem>
68
- <SidebarNavLink href="/admin/system/audit-log">
69
- <History strokeWidth={2} />
70
- <span>Audit Log</span>
71
- </SidebarNavLink>
72
- </SidebarMenuItem>
73
67
  </SidebarMenu>
74
68
  </SidebarGroup>
75
69
  </SidebarContent>
@@ -5,6 +5,7 @@ import type * as React from 'react'
5
5
 
6
6
  type DataGridProps = React.ComponentProps<'div'> & {
7
7
  containerClassName?: string
8
+ containerRef?: React.Ref<HTMLDivElement>
8
9
  gridTemplateColumns: string
9
10
  }
10
11
 
@@ -17,12 +18,14 @@ const dataGridCellRole = { role: 'cell' } as const
17
18
  function DataGrid({
18
19
  className,
19
20
  containerClassName,
21
+ containerRef,
20
22
  gridTemplateColumns,
21
23
  style,
22
24
  ...props
23
25
  }: DataGridProps) {
24
26
  return (
25
27
  <div
28
+ ref={containerRef}
26
29
  data-slot="data-grid-container"
27
30
  className={cn('relative min-w-0 overflow-auto', containerClassName)}
28
31
  >
@@ -42,7 +45,10 @@ function DataGridHeader({ className, ...props }: React.ComponentProps<'div'>) {
42
45
  <div
43
46
  {...dataGridRowGroupRole}
44
47
  data-slot="data-grid-header"
45
- className={cn('contents', className)}
48
+ className={cn(
49
+ "relative col-span-full -mb-1 grid grid-cols-subgrid rounded-t-sm bg-muted pb-1 after:pointer-events-none after:absolute after:inset-0 after:rounded-t-sm after:border-x after:border-t after:border-border after:content-['']",
50
+ className
51
+ )}
46
52
  {...props}
47
53
  />
48
54
  )
@@ -53,7 +59,10 @@ function DataGridBody({ className, ...props }: React.ComponentProps<'div'>) {
53
59
  <div
54
60
  {...dataGridRowGroupRole}
55
61
  data-slot="data-grid-body"
56
- className={cn('contents', className)}
62
+ className={cn(
63
+ "relative col-span-full grid grid-cols-subgrid overflow-hidden rounded-sm bg-card shadow-input after:pointer-events-none after:absolute after:inset-0 after:rounded-sm after:border after:border-border after:content-['']",
64
+ className
65
+ )}
57
66
  {...props}
58
67
  />
59
68
  )
@@ -65,7 +74,7 @@ function DataGridRow({ className, style, ...props }: React.ComponentProps<'div'>
65
74
  {...dataGridRowRole}
66
75
  data-slot="data-grid-row"
67
76
  className={cn(
68
- 'grid border-b transition-colors has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
77
+ 'grid border-b transition-colors last:border-b-0 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
69
78
  className
70
79
  )}
71
80
  style={{ ...style, gridColumn: '1 / -1', gridTemplateColumns: 'subgrid' }}
@@ -81,7 +90,7 @@ function DataGridHead({ className, ...props }: React.ComponentProps<'div'>) {
81
90
  {...dataGridColumnHeaderRole}
82
91
  data-slot="data-grid-head"
83
92
  className={cn(
84
- 'grid h-10 items-center px-3 text-left font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
93
+ 'grid h-9.5 items-center px-3 text-left font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0 *:h-full',
85
94
  className
86
95
  )}
87
96
  tabIndex={-1}
@@ -96,7 +105,7 @@ function DataGridCell({ className, ...props }: React.ComponentProps<'div'>) {
96
105
  {...dataGridCellRole}
97
106
  data-slot="data-grid-cell"
98
107
  className={cn(
99
- 'grid min-h-10 items-center px-3 py-1.5 whitespace-nowrap [&:has([role=checkbox])]:pr-0',
108
+ 'grid min-h-12 items-center px-3 py-1.5 whitespace-nowrap [&:has([role=checkbox])]:pr-0',
100
109
  className
101
110
  )}
102
111
  {...props}
@@ -46,7 +46,7 @@ export function DataTable<TData extends RowData, TValue>({
46
46
  isPending = false,
47
47
  error = null,
48
48
  emptyMessage = 'No results found.',
49
- loadingMessage = 'Loading...',
49
+ loadingMessage = 'Loading',
50
50
  selectedIds,
51
51
  onSelectedIdsChange,
52
52
  meta,
@@ -115,126 +115,125 @@ export function DataTable<TData extends RowData, TValue>({
115
115
 
116
116
  return (
117
117
  <div className="space-y-6 pb-24">
118
- <div ref={tableContainerRef} className="min-w-0 overflow-hidden material-sm border-border">
119
- <DataGrid gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
120
- <DataGridHeader>
121
- {table.getHeaderGroups().map((headerGroup) => (
122
- <DataGridRow key={headerGroup.id}>
123
- {headerGroup.headers.map((header, headerIndex) => {
124
- const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
125
- const resizeLabel =
126
- typeof header.column.columnDef.header === 'string'
127
- ? header.column.columnDef.header
128
- : header.column.id
129
- const nextHeader = headerGroup.headers[headerIndex + 1]
130
- const canResizeColumn =
131
- header.column.getCanResize() && nextHeader?.column.getCanResize() === true
132
- return (
133
- <DataGridHead
134
- key={header.id}
135
- className={cn(
136
- 'group/resize-header relative overflow-hidden text-foreground',
137
- {
138
- 'text-center': meta?.align === 'center',
139
- 'text-right': meta?.align === 'right'
118
+ <DataGrid
119
+ containerRef={tableContainerRef}
120
+ gridTemplateColumns={gridTemplateColumns}
121
+ style={{ width: table.getTotalSize() }}
122
+ >
123
+ <DataGridHeader>
124
+ {table.getHeaderGroups().map((headerGroup) => (
125
+ <DataGridRow key={headerGroup.id}>
126
+ {headerGroup.headers.map((header, headerIndex) => {
127
+ const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
128
+ const resizeLabel =
129
+ typeof header.column.columnDef.header === 'string'
130
+ ? header.column.columnDef.header
131
+ : header.column.id
132
+ const nextHeader = headerGroup.headers[headerIndex + 1]
133
+ const canResizeColumn =
134
+ header.column.getCanResize() && nextHeader?.column.getCanResize() === true
135
+ return (
136
+ <DataGridHead
137
+ key={header.id}
138
+ className={cn('group/resize-header relative overflow-hidden', {
139
+ 'text-center': meta?.align === 'center',
140
+ 'text-right': meta?.align === 'right'
141
+ })}
142
+ data-column-id={header.column.id}
143
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
144
+ onMouseLeave={() =>
145
+ setHoveredColumnId((current) =>
146
+ current === header.column.id ? null : current
147
+ )
148
+ }
149
+ >
150
+ {header.isPlaceholder
151
+ ? null
152
+ : flexRender(header.column.columnDef.header, header.getContext())}
153
+ {canResizeColumn ? (
154
+ <button
155
+ type="button"
156
+ aria-label={`Resize ${resizeLabel} column`}
157
+ 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-foreground opacity-0 transition-opacity 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"
158
+ data-resize-handle
159
+ data-column-hovered={
160
+ hoveredColumnId === header.column.id ? 'true' : 'false'
140
161
  }
141
- )}
142
- data-column-id={header.column.id}
143
- onMouseEnter={() => setHoveredColumnId(header.column.id)}
162
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
163
+ onDoubleClick={resetColumnSizingToContainer}
164
+ onMouseDown={header.getResizeHandler()}
165
+ onTouchStart={header.getResizeHandler()}
166
+ >
167
+ <GripVertical aria-hidden="true" className="size-3.5 text-foreground" />
168
+ </button>
169
+ ) : null}
170
+ </DataGridHead>
171
+ )
172
+ })}
173
+ </DataGridRow>
174
+ ))}
175
+ </DataGridHeader>
176
+ <DataGridBody>
177
+ {isPending ? (
178
+ <DataGridRow>
179
+ <DataGridCell
180
+ className="flex h-24 items-center justify-center text-center"
181
+ style={{ gridColumn: '1 / -1' }}
182
+ >
183
+ <div className="text-muted-foreground">{loadingMessage}</div>
184
+ </DataGridCell>
185
+ </DataGridRow>
186
+ ) : error ? (
187
+ <DataGridRow>
188
+ <DataGridCell
189
+ className="flex h-24 items-center justify-center text-center"
190
+ style={{ gridColumn: '1 / -1' }}
191
+ >
192
+ <div className="text-destructive">Error: {error.message}</div>
193
+ </DataGridCell>
194
+ </DataGridRow>
195
+ ) : table.getRowModel().rows?.length ? (
196
+ table.getRowModel().rows.map((row) => (
197
+ <DataGridRow
198
+ key={row.id}
199
+ className="hover:bg-muted/50"
200
+ data-state={row.getIsSelected() ? 'selected' : undefined}
201
+ >
202
+ {row.getVisibleCells().map((cell) => {
203
+ const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
204
+ return (
205
+ <DataGridCell
206
+ key={cell.id}
207
+ className={cn({
208
+ 'text-center': meta?.align === 'center',
209
+ 'text-right': meta?.align === 'right'
210
+ })}
211
+ data-column-id={cell.column.id}
212
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
144
213
  onMouseLeave={() =>
145
214
  setHoveredColumnId((current) =>
146
- current === header.column.id ? null : current
215
+ current === cell.column.id ? null : current
147
216
  )
148
217
  }
149
218
  >
150
- {header.isPlaceholder
151
- ? null
152
- : flexRender(header.column.columnDef.header, header.getContext())}
153
- {canResizeColumn ? (
154
- <button
155
- type="button"
156
- aria-label={`Resize ${resizeLabel} column`}
157
- 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-foreground opacity-0 transition-opacity 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"
158
- data-resize-handle
159
- data-column-hovered={
160
- hoveredColumnId === header.column.id ? 'true' : 'false'
161
- }
162
- data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
163
- onDoubleClick={resetColumnSizingToContainer}
164
- onMouseDown={header.getResizeHandler()}
165
- onTouchStart={header.getResizeHandler()}
166
- >
167
- <GripVertical aria-hidden="true" className="size-3.5 text-foreground" />
168
- </button>
169
- ) : null}
170
- </DataGridHead>
219
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
220
+ </DataGridCell>
171
221
  )
172
222
  })}
173
223
  </DataGridRow>
174
- ))}
175
- </DataGridHeader>
176
- <DataGridBody>
177
- {isPending ? (
178
- <DataGridRow>
179
- <DataGridCell
180
- className="flex h-24 items-center justify-center text-center"
181
- style={{ gridColumn: '1 / -1' }}
182
- >
183
- <div className="text-muted-foreground">{loadingMessage}</div>
184
- </DataGridCell>
185
- </DataGridRow>
186
- ) : error ? (
187
- <DataGridRow>
188
- <DataGridCell
189
- className="flex h-24 items-center justify-center text-center"
190
- style={{ gridColumn: '1 / -1' }}
191
- >
192
- <div className="text-destructive">Error: {error.message}</div>
193
- </DataGridCell>
194
- </DataGridRow>
195
- ) : table.getRowModel().rows?.length ? (
196
- table.getRowModel().rows.map((row) => (
197
- <DataGridRow
198
- key={row.id}
199
- className="hover:bg-muted/50"
200
- data-state={row.getIsSelected() ? 'selected' : undefined}
201
- >
202
- {row.getVisibleCells().map((cell) => {
203
- const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
204
- return (
205
- <DataGridCell
206
- key={cell.id}
207
- className={cn({
208
- 'text-center': meta?.align === 'center',
209
- 'text-right': meta?.align === 'right'
210
- })}
211
- data-column-id={cell.column.id}
212
- onMouseEnter={() => setHoveredColumnId(cell.column.id)}
213
- onMouseLeave={() =>
214
- setHoveredColumnId((current) =>
215
- current === cell.column.id ? null : current
216
- )
217
- }
218
- >
219
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
220
- </DataGridCell>
221
- )
222
- })}
223
- </DataGridRow>
224
- ))
225
- ) : (
226
- <DataGridRow>
227
- <DataGridCell
228
- className="flex h-24 items-center justify-center text-center"
229
- style={{ gridColumn: '1 / -1' }}
230
- >
231
- {emptyMessage}
232
- </DataGridCell>
233
- </DataGridRow>
234
- )}
235
- </DataGridBody>
236
- </DataGrid>
237
- </div>
224
+ ))
225
+ ) : (
226
+ <DataGridRow>
227
+ <DataGridCell
228
+ className="flex h-24 items-center justify-center text-center"
229
+ style={{ gridColumn: '1 / -1' }}
230
+ >
231
+ {emptyMessage}
232
+ </DataGridCell>
233
+ </DataGridRow>
234
+ )}
235
+ </DataGridBody>
236
+ </DataGrid>
238
237
 
239
238
  <DataTablePagination
240
239
  table={table}
@@ -23,6 +23,10 @@ export type DevModeLifecycleHookEvent =
23
23
  | 'afterPublish'
24
24
  | 'beforeUnpublish'
25
25
  | 'afterUnpublish'
26
+ | 'beforeRestore'
27
+ | 'afterRestore'
28
+ | 'beforeReorder'
29
+ | 'afterReorder'
26
30
 
27
31
  export interface DevModeSnippet {
28
32
  id: string
@@ -84,7 +84,7 @@ export function DevModeIntegrateButton({
84
84
  </Tabs>
85
85
  ) : isPending ? (
86
86
  <div className="flex min-h-80 items-center justify-center px-6 text-sm text-muted-foreground">
87
- Loading integration details...
87
+ Loading integration details
88
88
  </div>
89
89
  ) : loadError ? (
90
90
  <div
@@ -55,7 +55,7 @@ export function EntityVersionsDrawer({
55
55
  <EntityVersionsCurrentRow updatedAt={currentUpdatedAt} author={currentAuthor} />
56
56
  {isPending ? (
57
57
  <p className="rounded-sm border border-dashed border-border px-3 py-6 text-center text-sm text-muted-foreground">
58
- Loading versions...
58
+ Loading versions
59
59
  </p>
60
60
  ) : null}
61
61
  {!isPending && versions.length === 0 ? (