@vertebr/react 0.8.0 → 0.9.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 (70) hide show
  1. package/dist/components/admin/AddonEditor.d.ts +12 -0
  2. package/dist/components/admin/AddonEditor.d.ts.map +1 -0
  3. package/dist/components/admin/AddonEditor.js +25 -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.map +1 -1
  14. package/dist/components/admin/AdminContent.js +11 -3
  15. package/dist/components/admin/AdminHeader.d.ts +2 -3
  16. package/dist/components/admin/AdminHeader.d.ts.map +1 -1
  17. package/dist/components/admin/AdminHeader.js +10 -3
  18. package/dist/components/admin/AdminPoweredBy.d.ts +5 -0
  19. package/dist/components/admin/AdminPoweredBy.d.ts.map +1 -0
  20. package/dist/components/admin/AdminPoweredBy.js +8 -0
  21. package/dist/components/admin/AdminSidebar.d.ts.map +1 -1
  22. package/dist/components/admin/AdminSidebar.js +9 -7
  23. package/dist/components/admin/AdminSyncPage.d.ts +7 -0
  24. package/dist/components/admin/AdminSyncPage.d.ts.map +1 -0
  25. package/dist/components/admin/AdminSyncPage.js +169 -0
  26. package/dist/components/admin/AdminTopBar.d.ts +6 -2
  27. package/dist/components/admin/AdminTopBar.d.ts.map +1 -1
  28. package/dist/components/admin/AdminTopBar.js +3 -3
  29. package/dist/components/admin/PagePreviewPane.d.ts +3 -1
  30. package/dist/components/admin/PagePreviewPane.d.ts.map +1 -1
  31. package/dist/components/admin/PagePreviewPane.js +2 -2
  32. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  33. package/dist/components/admin/VertebrAdmin.js +76 -13
  34. package/dist/components/admin/addonLabels.d.ts +6 -0
  35. package/dist/components/admin/addonLabels.d.ts.map +1 -0
  36. package/dist/components/admin/addonLabels.js +20 -0
  37. package/dist/components/admin/adminSearch.d.ts +12 -0
  38. package/dist/components/admin/adminSearch.d.ts.map +1 -0
  39. package/dist/components/admin/adminSearch.js +111 -0
  40. package/dist/components/admin/breadcrumbs.d.ts.map +1 -1
  41. package/dist/components/admin/breadcrumbs.js +12 -0
  42. package/dist/components/admin/routes.d.ts.map +1 -1
  43. package/dist/components/admin/routes.js +31 -1
  44. package/dist/components/admin/types.d.ts +7 -0
  45. package/dist/components/admin/types.d.ts.map +1 -1
  46. package/dist/components/env/EnvMultiSelect.d.ts +20 -0
  47. package/dist/components/env/EnvMultiSelect.d.ts.map +1 -0
  48. package/dist/components/env/EnvMultiSelect.js +38 -0
  49. package/dist/components/theme/ThemeToggle.d.ts.map +1 -1
  50. package/dist/components/theme/ThemeToggle.js +1 -1
  51. package/dist/components/ui/icons.d.ts +2 -0
  52. package/dist/components/ui/icons.d.ts.map +1 -1
  53. package/dist/components/ui/icons.js +12 -0
  54. package/dist/styles/addon.css +540 -0
  55. package/dist/styles/base.css +1 -0
  56. package/dist/styles/font-library.css +2 -0
  57. package/dist/styles/index.css +2 -0
  58. package/dist/styles/main.css +0 -38
  59. package/dist/styles/modal.css +2 -0
  60. package/dist/styles/motion.css +55 -0
  61. package/dist/styles/nav.css +39 -4
  62. package/dist/styles/select.css +6 -0
  63. package/dist/styles/shell.css +203 -6
  64. package/dist/styles/sidebar.css +63 -3
  65. package/dist/styles/sync.css +540 -0
  66. package/dist/styles/table.css +15 -5
  67. package/dist/themes/dark.css +11 -11
  68. package/dist/themes/index.css +1 -0
  69. package/dist/themes/light.css +19 -19
  70. package/package.json +1 -1
