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
@@ -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 (
@@ -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 ? (
@@ -1,5 +1,6 @@
1
1
  'use client'
2
2
 
3
+ import { Card, CardContent } from '@admin/components/ui/card'
3
4
  import {
4
5
  InputGroup,
5
6
  InputGroupAddon,
@@ -88,52 +89,49 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
88
89
  }, [isImporting, onImportFromUrl, urlValue])
89
90
 
90
91
  return (
91
- <div
92
- className={cn(
93
- 'flex items-center justify-center transition-colors rounded-sm border bg-input border-border overflow-hidden h-full flex-col p-5 gap-5',
94
- className
95
- )}
96
- >
97
- <div className="flex w-full flex-col items-center gap-0.5 text-center">
98
- <p className="text-sm font-medium">Import from URL</p>
99
- <p className="text-sm text-muted-foreground">{urlHelpText}</p>
100
- </div>
101
-
102
- <InputGroup className="bg-background mx-auto max-w-2xl">
103
- <InputGroupInput
104
- type="url"
105
- name="url"
106
- aria-label="Media URL"
107
- value={urlValue}
108
- placeholder={urlPlaceholder}
109
- autoComplete="off"
110
- spellCheck={false}
111
- onChange={(event) => {
112
- setUrlValue(event.target.value)
113
- if (urlError) setUrlError(null)
114
- }}
115
- onKeyDown={(event) => {
116
- if (event.key === 'Enter') {
117
- event.preventDefault()
118
- void handleImport()
119
- }
120
- }}
121
- disabled={isImporting}
122
- />
123
-
124
- <InputGroupAddon align="inline-end">
125
- <InputGroupButton
126
- type="button"
127
- variant="outline"
128
- size="sm"
129
- onClick={() => void handleImport()}
130
- disabled={isImporting}
131
- >
132
- {isImporting ? <LoaderCircle className="animate-spin" /> : <Upload />}
133
- Import
134
- </InputGroupButton>
135
- </InputGroupAddon>
136
- </InputGroup>
137
- </div>
92
+ <Card className={cn('flex items-center justify-center', className)}>
93
+ <CardContent className="w-full h-full">
94
+ <div className="flex w-full flex-col h-full justify-center items-center gap-3 text-center">
95
+ <InputGroup className="bg-background mx-auto max-w-2xl">
96
+ <InputGroupInput
97
+ type="url"
98
+ name="url"
99
+ aria-label="Media URL"
100
+ value={urlValue}
101
+ placeholder={urlPlaceholder}
102
+ autoComplete="off"
103
+ spellCheck={false}
104
+ onChange={(event) => {
105
+ setUrlValue(event.target.value)
106
+ if (urlError) setUrlError(null)
107
+ }}
108
+ onKeyDown={(event) => {
109
+ if (event.key === 'Enter') {
110
+ event.preventDefault()
111
+ void handleImport()
112
+ }
113
+ }}
114
+ disabled={isImporting}
115
+ />
116
+ <InputGroupAddon align="inline-end">
117
+ <InputGroupButton
118
+ type="button"
119
+ variant="outline"
120
+ onClick={() => void handleImport()}
121
+ disabled={isImporting}
122
+ className="h-full px-5"
123
+ >
124
+ {isImporting ? <LoaderCircle className="animate-spin" /> : <Upload />}
125
+ Import
126
+ </InputGroupButton>
127
+ </InputGroupAddon>
128
+ </InputGroup>
129
+
130
+ <div className="flex w-full flex-col items-center text-center">
131
+ <p className="text-sm text-muted-foreground">{urlHelpText}</p>
132
+ </div>
133
+ </div>
134
+ </CardContent>
135
+ </Card>
138
136
  )
139
137
  }