@vertebr/react 0.8.1 → 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 (109) hide show
  1. package/dist/components/admin/AddonEditor.d.ts +13 -0
  2. package/dist/components/admin/AddonEditor.d.ts.map +1 -0
  3. package/dist/components/admin/AddonEditor.js +27 -0
  4. package/dist/components/admin/AddonOverview.d.ts +8 -0
  5. package/dist/components/admin/AddonOverview.d.ts.map +1 -0
  6. package/dist/components/admin/AddonOverview.js +125 -0
  7. package/dist/components/admin/AddonPreviewThumb.d.ts +11 -0
  8. package/dist/components/admin/AddonPreviewThumb.d.ts.map +1 -0
  9. package/dist/components/admin/AddonPreviewThumb.js +52 -0
  10. package/dist/components/admin/AdminCommandPalette.d.ts +9 -0
  11. package/dist/components/admin/AdminCommandPalette.d.ts.map +1 -0
  12. package/dist/components/admin/AdminCommandPalette.js +68 -0
  13. package/dist/components/admin/AdminContent.d.ts +4 -1
  14. package/dist/components/admin/AdminContent.d.ts.map +1 -1
  15. package/dist/components/admin/AdminContent.js +32 -10
  16. package/dist/components/admin/AdminHeader.d.ts +2 -3
  17. package/dist/components/admin/AdminHeader.d.ts.map +1 -1
  18. package/dist/components/admin/AdminHeader.js +10 -3
  19. package/dist/components/admin/AdminPoweredBy.d.ts +5 -0
  20. package/dist/components/admin/AdminPoweredBy.d.ts.map +1 -0
  21. package/dist/components/admin/AdminPoweredBy.js +8 -0
  22. package/dist/components/admin/AdminSidebar.d.ts.map +1 -1
  23. package/dist/components/admin/AdminSidebar.js +9 -7
  24. package/dist/components/admin/AdminSyncPage.d.ts +7 -0
  25. package/dist/components/admin/AdminSyncPage.d.ts.map +1 -0
  26. package/dist/components/admin/AdminSyncPage.js +169 -0
  27. package/dist/components/admin/AdminTopBar.d.ts +11 -3
  28. package/dist/components/admin/AdminTopBar.d.ts.map +1 -1
  29. package/dist/components/admin/AdminTopBar.js +27 -4
  30. package/dist/components/admin/AuditBoard.d.ts +7 -0
  31. package/dist/components/admin/AuditBoard.d.ts.map +1 -0
  32. package/dist/components/admin/AuditBoard.js +43 -0
  33. package/dist/components/admin/HealthModal.d.ts +9 -0
  34. package/dist/components/admin/HealthModal.d.ts.map +1 -0
  35. package/dist/components/admin/HealthModal.js +59 -0
  36. package/dist/components/admin/PagePreviewPane.d.ts +3 -1
  37. package/dist/components/admin/PagePreviewPane.d.ts.map +1 -1
  38. package/dist/components/admin/PagePreviewPane.js +2 -2
  39. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  40. package/dist/components/admin/VertebrAdmin.js +146 -17
  41. package/dist/components/admin/addonLabels.d.ts +6 -0
  42. package/dist/components/admin/addonLabels.d.ts.map +1 -0
  43. package/dist/components/admin/addonLabels.js +20 -0
  44. package/dist/components/admin/adminSearch.d.ts +12 -0
  45. package/dist/components/admin/adminSearch.d.ts.map +1 -0
  46. package/dist/components/admin/adminSearch.js +120 -0
  47. package/dist/components/admin/breadcrumbs.d.ts.map +1 -1
  48. package/dist/components/admin/breadcrumbs.js +14 -0
  49. package/dist/components/admin/routes.d.ts.map +1 -1
  50. package/dist/components/admin/routes.js +43 -1
  51. package/dist/components/admin/types.d.ts +9 -0
  52. package/dist/components/admin/types.d.ts.map +1 -1
  53. package/dist/components/config/ReferenceInput.d.ts +11 -0
  54. package/dist/components/config/ReferenceInput.d.ts.map +1 -0
  55. package/dist/components/config/ReferenceInput.js +99 -0
  56. package/dist/components/config/RevisionHistory.d.ts +8 -0
  57. package/dist/components/config/RevisionHistory.d.ts.map +1 -0
  58. package/dist/components/config/RevisionHistory.js +84 -0
  59. package/dist/components/config/form/ConfigField.d.ts +3 -2
  60. package/dist/components/config/form/ConfigField.d.ts.map +1 -1
  61. package/dist/components/config/form/ConfigField.js +3 -2
  62. package/dist/components/config/form/ConfigForm.d.ts +1 -1
  63. package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
  64. package/dist/components/config/form/ConfigForm.js +13 -6
  65. package/dist/components/config/form/RecordConfigForm.d.ts +3 -2
  66. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
  67. package/dist/components/config/form/RecordConfigForm.js +2 -1
  68. package/dist/components/config/form/SettingsConfigForm.d.ts +3 -2
  69. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
  70. package/dist/components/config/form/SettingsConfigForm.js +2 -2
  71. package/dist/components/config/form/types.d.ts +5 -1
  72. package/dist/components/config/form/types.d.ts.map +1 -1
  73. package/dist/components/env/EnvMultiSelect.d.ts +20 -0
  74. package/dist/components/env/EnvMultiSelect.d.ts.map +1 -0
  75. package/dist/components/env/EnvMultiSelect.js +38 -0
  76. package/dist/components/resources/ResourceTable.d.ts +7 -1
  77. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  78. package/dist/components/resources/ResourceTable.js +56 -6
  79. package/dist/components/theme/ThemeToggle.d.ts +3 -1
  80. package/dist/components/theme/ThemeToggle.d.ts.map +1 -1
  81. package/dist/components/theme/ThemeToggle.js +2 -2
  82. package/dist/components/ui/icons.d.ts +3 -0
  83. package/dist/components/ui/icons.d.ts.map +1 -1
  84. package/dist/components/ui/icons.js +17 -0
  85. package/dist/index.d.ts +2 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +1 -0
  88. package/dist/lib/capabilities.d.ts +4 -0
  89. package/dist/lib/capabilities.d.ts.map +1 -0
  90. package/dist/lib/capabilities.js +9 -0
  91. package/dist/styles/addon.css +540 -0
  92. package/dist/styles/audit.css +34 -0
  93. package/dist/styles/base.css +1 -0
  94. package/dist/styles/font-library.css +2 -0
  95. package/dist/styles/form.css +143 -0
  96. package/dist/styles/index.css +3 -0
  97. package/dist/styles/main.css +0 -38
  98. package/dist/styles/modal.css +113 -0
  99. package/dist/styles/motion.css +55 -0
  100. package/dist/styles/nav.css +39 -4
  101. package/dist/styles/select.css +6 -0
  102. package/dist/styles/shell.css +417 -14
  103. package/dist/styles/sidebar.css +63 -3
  104. package/dist/styles/sync.css +540 -0
  105. package/dist/styles/table.css +15 -5
  106. package/dist/themes/dark.css +11 -11
  107. package/dist/themes/index.css +1 -0
  108. package/dist/themes/light.css +19 -19
  109. package/package.json +1 -1