@@ -0,0 +1,169 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { EnvMultiSelect, EnvFlowBadge } from "../env/EnvMultiSelect";
4
+ import { EnvSelect } from "../env/EnvSelect";
5
+ import { useToast } from "../../hooks/useToast";
6
+ import { Badge, Card } from "../ui/primitives";
7
+ import { IconSync } from "../ui/icons";
8
+ function envById(environments, id) {
9
+ return environments.find((env) => env.id === id);
10
+ }
11
+ function groupTargets(catalog) {
12
+ const targets = catalog.sync?.targets ?? [];
13
+ const groups = new Map();
14
+ for (const target of targets) {
15
+ const group = target.group ?? "Content";
16
+ const list = groups.get(group) ?? [];
17
+ list.push(target);
18
+ groups.set(group, list);
19
+ }
20
+ return groups;
21
+ }
22
+ function statusTone(status) {
23
+ if (status === "error")
24
+ return "danger";
25
+ if (status === "changed" || status === "new")
26
+ return "warn";
27
+ if (status === "unchanged")
28
+ return "ok";
29
+ return "neutral";
30
+ }
31
+ function statusLabel(status) {
32
+ switch (status) {
33
+ case "changed":
34
+ return "Changes";
35
+ case "new":
36
+ return "New";
37
+ case "unchanged":
38
+ return "Match";
39
+ case "missing":
40
+ return "Missing";
41
+ case "skipped":
42
+ return "Skipped";
43
+ case "error":
44
+ return "Error";
45
+ default:
46
+ return status;
47
+ }
48
+ }
49
+ export function AdminSyncPage({ client, catalog }) {
50
+ const sync = catalog.sync;
51
+ const { push } = useToast();
52
+ const environments = catalog.environments;
53
+ const [fromEnv, setFromEnv] = useState(() => sync?.defaultFrom ?? catalog.defaultEnvironment);
54
+ const [toEnv, setToEnv] = useState(() => {
55
+ const defaults = sync?.defaultTo ?? [];
56
+ const from = sync?.defaultFrom ?? catalog.defaultEnvironment;
57
+ return defaults.filter((id) => id !== from);
58
+ });
59
+ const [selectedTargets, setSelectedTargets] = useState(() => (sync?.targets ?? [])
60
+ .filter((target) => target.defaultSelected)
61
+ .map((target) => target.id));
62
+ const [plan, setPlan] = useState(null);
63
+ const [planLoading, setPlanLoading] = useState(false);
64
+ const [running, setRunning] = useState(false);
65
+ const [error, setError] = useState(null);
66
+ const fromEnvironment = envById(environments, fromEnv);
67
+ const toEnvironments = useMemo(() => toEnv
68
+ .map((id) => envById(environments, id))
69
+ .filter((env) => Boolean(env)), [environments, toEnv]);
70
+ const targetGroups = useMemo(() => groupTargets(catalog), [catalog]);
71
+ const hasTargets = (sync?.targets.length ?? 0) > 0;
72
+ const refreshPlan = useCallback(async () => {
73
+ if (!hasTargets || toEnv.length === 0) {
74
+ setPlan(null);
75
+ return;
76
+ }
77
+ setPlanLoading(true);
78
+ setError(null);
79
+ try {
80
+ const next = await client.getSyncPlan({
81
+ from: fromEnv,
82
+ to: toEnv,
83
+ targets: selectedTargets,
84
+ });
85
+ setPlan(next);
86
+ }
87
+ catch (err) {
88
+ setPlan(null);
89
+ setError(err instanceof Error ? err.message : "Could not load sync preview");
90
+ }
91
+ finally {
92
+ setPlanLoading(false);
93
+ }
94
+ }, [client, fromEnv, toEnv, selectedTargets, hasTargets]);
95
+ useEffect(() => {
96
+ void refreshPlan();
97
+ }, [refreshPlan]);
98
+ const toggleTarget = (targetId) => {
99
+ setSelectedTargets((current) => current.includes(targetId)
100
+ ? current.filter((id) => id !== targetId)
101
+ : [...current, targetId]);
102
+ };
103
+ const runSync = async () => {
104
+ if (!hasTargets || toEnv.length === 0 || selectedTargets.length === 0)
105
+ return;
106
+ const fromLabel = fromEnvironment?.label ?? fromEnv;
107
+ const toLabels = toEnvironments.map((env) => env.label).join(", ");
108
+ const targetLabels = (sync?.targets ?? [])
109
+ .filter((target) => selectedTargets.includes(target.id))
110
+ .map((target) => target.label)
111
+ .join(", ");
112
+ const dangerTargets = toEnvironments.some((env) => env.tone === "danger");
113
+ const confirmLines = [
114
+ `Copy from ${fromLabel} to ${toLabels}.`,
115
+ "",
116
+ `Targets: ${targetLabels}`,
117
+ "",
118
+ "This may overwrite content in the destination environment(s).",
119
+ ];
120
+ if (dangerTargets) {
121
+ confirmLines.push("", "You are writing to a production environment.");
122
+ }
123
+ if (!window.confirm(confirmLines.join("\n")))
124
+ return;
125
+ setRunning(true);
126
+ setError(null);
127
+ try {
128
+ const result = await client.runSync({
129
+ from: fromEnv,
130
+ to: toEnv,
131
+ targets: selectedTargets,
132
+ });
133
+ push(result.message ?? (result.ok ? "Sync complete" : "Sync finished with errors"), result.ok ? "ok" : "danger");
134
+ await refreshPlan();
135
+ }
136
+ catch (err) {
137
+ setError(err instanceof Error ? err.message : "Sync failed");
138
+ push(err instanceof Error ? err.message : "Sync failed", "danger");
139
+ }
140
+ finally {
141
+ setRunning(false);
142
+ }
143
+ };
144
+ const planForTarget = (destinationId, targetId) => plan?.destinations
145
+ .find((item) => item.to === destinationId)
146
+ ?.targets.find((item) => item.id === targetId);
147
+ const syncSummary = toEnvironments.length > 0 && selectedTargets.length > 0
148
+ ? `Sync ${selectedTargets.length} item${selectedTargets.length === 1 ? "" : "s"} to ${toEnvironments.map((env) => env.label).join(", ")}`
149
+ : "Select destinations and content to sync";
150
+ if (!hasTargets) {
151
+ return (_jsx("div", { className: "vertebr-sync-page", children: _jsx("div", { className: "vertebr-sync-scroll", children: _jsxs("div", { className: "vertebr-empty-state vertebr-sync-empty", children: [_jsx("h3", { children: "Sync not configured" }), _jsxs("p", { children: ["This admin has multiple environments but no sync targets yet. Hosts register targets with ", _jsx("code", { children: "admin.sync()" }), "."] })] }) }) }));
152
+ }
153
+ return (_jsxs("div", { className: "vertebr-sync-page", children: [_jsxs("div", { className: "vertebr-sync-scroll", children: [_jsx(Card, { title: "Direction", children: _jsxs("div", { className: "vertebr-sync-direction", children: [_jsxs("div", { className: "vertebr-sync-direction-grid", children: [_jsxs("label", { className: "vertebr-sync-field", children: [_jsx("span", { className: "vertebr-sync-field-label", children: "From" }), _jsx(EnvSelect, { environments: environments, value: fromEnv, onChange: (next) => {
154
+ setFromEnv(next);
155
+ setToEnv((current) => current.filter((id) => id !== next));
156
+ } })] }), _jsx("div", { className: "vertebr-sync-connector", "aria-hidden": "true", children: toEnvironments.length > 0 ? (toEnvironments.map((destination) => (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" }, destination.id)))) : (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" })) }), _jsxs("label", { className: "vertebr-sync-field", children: [_jsx("span", { className: "vertebr-sync-field-label", children: "To" }), _jsx(EnvMultiSelect, { environments: environments, value: toEnv, excludeId: fromEnv, onChange: setToEnv, placeholder: "Select one or more destinations" })] })] }), fromEnvironment && toEnvironments.length > 0 ? (_jsx("div", { className: "vertebr-sync-preview", "aria-label": "Sync direction", children: _jsxs("div", { className: "vertebr-sync-preview-flow", "data-dest-count": toEnvironments.length, children: [_jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-from", children: [_jsx("span", { className: "vertebr-sync-preview-caption", children: "From" }), _jsx(EnvFlowBadge, { environment: fromEnvironment })] }), _jsx("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-routes", children: toEnvironments.map((destination, index) => (_jsxs("div", { className: "vertebr-sync-preview-route", children: [_jsx("span", { className: "vertebr-sync-preview-arrow", "aria-hidden": "true", children: "\u2192" }), _jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-to", children: [index === 0 ? (_jsx("span", { className: "vertebr-sync-preview-caption", children: "To" })) : (_jsx("span", { className: "vertebr-sync-preview-caption", "aria-hidden": "true", children: "\u00A0" })), _jsx(EnvFlowBadge, { environment: destination })] })] }, destination.id))) })] }) })) : (_jsx("p", { className: "vertebr-sync-direction-hint", children: "Pick a source environment and one or more destinations to preview changes." }))] }) }), _jsxs(Card, { title: "What to sync", children: [_jsxs("div", { className: "vertebr-sync-targets-head", children: [_jsx("p", { className: "vertebr-sync-targets-copy", children: "Review what will change in each destination before syncing." }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-sync-refresh", disabled: planLoading || toEnv.length === 0, onClick: () => void refreshPlan(), children: planLoading ? "Refreshing…" : "Refresh preview" })] }), [...targetGroups.entries()].map(([group, targets]) => (_jsxs("div", { className: "vertebr-sync-target-group", children: [_jsx("div", { className: "vertebr-sync-target-group-label", children: group }), _jsx("ul", { className: "vertebr-sync-target-list", children: targets.map((target) => {
157
+ const selected = selectedTargets.includes(target.id);
158
+ return (_jsx("li", { children: _jsxs("button", { type: "button", className: "vertebr-sync-target-row", "data-selected": selected || undefined, onClick: () => toggleTarget(target.id), children: [_jsx("span", { className: "vertebr-sync-target-check", "aria-hidden": "true", children: selected ? "✓" : "" }), _jsxs("span", { className: "vertebr-sync-target-main", children: [_jsx("span", { className: "vertebr-sync-target-title", children: target.label }), target.description ? (_jsx("span", { className: "vertebr-sync-target-desc", children: target.description })) : null] }), _jsx("span", { className: "vertebr-sync-target-badges", children: toEnvironments.length === 0 ? (_jsx(Badge, { children: "Choose destination" })) : planLoading ? (_jsx(Badge, { children: "Previewing\u2026" })) : (toEnvironments.map((destination) => {
159
+ const item = planForTarget(destination.id, target.id);
160
+ if (!item) {
161
+ return (_jsx(Badge, { children: destination.label }, destination.id));
162
+ }
163
+ return (_jsxs("span", { className: "vertebr-sync-target-dest", title: item.summary, children: [_jsxs(Badge, { tone: statusTone(item.status), children: [destination.label, ": ", statusLabel(item.status)] }), _jsx("span", { className: "vertebr-sync-target-summary", children: item.summary })] }, destination.id));
164
+ })) })] }) }, target.id));
165
+ }) })] }, group)))] }), sync?.excluded.length ? (_jsx(Card, { title: "Never synced", subdued: true, children: _jsx("ul", { className: "vertebr-sync-excluded-list", children: sync.excluded.map((item) => (_jsxs("li", { children: [_jsx("span", { className: "vertebr-sync-excluded-name", children: item.label }), _jsx("span", { className: "vertebr-sync-excluded-reason", children: item.reason })] }, item.label))) }) })) : null, error ? _jsx("div", { className: "vertebr-banner", "data-tone": "danger", children: error }) : null] }), _jsxs("div", { className: "vertebr-savebar vertebr-sync-footer", children: [_jsx("div", { className: "vertebr-savebar-message", children: syncSummary }), _jsx("div", { className: "vertebr-savebar-actions", children: _jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-primary vertebr-sync-run-btn", disabled: running ||
166
+ planLoading ||
167
+ toEnv.length === 0 ||
168
+ selectedTargets.length === 0, onClick: () => void runSync(), children: [_jsx(IconSync, {}), _jsx("span", { children: running ? "Syncing…" : "Sync selected" })] }) })] })] }));
169
+ }
@@ -2,10 +2,11 @@ import type { AdminEnvironment } from "@vertebr/client";
2
2
  import type { VbTheme } from "../../hooks/useVertebrTheme";
