@vertebr/react 0.9.0 → 0.10.0

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 (66) hide show
  1. package/dist/components/admin/AddonEditor.d.ts +3 -2
  2. package/dist/components/admin/AddonEditor.d.ts.map +1 -1
  3. package/dist/components/admin/AddonEditor.js +4 -2
  4. package/dist/components/admin/AdminContent.d.ts +4 -1
  5. package/dist/components/admin/AdminContent.d.ts.map +1 -1
  6. package/dist/components/admin/AdminContent.js +25 -11
  7. package/dist/components/admin/AdminTopBar.d.ts +6 -2
  8. package/dist/components/admin/AdminTopBar.d.ts.map +1 -1
  9. package/dist/components/admin/AdminTopBar.js +27 -4
  10. package/dist/components/admin/AuditBoard.d.ts +7 -0
  11. package/dist/components/admin/AuditBoard.d.ts.map +1 -0
  12. package/dist/components/admin/AuditBoard.js +43 -0
  13. package/dist/components/admin/HealthModal.d.ts +9 -0
  14. package/dist/components/admin/HealthModal.d.ts.map +1 -0
  15. package/dist/components/admin/HealthModal.js +59 -0
  16. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  17. package/dist/components/admin/VertebrAdmin.js +88 -22
  18. package/dist/components/admin/adminSearch.d.ts.map +1 -1
  19. package/dist/components/admin/adminSearch.js +9 -0
  20. package/dist/components/admin/breadcrumbs.d.ts.map +1 -1
  21. package/dist/components/admin/breadcrumbs.js +2 -0
  22. package/dist/components/admin/routes.d.ts.map +1 -1
  23. package/dist/components/admin/routes.js +13 -1
  24. package/dist/components/admin/types.d.ts +2 -0
  25. package/dist/components/admin/types.d.ts.map +1 -1
  26. package/dist/components/config/ReferenceInput.d.ts +11 -0
  27. package/dist/components/config/ReferenceInput.d.ts.map +1 -0
  28. package/dist/components/config/ReferenceInput.js +99 -0
  29. package/dist/components/config/RevisionHistory.d.ts +8 -0
  30. package/dist/components/config/RevisionHistory.d.ts.map +1 -0
  31. package/dist/components/config/RevisionHistory.js +84 -0
  32. package/dist/components/config/form/ConfigField.d.ts +3 -2
  33. package/dist/components/config/form/ConfigField.d.ts.map +1 -1
  34. package/dist/components/config/form/ConfigField.js +3 -2
  35. package/dist/components/config/form/ConfigForm.d.ts +1 -1
  36. package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
  37. package/dist/components/config/form/ConfigForm.js +13 -6
  38. package/dist/components/config/form/RecordConfigForm.d.ts +3 -2
  39. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
  40. package/dist/components/config/form/RecordConfigForm.js +2 -1
  41. package/dist/components/config/form/SettingsConfigForm.d.ts +3 -2
  42. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
  43. package/dist/components/config/form/SettingsConfigForm.js +2 -2
  44. package/dist/components/config/form/types.d.ts +5 -1
  45. package/dist/components/config/form/types.d.ts.map +1 -1
  46. package/dist/components/resources/ResourceTable.d.ts +7 -1
  47. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  48. package/dist/components/resources/ResourceTable.js +56 -6
  49. package/dist/components/theme/ThemeToggle.d.ts +3 -1
  50. package/dist/components/theme/ThemeToggle.d.ts.map +1 -1
  51. package/dist/components/theme/ThemeToggle.js +2 -2
  52. package/dist/components/ui/icons.d.ts +1 -0
  53. package/dist/components/ui/icons.d.ts.map +1 -1
  54. package/dist/components/ui/icons.js +6 -1
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -0
  58. package/dist/lib/capabilities.d.ts +4 -0
  59. package/dist/lib/capabilities.d.ts.map +1 -0
  60. package/dist/lib/capabilities.js +9 -0
  61. package/dist/styles/audit.css +34 -0
  62. package/dist/styles/form.css +143 -0
  63. package/dist/styles/index.css +1 -0
  64. package/dist/styles/modal.css +111 -0
  65. package/dist/styles/shell.css +234 -18
  66. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
2
  import { createVertebrClient, } from "@vertebr/client";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useVertebrTheme } from "../../hooks/useVertebrTheme";
5
+ import { hasCap } from "../../lib/capabilities";
5
6
  import { parseMetricsRangeId, } from "../../lib/metricsRanges";
6
7
  import { AdminCommandPalette } from "./AdminCommandPalette";