@@ -0,0 +1,13 @@
1
+ import type { CatalogAddon, CatalogResponse, ConfigResponse, VertebrClient } from "@vertebr/client";
2
+ import type { AdminContentProps } from "./AdminContent";
3
+ export type AddonEditorProps = {
4
+ client: VertebrClient;
5
+ catalog: CatalogResponse;
6
+ addon: CatalogAddon;
7
+ configData: ConfigResponse;
8
+ onConfigSaved: (next: ConfigResponse) => void;
9
+ galleryPicker: AdminContentProps["galleryPicker"];
10
+ fontLibraryPicker: AdminContentProps["fontLibraryPicker"];
11
+ };
12
+ export declare function AddonEditor({ client, catalog, addon, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AddonEditorProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=AddonEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddonEditor.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AddonEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,aAAa,EACd,MAAM,iBAAiB,CAAC;AAKzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAClD,iBAAiB,EAAE,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;CAC3D,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,KAAK,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB,+BAyFlB"}
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { ConfigForm } from "../config/ConfigForm";
4
+ import { hasCap } from "../../lib/capabilities";
5
+ import { addonCategoryLabel } from "./addonLabels";
6
+ import { PagePreviewPane } from "./PagePreviewPane";
7
+ export function AddonEditor({ client, catalog, addon, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
8
+ const [draft, setDraft] = useState(configData.value);
9
+ const onChange = useCallback((value) => {
10
+ setDraft(value);
11
+ }, []);
12
+ const enabled = Boolean(draft?.enabled ?? configData.value.enabled);
13
+ const previewPath = addon.previewPath;
14
+ const showPreview = Boolean(previewPath);
15
+ const canWrite = hasCap(catalog, "addon.write");
16
+ const previewDraft = useMemo(() => {
17
+ const base = draft ?? configData.value;
18
+ if (!showPreview)
19
+ return base;
20
+ return { ...base, enabled: true };
21
+ }, [configData.value, draft, showPreview]);
22
+ const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
23
+ const saved = await client.setConfig(configData.id, next);
24
+ onConfigSaved(saved);
25
+ }, ...galleryPicker, ...fontLibraryPicker }));
26
+ return (_jsxs("div", { className: "vertebr-addon-editor", children: [_jsxs("div", { className: "vertebr-addon-editor-head", "data-category": addon.category ?? "engagement", children: [_jsxs("div", { className: "vertebr-addon-editor-head-start", children: [_jsx("span", { className: "vertebr-addon-category", "data-category": addon.category ?? "engagement", children: addonCategoryLabel(addon.category) }), _jsxs("span", { className: "vertebr-addon-status", "data-enabled": enabled ? "true" : "false", children: [_jsx("span", { className: "vertebr-addon-status-dot", "aria-hidden": "true" }), enabled ? "Enabled on storefront" : "Disabled"] })] }), previewPath ? (_jsxs("div", { className: "vertebr-addon-editor-head-end", children: [_jsx("span", { className: "vertebr-addon-preview-label", children: "Live preview" }), _jsx("code", { className: "vertebr-addon-preview-path", children: previewPath })] })) : null] }), showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor vertebr-addon-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: previewDraft, previewKind: "addon" })] })) : (_jsxs("div", { className: "vertebr-addon-editor-form-only", children: [form, _jsxs("div", { className: "vertebr-addon-preview-missing", children: [_jsx("strong", { children: "Live preview unavailable" }), _jsxs("p", { children: ["Register a ", _jsx("code", { children: "previewPath" }), " on this add-on so Vertebr can iframe your storefront while you edit."] })] })] }))] }));
27
+ }
@@ -0,0 +1,8 @@
1
+ import type { CatalogResponse, VertebrClient } from "@vertebr/client";
2
+ export type AddonOverviewProps = {
3
+ client: VertebrClient;
4
+ catalog: CatalogResponse;
5
+ onSelectAddon: (id: string) => void;
6
+ };
7
+ export declare function AddonOverview({ client, catalog, onSelectAddon }: AddonOverviewProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=AddonOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddonOverview.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AddonOverview.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;IACzB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAyFF,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,kBAAkB,+BAwKnF"}
@@ -0,0 +1,125 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { AddonPreviewThumb } from "./AddonPreviewThumb";
4
+ import { addonCategoryHint, addonCategoryLabel } from "./addonLabels";
5
+ const CATEGORY_ORDER = ["product", "engagement", "sales"];
6
+ function groupAddons(addons) {
7
+ const buckets = new Map();
8
+ for (const addon of addons) {
9
+ const key = addon.category ?? "other";
10
+ if (!buckets.has(key))
11
+ buckets.set(key, []);
12
+ buckets.get(key).push(addon);
13
+ }
14
+ const groups = [];
15
+ for (const category of CATEGORY_ORDER) {
16
+ const items = buckets.get(category);
17
+ if (!items?.length)
18
+ continue;
19
+ groups.push({
20
+ category,
21
+ label: addonCategoryLabel(category),
22
+ hint: addonCategoryHint(category),
23
+ items,
24
+ });
25
+ buckets.delete(category);
26
+ }
27
+ const other = buckets.get("other");
28
+ if (other?.length) {
29
+ groups.push({
30
+ category: "other",
31
+ label: "Add-ons",
32
+ hint: "Optional storefront features",
33
+ items: other,
34
+ });
35
+ }
36
+ return groups;
37
+ }
38
+ function AddonScrollRow({ children }) {
39
+ const wrapRef = useRef(null);
40
+ const scrollRef = useRef(null);
41
+ useEffect(() => {
42
+ const el = scrollRef.current;
43
+ const wrap = wrapRef.current;
44
+ if (!el || !wrap)
45
+ return;
46
+ const update = () => {
47
+ const canLeft = el.scrollLeft > 4;
48
+ const canRight = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
49
+ wrap.classList.toggle("can-scroll-left", canLeft);
50
+ wrap.classList.toggle("can-scroll-right", canRight);
51
+ };
52
+ update();
53
+ el.addEventListener("scroll", update, { passive: true });
54
+ const observer = new ResizeObserver(update);
55
+ observer.observe(el);
56
+ return () => {
57
+ el.removeEventListener("scroll", update);
58
+ observer.disconnect();
59
+ };
60
+ }, [children]);
61
+ return (_jsx("div", { ref: wrapRef, className: "vertebr-addon-scroll-wrap", children: _jsx("div", { ref: scrollRef, className: "vertebr-addon-scroll", tabIndex: 0, children: children }) }));
62
+ }
63
+ export function AddonOverview({ client, catalog, onSelectAddon }) {
64
+ const addons = catalog.addons ?? [];
65
+ const [states, setStates] = useState({});
66
+ const [loading, setLoading] = useState(true);
67
+ useEffect(() => {
68
+ let cancelled = false;
69
+ void (async () => {
70
+ setLoading(true);
71
+ const next = {};
72
+ await Promise.all(addons.map(async (addon) => {
73
+ try {
74
+ const config = await client.getConfig(addon.id);
75
+ next[addon.id] = {
76
+ enabled: Boolean(config.value.enabled),
77
+ value: config.value,
78
+ };
79
+ }
80
+ catch {
81
+ next[addon.id] = { enabled: false, value: {} };
82
+ }
83
+ }));
84
+ if (!cancelled) {
85
+ setStates(next);
86
+ setLoading(false);
87
+ }
88
+ })();
89
+ return () => {
90
+ cancelled = true;
91
+ };
92
+ }, [addons, client]);
93
+ const groups = useMemo(() => groupAddons(addons), [addons]);
94
+ const stats = useMemo(() => {
95
+ let enabled = 0;
96
+ const byCategory = {};
97
+ for (const addon of addons) {
98
+ const category = addon.category ?? "other";
99
+ byCategory[category] = (byCategory[category] ?? 0) + 1;
100
+ if (states[addon.id]?.enabled)
101
+ enabled += 1;
102
+ }
103
+ return {
104
+ total: addons.length,
105
+ enabled,
106
+ products: byCategory.product ?? 0,
107
+ engagement: byCategory.engagement ?? 0,
108
+ sales: byCategory.sales ?? 0,
109
+ };
110
+ }, [addons, states]);
111
+ if (!addons.length) {
112
+ return (_jsx("div", { className: "vertebr-addon-overview", children: _jsx("div", { className: "vertebr-empty", children: "No add-ons registered for this host." }) }));
113
+ }
114
+ return (_jsxs("div", { className: "vertebr-addon-overview", children: [_jsxs("header", { className: "vertebr-addon-overview-toolbar", children: [_jsx("div", { className: "vertebr-addon-overview-intro", children: _jsx("p", { children: "Optional storefront features grouped by purpose. Scroll each row to browse, then configure one to edit with a live shop preview." }) }), _jsxs("div", { className: "vertebr-addon-overview-stats", "aria-label": "Add-on summary", children: [_jsxs("div", { className: "vertebr-addon-stat", children: [_jsx("strong", { children: loading ? "—" : stats.enabled }), _jsx("span", { children: "Enabled" })] }), _jsxs("div", { className: "vertebr-addon-stat", children: [_jsx("strong", { children: stats.total }), _jsx("span", { children: "Total" })] }), _jsxs("div", { className: "vertebr-addon-stat", "data-category": "product", children: [_jsx("strong", { children: stats.products }), _jsx("span", { children: "Products" })] }), _jsxs("div", { className: "vertebr-addon-stat", "data-category": "engagement", children: [_jsx("strong", { children: stats.engagement }), _jsx("span", { children: "Engagement" })] }), _jsxs("div", { className: "vertebr-addon-stat", "data-category": "sales", children: [_jsx("strong", { children: stats.sales }), _jsx("span", { children: "Sales" })] })] })] }), groups.map((group) => (_jsxs("section", { className: "vertebr-addon-overview-section", "data-category": group.category, children: [_jsxs("div", { className: "vertebr-addon-section-head", children: [_jsxs("div", { className: "vertebr-addon-section-title", children: [_jsx("h3", { children: group.label }), _jsx("p", { children: group.hint })] }), _jsxs("span", { className: "vertebr-addon-section-count", children: [group.items.length, " add-on", group.items.length === 1 ? "" : "s"] })] }), _jsx(AddonScrollRow, { children: group.items.map((addon) => {
115
+ const state = states[addon.id];
116
+ const previewDraft = state
117
+ ? { ...state.value, enabled: true }
118
+ : { enabled: true };
119
+ return (_jsxs("article", { className: "vertebr-addon-card", "data-category": addon.category ?? "engagement", children: [_jsx(AddonPreviewThumb, { addonId: addon.id, previewPath: addon.previewPath, draft: previewDraft, label: addon.label }), _jsxs("div", { className: "vertebr-addon-card-body", children: [_jsxs("div", { className: "vertebr-addon-card-head", children: [_jsx("h4", { children: addon.label }), _jsx("span", { className: "vertebr-addon-status", "data-enabled": loading
120
+ ? undefined
121
+ : state?.enabled
122
+ ? "true"
123
+ : "false", children: loading ? "…" : state?.enabled ? "On" : "Off" })] }), addon.description ? _jsx("p", { children: addon.description }) : null, _jsxs("div", { className: "vertebr-addon-card-foot", children: [addon.previewPath ? (_jsx("span", { className: "vertebr-addon-route", children: addon.previewPath })) : null, _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: () => onSelectAddon(addon.id), children: "Configure" })] })] })] }, addon.id));
124
+ }) })] }, group.category)))] }));
125
+ }
@@ -0,0 +1,11 @@
1
+ export type AddonPreviewThumbProps = {
2
+ addonId: string;
3
+ previewPath?: string;
4
+ draft?: Record<string, unknown> | null;
5
+ label?: string;
6
+ };
7
+ /**
8
+ * Small live host iframe for add-on overview cards.
9
+ */
10
+ export declare function AddonPreviewThumb({ addonId, previewPath, draft, label, }: AddonPreviewThumbProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=AddonPreviewThumb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddonPreviewThumb.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AddonPreviewThumb.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,WAAW,EACX,KAAK,EACL,KAAK,GACN,EAAE,sBAAsB,+BA6ExB"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { buildVertebrPreviewUrl, postVertebrPreviewDraft, subscribeVertebrPreviewReady, } from "@vertebr/client";
4
+ /**
5
+ * Small live host iframe for add-on overview cards.
6
+ */
7
+ export function AddonPreviewThumb({ addonId, previewPath, draft, label, }) {
8
+ const iframeRef = useRef(null);
9
+ const draftRef = useRef(draft);
10
+ const [ready, setReady] = useState(false);
11
+ const [error, setError] = useState(false);
12
+ draftRef.current = draft;
13
+ const src = useMemo(() => {
14
+ if (!previewPath)
15
+ return null;
16
+ return buildVertebrPreviewUrl(previewPath, { controlId: addonId });
17
+ }, [addonId, previewPath]);
18
+ const pushDraft = () => {
19
+ const win = iframeRef.current?.contentWindow;
20
+ const value = draftRef.current;
21
+ if (!win || !value)
22
+ return;
23
+ postVertebrPreviewDraft(win, addonId, value);
24
+ };
25
+ useEffect(() => {
26
+ setReady(false);
27
+ setError(false);
28
+ }, [src]);
29
+ useEffect(() => {
30
+ if (!src)
31
+ return;
32
+ return subscribeVertebrPreviewReady((source) => {
33
+ const win = iframeRef.current?.contentWindow;
34
+ if (!win || source !== win)
35
+ return;
36
+ setReady(true);
37
+ pushDraft();
38
+ });
39
+ }, [src]);
40
+ useEffect(() => {
41
+ if (!ready || !draft)
42
+ return;
43
+ pushDraft();
44
+ }, [draft, ready]);
45
+ if (!previewPath) {
46
+ return (_jsx("div", { className: "vertebr-addon-thumb vertebr-addon-thumb-empty", children: _jsx("span", { children: "No preview route" }) }));
47
+ }
48
+ return (_jsxs("div", { className: "vertebr-addon-thumb", "data-ready": ready ? "true" : undefined, "aria-label": label ? `Preview ${label}` : `Preview ${addonId}`, children: [_jsx("div", { className: "vertebr-addon-thumb-scale", children: error ? (_jsx("div", { className: "vertebr-addon-thumb-fallback", children: "Preview unavailable" })) : (_jsx("iframe", { ref: iframeRef, title: label ? `Preview ${label}` : `Preview ${addonId}`, src: src ?? undefined, tabIndex: -1, loading: "lazy", onLoad: () => {
49
+ setReady(true);
50
+ pushDraft();
51
+ }, onError: () => setError(true) })) }), !ready && !error ? (_jsx("div", { className: "vertebr-addon-thumb-loading", children: "Loading preview\u2026" })) : null] }));
52
+ }
@@ -0,0 +1,9 @@
1
+ import type { CatalogResponse } from "@vertebr/client";
2
+ import type { Selection } from "./types";
3
+ export type AdminCommandPaletteProps = {
4
+ catalog: CatalogResponse;
5
+ onClose: () => void;
6
+ onSelect: (selection: Selection) => void;
7
+ };
8
+ export declare function AdminCommandPalette({ catalog, onClose, onSelect, }: AdminCommandPaletteProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=AdminCommandPalette.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminCommandPalette.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminCommandPalette.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAOvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,QAAQ,GACT,EAAE,wBAAwB,+BAqI1B"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { IconSearch } from "../ui/icons";
4
+ import { adminSearchItems, filterAdminSearchItems, } from "./adminSearch";
5
+ export function AdminCommandPalette({ catalog, onClose, onSelect, }) {
6
+ const inputRef = useRef(null);
7
+ const [query, setQuery] = useState("");
8
+ const [activeIndex, setActiveIndex] = useState(0);
9
+ const items = useMemo(() => adminSearchItems(catalog), [catalog]);
10
+ const results = useMemo(() => filterAdminSearchItems(items, query), [items, query]);
11
+ useEffect(() => {
12
+ inputRef.current?.focus();
13
+ }, []);
14
+ useEffect(() => {
15
+ setActiveIndex(0);
16
+ }, [query]);
17
+ useEffect(() => {
18
+ const onKeyDown = (event) => {
19
+ if (event.key === "Escape") {
20
+ event.preventDefault();
21
+ onClose();
22
+ return;
23
+ }
24
+ if (results.length === 0)
25
+ return;
26
+ if (event.key === "ArrowDown") {
27
+ event.preventDefault();
28
+ setActiveIndex((current) => (current + 1) % results.length);
29
+ }
30
+ else if (event.key === "ArrowUp") {
31
+ event.preventDefault();
32
+ setActiveIndex((current) => (current - 1 + results.length) % results.length);
33
+ }
34
+ else if (event.key === "Enter") {
35
+ event.preventDefault();
36
+ const item = results[activeIndex];
37
+ if (item) {
38
+ onSelect(item.selection);
39
+ onClose();
40
+ }
41
+ }
42
+ };
43
+ window.addEventListener("keydown", onKeyDown);
44
+ return () => window.removeEventListener("keydown", onKeyDown);
45
+ }, [activeIndex, onClose, onSelect, results]);
46
+ const pick = (item) => {
47
+ onSelect(item.selection);
48
+ onClose();
49
+ };
50
+ const grouped = useMemo(() => {
51
+ const map = new Map();
52
+ for (const item of results) {
53
+ const list = map.get(item.group) ?? [];
54
+ list.push(item);
55
+ map.set(item.group, list);
56
+ }
57
+ return map;
58
+ }, [results]);
59
+ let resultIndex = -1;
60
+ return (_jsx("div", { className: "vertebr-command-backdrop", role: "presentation", onMouseDown: (event) => {
61
+ if (event.target === event.currentTarget)
62
+ onClose();
63
+ }, children: _jsxs("div", { className: "vertebr-command", role: "dialog", "aria-modal": "true", "aria-label": "Search admin", children: [_jsxs("div", { className: "vertebr-command-input-wrap", children: [_jsx(IconSearch, {}), _jsx("input", { ref: inputRef, className: "vertebr-command-input", type: "search", placeholder: "Search pages, resources, settings\u2026", value: query, onChange: (event) => setQuery(event.target.value), autoComplete: "off", spellCheck: false }), _jsx("kbd", { children: "esc" })] }), _jsx("div", { className: "vertebr-command-results", role: "listbox", children: results.length === 0 ? (_jsx("div", { className: "vertebr-command-empty", children: "No matches found." })) : ([...grouped.entries()].map(([group, groupItems]) => (_jsxs("div", { className: "vertebr-command-group", children: [_jsx("div", { className: "vertebr-command-group-label", children: group }), groupItems.map((item) => {
64
+ resultIndex += 1;
65
+ const index = resultIndex;
66
+ return (_jsxs("button", { type: "button", className: "vertebr-command-item", role: "option", "aria-selected": index === activeIndex, "data-active": index === activeIndex || undefined, onMouseEnter: () => setActiveIndex(index), onClick: () => pick(item), children: [_jsx("span", { className: "vertebr-command-item-label", children: item.label }), item.hint ? (_jsx("span", { className: "vertebr-command-item-hint", children: item.hint })) : null] }, item.id));
67
+ })] }, group)))) })] }) }));
68
+ }
@@ -24,7 +24,10 @@ export type AdminContentProps = {
24
24
  emailsLoading: boolean;
25
25
  onRefreshEmails: () => Promise<void>;
26
26
  resourceData: ResourceListResponse | null;
27
+ resourceSearch?: string;
28
+ onResourceSearchChange?: (q: string) => void;
27
29
  onRefreshResource: () => Promise<void>;
30
+ onRefreshConfig?: () => Promise<void>;
28
31
  configData: ConfigResponse | null;
29
32
  onConfigSaved: (next: ConfigResponse) => void;
30
33
  galleryPicker: {
@@ -42,5 +45,5 @@ export type AdminContentProps = {
42
45
  onRefreshFonts?: () => Promise<void>;
43
46
  };
44
47
  };
45
- export declare function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
48
+ export declare function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceSearch, onResourceSearchChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
46
49
  //# sourceMappingURL=AdminContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"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"}
@@ -1,25 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useState } from "react";
3
3
  import { ConfigForm } from "../config/ConfigForm";
4
+ import { RevisionHistory } from "../config/RevisionHistory";
4
5
  import { EmailsBoard } from "../emails/EmailsBoard";
5
6
  import { FontLibraryBoard } from "../fonts/FontLibraryBoard";
6
7
  import { GalleryBoard } from "../gallery/GalleryBoard";
7
8
  import { MetricsBoard } from "../metrics/MetricsBoard";
8
9
  import { ResourcePanel } from "../resources/ResourceTable";
10
+ import { hasCap } from "../../lib/capabilities";
11
+ import { AddonEditor } from "./AddonEditor";
12
+ import { AddonOverview } from "./AddonOverview";
13
+ import { AuditBoard } from "./AuditBoard";
9
14
  import { PagePreviewPane } from "./PagePreviewPane";
10
- function ConfigEditor({ client, configData, isPageSelection, previewPath, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
15
+ import { AdminSyncPage } from "./AdminSyncPage";
16
+ function ConfigEditor({ client, catalog, configData, isPageSelection, previewPath, onConfigSaved, onRefreshConfig, galleryPicker, fontLibraryPicker, }) {
11
17
  const [draft, setDraft] = useState(configData.value);
12
18
  const showPreview = Boolean(previewPath);
19
+ const canWrite = hasCap(catalog, "config.write");
20
+ const showRevisions = Boolean(catalog.revisionsEnabled) || Boolean(configData.revisionsEnabled);
13
21
  const onChange = useCallback((value) => {
14
22
  setDraft(value);
15
23
  }, []);
16
- const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
24
+ const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
17
25
  const saved = await client.setConfig(configData.id, next);
18
26
  onConfigSaved(saved);
19
27
  }, ...galleryPicker, ...fontLibraryPicker }));
20
- return (_jsxs("div", { className: "vertebr-config-editor", children: [showPreview ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : configData.label }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : isPageSelection ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Page content" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : null, showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: draft ?? configData.value })] })) : (form)] }));
28
+ return (_jsxs("div", { className: "vertebr-config-editor", children: [showPreview ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : configData.label }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : isPageSelection ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Page content" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : null, showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: draft ?? configData.value })] })) : (form), showRevisions ? (_jsx(RevisionHistory, { client: client, configId: configData.id, onPublished: async () => {
29
+ await onRefreshConfig?.();
30
+ } })) : null] }));
21
31
  }