3
3
  export type AdminTopBarProps = {
4
4
  brand: string;
5
+ hostName?: string;
6
+ hostLogoUrl?: string;
5
7
  envList: AdminEnvironment[];
6
8
  environment: string;
7
9
  onEnvironmentChange: (nextEnv: string) => void;
8
- activeEnv?: AdminEnvironment;
9
10
  authUserLabel?: string;
10
11
  onSignOut?: () => void | Promise<void>;
11
12
  getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
@@ -19,6 +20,9 @@ export type AdminTopBarProps = {
19
20
  onThemeChange: (theme: VbTheme) => void;
20
21
  sidebarCollapsed: boolean;
21
22
  onToggleSidebar: () => void;
23
+ syncEnabled?: boolean;
24
+ onOpenSync?: () => void;
25
+ onOpenSearch?: () => void;
22
26
  };
23
- export declare function AdminTopBar({ brand, envList, environment, onEnvironmentChange, activeEnv, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, }: AdminTopBarProps): import("react").JSX.Element;
27
+ export declare function AdminTopBar({ brand, hostName, hostLogoUrl, envList, environment, onEnvironmentChange, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, syncEnabled, onOpenSync, onOpenSearch, }: AdminTopBarProps): import("react").JSX.Element;
24
28
  //# sourceMappingURL=AdminTopBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminTopBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminTopBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAG3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,EAAE,gBAAgB,+BAsFlB"}
1
+ {"version":3,"file":"AdminTopBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminTopBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAG3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,WAAW,EACX,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE,gBAAgB,+BA6GlB"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { EnvSelect } from "../env/EnvSelect";
3
3
  import { ThemeToggle } from "../theme/ThemeToggle";
4
- import { IconSearch } from "../ui/icons";
5
- export function AdminTopBar({ brand, envList, environment, onEnvironmentChange, activeEnv, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, }) {
6
- return (_jsxs("header", { className: "vertebr-topbar", children: [_jsxs("div", { className: "vertebr-topbar-start", children: [_jsx("button", { type: "button", className: "vertebr-topbar-toggle", "aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar", onClick: onToggleSidebar, children: "\u2630" }), _jsx("strong", { className: "vertebr-topbar-brand", children: brand }), envList.length > 0 ? (_jsxs("div", { className: "vertebr-topbar-env", children: [_jsx(EnvSelect, { environments: envList, value: environment, onChange: onEnvironmentChange }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] })) : null] }), _jsxs("div", { className: "vertebr-topbar-search", "aria-hidden": "true", children: [_jsx(IconSearch, {}), _jsx("span", { children: "Search admin" }), _jsx("kbd", { children: "\u2318K" })] }), _jsxs("div", { className: "vertebr-topbar-end", children: [!getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-topbar-token", children: [_jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Access token", value: tokenDraft, onChange: (event) => onTokenDraftChange(event.target.value), autoComplete: "off" }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onSaveToken, children: "Use" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onClearToken, children: "Clear" })) : null] })) : null, getAccessToken && authUserLabel ? (_jsx("span", { className: "vertebr-topbar-user", children: authUserLabel })) : null, onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => void onSignOut(), children: "Sign out" })) : null, _jsx(ThemeToggle, { theme: theme, onChange: onThemeChange })] })] }));
4
+ import { IconSearch, IconSync } from "../ui/icons";
5
+ export function AdminTopBar({ brand, hostName, hostLogoUrl, envList, environment, onEnvironmentChange, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, syncEnabled, onOpenSync, onOpenSearch, }) {
6
+ return (_jsxs("header", { className: "vertebr-topbar", children: [_jsxs("div", { className: "vertebr-topbar-start", children: [_jsx("button", { type: "button", className: "vertebr-topbar-toggle", "aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar", onClick: onToggleSidebar, children: "\u2630" }), _jsxs("div", { className: "vertebr-topbar-brand-wrap", children: [_jsx("strong", { className: "vertebr-topbar-brand", children: brand }), hostName ? (_jsxs("span", { className: "vertebr-topbar-host", children: [hostLogoUrl ? (_jsx("img", { className: "vertebr-topbar-host-logo", src: hostLogoUrl, alt: "" })) : null, _jsx("span", { className: "vertebr-topbar-host-label", children: hostName })] })) : null] }), envList.length > 1 ? (_jsx("div", { className: "vertebr-topbar-env", children: _jsx(EnvSelect, { environments: envList, value: environment, onChange: onEnvironmentChange }) })) : null] }), _jsx("div", { className: "vertebr-topbar-center", children: _jsxs("button", { type: "button", className: "vertebr-topbar-search", onClick: onOpenSearch, children: [_jsx(IconSearch, {}), _jsx("span", { children: "Search admin" }), _jsx("kbd", { children: "\u2318K" })] }) }), _jsxs("div", { className: "vertebr-topbar-end", children: [syncEnabled && onOpenSync ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-topbar-sync", onClick: onOpenSync, children: [_jsx(IconSync, {}), _jsx("span", { children: "Sync" })] })) : null, !getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-topbar-token", children: [_jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Access token", value: tokenDraft, onChange: (event) => onTokenDraftChange(event.target.value), autoComplete: "off" }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onSaveToken, children: "Use" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onClearToken, children: "Clear" })) : null] })) : null, getAccessToken && authUserLabel ? (_jsx("span", { className: "vertebr-topbar-user", children: authUserLabel })) : null, onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => void onSignOut(), children: "Sign out" })) : null, _jsx(ThemeToggle, { theme: theme, onChange: onThemeChange })] })] }));
7
7
  }
@@ -6,9 +6,11 @@ export type PagePreviewPaneProps = {
6
6
  controlId: string;
7
7
  /** Latest unsaved form values. */
8
8
  draft: Record<string, unknown> | null;
9
+ /** Label for the preview chrome. */
10
+ previewKind?: "page" | "addon";
9
11
  };
10
12
  /**
11
13
  * Live host iframe for Pages. Vertebr posts drafts; the host renders.
12
14
  */
13
- export declare function PagePreviewPane({ previewPath, controlId, draft, }: PagePreviewPaneProps): import("react").JSX.Element;
15
+ export declare function PagePreviewPane({ previewPath, controlId, draft, previewKind, }: PagePreviewPaneProps): import("react").JSX.Element;
14
16
  //# sourceMappingURL=PagePreviewPane.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PagePreviewPane.d.ts","sourceRoot":"","sources":["../../../src/components/admin/PagePreviewPane.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAWF;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,SAAS,EACT,KAAK,GACN,EAAE,oBAAoB,+BAmMtB"}
1
+ {"version":3,"file":"PagePreviewPane.d.ts","sourceRoot":"","sources":["../../../src/components/admin/PagePreviewPane.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC,CAAC;AAWF;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,SAAS,EACT,KAAK,EACL,WAAoB,GACrB,EAAE,oBAAoB,+BAmMtB"}
@@ -11,7 +11,7 @@ const MOBILE_HEIGHT = 844;
11
11
  /**
12
12
  * Live host iframe for Pages. Vertebr posts drafts; the host renders.
13
13
  */
14
- export function PagePreviewPane({ previewPath, controlId, draft, }) {
14
+ export function PagePreviewPane({ previewPath, controlId, draft, previewKind = "page", }) {
15
15
  const iframeRef = useRef(null);
16
16
  const stageRef = useRef(null);
17
17
  const draftRef = useRef(draft);
@@ -93,7 +93,7 @@ export function PagePreviewPane({ previewPath, controlId, draft, }) {
93
93
  const timer = window.setTimeout(notifyHostResize, 50);
94
94
  return () => window.clearTimeout(timer);
95
95
  }, [viewport, mobileScale, frameReady, notifyHostResize]);
96
- return (_jsxs("div", { className: "vertebr-page-preview", children: [_jsxs("div", { className: "vertebr-page-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: "Preview" }), _jsx("strong", { children: previewPath })] }), _jsxs("p", { children: [_jsx("span", { children: "Status" }), _jsx("strong", { children: frameError
96
+ return (_jsxs("div", { className: "vertebr-page-preview", "data-preview-kind": previewKind, children: [_jsxs("div", { className: "vertebr-page-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: previewKind === "addon" ? "Storefront" : "Preview" }), _jsx("strong", { children: previewPath })] }), _jsxs("p", { children: [_jsx("span", { children: "Status" }), _jsx("strong", { children: frameError
97
97
  ? "Unavailable"
98
98
  : frameReady
99
99
  ? "Live"
@@ -1 +1 @@
1
- {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,QAAQ,EAAE,YAAY,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,sBAAgD,EAChD,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAskBnB"}
1
+ {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,QAAQ,EAAE,YAAY,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,sBAAgD,EAChD,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAopBnB"}
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { createVertebrClient, } from "@vertebr/client";
4
4
  import { useVertebrTheme } from "../../hooks/useVertebrTheme";
5
5
  import { parseMetricsRangeId, } from "../../lib/metricsRanges";
6
+ import { AdminCommandPalette } from "./AdminCommandPalette";
6
7
  import { AdminContent } from "./AdminContent";
7
8
  import { AdminHeader } from "./AdminHeader";
8
9
  import { AdminSaveBar } from "./AdminSaveBar";
@@ -51,6 +52,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
51
52
  const [error, setError] = useState(null);
52
53
  const [loading, setLoading] = useState(true);
53
54
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
55
+ const [searchOpen, setSearchOpen] = useState(false);
54
56
  /** Skip pushState when selection was applied from the URL (catalog/popstate). */
55
57
  const skipNextUrlPush = useRef(true);
56
58
  const syncUrl = useCallback((next, catalogSnapshot, replace = false) => {
@@ -130,6 +132,16 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
130
132
  }
131
133
  syncUrl(selection, catalog, false);
132
134
  }, [selection, catalog, syncUrl]);
135
+ useEffect(() => {
136
+ const onKeyDown = (event) => {
137
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
138
+ event.preventDefault();
139
+ setSearchOpen(true);
140
+ }
141
+ };
142
+ window.addEventListener("keydown", onKeyDown);
143
+ return () => window.removeEventListener("keydown", onKeyDown);
144
+ }, []);
133
145
  useEffect(() => {
134
146
  const onPopState = () => {
135
147
  if (!catalog)
@@ -208,7 +220,17 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
208
220
  setEmailsData(null);
209
221
  }
210
222
  }
211
- else {
223
+ else if (selection.kind === "sync" || selection.kind === "addons") {
224
+ if (!cancelled) {
225
+ setResourceData(null);
226
+ setConfigData(null);
227
+ setMetricsData(null);
228
+ setGalleryData(null);
229
+ setFontLibraryData(null);
230
+ setEmailsData(null);
231
+ }
232
+ }
233
+ else if (selection.kind === "config" || selection.kind === "addon") {
212
234
  const data = await client.getConfig(selection.id);
213
235
  if (!cancelled) {
214
236
  setConfigData(data);
@@ -445,6 +467,26 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
445
467
  ],
446
468
  });
447
469
  }
470
+ if (catalog.addons?.length) {
471
+ entries.push({
472
+ key: "addons:overview",
473
+ group: "Add-ons",
474
+ label: "Browse add-ons",
475
+ icon: iconForNavKey("addons:overview"),
476
+ active: selection?.kind === "addons",
477
+ onSelect: () => setSelection({ kind: "addons" }),
478
+ });
479
+ }
480
+ for (const addon of catalog.addons ?? []) {
481
+ entries.push({
482
+ key: `addon:${addon.id}`,
483
+ group: addon.group ?? "Add-ons",
484
+ label: addon.label,
485
+ icon: iconForNavKey(`addon:${addon.id}`),
486
+ active: selection?.kind === "addon" && selection.id === addon.id,
487
+ onSelect: () => setSelection({ kind: "addon", id: addon.id }),
488
+ });
489
+ }
448
490
  for (const config of catalog.configs) {
449
491
  const fallback = config.section === "page" ? "Pages" : "Config";
450
492
  entries.push({
@@ -456,6 +498,16 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
456
498
  onSelect: () => setSelection({ kind: "config", id: config.id }),
457
499
  });
458
500
  }
501
+ if (catalog.sync?.enabled) {
502
+ entries.push({
503
+ key: "sync",
504
+ group: "Settings",
505
+ label: "Sync environments",
506
+ icon: iconForNavKey("sync"),
507
+ active: selection?.kind === "sync",
508
+ onSelect: () => setSelection({ kind: "sync" }),
509
+ });
510
+ }
459
511
  return sidebarGroups(entries);
460
512
  }, [catalog, selection]);
461
513
  if (!bootstrap && loading) {
@@ -466,16 +518,27 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
466
518
  }
467
519
  const activeLabel = selection?.kind === "overview"
468
520
  ? (catalog?.metrics?.label ?? "Overview")
469
- : selection?.kind === "gallery"
470
- ? (catalog?.gallery?.label ?? "Gallery")
471
- : selection?.kind === "fontLibrary"
472
- ? (catalog?.fontLibrary?.label ?? "Font Library")
473
- : selection?.kind === "emails"
474
- ? (catalog?.emails?.label ?? "Emails")
475
- : selection?.kind === "resource"
476
- ? catalog?.resources.find((r) => r.id === selection.id)?.label
477
- : selection?.kind === "config"
478
- ? catalog?.configs.find((c) => c.id === selection.id)?.label
479
- : null;
480
- 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", envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, activeEnv: activeEnv, 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) }), _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), activeEnv: activeEnv, catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, 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, {})] }) }) }));
521
+ : selection?.kind === "sync"
522
+ ? "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;
538
+ const selectionDescription = selection?.kind === "addons"
539
+ ? "Preview how optional storefront features look in your shop, then configure each one."
540
+ : selection?.kind === "addon"
541
+ ? catalog?.addons?.find((item) => item.id === selection.id)?.description
542
+ : 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, {})] }) }) }));
481
544
  }
@@ -0,0 +1,6 @@
1
+ import type { CatalogAddonCategory } from "@vertebr/client";
2
+ export declare const ADDON_CATEGORY_LABELS: Record<CatalogAddonCategory, string>;
3
+ export declare function addonCategoryLabel(category: CatalogAddonCategory | undefined): string;
4
+ export declare const ADDON_CATEGORY_HINTS: Record<CatalogAddonCategory, string>;
5
+ export declare function addonCategoryHint(category: CatalogAddonCategory | undefined): string;
6
+ //# sourceMappingURL=addonLabels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addonLabels.d.ts","sourceRoot":"","sources":["../../../src/components/admin/addonLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAItE,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,GACzC,MAAM,CAGR;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAIrE,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,oBAAoB,GAAG,SAAS,GACzC,MAAM,CAGR"}
@@ -0,0 +1,20 @@
1
+ export const ADDON_CATEGORY_LABELS = {
2
+ product: "Products",
3
+ engagement: "Engagement",
4
+ sales: "Sales",
5
+ };
6
+ export function addonCategoryLabel(category) {
7
+ if (!category)
8
+ return "Add-on";
9
+ return ADDON_CATEGORY_LABELS[category] ?? "Add-on";
10
+ }
11
+ export const ADDON_CATEGORY_HINTS = {
12
+ product: "Tied to products, variants, and checkout",
13
+ engagement: "Brand story and feedback on the side",
14
+ sales: "Promos, urgency, and cart nudges",
15
+ };
16
+ export function addonCategoryHint(category) {
17
+ if (!category)
18
+ return "Optional storefront feature";
19
+ return ADDON_CATEGORY_HINTS[category] ?? "Optional storefront feature";
20
+ }
@@ -0,0 +1,12 @@
1
+ import type { CatalogResponse } from "@vertebr/client";
2
+ import type { Selection } from "./types";
3
+ export type AdminSearchItem = {
4
+ id: string;
5
+ label: string;
6
+ group: string;
7
+ hint?: string;
8
+ selection: Selection;
9
+ };
10
+ export declare function adminSearchItems(catalog: CatalogResponse): AdminSearchItem[];
11
+ export declare function filterAdminSearchItems(items: AdminSearchItem[], query: string): AdminSearchItem[];
12
+ //# sourceMappingURL=adminSearch.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,111 @@
1
+ export function adminSearchItems(catalog) {
2
+ const items = [];
3
+ if (catalog.metrics) {
4
+ items.push({
5
+ id: "overview",
6
+ label: catalog.metrics.label,
7
+ group: catalog.metrics.group ?? "Home",
8
+ hint: "Overview and metrics",
9
+ selection: { kind: "overview" },
10
+ });
11
+ }
12
+ for (const resource of catalog.resources) {
13
+ items.push({
14
+ id: `resource:${resource.id}`,
15
+ label: resource.label,
16
+ group: resource.group ?? "Resources",
17
+ hint: "Resource table",
18
+ selection: { kind: "resource", id: resource.id },
19
+ });
20
+ }
21
+ if (catalog.gallery) {
22
+ items.push({
23
+ id: "gallery",
24
+ label: catalog.gallery.label,
25
+ group: catalog.gallery.group ?? "Resources",
26
+ hint: "Media library",
27
+ selection: { kind: "gallery" },
28
+ });
29
+ }
30
+ if (catalog.fontLibrary) {
31
+ items.push({
32
+ id: "fonts",
33
+ label: catalog.fontLibrary.label,
34
+ group: catalog.fontLibrary.group ?? "Resources",
35
+ hint: "Font library",
36
+ selection: { kind: "fontLibrary" },
37
+ });
38
+ }
39
+ if (catalog.emails) {
40
+ items.push({
41
+ id: "emails:compose",
42
+ label: "Compose email",
43
+ group: catalog.emails.group ?? "Growth",
44
+ hint: catalog.emails.label,
45
+ selection: { kind: "emails", tab: "compose" },
46
+ });
47
+ items.push({
48
+ id: "emails:templates",
49
+ label: "Email templates",
50
+ group: catalog.emails.group ?? "Growth",
51
+ hint: catalog.emails.label,
52
+ selection: { kind: "emails", tab: "templates" },
53
+ });
54
+ items.push({
55
+ id: "emails:activity",
56
+ label: "Email activity",
57
+ group: catalog.emails.group ?? "Growth",
58
+ hint: catalog.emails.label,
59
+ selection: { kind: "emails", tab: "activity" },
60
+ });
61
+ }
62
+ if (catalog.addons?.length) {
63
+ items.push({
64
+ id: "addons:overview",
65
+ label: "Browse add-ons",
66
+ group: "Add-ons",
67
+ hint: "Preview and configure storefront features",
68
+ selection: { kind: "addons" },
69
+ });
70
+ }
71
+ for (const addon of catalog.addons ?? []) {
72
+ items.push({
73
+ id: `addon:${addon.id}`,
74
+ label: addon.label,
75
+ group: addon.group ?? "Add-ons",
76
+ hint: addon.description ?? "Storefront add-on",
77
+ selection: { kind: "addon", id: addon.id },
78
+ });
79
+ }
80
+ for (const config of catalog.configs) {
81
+ const bucket = config.section === "page" ? "Pages" : "Config";
82
+ items.push({
83
+ id: `config:${config.id}`,
84
+ label: config.label,
85
+ group: config.group ?? bucket,
86
+ hint: config.section === "page" ? "Page content" : "Settings",
87
+ selection: { kind: "config", id: config.id },
88
+ });
89
+ }
90
+ if (catalog.sync?.enabled) {
91
+ items.push({
92
+ id: "sync",
93
+ label: "Sync environments",
94
+ group: "Settings",
95
+ hint: "Copy content between environments",
96
+ selection: { kind: "sync" },
97
+ });
98
+ }
99
+ return items;
100
+ }
101
+ export function filterAdminSearchItems(items, query) {
102
+ const needle = query.trim().toLowerCase();
103
+ if (!needle)
104
+ return items;
105
+ return items.filter((item) => {
106
+ const haystack = [item.label, item.group, item.hint ?? "", item.id]
107
+ .join(" ")
108
+ .toLowerCase();
109
+ return haystack.includes(needle);
110
+ });
111
+ }
@@ -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,CA6Cd"}
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"}
@@ -5,6 +5,18 @@ export function breadcrumbsForSelection(selection, catalog) {
5
5
  switch (selection.kind) {
6
6
  case "overview":
7
7
  return [...crumbs, { label: catalog.metrics?.label ?? "Overview" }];
8
+ case "sync":
9
+ return [...crumbs, { label: "Sync environments" }];
10
+ case "addons":
11
+ return [...crumbs, { label: "Add-ons" }];
12
+ case "addon": {
13
+ const addon = catalog.addons?.find((item) => item.id === selection.id);
14
+ return [
15
+ ...crumbs,
16
+ { label: "Add-ons", href: "/addons" },
17
+ { label: addon?.label ?? selection.id },
18
+ ];
19
+ }
8
20
  case "gallery":
9
21
  return [...crumbs, { label: catalog.gallery?.label ?? "Gallery" }];
10
22
  case "fontLibrary":
@@ -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,CA0BR;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,CAuClB;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,CAyBX;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAenE"}
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"}