7
8
  import { AdminContent } from "./AdminContent";
@@ -39,6 +40,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
39
40
  });
40
41
  });
41
42
  const [resourceData, setResourceData] = useState(null);
43
+ const [resourceSearch, setResourceSearch] = useState("");
42
44
  const [configData, setConfigData] = useState(null);
43
45
  const [metricsData, setMetricsData] = useState(null);
44
46
  const [metricsLoading, setMetricsLoading] = useState(false);
@@ -49,6 +51,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
49
51
  const [fontLibraryLoading, setFontLibraryLoading] = useState(false);
50
52
  const [emailsData, setEmailsData] = useState(null);
51
53
  const [emailsLoading, setEmailsLoading] = useState(false);
54
+ const [healthChecks, setHealthChecks] = useState(null);
52
55
  const [error, setError] = useState(null);
53
56
  const [loading, setLoading] = useState(true);
54
57
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
@@ -210,7 +213,10 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
210
213
  }
211
214
  }
212
215
  else if (selection.kind === "resource") {
213
- const data = await client.listResource(selection.id);
216
+ const data = await client.listResource(selection.id, {
217
+ ...(resourceSearch.trim() ? { q: resourceSearch.trim() } : {}),
218
+ fresh: true,
219
+ });
214
220
  if (!cancelled) {
215
221
  setResourceData(data);
216
222
  setConfigData(null);
@@ -220,7 +226,9 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
220
226
  setEmailsData(null);
221
227
  }
222
228
  }
223
- else if (selection.kind === "sync" || selection.kind === "addons") {
229
+ else if (selection.kind === "sync" ||
230
+ selection.kind === "addons" ||
231
+ selection.kind === "audit") {
224
232
  if (!cancelled) {
225
233
  setResourceData(null);
226
234
  setConfigData(null);
@@ -270,12 +278,54 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
270
278
  return () => {
271
279
  cancelled = true;
272
280
  };
273
- }, [client, selection, catalog, environment, metricsRange]);
281
+ }, [client, selection, catalog, environment, metricsRange, resourceSearch]);
282
+ const resourceSelectionId = selection?.kind === "resource" ? selection.id : null;
283
+ useEffect(() => {
284
+ setResourceSearch("");
285
+ }, [resourceSelectionId]);
286
+ const refreshHealth = useCallback(async () => {
287
+ if (!catalog?.health) {
288
+ setHealthChecks(null);
289
+ return;
290
+ }
291
+ const data = await client.getHealth({ fresh: true });
292
+ setHealthChecks(data.checks);
293
+ }, [client, catalog]);
294
+ useEffect(() => {
295
+ if (!catalog?.health) {
296
+ setHealthChecks(null);
297
+ return;
298
+ }
299
+ let cancelled = false;
300
+ void (async () => {
301
+ try {
302
+ const data = await client.getHealth({ fresh: true });
303
+ if (!cancelled)
304
+ setHealthChecks(data.checks);
305
+ }
306
+ catch {
307
+ if (!cancelled)
308
+ setHealthChecks(null);
309
+ }
310
+ })();
311
+ return () => {
312
+ cancelled = true;
313
+ };
314
+ }, [client, catalog?.health, environment]);
274
315
  const refreshResource = useCallback(async () => {
275
316
  if (selection?.kind !== "resource")
276
317
  return;
277
- const data = await client.listResource(selection.id);
318
+ const data = await client.listResource(selection.id, {
319
+ ...(resourceSearch.trim() ? { q: resourceSearch.trim() } : {}),
320
+ fresh: true,
321
+ });
278
322
  setResourceData(data);
323
+ }, [client, selection, resourceSearch]);
324
+ const refreshConfig = useCallback(async () => {
325
+ if (selection?.kind !== "config" && selection?.kind !== "addon")
326
+ return;
327
+ const data = await client.getConfig(selection.id, { fresh: true });
328
+ setConfigData(data);
279
329
  }, [client, selection]);