22
- export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
32
+ export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceSearch, onResourceSearchChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
23
33
  if (!catalog) {
24
34
  return (_jsx("div", { className: "vertebr-content", children: _jsx("section", { className: "vertebr-panel", children: _jsx("div", { className: "vertebr-empty", children: authRequired
25
35
  ? "Authenticated access required for this environment."
@@ -27,7 +37,7 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
27
37
  ? "Loading…"
28
38
  : "Unable to load catalog." }) }) }));
29
39
  }
30
- return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void onRefreshGallery(), onUpload: async (files) => {
40
+ return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "audit" ? (_jsx(AuditBoard, { client: client, label: catalog.audit?.label })) : null, selection?.kind === "sync" ? (_jsx(AdminSyncPage, { client: client, catalog: catalog })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void onRefreshGallery(), onUpload: async (files) => {
31
41
  if (files.length === 1 && files[0]) {
32
42
  await client.uploadGalleryFile(files[0]);
33
43
  }
@@ -97,7 +107,7 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
97
107
  ? async (id) => {
98
108
  await client.deleteEmailLibraryTemplate(id);
99
109
  }
100
- : undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, label: resourceData.label, initialEditRowId: selection.rowId && selection.id === resourceData.id
110
+ : undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, label: resourceData.label, listQuery: resourceData.listQuery, total: resourceData.total, searchQuery: resourceSearch, onSearchChange: onResourceSearchChange, initialEditRowId: selection.rowId && selection.id === resourceData.id
101
111
  ? selection.rowId
102
112
  : undefined, onEditRowIdChange: (rowId) => {
103
113
  onSelectionChange?.({
@@ -105,7 +115,7 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
105
115
  id: resourceData.id,
106
116
  rowId: rowId ?? undefined,
107
117
  });
108
- }, permissions: resourceData.permissions, actions: resourceData.actions, collectionActions: resourceData.collectionActions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
118
+ }, permissions: resourceData.permissions, actions: resourceData.actions, collectionActions: resourceData.collectionActions, bulkActions: resourceData.bulkActions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
109
119
  ? async (rowId, value) => {
110
120
  await client.updateResource(resourceData.id, rowId, value);
111
121
  await onRefreshResource();
@@ -126,7 +136,14 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
126
136
  return client.previewResourceMail(resourceData.id, rowId, actionId, input);
127
137
  }, onCollectionAction: async (actionId, input) => {
128
138
  return client.runCollectionAction(resourceData.id, actionId, input);
129
- }, onUpload: resourceData.uploads
139
+ }, onBulkAction: resourceData.bulkActions
140
+ ? async (actionId, rowIds, input) => {
141
+ await client.runBulkAction(resourceData.id, actionId, {
142
+ rowIds,
143
+ input,
144
+ });
145
+ }
146
+ : undefined, onUpload: resourceData.uploads
130
147
  ? async (field, files) => {
131
148
  if (files.length === 1 && files[0]) {
132
149
  const result = await client.uploadResourceFile(resourceData.id, field, files[0]);
@@ -135,6 +152,11 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
135
152
  const result = await client.uploadResourceFiles(resourceData.id, field, files);
136
153
  return result.files;
137
154
  }
138
- : undefined, ...galleryPicker })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
139
- ?.previewPath, onConfigSaved: onConfigSaved, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id)) : null] }) }));
155
+ : undefined, ...galleryPicker })) : null, selection?.kind === "addons" ? (_jsx(AddonOverview, { client: client, catalog: catalog, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }) })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, catalog: catalog, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
156
+ ?.previewPath, onConfigSaved: onConfigSaved, onRefreshConfig: onRefreshConfig, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id)) : null, selection?.kind === "addon" && configData ? ((() => {
157
+ const addon = catalog.addons?.find((item) => item.id === configData.id);
158
+ if (!addon)
159
+ return null;
160
+ return (_jsx(AddonEditor, { client: client, catalog: catalog, addon: addon, configData: configData, onConfigSaved: onConfigSaved, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id));
161
+ })()) : null] }) }));
140
162
  }
