betterstart-cli 0.0.28 → 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 (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 +806 -554
  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
package/dist/cli.js CHANGED
@@ -1410,16 +1410,29 @@ import path9 from "path";
1410
1410
  var PG_SSL_WARNING_START = "Warning: SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.";
1411
1411
  var PG_SSL_WARNING_END = "See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode definitions.";
1412
1412
  var NODE_TRACE_WARNING_HINT = "(Use `node --trace-warnings";
1413
- var DRIZZLE_CONFIG_BANNER_PATTERNS = [
1413
+ var DRIZZLE_NOISE_PATTERNS = [
1414
1414
  /^No config path provided, using default 'drizzle\.config\.ts'$/,
1415
1415
  /^Reading config file '.+drizzle\.config\.ts'$/,
1416
- /^Using 'pg' driver for database querying$/
1416
+ /^Using 'pg' driver for database querying$/,
1417
+ /^\[.\] Pulling schema from database\.\.\.$/u,
1418
+ /^\[.\] Changes applied$/u
1417
1419
  ];
1418
- function resolveDrizzlePushStdio(inheritStdio) {
1419
- return inheritStdio ? "inherit" : "pipe";
1420
+ var SUPPRESSIBLE_LINE_STARTS = [
1421
+ "No config path provided",
1422
+ "Reading config file '",
1423
+ "Using 'pg' driver",
1424
+ "(node:",
1425
+ "["
1426
+ ];
1427
+ var ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*[A-Za-z]/g;
1428
+ function resolveDrizzlePushStdio(interactive) {
1429
+ return interactive ? ["inherit", "pipe", "pipe"] : "pipe";
1430
+ }
1431
+ function stripAnsi(text7) {
1432
+ return text7.replace(ANSI_ESCAPE_PATTERN, "").replaceAll("\r", "");
1420
1433
  }
1421
- function shouldSuppressWarningLine(line, state) {
1422
- const text7 = line.trimEnd();
1434
+ function shouldSuppressOutputLine(line, state) {
1435
+ const text7 = stripAnsi(line).trim();
1423
1436
  if (state.suppressNextTraceHint) {
1424
1437
  state.suppressNextTraceHint = false;
1425
1438
  if (text7.startsWith(NODE_TRACE_WARNING_HINT)) {
@@ -1437,20 +1450,20 @@ function shouldSuppressWarningLine(line, state) {
1437
1450
  }
1438
1451
  return true;
1439
1452
  }
1440
- return false;
1441
- }
1442
- function shouldSuppressDrizzleConfigBannerLine(line) {
1443
- const text7 = line.trim();
1444
- return DRIZZLE_CONFIG_BANNER_PATTERNS.some((pattern) => pattern.test(text7));
1453
+ return DRIZZLE_NOISE_PATTERNS.some((pattern) => pattern.test(text7));
1445
1454
  }
1446
- function shouldSuppressOutputLine(line, state) {
1447
- return shouldSuppressWarningLine(line, state) || shouldSuppressDrizzleConfigBannerLine(line);
1448
- }
1449
- function filterWarningLines(output, state) {
1455
+ function filterNoiseLines(output, state) {
1450
1456
  const lines = output.match(/[^\n]*\n|[^\n]+/g) ?? [];
1451
1457
  return lines.filter((line) => !shouldSuppressOutputLine(line, state)).join("");
1452
1458
  }
1453
- function createPgConnectionStringSslWarningFilter(write) {
1459
+ function couldBecomeSuppressible(partial, state) {
1460
+ if (state.suppressingPgSslWarning || state.suppressNextTraceHint) {
1461
+ return true;
1462
+ }
1463
+ const text7 = stripAnsi(partial);
1464
+ return SUPPRESSIBLE_LINE_STARTS.some((start) => text7.startsWith(start) || start.startsWith(text7));
1465
+ }
1466
+ function createDrizzleNoiseFilter(forward) {
1454
1467
  const state = {
1455
1468
  suppressingPgSslWarning: false,
1456
1469
  suppressNextTraceHint: false
@@ -1460,24 +1473,27 @@ function createPgConnectionStringSslWarningFilter(write) {
1460
1473
  write(chunk) {
1461
1474
  buffer += chunk;
1462
1475
  const lastNewlineIndex = buffer.lastIndexOf("\n");
1463
- if (lastNewlineIndex === -1) {
1464
- return;
1476
+ if (lastNewlineIndex !== -1) {
1477
+ const complete = buffer.slice(0, lastNewlineIndex + 1);
1478
+ buffer = buffer.slice(lastNewlineIndex + 1);
1479
+ const filtered = filterNoiseLines(complete, state);
1480
+ if (filtered) {
1481
+ forward(filtered);
1482
+ }
1465
1483
  }
1466
- const complete = buffer.slice(0, lastNewlineIndex + 1);
1467
- buffer = buffer.slice(lastNewlineIndex + 1);
1468
- const filtered = filterWarningLines(complete, state);
1469
- if (filtered) {
1470
- write(filtered);
1484
+ if (buffer && !couldBecomeSuppressible(buffer, state)) {
1485
+ forward(buffer);
1486
+ buffer = "";
1471
1487
  }
1472
1488
  },
1473
1489
  end() {
1474
1490
  if (!buffer) {
1475
1491
  return;
1476
1492
  }
1477
- const filtered = filterWarningLines(buffer, state);
1493
+ const filtered = filterNoiseLines(buffer, state);
1478
1494
  buffer = "";
1479
1495
  if (filtered) {
1480
- write(filtered);
1496
+ forward(filtered);
1481
1497
  }
1482
1498
  }
1483
1499
  };
@@ -1487,20 +1503,20 @@ function runDrizzlePush(cwd, options = {}) {
1487
1503
  const drizzleBin = path9.join(cwd, "node_modules", ".bin", "drizzle-kit");
1488
1504
  const child = spawn2(drizzleBin, ["push", "--force"], {
1489
1505
  cwd,
1490
- stdio: resolveDrizzlePushStdio(options.inheritStdio),
1506
+ stdio: resolveDrizzlePushStdio(options.interactive),
1491
1507
  env: { ...process.env }
1492
1508
  });
1493
1509
  let stdout = "";
1494
1510
  let stderr = "";
1495
- const stderrFilter = createPgConnectionStringSslWarningFilter((chunk) => {
1496
- if (options.inheritStdio) {
1511
+ const stderrFilter = createDrizzleNoiseFilter((chunk) => {
1512
+ if (options.interactive) {
1497
1513
  process.stderr.write(chunk);
1498
1514
  return;
1499
1515
  }
1500
1516
  stderr += chunk;
1501
1517
  });
1502
- const stdoutFilter = createPgConnectionStringSslWarningFilter((chunk) => {
1503
- if (options.inheritStdio) {
1518
+ const stdoutFilter = createDrizzleNoiseFilter((chunk) => {
1519
+ if (options.interactive) {
1504
1520
  process.stdout.write(chunk);
1505
1521
  return;
1506
1522
  }
@@ -1515,7 +1531,7 @@ function runDrizzlePush(cwd, options = {}) {
1515
1531
  child.on("close", (code, signal) => {
1516
1532
  stdoutFilter.end();
1517
1533
  stderrFilter.end();
1518
- if (options.inheritStdio) {
1534
+ if (options.interactive) {
1519
1535
  if (code === 0) {
1520
1536
  resolve({ success: true, error: null });
1521
1537
  return;
@@ -1733,7 +1749,7 @@ async function runPostGenerate(cwd, schemaName, options = {}) {
1733
1749
  } else {
1734
1750
  console.log("\n Running drizzle-kit push...");
1735
1751
  try {
1736
- const pushResult = await runDrizzlePush(cwd, { inheritStdio: true });
1752
+ const pushResult = await runDrizzlePush(cwd, { interactive: true });
1737
1753
  if (!pushResult.success) {
1738
1754
  throw new Error(pushResult.error ?? "drizzle-kit push failed");
1739
1755
  }
@@ -2856,6 +2872,8 @@ function resolveProjectPaths(config) {
2856
2872
  const adminTypesDir = path14.posix.join(adminDir, "types");
2857
2873
  const adminNavigationFile = path14.posix.join(adminDir, "data", "navigation.ts");
2858
2874
  const adminNavigationDir = path14.posix.join(adminDir, "data", "navigation");
2875
+ const adminWebhookEventsFile = path14.posix.join(adminDir, "data", "webhook-events.ts");
2876
+ const adminWebhookEventsDir = path14.posix.join(adminDir, "data", "webhook-events");
2859
2877
  const adminActionsDir = path14.posix.join(adminDir, "lib", "actions");
2860
2878
  return {
2861
2879
  adminDir,
@@ -2872,6 +2890,8 @@ function resolveProjectPaths(config) {
2872
2890
  adminTypesNavigationFile: path14.posix.join(adminTypesDir, "navigation.ts"),
2873
2891
  adminNavigationFile,
2874
2892
  adminNavigationDir,
2893
+ adminWebhookEventsFile,
2894
+ adminWebhookEventsDir,
2875
2895
  adminActionsDir,
2876
2896
  adminEmailTemplatesDir: path14.posix.join(adminDir, "components", "email-templates"),
2877
2897
  adminHooksDir: path14.posix.join(adminDir, "hooks")
@@ -6322,6 +6342,22 @@ function lifecycleHookContextExample(event, schemaName, input, data, schemaType
6322
6342
  if (schemaType === "form") {
6323
6343
  context.schemaType = "form";
6324
6344
  }
6345
+ if (event === "beforeRestore" || event === "afterRestore") {
6346
+ context.entityId = SAMPLE_UUID;
6347
+ context.changedFields = [];
6348
+ context.metadata = { restoredFromVersion: 2 };
6349
+ if (phase === "after") {
6350
+ context.data = data;
6351
+ }
6352
+ return createExample("Context", context);
6353
+ }
6354
+ if (event === "beforeReorder" || event === "afterReorder") {
6355
+ context.changedFields = ["sortOrder"];
6356
+ if (phase === "after") {
6357
+ context.data = { order: [{ id: SAMPLE_UUID, sortOrder: 0 }] };
6358
+ }
6359
+ return createExample("Context", context);
6360
+ }
6325
6361
  if (event !== "beforeCreate" && event !== "afterCreate") {
6326
6362
  context.entityId = SAMPLE_UUID;
6327
6363
  }
@@ -6348,7 +6384,11 @@ function buildEntityDevModeLifecycleHooks(schema, adminImportAlias) {
6348
6384
  "beforeUpdate",
6349
6385
  "afterUpdate",
6350
6386
  "beforeDelete",
6351
- "afterDelete"
6387
+ "afterDelete",
6388
+ "beforeRestore",
6389
+ "afterRestore",
6390
+ "beforeReorder",
6391
+ "afterReorder"
6352
6392
  ];
6353
6393
  if (schema.actions?.draft === true) {
6354
6394
  events.push("beforePublish", "afterPublish", "beforeUnpublish", "afterUnpublish");
@@ -6896,7 +6936,7 @@ function generatePageSkeleton(pascal, label) {
6896
6936
  return `export function ${pascal}SubmissionsPageSkeleton() {
6897
6937
  return (
6898
6938
  <div className="flex items-center justify-center h-48">
6899
- <div className="text-muted-foreground">Loading ${label}...</div>
6939
+ <div className="text-muted-foreground">Loading ${label}\u2026</div>
6900
6940
  </div>
6901
6941
  )
6902
6942
  }
@@ -7059,7 +7099,7 @@ ${deleteTransition}${bulkDeleteHandler}
7059
7099
  <div className="flex items-center gap-1">
7060
7100
  <${devModeIntegrateButtonExportName} initialSettings={initialSettings} />${exportButton}
7061
7101
  <Button variant="outline" asChild>
7062
- <Link href="${adminRoutePath}/forms/${kebab}/settings">
7102
+ <Link href="${adminRoutePath}/settings/forms">
7063
7103
  <Settings className="size-3.5" />
7064
7104
  Settings
7065
7105
  </Link>
@@ -7105,199 +7145,6 @@ ${bulkDeleteActions}
7105
7145
  `;
7106
7146
  }
7107
7147
 
7108
- // adapters/next/generators/form-admin/admin-settings-page.ts
7109
- function generateSettingsPage(pascal, kebab) {
7110
- return `import { getFormSettings } from '@admin/actions/forms'
7111
- import { ${pascal}SettingsPageContent } from './settings-page-content'
7112
-
7113
- export default async function ${pascal}SettingsPage() {
7114
- const settings = await getFormSettings('${kebab}')
7115
-
7116
- return <${pascal}SettingsPageContent initialSettings={settings} />
7117
- }
7118
- `;
7119
- }
7120
- function generateSettingsPageContent(pascal, kebab, label) {
7121
- return `'use client'
7122
-
7123
- import {
7124
- testFormWebhook,
7125
- type FormSettingsData,
7126
- upsertFormSettings,
7127
- } from '@admin/actions/forms'
7128
- import { PageHeader } from '@admin/components/shared/page-header'
7129
- import { Button } from '@admin/components/ui/button'
7130
- import {
7131
- Form,
7132
- FormControl,
7133
- FormDescription,
7134
- FormField,
7135
- FormItem,
7136
- FormLabel,
7137
- } from '@admin/components/ui/form'
7138
- import { Input } from '@admin/components/ui/input'
7139
- import { Switch } from '@admin/components/ui/switch'
7140
- import { Textarea } from '@admin/components/ui/textarea'
7141
- import { LoaderCircle } from 'lucide-react'
7142
- import * as React from 'react'
7143
- import { useForm } from 'react-hook-form'
7144
- import { toast } from 'sonner'
7145
-
7146
- interface ${pascal}SettingsPageContentProps {
7147
- initialSettings: FormSettingsData | null
7148
- }
7149
-
7150
- interface ${pascal}SettingsFormValues {
7151
- notificationEmails: string
7152
- webhookUrl: string
7153
- webhookEnabled: boolean
7154
- }
7155
-
7156
- export function ${pascal}SettingsPageContent({
7157
- initialSettings,
7158
- }: ${pascal}SettingsPageContentProps) {
7159
- const form = useForm<${pascal}SettingsFormValues>({
7160
- defaultValues: {
7161
- notificationEmails: initialSettings?.notificationEmails ?? '',
7162
- webhookUrl: initialSettings?.webhookUrl ?? '',
7163
- webhookEnabled: initialSettings?.webhookEnabled ?? false,
7164
- },
7165
- })
7166
- const [isSaving, startSaveTransition] = React.useTransition()
7167
- const [isTesting, startTestTransition] = React.useTransition()
7168
- const webhookUrl = form.watch('webhookUrl')
7169
- const webhookEnabled = form.watch('webhookEnabled')
7170
-
7171
- const handleSave = (values: ${pascal}SettingsFormValues) => {
7172
- startSaveTransition(async () => {
7173
- const result = await upsertFormSettings('${kebab}', {
7174
- notificationEmails: values.notificationEmails || null,
7175
- webhookUrl: values.webhookUrl || null,
7176
- webhookEnabled: values.webhookEnabled,
7177
- })
7178
- if (result.success) {
7179
- toast.success('Settings saved successfully')
7180
- } else {
7181
- toast.error(result.error || 'Failed to save settings')
7182
- }
7183
- })
7184
- }
7185
-
7186
- const handleTestWebhook = () => {
7187
- startTestTransition(async () => {
7188
- const result = await testFormWebhook('${kebab}')
7189
- if (result.success) {
7190
- toast.success('Test webhook sent successfully')
7191
- } else {
7192
- toast.error(result.error || 'Failed to send test webhook')
7193
- }
7194
- })
7195
- }
7196
-
7197
- return (
7198
- <>
7199
- <PageHeader title="${label} Settings" />
7200
- <Form {...form}>
7201
- <form
7202
- onSubmit={form.handleSubmit(handleSave)}
7203
- className="space-y-6 rounded-sm border p-6 mx-6 mt-6"
7204
- >
7205
- <FormField
7206
- control={form.control}
7207
- name="notificationEmails"
7208
- render={({ field }) => (
7209
- <FormItem>
7210
- <FormLabel>Notification Emails</FormLabel>
7211
- <FormControl>
7212
- <Textarea
7213
- placeholder="email@example.com, another@example.com"
7214
- rows={3}
7215
- {...field}
7216
- />
7217
- </FormControl>
7218
- <FormDescription>
7219
- Comma-separated list of email addresses to notify on new submissions.
7220
- </FormDescription>
7221
- </FormItem>
7222
- )}
7223
- />
7224
-
7225
- <div className="space-y-4 border-t pt-6">
7226
- <FormField
7227
- control={form.control}
7228
- name="webhookUrl"
7229
- render={({ field }) => (
7230
- <FormItem>
7231
- <FormLabel>Webhook URL</FormLabel>
7232
- <FormControl>
7233
- <Input
7234
- type="url"
7235
- placeholder="https://hooks.example.com/endpoint"
7236
- {...field}
7237
- />
7238
- </FormControl>
7239
- <FormDescription>
7240
- Receive a POST request with form data on each submission.
7241
- </FormDescription>
7242
- </FormItem>
7243
- )}
7244
- />
7245
-
7246
- <FormField
7247
- control={form.control}
7248
- name="webhookEnabled"
7249
- render={({ field }) => (
7250
- <FormItem className="flex flex-row items-center space-x-2 space-y-0">
7251
- <FormControl>
7252
- <Switch
7253
- checked={field.value}
7254
- onCheckedChange={field.onChange}
7255
- />
7256
- </FormControl>
7257
- <FormLabel>Enable Webhook</FormLabel>
7258
- </FormItem>
7259
- )}
7260
- />
7261
-
7262
- {webhookUrl && webhookEnabled && (
7263
- <Button
7264
- type="button"
7265
- variant="outline"
7266
- onClick={handleTestWebhook}
7267
- disabled={isTesting}
7268
- >
7269
- {isTesting ? (
7270
- <>
7271
- <LoaderCircle className="animate-spin" />
7272
- Sending...
7273
- </>
7274
- ) : (
7275
- 'Test Webhook'
7276
- )}
7277
- </Button>
7278
- )}
7279
- </div>
7280
-
7281
- <div className="border-t pt-6">
7282
- <Button type="submit" disabled={isSaving}>
7283
- {isSaving ? (
7284
- <>
7285
- <LoaderCircle className="animate-spin" />
7286
- Saving...
7287
- </>
7288
- ) : (
7289
- 'Save Settings'
7290
- )}
7291
- </Button>
7292
- </div>
7293
- </form>
7294
- </Form>
7295
- </>
7296
- )
7297
- }
7298
- `;
7299
- }
7300
-
7301
7148
  // adapters/next/generators/form-admin/admin-submission-drawer.ts
7302
7149
  function generateSubmissionDrawer(pascal, kebab, fields, label, includeDynamic, _actionImportPath) {
7303
7150
  const fieldItems = fields.filter((f) => f.name).map((f) => {
@@ -7428,7 +7275,7 @@ export function ${pascal}SubmissionDrawer({
7428
7275
  </DrawerHeader>
7429
7276
  <ScrollArea className="min-h-0 flex-1">
7430
7277
  {isPending ? (
7431
- <div className="p-4 text-sm text-muted-foreground">Loading submission...</div>
7278
+ <div className="p-4 text-sm text-muted-foreground">Loading submission\u2026</div>
7432
7279
  ) : error ? (
7433
7280
  <div className="p-4 text-sm text-destructive">
7434
7281
  Error loading submission: {error.message}
@@ -7468,7 +7315,6 @@ import {
7468
7315
  DataGridRow
7469
7316
  } from '@admin/components/shared/data-table/data-grid'
7470
7317
  import { DataTablePagination } from '@admin/components/shared/data-table/data-table-pagination'
7471
- import { Card, CardContent } from '@admin/components/ui/card'
7472
7318
  import { use${pascal}Submissions } from '@admin/hooks/use-${kebab}-form'
7473
7319
  import { useTableUtils } from '@admin/hooks/use-table-utils'
7474
7320
  import {
@@ -7602,108 +7448,104 @@ export function ${pascal}SubmissionsTable<TValue>({
7602
7448
 
7603
7449
  return (
7604
7450
  <div className={cn("flex flex-col gap-6 pt-px pb-2 h-full", className)}>
7605
- <Card className="min-h-0 flex-1 p-0">
7606
- <CardContent ref={tableContainerRef} className="flex min-h-0 flex-1 flex-col p-0">
7607
- <DataGrid containerClassName="min-h-0 flex-1" gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
7608
- <DataGridHeader>
7609
- {table.getHeaderGroups().map((headerGroup) => (
7610
- <DataGridRow key={headerGroup.id}>
7611
- {headerGroup.headers.map((header, headerIndex) => {
7612
- const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
7613
- const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
7614
- const nextHeader = headerGroup.headers[headerIndex + 1]
7615
- const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
7616
- return (
7617
- <DataGridHead
7618
- key={header.id}
7619
- className={cn('group/resize-header relative overflow-hidden text-foreground', {
7620
- 'text-center': meta?.align === 'center',
7621
- 'text-right': meta?.align === 'right'
7622
- })}
7623
- data-column-id={header.column.id}
7624
- onMouseEnter={() => setHoveredColumnId(header.column.id)}
7625
- onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
7626
- >
7627
- {header.isPlaceholder
7628
- ? null
7629
- : flexRender(header.column.columnDef.header, header.getContext())}
7630
- {canResizeColumn ? (
7631
- <button
7632
- type="button"
7633
- aria-label={\`Resize \${resizeLabel} column\`}
7634
- 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"
7635
- data-resize-handle
7636
- data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
7637
- data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
7638
- onDoubleClick={resetColumnSizingToContainer}
7639
- onMouseDown={header.getResizeHandler()}
7640
- onTouchStart={header.getResizeHandler()}
7641
- >
7642
- <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
7643
- </button>
7644
- ) : null}
7645
- </DataGridHead>
7646
- )
7647
- })}
7648
- </DataGridRow>
7649
- ))}
7650
- </DataGridHeader>
7651
- <DataGridBody>
7652
- {isPending ? (
7653
- <DataGridRow>
7654
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7655
- <div className="text-muted-foreground">Loading ${label} submissions...</div>
7656
- </DataGridCell>
7657
- </DataGridRow>
7658
- ) : error ? (
7659
- <DataGridRow>
7660
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7661
- <div className="text-destructive">Error loading ${label} submissions: {error.message}</div>
7662
- </DataGridCell>
7663
- </DataGridRow>
7664
- ) : table.getRowModel().rows?.length ? (
7665
- table.getRowModel().rows.map((row) => (
7666
- <DataGridRow
7667
- key={row.id}
7668
- data-state={row.getIsSelected() ? 'selected' : undefined}
7669
- className="cursor-pointer hover:bg-muted/50"
7670
- onClick={(e) => {
7671
- const target = e.target as HTMLElement
7672
- if (!e.currentTarget.contains(target)) return
7673
- if (target.closest('button, a, input, [role="checkbox"]')) return
7674
- onViewSubmission(row.original.id)
7675
- }}
7676
- >
7677
- {row.getVisibleCells().map((cell) => {
7678
- const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
7679
- return (
7680
- <DataGridCell
7681
- key={cell.id}
7682
- className={cn({
7683
- 'text-center': meta?.align === 'center',
7684
- 'text-right': meta?.align === 'right'
7685
- })}
7686
- data-column-id={cell.column.id}
7687
- onMouseEnter={() => setHoveredColumnId(cell.column.id)}
7688
- onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
7689
- >
7690
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
7691
- </DataGridCell>
7692
- )
7451
+ <DataGrid containerClassName="min-h-0 flex-1" containerRef={tableContainerRef} gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
7452
+ <DataGridHeader>
7453
+ {table.getHeaderGroups().map((headerGroup) => (
7454
+ <DataGridRow key={headerGroup.id}>
7455
+ {headerGroup.headers.map((header, headerIndex) => {
7456
+ const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
7457
+ const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
7458
+ const nextHeader = headerGroup.headers[headerIndex + 1]
7459
+ const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
7460
+ return (
7461
+ <DataGridHead
7462
+ key={header.id}
7463
+ className={cn('group/resize-header relative overflow-hidden', {
7464
+ 'text-center': meta?.align === 'center',
7465
+ 'text-right': meta?.align === 'right'
7693
7466
  })}
7694
- </DataGridRow>
7695
- ))
7696
- ) : (
7697
- <DataGridRow>
7698
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7699
- No ${label} submissions found.
7700
- </DataGridCell>
7701
- </DataGridRow>
7702
- )}
7703
- </DataGridBody>
7704
- </DataGrid>
7705
- </CardContent>
7706
- </Card>
7467
+ data-column-id={header.column.id}
7468
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
7469
+ onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
7470
+ >
7471
+ {header.isPlaceholder
7472
+ ? null
7473
+ : flexRender(header.column.columnDef.header, header.getContext())}
7474
+ {canResizeColumn ? (
7475
+ <button
7476
+ type="button"
7477
+ aria-label={\`Resize \${resizeLabel} column\`}
7478
+ 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"
7479
+ data-resize-handle
7480
+ data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
7481
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
7482
+ onDoubleClick={resetColumnSizingToContainer}
7483
+ onMouseDown={header.getResizeHandler()}
7484
+ onTouchStart={header.getResizeHandler()}
7485
+ >
7486
+ <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
7487
+ </button>
7488
+ ) : null}
7489
+ </DataGridHead>
7490
+ )
7491
+ })}
7492
+ </DataGridRow>
7493
+ ))}
7494
+ </DataGridHeader>
7495
+ <DataGridBody>
7496
+ {isPending ? (
7497
+ <DataGridRow>
7498
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7499
+ <div className="text-muted-foreground">Loading ${label} submissions\u2026</div>
7500
+ </DataGridCell>
7501
+ </DataGridRow>
7502
+ ) : error ? (
7503
+ <DataGridRow>
7504
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7505
+ <div className="text-destructive">Error loading ${label} submissions: {error.message}</div>
7506
+ </DataGridCell>
7507
+ </DataGridRow>
7508
+ ) : table.getRowModel().rows?.length ? (
7509
+ table.getRowModel().rows.map((row) => (
7510
+ <DataGridRow
7511
+ key={row.id}
7512
+ data-state={row.getIsSelected() ? 'selected' : undefined}
7513
+ className="cursor-pointer hover:bg-muted/50"
7514
+ onClick={(e) => {
7515
+ const target = e.target as HTMLElement
7516
+ if (!e.currentTarget.contains(target)) return
7517
+ if (target.closest('button, a, input, [role="checkbox"]')) return
7518
+ onViewSubmission(row.original.id)
7519
+ }}
7520
+ >
7521
+ {row.getVisibleCells().map((cell) => {
7522
+ const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
7523
+ return (
7524
+ <DataGridCell
7525
+ key={cell.id}
7526
+ className={cn({
7527
+ 'text-center': meta?.align === 'center',
7528
+ 'text-right': meta?.align === 'right'
7529
+ })}
7530
+ data-column-id={cell.column.id}
7531
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
7532
+ onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
7533
+ >
7534
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
7535
+ </DataGridCell>
7536
+ )
7537
+ })}
7538
+ </DataGridRow>
7539
+ ))
7540
+ ) : (
7541
+ <DataGridRow>
7542
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7543
+ No ${label} submissions found.
7544
+ </DataGridCell>
7545
+ </DataGridRow>
7546
+ )}
7547
+ </DataGridBody>
7548
+ </DataGrid>
7707
7549
 
7708
7550
  {shouldShowPagination ? (
7709
7551
  <DataTablePagination
@@ -7812,14 +7654,6 @@ function generateFormAdminPages(schema, pagesDir, options) {
7812
7654
  hasDynamicFields(schema),
7813
7655
  actionImportPath
7814
7656
  )
7815
- ),
7816
- createGeneratedFile(
7817
- `${pagesDir}/forms/${kebab}/settings/page.tsx`,
7818
- generateSettingsPage(pascal, kebab)
7819
- ),
7820
- createGeneratedFile(
7821
- `${pagesDir}/forms/${kebab}/settings/settings-page-content.tsx`,
7822
- generateSettingsPageContent(pascal, kebab, schema.label)
7823
7657
  )
7824
7658
  ]
7825
7659
  };
@@ -8227,7 +8061,6 @@ import { ${tableName} } from '@admin/db/schema'
8227
8061
  import { getFormSettings } from '@admin/actions/forms'
8228
8062
  import { sendFormEmails } from '@admin/actions/email/form-delivery'
8229
8063
  import { getTouchedFieldNames } from '@admin/utils/audit/audit'
8230
- import { sendWebhook } from '@admin/utils/webhook/webhook'
8231
8064
  import { ${cacheInvalidationFn} } from 'next/cache'
8232
8065
  ${mailchimpImport}
8233
8066
  import { CACHE_TAG, CACHE_TAG_BY_ID } from './types'
@@ -8268,16 +8101,7 @@ export async function create${pascal}Submission(
8268
8101
  submitterEmail: ${submitterEmailField ? `data.${submitterEmailField}` : "null"},
8269
8102
  submissionData: { ...data },
8270
8103
  submittedAt: created${pascal}Submission.submittedAt,
8271
- })
8272
-
8273
- if (settings?.webhookEnabled && settings?.webhookUrl) {
8274
- sendWebhook(settings.webhookUrl, {
8275
- form_name: '${formName}',
8276
- submission_id: created${pascal}Submission.id,
8277
- ...data,
8278
- submitted_at: created${pascal}Submission.submittedAt,
8279
- })
8280
- }${mailchimpCall}
8104
+ })${mailchimpCall}
8281
8105
 
8282
8106
  ${cacheInvalidationFn}(CACHE_TAG)
8283
8107
  ${cacheInvalidationFn}(CACHE_TAG_BY_ID(created${pascal}Submission.id))
@@ -8792,6 +8616,58 @@ function namespaceGeneratedFiles(files, namespace) {
8792
8616
  }));
8793
8617
  }
8794
8618
 
8619
+ // adapters/next/generators/webhook-events.ts
8620
+ var NO_WEBHOOK_SCHEMAS = /* @__PURE__ */ new Set(["settings"]);
8621
+ function renderExtract(schemaName, kind, label, events, settingsKey) {
8622
+ const lines = [
8623
+ ` schema: '${schemaName}'`,
8624
+ ` kind: '${kind}'`,
8625
+ ` label: ${JSON.stringify(label)}`,
8626
+ ` events: [${events.map((event) => `'${event}'`).join(", ")}]`
8627
+ ];
8628
+ if (settingsKey) {
8629
+ lines.push(` settingsKey: '${settingsKey}'`);
8630
+ }
8631
+ return `import type { WebhookEventSource } from '@admin/types/webhooks'
8632
+
8633
+ export const ${toCamelCase(schemaName)}WebhookEvents: WebhookEventSource = {
8634
+ ${lines.join(",\n")}
8635
+ }
8636
+ `;
8637
+ }
8638
+ function updateWebhookEvents(schema, webhookEventsDir, kind) {
8639
+ if (NO_WEBHOOK_SCHEMAS.has(schema.name)) {
8640
+ return { files: [] };
8641
+ }
8642
+ const events = kind === "single" ? [`${schema.name}.updated`] : [
8643
+ `${schema.name}.created`,
8644
+ `${schema.name}.updated`,
8645
+ `${schema.name}.deleted`,
8646
+ ...schema.actions?.draft === true ? [`${schema.name}.published`, `${schema.name}.unpublished`] : [],
8647
+ `${schema.name}.restored`,
8648
+ `${schema.name}.reordered`
8649
+ ];
8650
+ return {
8651
+ files: [
8652
+ createGeneratedFile(
8653
+ `${webhookEventsDir}/${schema.name}.ts`,
8654
+ renderExtract(schema.name, kind, schema.label, events)
8655
+ )
8656
+ ]
8657
+ };
8658
+ }
8659
+ function updateFormWebhookEvents(schema, webhookEventsDir) {
8660
+ const kebabName = toKebabCase(schema.name);
8661
+ return {
8662
+ files: [
8663
+ createGeneratedFile(
8664
+ `${webhookEventsDir}/${schema.name}.ts`,
8665
+ renderExtract(schema.name, "form", schema.label, [`form.${kebabName}.submitted`], kebabName)
8666
+ )
8667
+ ]
8668
+ };
8669
+ }
8670
+
8795
8671
  // adapters/next/generators/form-pipeline/pipeline.ts
8796
8672
  function runFormPipeline(schema, cwd, config, options = {}) {
8797
8673
  const paths = resolveProjectPaths(config);
@@ -8831,6 +8707,10 @@ function runFormPipeline(schema, cwd, config, options = {}) {
8831
8707
  {
8832
8708
  name: "Navigation",
8833
8709
  run: () => updateFormNavigation(schema, paths.adminNavigationDir, namespacedOptions).files
8710
+ },
8711
+ {
8712
+ name: "Webhook events",
8713
+ run: () => updateFormWebhookEvents(schema, paths.adminWebhookEventsDir).files
8834
8714
  }
8835
8715
  ];
8836
8716
  for (let i = 0; i < steps.length; i++) {
@@ -9839,6 +9719,13 @@ function buildRestoreManyToManyStatements(ctx) {
9839
9719
  }
9840
9720
  function genRestoreContent(ctx) {
9841
9721
  const kebabSingular = toKebabCase(ctx.singular);
9722
+ const restoreContext = lifecycleContext(
9723
+ ctx.plural,
9724
+ `,
9725
+ entityId: versionRow.entityId,
9726
+ changedFields: [],
9727
+ metadata: { restoredFromVersion: versionRow.version }`
9728
+ );
9842
9729
  const snapshotImport = ctx.hasM2M ? `build${ctx.Singular}VersionSnapshotWithRelationships` : `build${ctx.Singular}VersionSnapshot`;
9843
9730
  const currentSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(currentRow as unknown as ${ctx.Singular}, tx)` : `build${ctx.Singular}VersionSnapshot(currentRow as unknown as ${ctx.Singular})`;
9844
9731
  const dbImports = [
@@ -9864,6 +9751,7 @@ import {
9864
9751
  build${ctx.Singular}RestoreData,
9865
9752
  ${snapshotImport}
9866
9753
  } from './helpers'
9754
+ ${lifecycleHookImport()}
9867
9755
  import type { ${ctx.Singular}UpdateResult, ${ctx.Singular} } from './types'
9868
9756
 
9869
9757
  export async function restore${ctx.Singular}Version(
@@ -9885,6 +9773,9 @@ export async function restore${ctx.Singular}Version(
9885
9773
 
9886
9774
  const versionData = versionRow.data as Record<string, unknown>
9887
9775
  const restoreData = build${ctx.Singular}RestoreData(versionData)
9776
+ const auditRequestMetadata = await getAuditRequestMetadata()
9777
+ const lifecycleContext = ${restoreContext}
9778
+ ${beforeHook("beforeRestore", "lifecycleContext")}
9888
9779
 
9889
9780
  const result = await db.transaction(async (tx) => {
9890
9781
  const [currentRow] = await tx
@@ -9916,6 +9807,13 @@ ${restoreM2M}
9916
9807
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(versionRow.entityId))
9917
9808
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', versionRow.entityId))
9918
9809
  const restored${ctx.Singular} = await get${ctx.Singular}ById(versionRow.entityId)
9810
+ ${afterHook(
9811
+ "afterRestore",
9812
+ `{
9813
+ ...lifecycleContext,
9814
+ data: (restored${ctx.Singular} ?? result[0]) as unknown as Record<string, unknown>
9815
+ }`
9816
+ )}
9919
9817
 
9920
9818
  return {
9921
9819
  success: true,
@@ -10128,14 +10026,20 @@ export async function delete${ctx.Singular}(id: string): Promise<${ctx.Singular}
10128
10026
  ${beforeHook("beforeDelete", "lifecycleContext")}
10129
10027
 
10130
10028
  try {
10131
- await db.transaction(async (tx) => {
10029
+ const [deletedRow] = await db.transaction(async (tx) => {
10132
10030
  await deleteEntityVersions(tx, '${ctx.plural}', [id])
10133
- await tx.delete(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id))
10031
+ return tx.delete(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id)).returning()
10134
10032
  })
10135
10033
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10136
10034
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(id))
10137
10035
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', id))
10138
- ${afterHook("afterDelete", "lifecycleContext")}
10036
+ ${afterHook(
10037
+ "afterDelete",
10038
+ `{
10039
+ ...lifecycleContext,
10040
+ data: deletedRow as unknown as Record<string, unknown> | undefined
10041
+ }`
10042
+ )}
10139
10043
  return { success: true }
10140
10044
  } catch (error) {
10141
10045
  console.error('Error deleting ${ctx.singular}:', error)
@@ -10179,17 +10083,26 @@ export async function deleteBulk${ctx.Plural}(ids: string[]): Promise<${ctx.Sing
10179
10083
  }
10180
10084
 
10181
10085
  try {
10182
- await db.transaction(async (tx) => {
10086
+ const deletedRows = await db.transaction(async (tx) => {
10183
10087
  await deleteEntityVersions(tx, '${ctx.plural}', ids)
10184
- await tx.delete(${ctx.tableVar}).where(inArray(${ctx.tableVar}.id, ids))
10088
+ return tx.delete(${ctx.tableVar}).where(inArray(${ctx.tableVar}.id, ids)).returning()
10185
10089
  })
10090
+ const deletedRowsById = new Map(
10091
+ deletedRows.map((row) => [row.id, row as unknown as Record<string, unknown>])
10092
+ )
10186
10093
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10187
10094
  for (const id of ids) {
10188
10095
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(id))
10189
10096
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', id))
10190
10097
  }
10191
10098
  for (const lifecycleContext of lifecycleContexts) {
10192
- ${afterHook("afterDelete", "lifecycleContext")}
10099
+ ${afterHook(
10100
+ "afterDelete",
10101
+ `{
10102
+ ...lifecycleContext,
10103
+ data: deletedRowsById.get(lifecycleContext.entityId)
10104
+ }`
10105
+ )}
10193
10106
  }
10194
10107
  return { success: true }
10195
10108
  } catch (error) {
@@ -10200,6 +10113,8 @@ export async function deleteBulk${ctx.Plural}(ids: string[]): Promise<${ctx.Sing
10200
10113
  `;
10201
10114
  }
10202
10115
  function genBulkSortOrderContent(ctx) {
10116
+ const reorderContext = lifecycleContext(ctx.plural, `,
10117
+ changedFields: ['sortOrder']`);
10203
10118
  return `'use server'
10204
10119
 
10205
10120
  import db from '@admin/db'
@@ -10208,15 +10123,20 @@ import { ${ctx.tableVar} } from '@admin/db/schema'
10208
10123
  import { asc, eq, notInArray } from 'drizzle-orm'
10209
10124
  import { ${ctx.cacheInvalidationFn} } from 'next/cache'
10210
10125
  import { ${ctx.camelPlural}CacheTags } from './types'
10126
+ ${lifecycleHookImport()}
10211
10127
 
10212
10128
  export async function bulkUpdate${ctx.Plural}SortOrder(
10213
10129
  updates: Array<{ id: string; sortOrder: number }>
10214
10130
  ): Promise<{ success: boolean; error?: string }> {
10215
- await requireRole([UserRole.ADMIN, UserRole.EDITOR])
10131
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
10132
+ if (updates.length === 0) return { success: true }
10216
10133
 
10217
- try {
10218
- if (updates.length === 0) return { success: true }
10134
+ const now = new Date().toISOString()
10135
+ const auditRequestMetadata = await getAuditRequestMetadata()
10136
+ const lifecycleContext = ${reorderContext}
10137
+ ${beforeHook("beforeReorder", "lifecycleContext")}
10219
10138
 
10139
+ try {
10220
10140
  // Sort the provided items by their new position
10221
10141
  const sorted = [...updates].sort((a, b) => a.sortOrder - b.sortOrder)
10222
10142
  const updatedIds = sorted.map((u) => u.id)
@@ -10260,13 +10180,18 @@ export async function bulkUpdate${ctx.Plural}SortOrder(
10260
10180
  }
10261
10181
 
10262
10182
  // Renumber all items gap-free
10183
+ const appliedOrder = allIds
10184
+ .filter(Boolean)
10185
+ .map((id, index) => ({ id, sortOrder: index }))
10186
+
10263
10187
  await Promise.all(
10264
- allIds.filter(Boolean).map((id, index) =>
10265
- db.update(${ctx.tableVar}).set({ sortOrder: index }).where(eq(${ctx.tableVar}.id, id))
10188
+ appliedOrder.map(({ id, sortOrder }) =>
10189
+ db.update(${ctx.tableVar}).set({ sortOrder }).where(eq(${ctx.tableVar}.id, id))
10266
10190
  )
10267
10191
  )
10268
10192
 
10269
10193
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10194
+ ${afterHook("afterReorder", "{ ...lifecycleContext, data: { order: appliedOrder } }")}
10270
10195
  return { success: true }
10271
10196
  } catch (error) {
10272
10197
  console.error('Error bulk updating sort order:', error)
@@ -11628,7 +11553,7 @@ import { getAuditRequestMetadata } from '@admin/actions/audit/request-metadata'
11628
11553
  import db from '@admin/db'
11629
11554
  import { ${upsertDbImports.join(", ")} } from '@admin/db/schema'
11630
11555
  import { eq } from 'drizzle-orm'
11631
- import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
11556
+ import { runAfterHooks, runBeforeHooks } from '@admin/lib/lifecycle-hooks'
11632
11557
  import { getChangedFieldNames, getTouchedFieldNames } from '@admin/utils/audit/audit'
11633
11558
  import { ${cacheInvalidationFn} } from 'next/cache'
11634
11559
  import { get${Singular} } from './get-${kebabSingular}'
@@ -11680,6 +11605,22 @@ ${htmlUpsertBlock}
11680
11605
  ? getChangedFieldNames(previousRow as unknown as Record<string, unknown>, processedData)
11681
11606
  : getTouchedFieldNames(processedData)
11682
11607
 
11608
+ const lifecycleContext = {
11609
+ user: actor,
11610
+ entityType: '${schema.name}',
11611
+ schemaType: 'single' as const,
11612
+ auditCategory: '${auditCategory}' as const,
11613
+ timestamp: now,
11614
+ sourceType: 'admin_ui' as const,
11615
+ ipAddress: auditRequestMetadata.ipAddress,
11616
+ userAgent: auditRequestMetadata.userAgent,
11617
+ entityId: SINGLETON_ID,
11618
+ input: processedData,
11619
+ changedFields,
11620
+ targetLabel: ${JSON.stringify(schema.label)}
11621
+ }
11622
+ await runBeforeHooks(previousRow ? 'beforeUpdate' : 'beforeCreate', lifecycleContext)
11623
+
11683
11624
  const result = await db.transaction(async (tx) => {
11684
11625
  const rows = await tx
11685
11626
  .insert(${tableVar})
@@ -11704,18 +11645,9 @@ ${m2mWriteStatements}
11704
11645
 
11705
11646
  ${cacheInvalidationFn}(${cacheTagsVar}.all)
11706
11647
  const saved${Singular} = await get${Singular}()
11707
- runAdminEventHooks({
11708
- category: '${auditCategory}',
11709
- action: previousRow ? 'update' : 'create',
11710
- targetType: '${schema.name}',
11711
- targetId: SINGLETON_ID,
11712
- targetLabel: ${JSON.stringify(schema.label)},
11713
- user: actor,
11714
- timestamp: now,
11715
- sourceType: 'admin_ui' as const,
11716
- changedFields,
11717
- metadata: { schemaType: 'single' },
11718
- ...auditRequestMetadata
11648
+ runAfterHooks(previousRow ? 'afterUpdate' : 'afterCreate', {
11649
+ ...lifecycleContext,
11650
+ data: (saved${Singular} ?? result[0]) as unknown as Record<string, unknown>
11719
11651
  })
11720
11652
 
11721
11653
  return {
@@ -16574,7 +16506,7 @@ export default async function ${Plural}Page() {
16574
16506
  const skeletonContent = `export function ${Plural}PageSkeleton() {
16575
16507
  return (
16576
16508
  <div className="flex items-center justify-center h-48">
16577
- <div className="text-muted-foreground">Loading ${schema.label}...</div>
16509
+ <div className="text-muted-foreground">Loading ${schema.label}\u2026</div>
16578
16510
  </div>
16579
16511
  )
16580
16512
  }
@@ -16906,7 +16838,6 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16906
16838
  const Singular = toPascalCase(singular);
16907
16839
  const PageName = toPascalCase(schema.name);
16908
16840
  const actionImportPath = resolveSchemaActionImportPath(schema.name, options.outputNamespace);
16909
- const adminRoutePath = options.adminRoutePath ?? "/admin";
16910
16841
  const devModeIntegration = buildSingleDevModeIntegration(
16911
16842
  schema,
16912
16843
  options.adminDir ?? "admin",
@@ -16914,7 +16845,6 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16914
16845
  options.adminImportAlias
16915
16846
  );
16916
16847
  const settingsImport = schema.name === "settings" ? "" : "import { getSetting } from '@admin/actions/settings'\n";
16917
- const settingsAuditImports = schema.name === "settings" ? "import { Button } from '@admin/components/ui/button'\nimport { ScrollText } from 'lucide-react'\nimport Link from 'next/link'\n" : "";
16918
16848
  const dataLoad = schema.name === "settings" ? ` const data = await get${Singular}()
16919
16849
  ` : ` const [data, initialSettings] = await Promise.all([
16920
16850
  get${Singular}(),
@@ -16922,16 +16852,7 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16922
16852
  ])
16923
16853
  `;
16924
16854
  const initialSettingsProp = schema.name === "settings" ? "initialSettings={data}" : "initialSettings={initialSettings}";
16925
- const actionsContent = schema.name === "settings" ? `<div className="flex items-center gap-2">
16926
- <Button asChild variant="outline" size="sm">
16927
- <Link href="${adminRoutePath}/system/audit-log">
16928
- <ScrollText />
16929
- Audit Log
16930
- </Link>
16931
- </Button>
16932
- <${devModeIntegration.buttonExportName} ${initialSettingsProp} />
16933
- </div>` : `<${devModeIntegration.buttonExportName} ${initialSettingsProp} />`;
16934
- const content = `${settingsImport}${settingsAuditImports}import { get${Singular} } from '@admin/actions/${actionImportPath}'
16855
+ const content = `${settingsImport}import { get${Singular} } from '@admin/actions/${actionImportPath}'
16935
16856
  import { PageHeader } from '@admin/components/shared/page-header'
16936
16857
  import { ${devModeIntegration.buttonExportName} } from '${devModeIntegration.buttonImportPath}'
16937
16858
  import { ${Singular}Form } from './${schema.name}-form'
@@ -16944,9 +16865,9 @@ ${dataLoad}
16944
16865
  <React.Fragment>
16945
16866
  <PageHeader
16946
16867
  title="${schema.label}"
16947
- actions={${actionsContent}}
16868
+ actions={<${devModeIntegration.buttonExportName} ${initialSettingsProp} />}
16948
16869
  />
16949
- <main className="container mx-auto max-w-5xl w-full p-4">
16870
+ <main className="mx-auto max-w-5xl w-full p-4">
16950
16871
  <${Singular}Form initialData={data} />
16951
16872
  </main>
16952
16873
  </React.Fragment>
@@ -17007,7 +16928,6 @@ import { GripVertical } from 'lucide-react'
17007
16928
  import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
17008
16929
  ${hasEdit ? "import { useRouter } from 'next/navigation'\n" : ""}import { use${Plural} } from '@admin/hooks/use-${schema.name}'
17009
16930
  import type { ${Singular} } from '@admin/actions/${actionImportPath}'
17010
- import { Card, CardContent } from '@admin/components/ui/card'
17011
16931
  import { useTableUtils } from '@admin/hooks/use-table-utils'
17012
16932
  import { cn } from '@admin/utils/shared/cn'
17013
16933
 
@@ -17113,110 +17033,106 @@ ${hasEdit ? " const router = useRouter()\n" : ""} const [{
17113
17033
  <div
17114
17034
  className={cn("flex flex-col gap-6 pt-px pb-2 h-full", className)}
17115
17035
  >
17116
- <Card className="min-h-0 flex-1 p-0">
17117
- <CardContent ref={tableContainerRef} className="flex min-h-0 flex-1 flex-col p-0">
17118
- <DataGrid containerClassName="min-h-0 flex-1" gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
17119
- <DataGridHeader>
17120
- {table.getHeaderGroups().map((headerGroup) => (
17121
- <DataGridRow key={headerGroup.id}>
17122
- {headerGroup.headers.map((header, headerIndex) => {
17123
- const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
17124
- const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
17125
- const nextHeader = headerGroup.headers[headerIndex + 1]
17126
- const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
17127
- return (
17128
- <DataGridHead
17129
- key={header.id}
17130
- className={cn('group/resize-header relative overflow-hidden text-foreground', {
17131
- 'text-center': meta?.align === 'center',
17132
- 'text-right': meta?.align === 'right'
17133
- })}
17134
- data-column-id={header.column.id}
17135
- onMouseEnter={() => setHoveredColumnId(header.column.id)}
17136
- onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
17137
- >
17138
- {header.isPlaceholder
17139
- ? null
17140
- : flexRender(header.column.columnDef.header, header.getContext())}
17141
- {canResizeColumn ? (
17142
- <button
17143
- type="button"
17144
- aria-label={\`Resize \${resizeLabel} column\`}
17145
- 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"
17146
- data-resize-handle
17147
- data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
17148
- data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
17149
- onDoubleClick={resetColumnSizingToContainer}
17150
- onMouseDown={header.getResizeHandler()}
17151
- onTouchStart={header.getResizeHandler()}
17152
- >
17153
- <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
17154
- </button>
17155
- ) : null}
17156
- </DataGridHead>
17157
- )
17158
- })}
17159
- </DataGridRow>
17160
- ))}
17161
- </DataGridHeader>
17162
- <DataGridBody>
17163
- {isPending ? (
17164
- <DataGridRow>
17165
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17166
- <div className="text-muted-foreground">Loading ${schema.label}...</div>
17167
- </DataGridCell>
17168
- </DataGridRow>
17169
- ) : error ? (
17170
- <DataGridRow>
17171
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17172
- <div className="text-destructive">Error loading ${schema.label}: {error.message}</div>
17173
- </DataGridCell>
17174
- </DataGridRow>
17175
- ) : table.getRowModel().rows?.length ? (
17176
- table.getRowModel().rows.map((row) => (
17177
- <DataGridRow
17178
- key={row.id}
17179
- className={cn('hover:bg-muted/50', {
17180
- 'cursor-pointer': ${hasEdit ? "true" : "false"}
17036
+ <DataGrid containerClassName="min-h-0 flex-1" containerRef={tableContainerRef} gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
17037
+ <DataGridHeader>
17038
+ {table.getHeaderGroups().map((headerGroup) => (
17039
+ <DataGridRow key={headerGroup.id}>
17040
+ {headerGroup.headers.map((header, headerIndex) => {
17041
+ const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
17042
+ const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
17043
+ const nextHeader = headerGroup.headers[headerIndex + 1]
17044
+ const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
17045
+ return (
17046
+ <DataGridHead
17047
+ key={header.id}
17048
+ className={cn('group/resize-header relative overflow-hidden', {
17049
+ 'text-center': meta?.align === 'center',
17050
+ 'text-right': meta?.align === 'right'
17181
17051
  })}
17182
- data-state={row.getIsSelected() ? 'selected' : undefined}${hasEdit ? `
17183
- onClick={(e) => {
17184
- const target = e.target as HTMLElement
17185
- if (!e.currentTarget.contains(target)) return
17186
- if (target.closest('button, a, input, [role="checkbox"]')) return
17187
- router.push(\`${adminRoutePath}/${schema.name}/\${row.original.id}/edit\`)
17188
- }}` : ""}
17052
+ data-column-id={header.column.id}
17053
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
17054
+ onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
17189
17055
  >
17190
- {row.getVisibleCells().map((cell) => {
17191
- const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
17192
- return (
17193
- <DataGridCell
17194
- key={cell.id}
17195
- className={cn({
17196
- 'text-center': meta?.align === 'center',
17197
- 'text-right': meta?.align === 'right'
17198
- })}
17199
- data-column-id={cell.column.id}
17200
- onMouseEnter={() => setHoveredColumnId(cell.column.id)}
17201
- onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
17202
- >
17203
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
17204
- </DataGridCell>
17205
- )
17206
- })}
17207
- </DataGridRow>
17208
- ))
17209
- ) : (
17210
- <DataGridRow>
17211
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17212
- No ${schema.label} found.
17213
- </DataGridCell>
17214
- </DataGridRow>
17215
- )}
17216
- </DataGridBody>
17217
- </DataGrid>
17218
- </CardContent>
17219
- </Card>
17056
+ {header.isPlaceholder
17057
+ ? null
17058
+ : flexRender(header.column.columnDef.header, header.getContext())}
17059
+ {canResizeColumn ? (
17060
+ <button
17061
+ type="button"
17062
+ aria-label={\`Resize \${resizeLabel} column\`}
17063
+ 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"
17064
+ data-resize-handle
17065
+ data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
17066
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
17067
+ onDoubleClick={resetColumnSizingToContainer}
17068
+ onMouseDown={header.getResizeHandler()}
17069
+ onTouchStart={header.getResizeHandler()}
17070
+ >
17071
+ <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
17072
+ </button>
17073
+ ) : null}
17074
+ </DataGridHead>
17075
+ )
17076
+ })}
17077
+ </DataGridRow>
17078
+ ))}
17079
+ </DataGridHeader>
17080
+ <DataGridBody>
17081
+ {isPending ? (
17082
+ <DataGridRow>
17083
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17084
+ <div className="text-muted-foreground">Loading ${schema.label}\u2026</div>
17085
+ </DataGridCell>
17086
+ </DataGridRow>
17087
+ ) : error ? (
17088
+ <DataGridRow>
17089
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17090
+ <div className="text-destructive">Error loading ${schema.label}: {error.message}</div>
17091
+ </DataGridCell>
17092
+ </DataGridRow>
17093
+ ) : table.getRowModel().rows?.length ? (
17094
+ table.getRowModel().rows.map((row) => (
17095
+ <DataGridRow
17096
+ key={row.id}
17097
+ className={cn('hover:bg-muted/50', {
17098
+ 'cursor-pointer': ${hasEdit ? "true" : "false"}
17099
+ })}
17100
+ data-state={row.getIsSelected() ? 'selected' : undefined}${hasEdit ? `
17101
+ onClick={(e) => {
17102
+ const target = e.target as HTMLElement
17103
+ if (!e.currentTarget.contains(target)) return
17104
+ if (target.closest('button, a, input, [role="checkbox"]')) return
17105
+ router.push(\`${adminRoutePath}/${schema.name}/\${row.original.id}/edit\`)
17106
+ }}` : ""}
17107
+ >
17108
+ {row.getVisibleCells().map((cell) => {
17109
+ const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
17110
+ return (
17111
+ <DataGridCell
17112
+ key={cell.id}
17113
+ className={cn({
17114
+ 'text-center': meta?.align === 'center',
17115
+ 'text-right': meta?.align === 'right'
17116
+ })}
17117
+ data-column-id={cell.column.id}
17118
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
17119
+ onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
17120
+ >
17121
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
17122
+ </DataGridCell>
17123
+ )
17124
+ })}
17125
+ </DataGridRow>
17126
+ ))
17127
+ ) : (
17128
+ <DataGridRow>
17129
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17130
+ No ${schema.label} found.
17131
+ </DataGridCell>
17132
+ </DataGridRow>
17133
+ )}
17134
+ </DataGridBody>
17135
+ </DataGrid>
17220
17136
 
17221
17137
  {shouldShowPagination ? (
17222
17138
  <DataTablePagination
@@ -17331,6 +17247,10 @@ function runEntityPipeline(schema, cwd, config, options = {}) {
17331
17247
  name: "Navigation",
17332
17248
  run: () => updateNavigation(normalizedSchema, paths.adminNavigationDir, namespacedOptions).files
17333
17249
  });
17250
+ steps.push({
17251
+ name: "Webhook events",
17252
+ run: () => updateWebhookEvents(normalizedSchema, paths.adminWebhookEventsDir, "entity").files
17253
+ });
17334
17254
  for (let i = 0; i < steps.length; i++) {
17335
17255
  const step = steps[i];
17336
17256
  const stepNum = i + 1;
@@ -17393,6 +17313,10 @@ function runSinglePipeline(schema, cwd, config, options = {}) {
17393
17313
  {
17394
17314
  name: "Navigation",
17395
17315
  run: () => updateNavigation(normalizedSchema, paths.adminNavigationDir, namespacedOptions).files
17316
+ },
17317
+ {
17318
+ name: "Webhook events",
17319
+ run: () => updateWebhookEvents(normalizedSchema, paths.adminWebhookEventsDir, "single").files
17396
17320
  }
17397
17321
  ];
17398
17322
  for (let i = 0; i < steps.length; i++) {
@@ -17968,18 +17892,41 @@ others.sort((a, b) => {
17968
17892
  export const ${namespace.camel}Navigation = [...(dashboard ? [dashboard] : []), ...others]
17969
17893
  `;
17970
17894
  }
17895
+ function renderWebhookEventsBarrel(extractNames, namespaceValue) {
17896
+ const namespace = resolveAdminNamespace(namespaceValue);
17897
+ const importLines = extractNames.map(
17898
+ (name) => `import { ${toCamelCase(name)}WebhookEvents } from './webhook-events/${name}'`
17899
+ );
17900
+ const entryLines = extractNames.map((name) => ` ${toCamelCase(name)}WebhookEvents`);
17901
+ const extractImports = importLines.length > 0 ? `
17902
+ ${importLines.join("\n")}` : "";
17903
+ const entries = entryLines.length > 0 ? `[
17904
+ ${entryLines.join(",\n")}
17905
+ ]` : "[]";
17906
+ return `import type { WebhookEventSource } from '${namespace.alias}/types/webhooks'${extractImports}
17907
+
17908
+ export type { WebhookEventSource } from '${namespace.alias}/types/webhooks'
17909
+
17910
+ export const webhookEventSources: WebhookEventSource[] = ${entries}
17911
+ `;
17912
+ }
17971
17913
  function renderBarrelFiles(cwd, config) {
17972
17914
  const paths = resolveProjectPaths(config);
17973
17915
  const dbExtracts = listNamespacedExtracts(cwd, paths.adminDbDir).filter(
17974
17916
  (extract) => !(extract.namespace === "core" && extract.name === "schema")
17975
17917
  );
17976
17918
  const navExtractNames = listExtractNames(cwd, paths.adminNavigationDir, /* @__PURE__ */ new Set(["core"]));
17919
+ const webhookEventExtractNames = listExtractNames(cwd, paths.adminWebhookEventsDir);
17977
17920
  return namespaceGeneratedFiles(
17978
17921
  [
17979
17922
  createGeneratedFile(paths.adminDbSchemaFile, renderDbBarrel(dbExtracts)),
17980
17923
  createGeneratedFile(
17981
17924
  paths.adminNavigationFile,
17982
17925
  renderNavigationBarrel(navExtractNames, config.frameworkConfig.next.namespace)
17926
+ ),
17927
+ createGeneratedFile(
17928
+ paths.adminWebhookEventsFile,
17929
+ renderWebhookEventsBarrel(webhookEventExtractNames, config.frameworkConfig.next.namespace)
17983
17930
  )
17984
17931
  ],
17985
17932
  config.frameworkConfig.next.namespace
@@ -22337,6 +22284,10 @@ function scaffoldComponents({ cwd, config }) {
22337
22284
  "components/layouts/admin-sidebar-nav-link-skeleton.tsx",
22338
22285
  readTemplate("components/layouts/admin-sidebar-nav-link-skeleton.tsx")
22339
22286
  );
22287
+ write(
22288
+ "components/layouts/admin-settings-sidebar.tsx",
22289
+ readTemplate("components/layouts/admin-settings-sidebar.tsx")
22290
+ );
22340
22291
  write(
22341
22292
  "components/layouts/admin-sidebar-user-menu.tsx",
22342
22293
  readTemplate("components/layouts/admin-sidebar-user-menu.tsx")
@@ -22501,6 +22452,7 @@ function scaffoldComponents({ cwd, config }) {
22501
22452
  write("types/auth.ts", readTemplate("types/auth.ts"));
22502
22453
  write("types/navigation.ts", readTemplate("types/navigation.ts"));
22503
22454
  write("types/table-meta.ts", readTemplate("types/table-meta.ts"));
22455
+ write("types/webhooks.ts", readTemplate("types/webhooks.ts"));
22504
22456
  for (const relPath of [
22505
22457
  "utils/auth/roles.ts",
22506
22458
  "utils/audit/audit.ts",
@@ -22536,7 +22488,7 @@ function scaffoldComponents({ cwd, config }) {
22536
22488
  "utils/upload/upload.ts",
22537
22489
  "utils/user/user.ts",
22538
22490
  "utils/validation/validation.ts",
22539
- "utils/webhook/webhook.ts"
22491
+ "utils/webhook/signature.ts"
22540
22492
  ]) {
22541
22493
  write(relPath, readTemplate(relPath));
22542
22494
  }
@@ -22618,6 +22570,7 @@ function scaffoldComponents({ cwd, config }) {
22618
22570
  write("hooks/use-admin-theme.tsx", readTemplate("hooks/use-admin-theme.tsx"));
22619
22571
  write("hooks/use-audit-log.ts", readTemplate("hooks/use-audit-log.ts"));
22620
22572
  write("hooks/use-users.ts", readTemplate("hooks/use-users.ts"));
22573
+ write("hooks/use-webhooks.ts", readTemplate("hooks/use-webhooks.ts"));
22621
22574
  write("hooks/use-mobile.ts", readTemplate("hooks/use-mobile.ts"));
22622
22575
  write("hooks/use-failed-image-url.ts", readTemplate("hooks/use-failed-image-url.ts"));
22623
22576
  write("hooks/use-media.ts", readTemplate("hooks/use-media.ts"));
@@ -22632,14 +22585,28 @@ function scaffoldComponents({ cwd, config }) {
22632
22585
  const adminNamespace = resolveAdminNamespace(namespace);
22633
22586
  const projectName = detectProjectName(cwd);
22634
22587
  write("data/navigation.ts", readTemplate("data/navigation.ts"));
22588
+ write("data/webhook-events.ts", readTemplate("data/webhook-events.ts"));
22635
22589
  write(`utils/app/${adminNamespace.camel}.ts`, adminDataTemplate(projectName, namespace));
22636
22590
  for (const relPath of [
22637
22591
  "lib/actions/forms/types.ts",
22638
22592
  "lib/actions/forms/get-form-settings.ts",
22639
22593
  "lib/actions/forms/upsert-form-settings.ts",
22640
22594
  "lib/actions/forms/get-all-form-settings.ts",
22641
- "lib/actions/forms/test-form-webhook.ts",
22642
22595
  "lib/actions/forms/index.ts",
22596
+ "lib/actions/webhooks/types.ts",
22597
+ "lib/actions/webhooks/internal-get-active-endpoints.ts",
22598
+ "lib/actions/webhooks/dispatch.ts",
22599
+ "lib/actions/webhooks/get-webhooks.ts",
22600
+ "lib/actions/webhooks/create-webhook.ts",
22601
+ "lib/actions/webhooks/update-webhook.ts",
22602
+ "lib/actions/webhooks/delete-webhook.ts",
22603
+ "lib/actions/webhooks/get-webhook-secret.ts",
22604
+ "lib/actions/webhooks/regenerate-webhook-secret.ts",
22605
+ "lib/actions/webhooks/get-webhook-subscriptions.ts",
22606
+ "lib/actions/webhooks/send-test-webhook.ts",
22607
+ "lib/actions/webhooks/get-webhook-deliveries.ts",
22608
+ "lib/actions/webhooks/register.ts",
22609
+ "lib/actions/webhooks/index.ts",
22643
22610
  "lib/actions/entity-versions/types.ts",
22644
22611
  "lib/actions/entity-versions/get-entity-versions.ts",
22645
22612
  "lib/actions/entity-versions/internal-create-entity-version.ts",
@@ -22824,41 +22791,27 @@ function defaultSettingsSchema() {
22824
22791
  type: "section",
22825
22792
  name: "siteSettings",
22826
22793
  label: "Dashboard Settings",
22827
- description: "General settings for the dashboard",
22828
22794
  fields: [
22829
22795
  {
22830
22796
  name: "title",
22831
22797
  type: "string",
22832
- label: "Name",
22798
+ label: "Application Name",
22833
22799
  hint: "Displayed in the sidebar and throughout the dashboard",
22834
22800
  default: "BetterStart"
22835
22801
  },
22836
- {
22837
- name: "dashboardDescription",
22838
- type: "text",
22839
- label: "Description",
22840
- hint: "A brief description of the application"
22841
- }
22802
+ { name: "dashboardLogo", type: "image", label: "Logo" }
22842
22803
  ]
22843
22804
  },
22844
- {
22845
- type: "section",
22846
- name: "branding",
22847
- label: "Branding",
22848
- description: "Logo and visual identity",
22849
- fields: [{ name: "dashboardLogo", type: "image", label: "Logo" }]
22850
- },
22851
22805
  {
22852
22806
  type: "section",
22853
22807
  name: "developer",
22854
- label: "Developer",
22855
- description: "Developer tools for the dashboard",
22808
+ label: "Developers",
22856
22809
  fields: [
22857
22810
  {
22858
22811
  name: "devMode",
22859
22812
  type: "boolean",
22860
22813
  label: "Dev Mode",
22861
- hint: "Show generated integration snippets and schema details in the dashboard",
22814
+ hint: "Show generated integration snippets and schema details for developers",
22862
22815
  default: true
22863
22816
  }
22864
22817
  ]
@@ -22867,7 +22820,6 @@ function defaultSettingsSchema() {
22867
22820
  type: "section",
22868
22821
  name: "auditLog",
22869
22822
  label: "Audit Log",
22870
- description: "Audit retention controls",
22871
22823
  fields: [
22872
22824
  {
22873
22825
  name: "auditRetentionEnabled",
@@ -23188,6 +23140,78 @@ function scaffoldLayout({ cwd, config }) {
23188
23140
  path41.join(usersDir, "change-password-dialog.tsx"),
23189
23141
  readTemplate("pages/users/change-password-dialog.tsx")
23190
23142
  );
23143
+ const settingsDir = path41.join(config.paths.pages, "settings");
23144
+ write(path41.join(settingsDir, "layout.tsx"), readTemplate("pages/settings/settings-layout.tsx"));
23145
+ const settingsFormsDir = path41.join(settingsDir, "forms");
23146
+ write(
23147
+ path41.join(settingsFormsDir, "page.tsx"),
23148
+ readTemplate("pages/settings/forms/forms-settings-page.tsx")
23149
+ );
23150
+ write(
23151
+ path41.join(settingsFormsDir, "forms-settings-page-skeleton.tsx"),
23152
+ readTemplate("pages/settings/forms/forms-settings-page-skeleton.tsx")
23153
+ );
23154
+ write(
23155
+ path41.join(settingsFormsDir, "forms-settings-page-content.tsx"),
23156
+ readTemplate("pages/settings/forms/forms-settings-page-content.tsx")
23157
+ );
23158
+ write(
23159
+ path41.join(settingsFormsDir, "edit-form-notifications-dialog.tsx"),
23160
+ readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx")
23161
+ );
23162
+ const settingsWebhooksDir = path41.join(settingsDir, "webhooks");
23163
+ write(
23164
+ path41.join(settingsWebhooksDir, "page.tsx"),
23165
+ readTemplate("pages/settings/webhooks/webhooks-page.tsx")
23166
+ );
23167
+ write(
23168
+ path41.join(settingsWebhooksDir, "webhooks-page-skeleton.tsx"),
23169
+ readTemplate("pages/settings/webhooks/webhooks-page-skeleton.tsx")
23170
+ );
23171
+ write(
23172
+ path41.join(settingsWebhooksDir, "webhooks-page-content.tsx"),
23173
+ readTemplate("pages/settings/webhooks/webhooks-page-content.tsx")
23174
+ );
23175
+ write(
23176
+ path41.join(settingsWebhooksDir, "webhooks-table.tsx"),
23177
+ readTemplate("pages/settings/webhooks/webhooks-table.tsx")
23178
+ );
23179
+ write(
23180
+ path41.join(settingsWebhooksDir, "webhooks-columns.tsx"),
23181
+ readTemplate("pages/settings/webhooks/webhooks-columns.tsx")
23182
+ );
23183
+ write(
23184
+ path41.join(settingsWebhooksDir, "webhook-actions.tsx"),
23185
+ readTemplate("pages/settings/webhooks/webhook-actions.tsx")
23186
+ );
23187
+ write(
23188
+ path41.join(settingsWebhooksDir, "webhook-enabled-switch.tsx"),
23189
+ readTemplate("pages/settings/webhooks/webhook-enabled-switch.tsx")
23190
+ );
23191
+ write(
23192
+ path41.join(settingsWebhooksDir, "webhook-endpoint-dialog.tsx"),
23193
+ readTemplate("pages/settings/webhooks/webhook-endpoint-dialog.tsx")
23194
+ );
23195
+ write(
23196
+ path41.join(settingsWebhooksDir, "delete-webhook-dialog.tsx"),
23197
+ readTemplate("pages/settings/webhooks/delete-webhook-dialog.tsx")
23198
+ );
23199
+ write(
23200
+ path41.join(settingsWebhooksDir, "webhooks-logs-page-content.tsx"),
23201
+ readTemplate("pages/settings/webhooks/webhooks-logs-page-content.tsx")
23202
+ );
23203
+ write(
23204
+ path41.join(settingsWebhooksDir, "webhooks-logs-table.tsx"),
23205
+ readTemplate("pages/settings/webhooks/webhooks-logs-table.tsx")
23206
+ );
23207
+ write(
23208
+ path41.join(settingsWebhooksDir, "webhooks-logs-columns.tsx"),
23209
+ readTemplate("pages/settings/webhooks/webhooks-logs-columns.tsx")
23210
+ );
23211
+ write(
23212
+ path41.join(settingsWebhooksDir, "logs", "page.tsx"),
23213
+ readTemplate("pages/settings/webhooks/webhooks-logs-page.tsx")
23214
+ );
23191
23215
  const mediaDir = path41.join(config.paths.pages, "media");
23192
23216
  write(path41.join(mediaDir, "page.tsx"), readTemplate("pages/media/media-page.tsx"));
23193
23217
  write(
@@ -23198,23 +23222,34 @@ function scaffoldLayout({ cwd, config }) {
23198
23222
  path41.join(mediaDir, "media-page-content.tsx"),
23199
23223
  readTemplate("pages/media/media-page-content.tsx")
23200
23224
  );
23201
- const auditLogDir = path41.join(config.paths.pages, "system", "audit-log");
23202
- write(path41.join(auditLogDir, "page.tsx"), readTemplate("pages/audit-log/audit-log-page.tsx"));
23225
+ const auditLogDir = path41.join(config.paths.pages, "settings", "audit-log");
23226
+ write(
23227
+ path41.join(auditLogDir, "page.tsx"),
23228
+ readTemplate("pages/settings/audit-log/audit-log-page.tsx")
23229
+ );
23203
23230
  write(
23204
23231
  path41.join(auditLogDir, "audit-log-page-skeleton.tsx"),
23205
- readTemplate("pages/audit-log/audit-log-page-skeleton.tsx")
23232
+ readTemplate("pages/settings/audit-log/audit-log-page-skeleton.tsx")
23206
23233
  );
23207
23234
  write(
23208
23235
  path41.join(auditLogDir, "audit-log-page-content.tsx"),
23209
- readTemplate("pages/audit-log/audit-log-page-content.tsx")
23236
+ readTemplate("pages/settings/audit-log/audit-log-page-content.tsx")
23237
+ );
23238
+ write(
23239
+ path41.join(auditLogDir, "audit-log-table.tsx"),
23240
+ readTemplate("pages/settings/audit-log/audit-log-table.tsx")
23241
+ );
23242
+ write(
23243
+ path41.join(auditLogDir, "audit-log-columns.tsx"),
23244
+ readTemplate("pages/settings/audit-log/audit-log-columns.tsx")
23210
23245
  );
23211
23246
  write(
23212
23247
  path41.join(auditLogDir, "audit-log-detail-drawer.tsx"),
23213
- readTemplate("pages/audit-log/audit-log-detail-drawer.tsx")
23248
+ readTemplate("pages/settings/audit-log/audit-log-detail-drawer.tsx")
23214
23249
  );
23215
23250
  write(
23216
23251
  path41.join(auditLogDir, "audit-log-detail-row.tsx"),
23217
- readTemplate("pages/audit-log/audit-log-detail-row.tsx")
23252
+ readTemplate("pages/settings/audit-log/audit-log-detail-row.tsx")
23218
23253
  );
23219
23254
  const accountDir = path41.join(adminDir, "(account)");
23220
23255
  write(path41.join(accountDir, "layout.tsx"), readTemplate("pages/account-layout.tsx"));
@@ -25038,10 +25073,15 @@ async function main() {
25038
25073
  process.exit(0)
25039
25074
  }
25040
25075
 
25041
- const EMAIL = process.env.SEED_EMAIL!
25042
- const PASSWORD = process.env.SEED_PASSWORD!
25076
+ const EMAIL = process.env.SEED_EMAIL
25077
+ const PASSWORD = process.env.SEED_PASSWORD
25043
25078
  const NAME = process.env.SEED_NAME || 'Admin'
25044
25079
 
25080
+ if (!EMAIL || !PASSWORD) {
25081
+ console.error(' SEED_EMAIL and SEED_PASSWORD are required.')
25082
+ process.exit(1)
25083
+ }
25084
+
25045
25085
  // Check if a user already exists with the requested email
25046
25086
  const existingUser = await db
25047
25087
  .select({ id: schema.user.id, name: schema.user.name })
@@ -25221,7 +25261,7 @@ async function runSeedCommand(options) {
25221
25261
  }
25222
25262
 
25223
25263
  // adapters/next/commands/init.ts
25224
- var ANSI_ESCAPE_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
25264
+ var ANSI_ESCAPE_PATTERN2 = /\u001B\[[0-?]*[ -/]*[@-~]/g;
25225
25265
  var REMOVABLE_NAMESPACE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25226
25266
  function normalizeNamespaceForRemoval(value) {
25227
25267
  if (typeof value !== "string") {
@@ -25868,7 +25908,7 @@ async function runInitCommand(name, options) {
25868
25908
  } else {
25869
25909
  s.start("Pushing database schema (drizzle-kit push)");
25870
25910
  }
25871
- const pushResult = await runDrizzlePush(cwd, { inheritStdio: useTerminalForDbPush });
25911
+ const pushResult = await runDrizzlePush(cwd, { interactive: useTerminalForDbPush });
25872
25912
  if (pushResult.success) {
25873
25913
  if (useTerminalForDbPush) {
25874
25914
  const verification = await verifyDatabaseReachable(cwd);
@@ -26463,8 +26503,8 @@ function runQuietCommand(bin, args, options) {
26463
26503
  });
26464
26504
  });
26465
26505
  }
26466
- function stripAnsi(value) {
26467
- return value.replace(ANSI_ESCAPE_PATTERN, "");
26506
+ function stripAnsi2(value) {
26507
+ return value.replace(ANSI_ESCAPE_PATTERN2, "");
26468
26508
  }
26469
26509
  function printAdminReadyNote(state) {
26470
26510
  const lines = [`Admin: ${pc7.cyan(state.adminLoginUrl)}`];
@@ -26475,7 +26515,7 @@ function printAdminReadyNote(state) {
26475
26515
  p17.note(lines.join("\n"), "Admin ready");
26476
26516
  }
26477
26517
  function shouldSuppressDevServerStartupLine(line) {
26478
- const plain = stripAnsi(line).trim();
26518
+ const plain = stripAnsi2(line).trim();
26479
26519
  if (plain.length === 0) {
26480
26520
  return { suppress: true, isReadyLine: false };
26481
26521
  }
@@ -26718,6 +26758,7 @@ function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
26718
26758
  const stopRoots = /* @__PURE__ */ new Set([
26719
26759
  path54.join(cwd, ...configPaths.adminDir.split("/")),
26720
26760
  path54.join(cwd, ...configPaths.adminNavigationDir.split("/")),
26761
+ path54.join(cwd, ...configPaths.adminWebhookEventsDir.split("/")),
26721
26762
  path54.join(cwd, ...configPaths.pagesDir.split("/"))
26722
26763
  ]);
26723
26764
  for (const deletedPath of deletedPaths) {
@@ -27504,6 +27545,11 @@ var TEMPLATE_REGISTRY = {
27504
27545
  relPath: "components/layouts/admin-sidebar-nav-link-skeleton.tsx",
27505
27546
  content: () => readTemplate("components/layouts/admin-sidebar-nav-link-skeleton.tsx")
27506
27547
  },
27548
+ "admin-settings-sidebar": {
27549
+ relPath: "components/layouts/admin-settings-sidebar.tsx",
27550
+ content: () => readTemplate("components/layouts/admin-settings-sidebar.tsx"),
27551
+ dependencies: ["admin-sidebar-nav-link"]
27552
+ },
27507
27553
  "admin-sidebar-user-menu": {
27508
27554
  relPath: "components/layouts/admin-sidebar-user-menu.tsx",
27509
27555
  content: () => readTemplate("components/layouts/admin-sidebar-user-menu.tsx"),
@@ -27898,6 +27944,11 @@ var TEMPLATE_REGISTRY = {
27898
27944
  dependencies: ["audit-action"]
27899
27945
  },
27900
27946
  "use-users": { relPath: "hooks/use-users.ts", content: () => readTemplate("hooks/use-users.ts") },
27947
+ "use-webhooks": {
27948
+ relPath: "hooks/use-webhooks.ts",
27949
+ content: () => readTemplate("hooks/use-webhooks.ts"),
27950
+ dependencies: ["webhooks-action", "form-settings-action"]
27951
+ },
27901
27952
  "use-mobile": {
27902
27953
  relPath: "hooks/use-mobile.ts",
27903
27954
  content: () => readTemplate("hooks/use-mobile.ts")
@@ -27946,6 +27997,10 @@ var TEMPLATE_REGISTRY = {
27946
27997
  relPath: "types/table-meta.ts",
27947
27998
  content: () => readTemplate("types/table-meta.ts")
27948
27999
  },
28000
+ "webhooks-types": {
28001
+ relPath: "types/webhooks.ts",
28002
+ content: () => readTemplate("types/webhooks.ts")
28003
+ },
27949
28004
  // Utils
27950
28005
  "app-admin": {
27951
28006
  relPath: ({ config }) => `utils/app/${resolveAdminNamespace(config.frameworkConfig.next.namespace).camel}.ts`,
@@ -28078,9 +28133,9 @@ var TEMPLATE_REGISTRY = {
28078
28133
  relPath: "utils/validation/validation.ts",
28079
28134
  content: () => readTemplate("utils/validation/validation.ts")
28080
28135
  },
28081
- webhook: {
28082
- relPath: "utils/webhook/webhook.ts",
28083
- content: () => readTemplate("utils/webhook/webhook.ts")
28136
+ "webhook-signature": {
28137
+ relPath: "utils/webhook/signature.ts",
28138
+ content: () => readTemplate("utils/webhook/signature.ts")
28084
28139
  },
28085
28140
  "dev-mode-code-block-height-utils": {
28086
28141
  relPath: "utils/dev-mode/code-block-height.ts",
@@ -28113,7 +28168,8 @@ var TEMPLATE_REGISTRY = {
28113
28168
  },
28114
28169
  "lifecycle-hooks-register": {
28115
28170
  relPath: "lib/lifecycle-hooks/register.ts",
28116
- content: () => readTemplate("lib/lifecycle-hooks/register.ts")
28171
+ content: () => readTemplate("lib/lifecycle-hooks/register.ts"),
28172
+ dependencies: ["webhooks-register-action"]
28117
28173
  },
28118
28174
  "lifecycle-hooks-register-local": {
28119
28175
  relPath: "lib/lifecycle-hooks/register.local.ts",
@@ -28298,6 +28354,119 @@ var TEMPLATE_REGISTRY = {
28298
28354
  base: "cwd",
28299
28355
  dependencies: ["profile-action", "users-action"]
28300
28356
  },
28357
+ "settings-layout": {
28358
+ relPath: "app/(admin)/admin/(authenticated)/settings/layout.tsx",
28359
+ content: () => readTemplate("pages/settings/settings-layout.tsx"),
28360
+ base: "cwd",
28361
+ dependencies: ["admin-settings-sidebar"]
28362
+ },
28363
+ "forms-settings-page": {
28364
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/page.tsx",
28365
+ content: () => readTemplate("pages/settings/forms/forms-settings-page.tsx"),
28366
+ base: "cwd",
28367
+ dependencies: ["forms-settings-page-skeleton", "forms-settings-page-content"]
28368
+ },
28369
+ "forms-settings-page-skeleton": {
28370
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-skeleton.tsx",
28371
+ content: () => readTemplate("pages/settings/forms/forms-settings-page-skeleton.tsx"),
28372
+ base: "cwd"
28373
+ },
28374
+ "forms-settings-page-content": {
28375
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-content.tsx",
28376
+ content: () => readTemplate("pages/settings/forms/forms-settings-page-content.tsx"),
28377
+ base: "cwd",
28378
+ dependencies: ["edit-form-notifications-dialog", "use-webhooks"]
28379
+ },
28380
+ "edit-form-notifications-dialog": {
28381
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/edit-form-notifications-dialog.tsx",
28382
+ content: () => readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx"),
28383
+ base: "cwd",
28384
+ dependencies: ["form-settings-action"]
28385
+ },
28386
+ "webhooks-page": {
28387
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/page.tsx",
28388
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page.tsx"),
28389
+ base: "cwd",
28390
+ dependencies: ["webhooks-page-skeleton", "webhooks-page-content"]
28391
+ },
28392
+ "webhooks-page-skeleton": {
28393
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-page-skeleton.tsx",
28394
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page-skeleton.tsx"),
28395
+ base: "cwd"
28396
+ },
28397
+ "webhooks-page-content": {
28398
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-page-content.tsx",
28399
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page-content.tsx"),
28400
+ base: "cwd",
28401
+ dependencies: [
28402
+ "webhooks-table",
28403
+ "webhooks-columns",
28404
+ "webhook-endpoint-dialog",
28405
+ "delete-webhook-dialog",
28406
+ "use-webhooks",
28407
+ "webhooks-action"
28408
+ ]
28409
+ },
28410
+ "webhooks-table": {
28411
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-table.tsx",
28412
+ content: () => readTemplate("pages/settings/webhooks/webhooks-table.tsx"),
28413
+ base: "cwd",
28414
+ dependencies: ["use-webhooks"]
28415
+ },
28416
+ "webhooks-columns": {
28417
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-columns.tsx",
28418
+ content: () => readTemplate("pages/settings/webhooks/webhooks-columns.tsx"),
28419
+ base: "cwd",
28420
+ dependencies: ["webhook-actions", "webhook-enabled-switch"]
28421
+ },
28422
+ "webhook-actions": {
28423
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-actions.tsx",
28424
+ content: () => readTemplate("pages/settings/webhooks/webhook-actions.tsx"),
28425
+ base: "cwd",
28426
+ dependencies: ["use-webhooks"]
28427
+ },
28428
+ "webhook-enabled-switch": {
28429
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-enabled-switch.tsx",
28430
+ content: () => readTemplate("pages/settings/webhooks/webhook-enabled-switch.tsx"),
28431
+ base: "cwd",
28432
+ dependencies: ["use-webhooks"]
28433
+ },
28434
+ "webhook-endpoint-dialog": {
28435
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-endpoint-dialog.tsx",
28436
+ content: () => readTemplate("pages/settings/webhooks/webhook-endpoint-dialog.tsx"),
28437
+ base: "cwd",
28438
+ dependencies: ["use-webhooks", "webhooks-action"]
28439
+ },
28440
+ "delete-webhook-dialog": {
28441
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/delete-webhook-dialog.tsx",
28442
+ content: () => readTemplate("pages/settings/webhooks/delete-webhook-dialog.tsx"),
28443
+ base: "cwd",
28444
+ dependencies: ["webhooks-action"]
28445
+ },
28446
+ "webhooks-logs-page": {
28447
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/logs/page.tsx",
28448
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-page.tsx"),
28449
+ base: "cwd",
28450
+ dependencies: ["webhooks-logs-page-content", "webhooks-page-skeleton"]
28451
+ },
28452
+ "webhooks-logs-page-content": {
28453
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-page-content.tsx",
28454
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-page-content.tsx"),
28455
+ base: "cwd",
28456
+ dependencies: ["webhooks-logs-table", "webhooks-logs-columns", "use-webhooks"]
28457
+ },
28458
+ "webhooks-logs-table": {
28459
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-table.tsx",
28460
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-table.tsx"),
28461
+ base: "cwd",
28462
+ dependencies: ["use-webhooks"]
28463
+ },
28464
+ "webhooks-logs-columns": {
28465
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-columns.tsx",
28466
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-columns.tsx"),
28467
+ base: "cwd",
28468
+ dependencies: ["webhooks-action"]
28469
+ },
28301
28470
  // Lib
28302
28471
  "email-index": {
28303
28472
  relPath: "lib/actions/email/index.ts",
@@ -28375,8 +28544,7 @@ var TEMPLATE_REGISTRY = {
28375
28544
  "form-settings-types",
28376
28545
  "get-form-settings-action",
28377
28546
  "upsert-form-settings-action",
28378
- "get-all-form-settings-action",
28379
- "test-form-webhook-action"
28547
+ "get-all-form-settings-action"
28380
28548
  ]
28381
28549
  },
28382
28550
  "form-settings-types": {
@@ -28398,9 +28566,87 @@ var TEMPLATE_REGISTRY = {
28398
28566
  content: () => readTemplate("lib/actions/forms/get-all-form-settings.ts"),
28399
28567
  dependencies: ["form-settings-types"]
28400
28568
  },
28401
- "test-form-webhook-action": {
28402
- relPath: "lib/actions/forms/test-form-webhook.ts",
28403
- content: () => readTemplate("lib/actions/forms/test-form-webhook.ts")
28569
+ "webhooks-action": {
28570
+ relPath: "lib/actions/webhooks/index.ts",
28571
+ content: () => readTemplate("lib/actions/webhooks/index.ts"),
28572
+ dependencies: [
28573
+ "webhooks-action-types",
28574
+ "internal-get-active-endpoints-action",
28575
+ "webhooks-dispatch",
28576
+ "get-webhooks-action",
28577
+ "create-webhook-action",
28578
+ "update-webhook-action",
28579
+ "delete-webhook-action",
28580
+ "get-webhook-secret-action",
28581
+ "regenerate-webhook-secret-action",
28582
+ "get-webhook-subscriptions-action",
28583
+ "send-test-webhook-action",
28584
+ "get-webhook-deliveries-action",
28585
+ "webhooks-register-action"
28586
+ ]
28587
+ },
28588
+ "webhooks-action-types": {
28589
+ relPath: "lib/actions/webhooks/types.ts",
28590
+ content: () => readTemplate("lib/actions/webhooks/types.ts")
28591
+ },
28592
+ "internal-get-active-endpoints-action": {
28593
+ relPath: "lib/actions/webhooks/internal-get-active-endpoints.ts",
28594
+ content: () => readTemplate("lib/actions/webhooks/internal-get-active-endpoints.ts")
28595
+ },
28596
+ "webhooks-dispatch": {
28597
+ relPath: "lib/actions/webhooks/dispatch.ts",
28598
+ content: () => readTemplate("lib/actions/webhooks/dispatch.ts"),
28599
+ dependencies: ["webhook-signature", "internal-get-active-endpoints-action"]
28600
+ },
28601
+ "get-webhooks-action": {
28602
+ relPath: "lib/actions/webhooks/get-webhooks.ts",
28603
+ content: () => readTemplate("lib/actions/webhooks/get-webhooks.ts"),
28604
+ dependencies: ["webhooks-action-types"]
28605
+ },
28606
+ "create-webhook-action": {
28607
+ relPath: "lib/actions/webhooks/create-webhook.ts",
28608
+ content: () => readTemplate("lib/actions/webhooks/create-webhook.ts"),
28609
+ dependencies: ["webhooks-action-types", "webhook-signature"]
28610
+ },
28611
+ "update-webhook-action": {
28612
+ relPath: "lib/actions/webhooks/update-webhook.ts",
28613
+ content: () => readTemplate("lib/actions/webhooks/update-webhook.ts"),
28614
+ dependencies: ["webhooks-action-types"]
28615
+ },
28616
+ "delete-webhook-action": {
28617
+ relPath: "lib/actions/webhooks/delete-webhook.ts",
28618
+ content: () => readTemplate("lib/actions/webhooks/delete-webhook.ts"),
28619
+ dependencies: ["webhooks-action-types"]
28620
+ },
28621
+ "get-webhook-secret-action": {
28622
+ relPath: "lib/actions/webhooks/get-webhook-secret.ts",
28623
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-secret.ts"),
28624
+ dependencies: ["webhooks-action-types"]
28625
+ },
28626
+ "regenerate-webhook-secret-action": {
28627
+ relPath: "lib/actions/webhooks/regenerate-webhook-secret.ts",
28628
+ content: () => readTemplate("lib/actions/webhooks/regenerate-webhook-secret.ts"),
28629
+ dependencies: ["webhooks-action-types", "webhook-signature"]
28630
+ },
28631
+ "get-webhook-subscriptions-action": {
28632
+ relPath: "lib/actions/webhooks/get-webhook-subscriptions.ts",
28633
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-subscriptions.ts"),
28634
+ dependencies: ["webhooks-action-types"]
28635
+ },
28636
+ "send-test-webhook-action": {
28637
+ relPath: "lib/actions/webhooks/send-test-webhook.ts",
28638
+ content: () => readTemplate("lib/actions/webhooks/send-test-webhook.ts"),
28639
+ dependencies: ["webhooks-action-types", "webhook-signature", "webhooks-dispatch"]
28640
+ },
28641
+ "get-webhook-deliveries-action": {
28642
+ relPath: "lib/actions/webhooks/get-webhook-deliveries.ts",
28643
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-deliveries.ts"),
28644
+ dependencies: ["webhooks-action-types"]
28645
+ },
28646
+ "webhooks-register-action": {
28647
+ relPath: "lib/actions/webhooks/register.ts",
28648
+ content: () => readTemplate("lib/actions/webhooks/register.ts"),
28649
+ dependencies: ["webhooks-dispatch", "webhooks-types"]
28404
28650
  },
28405
28651
  "entity-versions-action": {
28406
28652
  relPath: "lib/actions/entity-versions/index.ts",
@@ -28688,44 +28934,50 @@ var TEMPLATE_REGISTRY = {
28688
28934
  dependencies: ["use-media", "media-grid-pagination"]
28689
28935
  },
28690
28936
  "audit-log-page": {
28691
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/page.tsx",
28692
- content: () => readTemplate("pages/audit-log/audit-log-page.tsx"),
28937
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/page.tsx",
28938
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page.tsx"),
28693
28939
  base: "cwd",
28694
28940
  dependencies: ["audit-log-page-content", "audit-log-page-skeleton"]
28695
28941
  },
28696
28942
  "audit-log-page-skeleton": {
28697
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-page-skeleton.tsx",
28698
- content: () => readTemplate("pages/audit-log/audit-log-page-skeleton.tsx"),
28943
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-page-skeleton.tsx",
28944
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page-skeleton.tsx"),
28699
28945
  base: "cwd",
28700
28946
  dependencies: ["page-header", "skeleton"]
28701
28947
  },
28702
28948
  "audit-log-page-content": {
28703
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-page-content.tsx",
28704
- content: () => readTemplate("pages/audit-log/audit-log-page-content.tsx"),
28949
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-page-content.tsx",
28950
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page-content.tsx"),
28705
28951
  base: "cwd",
28706
28952
  dependencies: [
28953
+ "audit-log-columns",
28707
28954
  "audit-log-detail-drawer",
28708
- "badge",
28709
- "button",
28710
- "card",
28711
- "data-grid",
28712
- "data-table-pagination",
28955
+ "audit-log-table",
28713
28956
  "entity-filters-bar",
28714
- "page-header",
28715
- "sort-indicator",
28716
- "use-table-utils",
28717
- "use-audit-log"
28957
+ "page-header"
28718
28958
  ]
28719
28959
  },
28960
+ "audit-log-table": {
28961
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-table.tsx",
28962
+ content: () => readTemplate("pages/settings/audit-log/audit-log-table.tsx"),
28963
+ base: "cwd",
28964
+ dependencies: ["data-grid", "data-table-pagination", "use-audit-log", "use-table-utils"]
28965
+ },
28966
+ "audit-log-columns": {
28967
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-columns.tsx",
28968
+ content: () => readTemplate("pages/settings/audit-log/audit-log-columns.tsx"),
28969
+ base: "cwd",
28970
+ dependencies: ["badge", "button", "sort-indicator"]
28971
+ },
28720
28972
  "audit-log-detail-drawer": {
28721
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-detail-drawer.tsx",
28722
- content: () => readTemplate("pages/audit-log/audit-log-detail-drawer.tsx"),
28973
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-detail-drawer.tsx",
28974
+ content: () => readTemplate("pages/settings/audit-log/audit-log-detail-drawer.tsx"),
28723
28975
  base: "cwd",
28724
28976
  dependencies: ["audit-log-detail-row", "badge", "drawer", "scroll-area"]
28725
28977
  },
28726
28978
  "audit-log-detail-row": {
28727
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-detail-row.tsx",
28728
- content: () => readTemplate("pages/audit-log/audit-log-detail-row.tsx"),
28979
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-detail-row.tsx",
28980
+ content: () => readTemplate("pages/settings/audit-log/audit-log-detail-row.tsx"),
28729
28981
  base: "cwd"
28730
28982
  }
28731
28983
  };