280
330
  const refreshMetrics = useCallback(async () => {
281
331
  setMetricsLoading(true);
@@ -358,11 +408,14 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
358
408
  const envList = catalog?.environments ?? bootstrap?.environments ?? [];
359
409
  const activeEnv = envList.find((env) => env.id === environment);
360
410
  const authRequired = Boolean(activeEnv?.auth?.required);
411
+ const authProvider = activeEnv?.auth?.client?.provider;
412
+ const authViaTokenOnly = authProvider === "custom" && Boolean(getAccessToken);
361
413
  const activeConfig = selection?.kind === "config"
362
414
  ? catalog?.configs.find((c) => c.id === selection.id)
363
415
  : undefined;
364
416
  const isPageSelection = activeConfig?.section === "page";
365
417
  const hasPagePreview = Boolean(activeConfig?.previewPath);
418
+ const syncEnabled = Boolean(catalog?.sync?.enabled) && hasCap(catalog, "sync.run");
366
419
  const switchEnvironment = (nextEnv) => {
367
420
  if (nextEnv === environment)
368
421
  return;
@@ -375,6 +428,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
375
428
  writeStored(envStorageKey, nextEnv);
376
429
  setEnvironment(nextEnv);
377
430
  setResourceData(null);
431
+ setResourceSearch("");
378
432
  setConfigData(null);
379
433
  setGalleryData(null);
380
434
  setFontLibraryData(null);
@@ -406,6 +460,16 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
406
460
  onSelect: () => setSelection({ kind: "overview" }),
407
461
  });
408
462
  }
463
+ if (catalog.audit) {
464
+ entries.push({
465
+ key: "audit",
466
+ group: catalog.audit.group ?? "Analytics",
467
+ label: catalog.audit.label,
468
+ icon: iconForNavKey("audit"),
469
+ active: selection?.kind === "audit",
470
+ onSelect: () => setSelection({ kind: "audit" }),
471
+ });
472
+ }
409
473
  for (const resource of catalog.resources) {
410
474
  entries.push({
411
475
  key: `resource:${resource.id}`,
@@ -498,7 +562,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
498
562
  onSelect: () => setSelection({ kind: "config", id: config.id }),
499
563
  });
500
564
  }