@@ -1,16 +1,15 @@
1
- import type { AdminEnvironment } from "@vertebr/client";
2
1
  import type { Breadcrumb } from "./breadcrumbs";
3
2
  import type { Selection } from "./types";
4
3
  export type AdminHeaderProps = {
5
4
  activeLabel: string;
6
- activeEnv?: AdminEnvironment;
7
5
  catalogLoaded: boolean;
8
6
  authRequired: boolean;
9
7
  selection: Selection;
10
8
  isPageSelection: boolean;
11
9
  hasPagePreview?: boolean;
10
+ selectionDescription?: string;
12
11
  error: string | null;
13
12
  breadcrumbs: Breadcrumb[];
14
13
  };
15
- export declare function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, breadcrumbs, }: AdminHeaderProps): import("react").JSX.Element;
14
+ export declare function AdminHeader({ activeLabel, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, selectionDescription, error, breadcrumbs, }: AdminHeaderProps): import("react").JSX.Element;
16
15
  //# sourceMappingURL=AdminHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminHeader.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AAiDF,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,KAAK,EACL,WAAW,GACZ,EAAE,gBAAgB,+BA6ClB"}
1
+ {"version":3,"file":"AdminHeader.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AAyDF,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,aAAa,EACb,YAAY,EACZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,KAAK,EACL,WAAW,GACZ,EAAE,gBAAgB,+BAoClB"}
@@ -1,11 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, }) {
2
+ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, selectionDescription, }) {
3
3
  if (!catalogLoaded && authRequired) {
4
4
  return "Provide an access token to load this environment.";
5
5
  }
6
+ if (selectionDescription) {
7
+ return selectionDescription;
8
+ }
6
9
  if (selection?.kind === "overview") {
7
10
  return "Commerce, growth, and catalog for the selected environment.";
8
11
  }
12
+ if (selection?.kind === "sync") {
13
+ return "Copy selected content between environments.";
14
+ }
9
15
  if (selection?.kind === "gallery") {
10
16
  return "Browse and upload shared media assets.";
11
17
  }
@@ -32,12 +38,13 @@ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, has
32
38
  }