501
- if (catalog.sync?.enabled) {
565
+ if (catalog.sync?.enabled && hasCap(catalog, "sync.run")) {
502
566
  entries.push({
503
567
  key: "sync",
504
568
  group: "Settings",
@@ -520,25 +584,27 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
520
584
  ? (catalog?.metrics?.label ?? "Overview")
521
585
  : selection?.kind === "sync"
522
586
  ? "Sync environments"
523
- : selection?.kind === "addons"
524
- ? "Add-ons"
525
- : selection?.kind === "gallery"
526
- ? (catalog?.gallery?.label ?? "Gallery")
527
- : selection?.kind === "fontLibrary"
528
- ? (catalog?.fontLibrary?.label ?? "Font Library")
529
- : selection?.kind === "emails"
530
- ? (catalog?.emails?.label ?? "Emails")
531
- : selection?.kind === "resource"
532
- ? catalog?.resources.find((r) => r.id === selection.id)?.label
533
- : selection?.kind === "config"
534
- ? catalog?.configs.find((c) => c.id === selection.id)?.label
535
- : selection?.kind === "addon"
536
- ? catalog?.addons?.find((a) => a.id === selection.id)?.label
537
- : null;
587
+ : selection?.kind === "audit"
588
+ ? (catalog?.audit?.label ?? "Audit")
589
+ : selection?.kind === "addons"
590
+ ? "Add-ons"
591
+ : selection?.kind === "gallery"
592
+ ? (catalog?.gallery?.label ?? "Gallery")
593
+ : selection?.kind === "fontLibrary"
594
+ ? (catalog?.fontLibrary?.label ?? "Font Library")
595
+ : selection?.kind === "emails"
596
+ ? (catalog?.emails?.label ?? "Emails")
597
+ : selection?.kind === "resource"
598
+ ? catalog?.resources.find((r) => r.id === selection.id)?.label
599
+ : selection?.kind === "config"
600
+ ? catalog?.configs.find((c) => c.id === selection.id)?.label
601
+ : selection?.kind === "addon"
602
+ ? catalog?.addons?.find((a) => a.id === selection.id)?.label
603
+ : null;
538
604
  const selectionDescription = selection?.kind === "addons"
539
605
  ? "Preview how optional storefront features look in your shop, then configure each one."
540
606
  : selection?.kind === "addon"
541
607
  ? catalog?.addons?.find((item) => item.id === selection.id)?.description
542
608
  : undefined;
543
- return (_jsx(SaveBarProvider, { children: _jsx(ToastProvider, { children: _jsxs("div", { className: "vertebr-root", "data-env": environment, "data-sidebar-collapsed": sidebarCollapsed || undefined, children: [_jsx(AdminTopBar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", hostName: bootstrap?.appearance?.hostName ?? catalog?.appearance?.hostName, hostLogoUrl: bootstrap?.appearance?.hostLogoUrl ?? catalog?.appearance?.hostLogoUrl, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, authUserLabel: authUserLabel, onSignOut: onSignOut, getAccessToken: getAccessToken, authRequired: authRequired, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme, sidebarCollapsed: sidebarCollapsed, onToggleSidebar: () => setSidebarCollapsed((prev) => !prev), syncEnabled: Boolean(catalog?.sync?.enabled), onOpenSync: () => setSelection({ kind: "sync" }), onOpenSearch: () => setSearchOpen(true) }), catalog && searchOpen ? (_jsx(AdminCommandPalette, { catalog: catalog, onClose: () => setSearchOpen(false), onSelect: setSelection })) : null, _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, selectionDescription: selectionDescription, error: error, breadcrumbs: breadcrumbsForSelection(selection, catalog) }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, onSelectionChange: setSelection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }), _jsx(AdminSaveBar, {})] }), _jsx(AdminToastStack, {})] }) }) }));
609
+ return (_jsx(SaveBarProvider, { children: _jsx(ToastProvider, { children: _jsxs("div", { className: "vertebr-root", "data-env": environment, "data-sidebar-collapsed": sidebarCollapsed || undefined, children: [_jsx(AdminTopBar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", hostName: bootstrap?.appearance?.hostName ?? catalog?.appearance?.hostName, hostLogoUrl: bootstrap?.appearance?.hostLogoUrl ?? catalog?.appearance?.hostLogoUrl, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, authUserLabel: authUserLabel, onSignOut: onSignOut, getAccessToken: getAccessToken, authViaTokenOnly: authViaTokenOnly, authRequired: authRequired, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme, sidebarCollapsed: sidebarCollapsed, onToggleSidebar: () => setSidebarCollapsed((prev) => !prev), syncEnabled: syncEnabled, onOpenSync: () => setSelection({ kind: "sync" }), onOpenSearch: () => setSearchOpen(true), healthChecks: healthChecks, onRefreshHealth: catalog?.health ? refreshHealth : undefined }), catalog && searchOpen ? (_jsx(AdminCommandPalette, { catalog: catalog, onClose: () => setSearchOpen(false), onSelect: setSelection })) : null, _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, selectionDescription: selectionDescription, error: error, breadcrumbs: breadcrumbsForSelection(selection, catalog) }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, onSelectionChange: setSelection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, resourceSearch: resourceSearch, onResourceSearchChange: setResourceSearch, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, onRefreshConfig: refreshConfig, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }), _jsx(AdminSaveBar, {})] }), _jsx(AdminToastStack, {})] }) }) }));
544
610
  }
@@ -1 +1 @@
1
- {"version":3,"file":"adminSearch.d.ts","sourceRoot":"","sources":["../../../src/components/admin/adminSearch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,EAAE,CA6G5E;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,EAAE,EACxB,KAAK,EAAE,MAAM,GACZ,eAAe,EAAE,CASnB"}
1
+ {"version":3,"file":"adminSearch.d.ts","sourceRoot":"","sources":["../../../src/components/admin/adminSearch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,EAAE,CAuH5E;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,EAAE,EACxB,KAAK,EAAE,MAAM,GACZ,eAAe,EAAE,CASnB"}
@@ -9,6 +9,15 @@ export function adminSearchItems(catalog) {
9
9
  selection: { kind: "overview" },
10
10
  });
11
11
  }
12
+ if (catalog.audit) {
13
+ items.push({
14
+ id: "audit",
15
+ label: catalog.audit.label,
16
+ group: catalog.audit.group ?? "Analytics",
17
+ hint: "Audit log",
18
+ selection: { kind: "audit" },
19
+ });
20
+ }
12
21
  for (const resource of catalog.resources) {
13
22
  items.push({
14
23
  id: `resource:${resource.id}`,
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../../src/components/admin/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,UAAU,EAAE,CAyDd"}
1
+ {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../../src/components/admin/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,UAAU,EAAE,CA2Dd"}
@@ -7,6 +7,8 @@ export function breadcrumbsForSelection(selection, catalog) {
7
7
  return [...crumbs, { label: catalog.metrics?.label ?? "Overview" }];
8
8
  case "sync":
9
9
  return [...crumbs, { label: "Sync environments" }];
10
+ case "audit":
11
+ return [...crumbs, { label: catalog.audit?.label ?? "Audit" }];
10
12
  case "addons":
11
13
  return [...crumbs, { label: "Add-ons" }];
12
14
  case "addon": {
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/components/admin/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,SAAS,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO5D;AAOD,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAgCR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAIR;AASD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,SAAS,GAAG,IAAI,CAgDlB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAUpE;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,SAAS,CAoCX;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAgBnE"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/components/admin/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,SAAS,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO5D;AAOD,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAkCR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAIR;AASD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,SAAS,GAAG,IAAI,CAiDlB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAUpE;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,SAAS,CAuCX;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAuBnE"}
@@ -25,6 +25,8 @@ export function pathForSelection(selection, catalog) {
25
25
  return "/overview";
26
26
  case "sync":
27
27
  return "/sync";
28
+ case "audit":
29
+ return "/audit";
28
30
  case "addons":
29
31
  return "/addons";
30
32
  case "addon":
@@ -85,6 +87,8 @@ export function selectionFromPath(pathname, basePath, catalog) {
85
87
  return { kind: "overview" };
86
88
  if (head === "sync")
87
89
  return { kind: "sync" };
90
+ if (head === "audit")
91
+ return { kind: "audit" };
88
92
  if (head === "addons") {
89
93
  if (!tail[0])
90
94
  return { kind: "addons" };
@@ -154,6 +158,9 @@ export function coerceSelection(selection, catalog) {
154
158
  if (selection.kind === "sync") {
155
159
  return catalog.sync?.enabled ? selection : defaultSelection(catalog);
156
160
  }
161
+ if (selection.kind === "audit") {
162
+ return catalog.audit ? selection : defaultSelection(catalog);
163
+ }
157
164
  if (selection.kind === "addons") {
158
165
  return catalog.addons?.length ? selection : defaultSelection(catalog);
159
166
  }
@@ -190,7 +197,12 @@ export function selectionsEqual(a, b) {
190
197
  return a === b;
191
198
  if (a.kind !== b.kind)
192
199
  return false;
193
- if (a.kind === "overview" || a.kind === "gallery" || a.kind === "fontLibrary" || a.kind === "sync" || a.kind === "addons") {
200
+ if (a.kind === "overview" ||
201
+ a.kind === "gallery" ||
202
+ a.kind === "fontLibrary" ||
203
+ a.kind === "sync" ||
204
+ a.kind === "audit" ||
205
+ a.kind === "addons") {
194
206
  return true;
195
207
  }
196
208
  if (a.kind === "addon" && b.kind === "addon")
@@ -47,6 +47,8 @@ export type Selection = {
47
47
  } | {
48
48
  kind: "emails";
49
49
  tab?: EmailsTab;
50
+ } | {
51
+ kind: "audit";
50
52
  } | {
51
53
  kind: "config";
52
54
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/admin/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,YAAY,CAAC;AAEtE,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B,IAAI,CAAC;AAET,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/admin/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,YAAY,CAAC;AAEtE,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B,IAAI,CAAC;AAET,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { FieldSchema, VertebrClient } from "@vertebr/client";
2
+ export type ReferenceInputProps = {
3
+ id?: string;
4
+ value: unknown;
5
+ field: FieldSchema;
6
+ client: VertebrClient;
7
+ readOnly?: boolean;
8
+ onChange: (value: string) => void;
9
+ };
10
+ export declare function ReferenceInput({ id: idProp, value, field, client, readOnly, onChange, }: ReferenceInputProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=ReferenceInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReferenceInput.d.ts","sourceRoot":"","sources":["../../../src/components/config/ReferenceInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAuBF,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EAAE,MAAM,EACV,KAAK,EACL,KAAK,EACL,MAAM,EACN,QAAQ,EACR,QAAQ,GACT,EAAE,mBAAmB,+BA0IrB"}
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ function rowValue(row, valueField) {
4
+ const raw = row[valueField] ?? row.id ?? row.slug ?? row.uid;
5
+ if (typeof raw === "string" && raw.trim())
6
+ return raw;
7
+ if (typeof raw === "number")
8
+ return String(raw);
9
+ return null;
10
+ }
11
+ function rowLabel(row, labelField, value) {
12
+ const raw = row[labelField] ?? row.name ?? row.title ?? row.label ?? value;
13
+ return typeof raw === "string" && raw.trim() ? raw.trim() : value;
14
+ }
15
+ export function ReferenceInput({ id: idProp, value, field, client, readOnly, onChange, }) {
16
+ const autoId = useId();
17
+ const id = idProp ?? autoId;
18
+ const resource = field.reference?.resource ?? "";
19
+ const valueField = field.reference?.valueField ?? "id";
20
+ const labelField = field.reference?.labelField ?? "name";
21
+ const current = typeof value === "string" ? value : "";
22
+ const [query, setQuery] = useState("");
23
+ const [open, setOpen] = useState(false);
24
+ const [options, setOptions] = useState([]);
25
+ const [loading, setLoading] = useState(false);
26
+ const [displayLabel, setDisplayLabel] = useState(current);
27
+ const rootRef = useRef(null);
28
+ useEffect(() => {
29
+ setDisplayLabel(current);
30
+ }, [current]);
31
+ useEffect(() => {
32
+ if (!open || !resource || readOnly)
33
+ return;
34
+ let cancelled = false;
35
+ const handle = window.setTimeout(() => {
36
+ void (async () => {
37
+ setLoading(true);
38
+ try {
39
+ const listed = await client.listResource(resource, {
40
+ q: query.trim() || undefined,
41
+ limit: 20,
42
+ fresh: true,
43
+ });
44
+ if (cancelled)
45
+ return;
46
+ const next = [];
47
+ for (const row of listed.items) {
48
+ const v = rowValue(row, valueField);
49
+ if (!v)
50
+ continue;
51
+ next.push({ value: v, label: rowLabel(row, labelField, v) });
52
+ }
53
+ setOptions(next);
54
+ }
55
+ catch {
56
+ if (!cancelled)
57
+ setOptions([]);
58
+ }
59
+ finally {
60
+ if (!cancelled)
61
+ setLoading(false);
62
+ }
63
+ })();
64
+ }, 180);
65
+ return () => {
66
+ cancelled = true;
67
+ window.clearTimeout(handle);
68
+ };
69
+ }, [open, query, resource, client, valueField, labelField, readOnly]);
70
+ useEffect(() => {
71
+ const onPointer = (event) => {
72
+ if (!rootRef.current?.contains(event.target)) {
73
+ setOpen(false);
74
+ }
75
+ };
76
+ document.addEventListener("mousedown", onPointer);
77
+ return () => document.removeEventListener("mousedown", onPointer);
78
+ }, []);
79
+ if (!resource) {
80
+ return (_jsx("input", { id: id, type: "text", readOnly: readOnly, value: current, onChange: (event) => onChange(event.target.value), placeholder: "Missing reference.resource" }));
81
+ }
82
+ return (_jsxs("div", { className: "vertebr-reference", ref: rootRef, "data-open": open || undefined, children: [_jsx("input", { id: id, type: "text", className: "vertebr-reference-input", readOnly: readOnly, value: open ? query : displayLabel || current, placeholder: "Search\u2026", autoComplete: "off", onFocus: () => {
83
+ if (readOnly)
84
+ return;
85
+ setQuery("");
86
+ setOpen(true);
87
+ }, onChange: (event) => {
88
+ setQuery(event.target.value);
89
+ setOpen(true);
90
+ } }), current && !readOnly ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-reference-clear", onClick: () => {
91
+ onChange("");
92
+ setDisplayLabel("");
93
+ setQuery("");
94
+ }, children: "Clear" })) : null, open && !readOnly ? (_jsx("ul", { className: "vertebr-reference-menu", role: "listbox", children: loading ? (_jsx("li", { className: "vertebr-reference-empty", children: "Searching\u2026" })) : options.length === 0 ? (_jsx("li", { className: "vertebr-reference-empty", children: "No matches" })) : (options.map((option) => (_jsx("li", { children: _jsxs("button", { type: "button", role: "option", "aria-selected": option.value === current, className: "vertebr-reference-option", onClick: () => {
95
+ onChange(option.value);
96
+ setDisplayLabel(option.label);
97
+ setOpen(false);
98
+ }, children: [_jsx("span", { children: option.label }), _jsx("code", { children: option.value })] }) }, option.value)))) })) : null] }));
99
+ }
@@ -0,0 +1,8 @@
1
+ import type { VertebrClient } from "@vertebr/client";
2
+ export type RevisionHistoryProps = {
3
+ client: VertebrClient;
4
+ configId: string;
5
+ onPublished?: () => void | Promise<void>;
6
+ };
7
+ export declare function RevisionHistory({ client, configId, onPublished, }: RevisionHistoryProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=RevisionHistory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RevisionHistory.d.ts","sourceRoot":"","sources":["../../../src/components/config/RevisionHistory.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGnE,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAaF,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,QAAQ,EACR,WAAW,GACZ,EAAE,oBAAoB,+BAmHtB"}
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { useToast } from "../../hooks/useToast";
4
+ function formatWhen(at) {
5
+ const date = new Date(at);
6
+ if (Number.isNaN(date.getTime()))
7
+ return at;
8
+ return date.toLocaleString(undefined, {
9
+ month: "short",
10
+ day: "numeric",
11
+ hour: "numeric",
12
+ minute: "2-digit",
13
+ });
14
+ }
15
+ export function RevisionHistory({ client, configId, onPublished, }) {
16
+ const { push } = useToast();
17
+ const [items, setItems] = useState([]);
18
+ const [loading, setLoading] = useState(true);
19
+ const [busyId, setBusyId] = useState(null);
20
+ const refresh = useCallback(async () => {
21
+ setLoading(true);
22
+ try {
23
+ const data = await client.listRevisions(configId, { fresh: true });
24
+ setItems(data.items);
25
+ }
26
+ finally {
27
+ setLoading(false);
28
+ }
29
+ }, [client, configId]);
30
+ useEffect(() => {
31
+ let cancelled = false;
32
+ void (async () => {
33
+ try {
34
+ setLoading(true);
35
+ const data = await client.listRevisions(configId, { fresh: true });
36
+ if (!cancelled)
37
+ setItems(data.items);
38
+ }
39
+ catch (err) {
40
+ if (!cancelled) {
41
+ push(err instanceof Error ? err.message : "Failed to load revisions", "danger");
42
+ }
43
+ }
44
+ finally {
45
+ if (!cancelled)
46
+ setLoading(false);
47
+ }
48
+ })();
49
+ return () => {
50
+ cancelled = true;
51
+ };
52
+ }, [client, configId, push]);
53
+ const publish = async (revId) => {
54
+ setBusyId(revId);
55
+ try {
56
+ await client.publishRevision(configId, revId);
57
+ push("Revision published", "ok");
58
+ await refresh();
59
+ await onPublished?.();
60
+ }
61
+ catch (err) {
62
+ push(err instanceof Error ? err.message : "Publish failed", "danger");
63
+ }
64
+ finally {
65
+ setBusyId(null);
66
+ }
67
+ };
68
+ const restore = async (revId) => {
69
+ setBusyId(revId);
70
+ try {
71
+ await client.restoreRevision(configId, revId);
72
+ push("Draft restored from revision", "ok");
73
+ await refresh();
74
+ await onPublished?.();
75
+ }
76
+ catch (err) {
77
+ push(err instanceof Error ? err.message : "Restore failed", "danger");
78
+ }
79
+ finally {
80
+ setBusyId(null);
81
+ }
82
+ };
83
+ return (_jsxs("div", { className: "vertebr-revisions", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h3", { children: "Revisions" }), _jsx("span", { className: "vertebr-count", children: loading ? "…" : `${items.length}` })] }), items.length === 0 && !loading ? (_jsx("p", { className: "vertebr-revisions-empty", children: "No revisions yet." })) : (_jsx("ul", { className: "vertebr-revisions-list", children: items.map((rev) => (_jsxs("li", { className: "vertebr-revisions-item", children: [_jsxs("div", { className: "vertebr-revisions-meta", children: [_jsx("strong", { children: rev.label?.trim() || rev.id }), _jsx("span", { "data-status": rev.status, children: rev.status }), _jsxs("span", { className: "vertebr-revisions-when", children: [formatWhen(rev.createdAt), rev.createdBy ? ` · ${rev.createdBy}` : ""] })] }), _jsxs("div", { className: "vertebr-revisions-actions", children: [rev.status !== "published" ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyId != null, onClick: () => void publish(rev.id), children: busyId === rev.id ? "…" : "Publish" })) : null, _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyId != null, onClick: () => void restore(rev.id), children: "Restore" })] })] }, rev.id))) }))] }));
84
+ }
@@ -1,5 +1,5 @@
1
1
  import { type UseFormReturn } from "react-hook-form";
2
- import type { FieldSchema, ResourceUploadMeta } from "@vertebr/client";
2
+ import type { FieldSchema, ResourceUploadMeta, VertebrClient } from "@vertebr/client";
3
3
  import type { ConfigFormVariant, FontLibraryHandlers, GalleryHandlers } from "./types";
4
4
  export type ConfigFieldProps = GalleryHandlers & FontLibraryHandlers & {
5
5
  fieldKey: string;
@@ -19,6 +19,7 @@ export type ConfigFieldProps = GalleryHandlers & FontLibraryHandlers & {
19
19
  visibilityGroup?: string;
20
20
  /** Render boolean fields as rows in a settings list (config forms). */
21
21
  settingsRow?: boolean;
22
+ client?: VertebrClient;
22
23
  };
23
- export declare function ConfigField({ fieldKey, field, groupLabel, variant, schema, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, hideImagePreview, hideUpload, visibilityGroup, settingsRow, }: ConfigFieldProps): import("react").JSX.Element;
24
+ export declare function ConfigField({ fieldKey, field, groupLabel, variant, schema, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, hideImagePreview, hideUpload, visibilityGroup, settingsRow, client, }: ConfigFieldProps): import("react").JSX.Element;
24
25
  //# sourceMappingURL=ConfigField.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigField.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAqBvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAC5C,mBAAmB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,WAAW,GACZ,EAAE,gBAAgB,+BAmSlB"}
1
+ {"version":3,"file":"ConfigField.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAsBtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAC5C,mBAAmB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,WAAW,EACX,MAAM,GACP,EAAE,gBAAgB,+BAkTlB"}
@@ -12,8 +12,9 @@ import { DescriptionInput } from "../DescriptionInput";
12
12
  import { EnumInput } from "../EnumInput";
13
13
  import { EnumListInput } from "../EnumListInput";
14
14
  import { PriceInput } from "../PriceInput";
15
+ import { ReferenceInput } from "../ReferenceInput";
15
16
  import { displayLabel, firstImageUrl, isMultilineField, fieldVisibilityKey, spanFor, } from "./helpers";
16
- export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, hideImagePreview, hideUpload, visibilityGroup, settingsRow, }) {
17
+ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, hideImagePreview, hideUpload, visibilityGroup, settingsRow, client, }) {
17
18
  const label = displayLabel(fieldKey, field, groupLabel);
18
19
  const visibilityKey = fieldVisibilityKey(fieldKey, schema, visibilityGroup ?? groupLabel);
19
20
  const visibilityOn = !visibilityKey || watched?.[visibilityKey] !== false;
@@ -38,7 +39,7 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
38
39
  : "", onBlur: controllerField.onBlur, onChange: (event) => {
39
40
  const next = event.target.value;
40
41
  controllerField.onChange(next === "" ? undefined : Number(next));
41
- }, name: controllerField.name, ref: controllerField.ref })) })) : field.type === "enum" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "enum[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumListInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "font" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(FontPickerField, { id: fieldKey, value: controllerField.value, label: field.label, description: field.description, readOnly: field.readOnly, fonts: fonts ?? [], loading: fontsLoading, onChange: controllerField.onChange, onRefreshFonts: onRefreshFonts })) })) : isMultilineField(fieldKey, field) ? (_jsx("textarea", { id: fieldKey, rows: 5, readOnly: field.readOnly, ...form.register(fieldKey) })) : (_jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })), !hideUpload && uploads?.[fieldKey] && onUpload && !field.readOnly ? (_jsx(ImageUploadControl, { meta: uploads[fieldKey], multiple: field.type === "file[]", onUpload: async (files) => {
42
+ }, name: controllerField.name, ref: controllerField.ref })) })) : field.type === "enum" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "enum[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumListInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "font" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(FontPickerField, { id: fieldKey, value: controllerField.value, label: field.label, description: field.description, readOnly: field.readOnly, fonts: fonts ?? [], loading: fontsLoading, onChange: controllerField.onChange, onRefreshFonts: onRefreshFonts })) })) : field.type === "reference" && client ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(ReferenceInput, { id: fieldKey, value: controllerField.value, field: field, client: client, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : isMultilineField(fieldKey, field) ? (_jsx("textarea", { id: fieldKey, rows: 5, readOnly: field.readOnly, ...form.register(fieldKey) })) : (_jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })), !hideUpload && uploads?.[fieldKey] && onUpload && !field.readOnly ? (_jsx(ImageUploadControl, { meta: uploads[fieldKey], multiple: field.type === "file[]", onUpload: async (files) => {
42
43
  const uploaded = await onUpload(fieldKey, files);
43
44
  const urls = uploaded
44
45
  .map((item) => item.url)
@@ -1,4 +1,4 @@
1
1
  import type { ConfigFormProps } from "./types";
2
2
  export type { ConfigFormProps, ConfigFormVariant } from "./types";
3
- export declare function ConfigForm({ schema, value, onSave, submitLabel, variant, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, onChange, hideActions, }: ConfigFormProps): import("react").JSX.Element;
3
+ export declare function ConfigForm({ schema, value, onSave, submitLabel, variant, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, onChange, hideActions, client, canWrite, }: ConfigFormProps): import("react").JSX.Element;
4
4
  //# sourceMappingURL=ConfigForm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,GACZ,EAAE,eAAe,+BAqIjB"}
1
+ {"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAe,GAChB,EAAE,eAAe,+BA2IjB"}