33
39
  return "Select a panel.";
34
40
  }
35
- export function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, breadcrumbs, }) {
36
- return (_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [breadcrumbs.length > 1 ? (_jsx("ol", { className: "vertebr-breadcrumbs", children: breadcrumbs.map((crumb, index) => (_jsx("li", { children: index === breadcrumbs.length - 1 ? (_jsx("strong", { children: crumb.label })) : (crumb.label) }, `${crumb.label}-${index}`))) })) : null, _jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: subtitle({
41
+ export function AdminHeader({ activeLabel, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, selectionDescription, error, breadcrumbs, }) {
42
+ return (_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [breadcrumbs.length > 1 ? (_jsx("ol", { className: "vertebr-breadcrumbs", children: breadcrumbs.map((crumb, index) => (_jsx("li", { children: index === breadcrumbs.length - 1 ? (_jsx("strong", { children: crumb.label })) : (crumb.label) }, `${crumb.label}-${index}`))) })) : null, _jsx("h1", { children: activeLabel }), _jsx("p", { children: subtitle({
37
43
  catalogLoaded,
38
44
  authRequired,
39
45
  selection,
40
46
  isPageSelection,
41
47
  hasPagePreview,
48
+ selectionDescription,
42
49
  }) })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }));
43
50
  }
@@ -0,0 +1,5 @@
1
+ export type AdminPoweredByProps = {
2
+ collapsed?: boolean;
3
+ };
4
+ export declare function AdminPoweredBy({ collapsed }: AdminPoweredByProps): import("react").JSX.Element;
5
+ //# sourceMappingURL=AdminPoweredBy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminPoweredBy.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminPoweredBy.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,+BAwBhE"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const VERTEBR_URL = "https://github.com/yaboijams/vertebr";
3
+ export function AdminPoweredBy({ collapsed }) {
4
+ if (collapsed) {
5
+ return (_jsx("div", { className: "vertebr-powered-by", "data-collapsed": "true", children: _jsx("a", { href: VERTEBR_URL, target: "_blank", rel: "noopener noreferrer", title: "Powered by Vertebr", "aria-label": "Powered by Vertebr", children: "V" }) }));
6
+ }
7
+ return (_jsx("div", { className: "vertebr-powered-by", children: _jsxs("a", { href: VERTEBR_URL, target: "_blank", rel: "noopener noreferrer", children: ["Powered by ", _jsx("strong", { children: "Vertebr" })] }) }));
8
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"AdminSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSidebar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,SAAS,CAAC;AAG7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAwFF,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,iBAAiB,+BAuBvE"}
1
+ {"version":3,"file":"AdminSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSidebar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,SAAS,CAAC;AAG7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AA6FF,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,iBAAiB,+BAwBvE"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
+ import { AdminPoweredBy } from "./AdminPoweredBy";
3
4
  import { iconForNavKey } from "../ui/icons";
4
5
  function SidebarNavItem({ item, collapsed, }) {
5
6
  const hasChildren = Boolean(item.children?.length);
@@ -11,17 +12,18 @@ function SidebarNavItem({ item, collapsed, }) {
11
12
  if (sectionActive)
12
13
  setOpen(true);
13
14
  }, [sectionActive]);
14
- if (!hasChildren) {
15
- return (_jsxs("button", { type: "button", "data-active": item.active, title: collapsed ? item.label : undefined, onClick: item.onSelect, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }));
15
+ if (!hasChildren || collapsed) {
16
+ const active = hasChildren ? sectionActive : item.active;
17
+ return (_jsxs("button", { type: "button", "data-active": active || undefined, title: collapsed ? item.label : undefined, onClick: item.onSelect, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }));
16
18
  }
17
19
  return (_jsxs("div", { className: "vertebr-nav-item", "data-has-children": "true", "data-open": open || undefined, children: [_jsxs("div", { className: "vertebr-nav-parent", children: [_jsxs("button", { type: "button", className: "vertebr-nav-parent-label", "data-active": sectionActive && !childActive, "data-section": sectionActive || undefined, title: collapsed ? item.label : undefined, onClick: () => {
18
20
  setOpen(true);
19
21
  item.onSelect();
20
- }, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }), !collapsed ? (_jsx("button", { type: "button", className: "vertebr-nav-parent-toggle", "aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`, "aria-expanded": open, onClick: () => setOpen((prev) => !prev), children: _jsx("span", { "aria-hidden": "true", children: open ? "▾" : "▸" }) })) : null] }), open && !collapsed ? (_jsx("div", { className: "vertebr-nav-sub", role: "group", children: item.children.map((child) => (_jsx("button", { type: "button", className: "vertebr-nav-subitem", "data-active": child.active, onClick: () => {
21
- setOpen(true);
22
- child.onSelect();
23
- }, children: child.label }, child.key))) })) : null] }));
22
+ }, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }), !collapsed ? (_jsx("button", { type: "button", className: "vertebr-nav-parent-toggle", "aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`, "aria-expanded": open, onClick: () => setOpen((prev) => !prev), children: _jsx("span", { className: "vertebr-nav-chevron", "aria-hidden": "true", children: "\u25B8" }) })) : null] }), _jsx("div", { className: "vertebr-nav-sub-wrap", "data-open": open ? "true" : undefined, children: _jsx("div", { className: "vertebr-nav-sub-inner", children: _jsx("div", { className: "vertebr-nav-sub", role: "group", children: item.children.map((child) => (_jsx("button", { type: "button", className: "vertebr-nav-subitem", "data-active": child.active, onClick: () => {
23
+ setOpen(true);
24
+ child.onSelect();
25
+ }, children: child.label }, child.key))) }) }) })] }));
24
26
  }
25
27
  export function AdminSidebar({ navGroups, collapsed }) {
26
- return (_jsx("aside", { className: "vertebr-sidebar", "data-collapsed": collapsed || undefined, children: _jsx("div", { className: "vertebr-sidebar-top", children: _jsx("nav", { className: "vertebr-nav", children: navGroups.map((group) => (_jsxs("div", { className: "vertebr-nav-group", children: [!collapsed ? (_jsx("div", { className: "vertebr-nav-label", children: group.label })) : null, group.items.map((item) => (_jsx(SidebarNavItem, { item: item, collapsed: collapsed }, item.key)))] }, group.label))) }) }) }));
28
+ return (_jsxs("aside", { className: "vertebr-sidebar", "data-collapsed": collapsed || undefined, children: [_jsx("div", { className: "vertebr-sidebar-top", children: _jsx("nav", { className: "vertebr-nav", children: navGroups.map((group) => (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: group.label }), group.items.map((item) => (_jsx(SidebarNavItem, { item: item, collapsed: collapsed }, item.key)))] }, group.label))) }) }), _jsx("div", { className: "vertebr-sidebar-bottom", children: _jsx(AdminPoweredBy, { collapsed: collapsed }) })] }));
27
29
  }