@vertebr/react 0.5.1 → 0.7.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.
- package/dist/components/admin/AdminContent.d.ts +11 -2
- package/dist/components/admin/AdminContent.d.ts.map +1 -1
- package/dist/components/admin/AdminContent.js +35 -10
- package/dist/components/admin/AdminHeader.d.ts +3 -1
- package/dist/components/admin/AdminHeader.d.ts.map +1 -1
- package/dist/components/admin/AdminHeader.js +12 -3
- package/dist/components/admin/PagePreviewPane.d.ts +14 -0
- package/dist/components/admin/PagePreviewPane.d.ts.map +1 -0
- package/dist/components/admin/PagePreviewPane.js +121 -0
- package/dist/components/admin/VertebrAdmin.d.ts +1 -1
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
- package/dist/components/admin/VertebrAdmin.js +123 -41
- package/dist/components/admin/routes.d.ts +21 -0
- package/dist/components/admin/routes.d.ts.map +1 -0
- package/dist/components/admin/routes.js +170 -0
- package/dist/components/admin/types.d.ts +7 -0
- package/dist/components/admin/types.d.ts.map +1 -1
- package/dist/components/config/VisibilityEye.d.ts +12 -0
- package/dist/components/config/VisibilityEye.d.ts.map +1 -1
- package/dist/components/config/VisibilityEye.js +12 -4
- package/dist/components/config/form/ConfigField.d.ts +5 -3
- package/dist/components/config/form/ConfigField.d.ts.map +1 -1
- package/dist/components/config/form/ConfigField.js +7 -3
- package/dist/components/config/form/ConfigForm.d.ts +1 -1
- package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/ConfigForm.js +5 -1
- package/dist/components/config/form/RecordConfigForm.d.ts +1 -1
- package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/RecordConfigForm.js +2 -2
- package/dist/components/config/form/SettingsConfigForm.d.ts +3 -3
- package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/SettingsConfigForm.js +7 -2
- package/dist/components/config/form/helpers/fields.d.ts +2 -1
- package/dist/components/config/form/helpers/fields.d.ts.map +1 -1
- package/dist/components/config/form/helpers/fields.js +4 -0
- package/dist/components/config/form/types.d.ts +6 -1
- package/dist/components/config/form/types.d.ts.map +1 -1
- package/dist/components/fonts/FontFaceInjector.d.ts +9 -0
- package/dist/components/fonts/FontFaceInjector.d.ts.map +1 -0
- package/dist/components/fonts/FontFaceInjector.js +28 -0
- package/dist/components/fonts/FontLibraryBoard.d.ts +15 -0
- package/dist/components/fonts/FontLibraryBoard.d.ts.map +1 -0
- package/dist/components/fonts/FontLibraryBoard.js +41 -0
- package/dist/components/fonts/FontPickerField.d.ts +16 -0
- package/dist/components/fonts/FontPickerField.d.ts.map +1 -0
- package/dist/components/fonts/FontPickerField.js +60 -0
- package/dist/components/fonts/FontPreviewModal.d.ts +18 -0
- package/dist/components/fonts/FontPreviewModal.d.ts.map +1 -0
- package/dist/components/fonts/FontPreviewModal.js +57 -0
- package/dist/components/fonts/FontPreviewSample.d.ts +8 -0
- package/dist/components/fonts/FontPreviewSample.d.ts.map +1 -0
- package/dist/components/fonts/FontPreviewSample.js +13 -0
- package/dist/components/fonts/GoogleFontsBrowser.d.ts +8 -0
- package/dist/components/fonts/GoogleFontsBrowser.d.ts.map +1 -0
- package/dist/components/fonts/GoogleFontsBrowser.js +49 -0
- package/dist/components/resources/ResourceCell.js +3 -2
- package/dist/components/resources/ResourceTable.d.ts +1 -1
- package/dist/components/resources/ResourceTable.d.ts.map +1 -1
- package/dist/components/resources/ResourceTable.js +123 -5
- package/dist/hooks/useVertebrControls.d.ts +10 -1
- package/dist/hooks/useVertebrControls.d.ts.map +1 -1
- package/dist/hooks/useVertebrControls.js +32 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/styles/base.css +11 -17
- package/dist/styles/buttons.css +3 -12
- package/dist/styles/font-library.css +401 -0
- package/dist/styles/form.css +89 -3
- package/dist/styles/index.css +3 -1
- package/dist/styles/main.css +1 -1
- package/dist/styles/page-preview.css +208 -0
- package/dist/styles/panel.css +7 -2
- package/dist/styles/states.css +6 -3
- package/dist/styles/table.css +18 -0
- package/dist/themes/dark.css +48 -46
- package/dist/themes/index.css +1 -0
- package/dist/themes/light.css +91 -85
- package/dist/themes/tokens.css +52 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CatalogResponse, ConfigResponse, EmailsBoardResponse, GalleryResponse, MetricsResponse, ResourceListResponse, VertebrClient } from "@vertebr/client";
|
|
1
|
+
import type { CatalogResponse, ConfigResponse, EmailsBoardResponse, FontLibraryResponse, GalleryResponse, MetricsResponse, ResourceListResponse, VertebrClient } from "@vertebr/client";
|
|
2
2
|
import type { MetricsRangeId } from "../../lib/metricsRanges";
|
|
3
3
|
import type { Selection } from "./types";
|
|
4
4
|
export type AdminContentProps = {
|
|
@@ -16,6 +16,9 @@ export type AdminContentProps = {
|
|
|
16
16
|
galleryData: GalleryResponse | null;
|
|
17
17
|
galleryLoading: boolean;
|
|
18
18
|
onRefreshGallery: () => Promise<void>;
|
|
19
|
+
fontLibraryData: FontLibraryResponse | null;
|
|
20
|
+
fontLibraryLoading: boolean;
|
|
21
|
+
onRefreshFonts: () => Promise<void>;
|
|
19
22
|
emailsData: EmailsBoardResponse | null;
|
|
20
23
|
emailsLoading: boolean;
|
|
21
24
|
onRefreshEmails: () => Promise<void>;
|
|
@@ -31,6 +34,12 @@ export type AdminContentProps = {
|
|
|
31
34
|
onListGallery?: () => Promise<GalleryResponse["items"]>;
|
|
32
35
|
onUploadGallery?: (files: File[]) => Promise<GalleryResponse["items"]>;
|
|
33
36
|
};
|
|
37
|
+
fontLibraryPicker: {
|
|
38
|
+
fonts?: FontLibraryResponse["items"];
|
|
39
|
+
fontsLoading?: boolean;
|
|
40
|
+
onListFonts?: () => Promise<FontLibraryResponse["items"]>;
|
|
41
|
+
onRefreshFonts?: () => Promise<void>;
|
|
42
|
+
};
|
|
34
43
|
};
|
|
35
|
-
export declare function AdminContent({ client, catalog, selection, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, }: AdminContentProps): import("react").JSX.Element;
|
|
44
|
+
export declare function AdminContent({ client, catalog, selection, 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;
|
|
36
45
|
//# sourceMappingURL=AdminContent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAOzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE;QACb,aAAa,CAAC,EAAE;YACd,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;IACF,iBAAiB,EAAE;QACjB,KAAK,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACtC,CAAC;CACH,CAAC;AA6EF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,SAAS,EACT,YAAY,EACZ,OAAO,EACP,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,iBAAiB,+BAuQnB"}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
2
3
|
import { ConfigForm } from "../config/ConfigForm";
|
|
3
4
|
import { EmailsBoard } from "../emails/EmailsBoard";
|
|
5
|
+
import { FontLibraryBoard } from "../fonts/FontLibraryBoard";
|
|
4
6
|
import { GalleryBoard } from "../gallery/GalleryBoard";
|
|
5
7
|
import { MetricsBoard } from "../metrics/MetricsBoard";
|
|
6
8
|
import { ResourcePanel } from "../resources/ResourceTable";
|
|
7
|
-
|
|
9
|
+
import { PagePreviewPane } from "./PagePreviewPane";
|
|
10
|
+
function ConfigEditor({ client, configData, isPageSelection, previewPath, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
11
|
+
const [draft, setDraft] = useState(configData.value);
|
|
12
|
+
const showPreview = Boolean(previewPath);
|
|
13
|
+
const onChange = useCallback((value) => {
|
|
14
|
+
setDraft(value);
|
|
15
|
+
}, []);
|
|
16
|
+
const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
|
|
17
|
+
const saved = await client.setConfig(configData.id, next);
|
|
18
|
+
onConfigSaved(saved);
|
|
19
|
+
}, ...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)] }));
|
|
21
|
+
}
|
|
22
|
+
export function AdminContent({ client, catalog, selection, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
8
23
|
if (!catalog) {
|
|
9
24
|
return (_jsx("div", { className: "vertebr-content", children: _jsx("section", { className: "vertebr-panel", children: _jsx("div", { className: "vertebr-empty", children: authRequired
|
|
10
25
|
? "Authenticated access required for this environment."
|
|
@@ -25,6 +40,21 @@ export function AdminContent({ client, catalog, selection, authRequired, loading
|
|
|
25
40
|
await client.deleteGalleryItem(id);
|
|
26
41
|
await onRefreshGallery();
|
|
27
42
|
}
|
|
43
|
+
: undefined })) : null, selection?.kind === "fontLibrary" && fontLibraryData ? (_jsx(FontLibraryBoard, { label: fontLibraryData.label, data: fontLibraryData, loading: fontLibraryLoading, onRefresh: () => void onRefreshFonts(), onUpload: async (files, meta) => {
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
await client.uploadFontFile(file, meta);
|
|
46
|
+
}
|
|
47
|
+
await onRefreshFonts();
|
|
48
|
+
}, onImportGoogle: fontLibraryData.canImportGoogle
|
|
49
|
+
? async (payload) => {
|
|
50
|
+
await client.importGoogleFont(payload);
|
|
51
|
+
await onRefreshFonts();
|
|
52
|
+
}
|
|
53
|
+
: undefined, onDelete: fontLibraryData.canDelete
|
|
54
|
+
? async (id) => {
|
|
55
|
+
await client.deleteFontItem(id);
|
|
56
|
+
await onRefreshFonts();
|
|
57
|
+
}
|
|
28
58
|
: undefined })) : null, selection?.kind === "emails" && emailsData ? (_jsx(EmailsBoard, { data: emailsData, loading: emailsLoading, tab: selection.tab ?? "compose", onRefresh: () => void onRefreshEmails(), onLoadRecipients: async (resourceId) => {
|
|
29
59
|
const listed = await client.listResource(resourceId, {
|
|
30
60
|
fresh: true,
|
|
@@ -87,7 +117,7 @@ export function AdminContent({ client, catalog, selection, authRequired, loading
|
|
|
87
117
|
}, onActionDone: onRefreshResource, onPreviewMail: async (rowId, actionId, input) => {
|
|
88
118
|
return client.previewResourceMail(resourceData.id, rowId, actionId, input);
|
|
89
119
|
}, onCollectionAction: async (actionId, input) => {
|
|
90
|
-
|
|
120
|
+
return client.runCollectionAction(resourceData.id, actionId, input);
|
|
91
121
|
}, onUpload: resourceData.uploads
|
|
92
122
|
? async (field, files) => {
|
|
93
123
|
if (files.length === 1 && files[0]) {
|
|
@@ -97,11 +127,6 @@ export function AdminContent({ client, catalog, selection, authRequired, loading
|
|
|
97
127
|
const result = await client.uploadResourceFiles(resourceData.id, field, files);
|
|
98
128
|
return result.files;
|
|
99
129
|
}
|
|
100
|
-
: undefined, ...galleryPicker })) : null, selection?.kind === "config" && configData ? (
|
|
101
|
-
|
|
102
|
-
onConfigSaved({
|
|
103
|
-
...configData,
|
|
104
|
-
value: saved.value,
|
|
105
|
-
});
|
|
106
|
-
}, ...galleryPicker })] })) : null] }) }));
|
|
130
|
+
: undefined, ...galleryPicker })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
|
|
131
|
+
?.previewPath, onConfigSaved: onConfigSaved, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id)) : null] }) }));
|
|
107
132
|
}
|
|
@@ -7,7 +7,9 @@ export type AdminHeaderProps = {
|
|
|
7
7
|
authRequired: boolean;
|
|
8
8
|
selection: Selection;
|
|
9
9
|
isPageSelection: boolean;
|
|
10
|
+
/** True when the active page/layout has a host previewPath. */
|
|
11
|
+
hasPagePreview?: boolean;
|
|
10
12
|
error: string | null;
|
|
11
13
|
};
|
|
12
|
-
export declare function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, error, }: AdminHeaderProps): import("react").JSX.Element;
|
|
14
|
+
export declare function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, }: AdminHeaderProps): import("react").JSX.Element;
|
|
13
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,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,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;
|
|
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,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,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AA+CF,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,KAAK,GACN,EAAE,gBAAgB,+BAgClB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, }) {
|
|
2
|
+
function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, }) {
|
|
3
3
|
if (!catalogLoaded && authRequired) {
|
|
4
4
|
return "Provide an access token to load this environment.";
|
|
5
5
|
}
|
|
@@ -9,6 +9,9 @@ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, })
|
|
|
9
9
|
if (selection?.kind === "gallery") {
|
|
10
10
|
return "Browse and upload shared media assets.";
|
|
11
11
|
}
|
|
12
|
+
if (selection?.kind === "fontLibrary") {
|
|
13
|
+
return "Upload custom fonts or add Google Fonts to your library.";
|
|
14
|
+
}
|
|
12
15
|
if (selection?.kind === "emails") {
|
|
13
16
|
return "Send or schedule branded email to a list or specific people.";
|
|
14
17
|
}
|
|
@@ -16,17 +19,23 @@ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, })
|
|
|
16
19
|
return "Manage records scoped to the selected environment.";
|
|
17
20
|
}
|
|
18
21
|
if (selection?.kind === "config") {
|
|
22
|
+
if (hasPagePreview) {
|
|
23
|
+
return isPageSelection
|
|
24
|
+
? "Edit content — live storefront preview updates as you type."
|
|
25
|
+
: "Edit settings — live storefront preview updates as you change typography and colors.";
|
|
26
|
+
}
|
|
19
27
|
return isPageSelection
|
|
20
28
|
? "Edit content for this page."
|
|
21
|
-
: "
|
|
29
|
+
: "Environment settings — changes apply to this environment only.";
|
|
22
30
|
}
|
|
23
31
|
return "Select a panel.";
|
|
24
32
|
}
|
|
25
|
-
export function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, error, }) {
|
|
33
|
+
export function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, }) {
|
|
26
34
|
return (_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_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({
|
|
27
35
|
catalogLoaded,
|
|
28
36
|
authRequired,
|
|
29
37
|
selection,
|
|
30
38
|
isPageSelection,
|
|
39
|
+
hasPagePreview,
|
|
31
40
|
}) })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }));
|
|
32
41
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PagePreviewViewport = "desktop" | "mobile";
|
|
2
|
+
export type PagePreviewPaneProps = {
|
|
3
|
+
/** Host route from the page/layout definition, e.g. `/about`. */
|
|
4
|
+
previewPath: string;
|
|
5
|
+
/** Control id being edited. */
|
|
6
|
+
controlId: string;
|
|
7
|
+
/** Latest unsaved form values. */
|
|
8
|
+
draft: Record<string, unknown> | null;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Live host iframe for Pages. Vertebr posts drafts; the host renders.
|
|
12
|
+
*/
|
|
13
|
+
export declare function PagePreviewPane({ previewPath, controlId, draft, }: PagePreviewPaneProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=PagePreviewPane.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { buildVertebrPreviewUrl, postVertebrPreviewDraft, subscribeVertebrPreviewReady, } from "@vertebr/client";
|
|
4
|
+
const VIEWPORTS = [
|
|
5
|
+
{ id: "desktop", label: "Desktop" },
|
|
6
|
+
{ id: "mobile", label: "Mobile" },
|
|
7
|
+
];
|
|
8
|
+
/** Logical CSS viewport for mobile preview (host media queries use this). */
|
|
9
|
+
const MOBILE_WIDTH = 390;
|
|
10
|
+
const MOBILE_HEIGHT = 844;
|
|
11
|
+
/**
|
|
12
|
+
* Live host iframe for Pages. Vertebr posts drafts; the host renders.
|
|
13
|
+
*/
|
|
14
|
+
export function PagePreviewPane({ previewPath, controlId, draft, }) {
|
|
15
|
+
const iframeRef = useRef(null);
|
|
16
|
+
const stageRef = useRef(null);
|
|
17
|
+
const draftRef = useRef(draft);
|
|
18
|
+
const [frameReady, setFrameReady] = useState(false);
|
|
19
|
+
const [frameError, setFrameError] = useState(false);
|
|
20
|
+
const [viewport, setViewport] = useState("desktop");
|
|
21
|
+
const [mobileScale, setMobileScale] = useState(1);
|
|
22
|
+
draftRef.current = draft;
|
|
23
|
+
const isMobile = viewport === "mobile";
|
|
24
|
+
const src = useMemo(() => buildVertebrPreviewUrl(previewPath, {
|
|
25
|
+
controlId,
|
|
26
|
+
}), [previewPath, controlId]);
|
|
27
|
+
const pushDraft = useCallback(() => {
|
|
28
|
+
const win = iframeRef.current?.contentWindow;
|
|
29
|
+
const value = draftRef.current;
|
|
30
|
+
if (!win || !value)
|
|
31
|
+
return;
|
|
32
|
+
postVertebrPreviewDraft(win, controlId, value);
|
|
33
|
+
}, [controlId]);
|
|
34
|
+
const notifyHostResize = useCallback(() => {
|
|
35
|
+
const win = iframeRef.current?.contentWindow;
|
|
36
|
+
if (!win)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
win.dispatchEvent(new Event("resize"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Cross-origin should not happen on same-origin preview hosts.
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setFrameReady(false);
|
|
47
|
+
setFrameError(false);
|
|
48
|
+
}, [src]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
return subscribeVertebrPreviewReady((source) => {
|
|
51
|
+
const win = iframeRef.current?.contentWindow;
|
|
52
|
+
if (!win || source !== win)
|
|
53
|
+
return;
|
|
54
|
+
setFrameReady(true);
|
|
55
|
+
pushDraft();
|
|
56
|
+
});
|
|
57
|
+
}, [pushDraft, src]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!frameReady)
|
|
60
|
+
return;
|
|
61
|
+
pushDraft();
|
|
62
|
+
}, [draft, frameReady, pushDraft]);
|
|
63
|
+
// Fit the fixed mobile viewport into the stage without changing its
|
|
64
|
+
// layout width (transform scale keeps host media queries at 390px).
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!isMobile) {
|
|
67
|
+
setMobileScale(1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const stage = stageRef.current;
|
|
71
|
+
if (!stage || typeof ResizeObserver === "undefined")
|
|
72
|
+
return;
|
|
73
|
+
const update = () => {
|
|
74
|
+
const style = getComputedStyle(stage);
|
|
75
|
+
const padX = (Number.parseFloat(style.paddingLeft) || 0) +
|
|
76
|
+
(Number.parseFloat(style.paddingRight) || 0);
|
|
77
|
+
const padY = (Number.parseFloat(style.paddingTop) || 0) +
|
|
78
|
+
(Number.parseFloat(style.paddingBottom) || 0);
|
|
79
|
+
const availW = Math.max(0, stage.clientWidth - padX);
|
|
80
|
+
const availH = Math.max(0, stage.clientHeight - padY);
|
|
81
|
+
const next = Math.min(1, availW / MOBILE_WIDTH, availH / MOBILE_HEIGHT);
|
|
82
|
+
setMobileScale(Number.isFinite(next) && next > 0 ? next : 1);
|
|
83
|
+
};
|
|
84
|
+
update();
|
|
85
|
+
const observer = new ResizeObserver(update);
|
|
86
|
+
observer.observe(stage);
|
|
87
|
+
return () => observer.disconnect();
|
|
88
|
+
}, [isMobile]);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!frameReady)
|
|
91
|
+
return;
|
|
92
|
+
// After the iframe box size changes, nudge hosts that key off resize.
|
|
93
|
+
const timer = window.setTimeout(notifyHostResize, 50);
|
|
94
|
+
return () => window.clearTimeout(timer);
|
|
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
|
|
97
|
+
? "Unavailable"
|
|
98
|
+
: frameReady
|
|
99
|
+
? "Live"
|
|
100
|
+
: "Loading…" })] }), isMobile ? (_jsxs("p", { children: [_jsx("span", { children: "Viewport" }), _jsxs("strong", { children: [MOBILE_WIDTH, "\u00D7", MOBILE_HEIGHT] })] })) : null, _jsx("div", { className: "vertebr-page-preview-viewports", role: "group", "aria-label": "Preview viewport", children: VIEWPORTS.map((item) => (_jsx("button", { type: "button", "data-active": viewport === item.id ? "true" : undefined, onClick: () => setViewport(item.id), children: item.label }, item.id))) })] }), _jsx("div", { ref: stageRef, className: "vertebr-page-preview-stage", "data-viewport": viewport, children: _jsx("div", { className: "vertebr-page-preview-shell", style: isMobile
|
|
101
|
+
? {
|
|
102
|
+
width: MOBILE_WIDTH * mobileScale,
|
|
103
|
+
height: MOBILE_HEIGHT * mobileScale,
|
|
104
|
+
}
|
|
105
|
+
: undefined, children: _jsx("div", { className: "vertebr-page-preview-frame", style: isMobile
|
|
106
|
+
? {
|
|
107
|
+
width: MOBILE_WIDTH,
|
|
108
|
+
height: MOBILE_HEIGHT,
|
|
109
|
+
transform: `scale(${mobileScale})`,
|
|
110
|
+
transformOrigin: "top left",
|
|
111
|
+
}
|
|
112
|
+
: undefined, children: frameError ? (_jsxs("div", { className: "vertebr-page-preview-empty", children: ["Could not load host preview at ", previewPath, "."] })) : (_jsx("iframe", { ref: iframeRef, title: `Preview ${controlId}`, src: src, width: isMobile ? MOBILE_WIDTH : undefined, height: isMobile ? MOBILE_HEIGHT : undefined, style: isMobile
|
|
113
|
+
? { width: MOBILE_WIDTH, height: MOBILE_HEIGHT }
|
|
114
|
+
: undefined, onLoad: () => {
|
|
115
|
+
// Ready message is preferred; onLoad covers hosts that
|
|
116
|
+
// already mounted before the listener attached.
|
|
117
|
+
setFrameReady(true);
|
|
118
|
+
pushDraft();
|
|
119
|
+
notifyHostResize();
|
|
120
|
+
}, onError: () => setFrameError(true) })) }) }) })] }));
|
|
121
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { VertebrAdminProps } from "./types";
|
|
2
2
|
export type { VertebrAdminProps } from "./types";
|
|
3
|
-
export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, themeStorageKey, metricsRangeStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
|
|
3
|
+
export declare function VertebrAdmin({ apiBase, basePath: basePathProp, title, envStorageKey, tokenStorageKey, themeStorageKey, metricsRangeStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
|
|
4
4
|
//# sourceMappingURL=VertebrAdmin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AA8BA,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,+BA+iBnB"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
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
6
|
import { AdminContent } from "./AdminContent";
|
|
7
7
|
import { AdminHeader } from "./AdminHeader";
|
|
8
8
|
import { AdminSidebar } from "./AdminSidebar";
|
|
9
|
+
import { adminBaseFromApiBase, coerceSelection, defaultSelection, hrefForSelection, selectionFromPath, selectionsEqual, } from "./routes";
|
|
9
10
|
import { sidebarGroups } from "./sidebar";
|
|
10
11
|
import { clearStored, readStored, writeStored } from "./storage";
|
|
11
|
-
export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", themeStorageKey = "vertebr:theme", metricsRangeStorageKey = "vertebr:metrics-range", getAccessToken, authUserLabel, onSignOut, }) {
|
|
12
|
+
export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", themeStorageKey = "vertebr:theme", metricsRangeStorageKey = "vertebr:metrics-range", getAccessToken, authUserLabel, onSignOut, }) {
|
|
13
|
+
const basePath = basePathProp ?? adminBaseFromApiBase(apiBase);
|
|
12
14
|
const { theme, setTheme } = useVertebrTheme(themeStorageKey);
|
|
13
15
|
const [environment, setEnvironment] = useState(() => readStored(envStorageKey) ?? "");
|
|
14
16
|
const [token, setToken] = useState(() => readStored(tokenStorageKey) ?? "");
|
|
@@ -21,7 +23,13 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
21
23
|
}), [apiBase, getEnvironment, getAccessToken, token]);
|
|
22
24
|
const [bootstrap, setBootstrap] = useState(null);
|
|
23
25
|
const [catalog, setCatalog] = useState(null);
|
|
24
|
-
const [selection, setSelection] = useState(
|
|
26
|
+
const [selection, setSelection] = useState(() => {
|
|
27
|
+
if (typeof window === "undefined")
|
|
28
|
+
return { kind: "overview" };
|
|
29
|
+
return (selectionFromPath(window.location.pathname, basePath, null) ?? {
|
|
30
|
+
kind: "overview",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
25
33
|
const [resourceData, setResourceData] = useState(null);
|
|
26
34
|
const [configData, setConfigData] = useState(null);
|
|
27
35
|
const [metricsData, setMetricsData] = useState(null);
|
|
@@ -29,10 +37,23 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
29
37
|
const [metricsRange, setMetricsRange] = useState(() => parseMetricsRangeId(readStored(metricsRangeStorageKey)));
|
|
30
38
|
const [galleryData, setGalleryData] = useState(null);
|
|
31
39
|
const [galleryLoading, setGalleryLoading] = useState(false);
|
|
40
|
+
const [fontLibraryData, setFontLibraryData] = useState(null);
|
|
41
|
+
const [fontLibraryLoading, setFontLibraryLoading] = useState(false);
|
|
32
42
|
const [emailsData, setEmailsData] = useState(null);
|
|
33
43
|
const [emailsLoading, setEmailsLoading] = useState(false);
|
|
34
44
|
const [error, setError] = useState(null);
|
|
35
45
|
const [loading, setLoading] = useState(true);
|
|
46
|
+
/** Skip pushState when selection was applied from the URL (catalog/popstate). */
|
|
47
|
+
const skipNextUrlPush = useRef(true);
|
|
48
|
+
const syncUrl = useCallback((next, catalogSnapshot, replace = false) => {
|
|
49
|
+
if (typeof window === "undefined" || !next)
|
|
50
|
+
return;
|
|
51
|
+
const href = hrefForSelection(basePath, next, catalogSnapshot);
|
|
52
|
+
if (window.location.pathname === href)
|
|
53
|
+
return;
|
|
54
|
+
const method = replace ? "replaceState" : "pushState";
|
|
55
|
+
window.history[method](null, "", href);
|
|
56
|
+
}, [basePath]);
|
|
36
57
|
useEffect(() => {
|
|
37
58
|
let cancelled = false;
|
|
38
59
|
(async () => {
|
|
@@ -71,35 +92,11 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
71
92
|
if (cancelled)
|
|
72
93
|
return;
|
|
73
94
|
setCatalog(next);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (current?.kind === "emails" && next.emails)
|
|
80
|
-
return current;
|
|
81
|
-
if (current?.kind === "resource") {
|
|
82
|
-
if (next.resources.some((r) => r.id === current.id))
|
|
83
|
-
return current;
|
|
84
|
-
}
|
|
85
|
-
if (current?.kind === "config") {
|
|
86
|
-
if (next.configs.some((c) => c.id === current.id))
|
|
87
|
-
return current;
|
|
88
|
-
}
|
|
89
|
-
if (next.metrics)
|
|
90
|
-
return { kind: "overview" };
|
|
91
|
-
const firstResource = next.resources[0];
|
|
92
|
-
if (firstResource)
|
|
93
|
-
return { kind: "resource", id: firstResource.id };
|
|
94
|
-
if (next.gallery)
|
|
95
|
-
return { kind: "gallery" };
|
|
96
|
-
if (next.emails)
|
|
97
|
-
return { kind: "emails", tab: "compose" };
|
|
98
|
-
const firstConfig = next.configs[0];
|
|
99
|
-
if (firstConfig)
|
|
100
|
-
return { kind: "config", id: firstConfig.id };
|
|
101
|
-
return { kind: "overview" };
|
|
102
|
-
});
|
|
95
|
+
const fromUrl = selectionFromPath(window.location.pathname, basePath, next);
|
|
96
|
+
const resolved = coerceSelection(fromUrl ?? defaultSelection(next), next);
|
|
97
|
+
skipNextUrlPush.current = true;
|
|
98
|
+
setSelection(resolved);
|
|
99
|
+
syncUrl(resolved, next, true);
|
|
103
100
|
}
|
|
104
101
|
catch (err) {
|
|
105
102
|
if (!cancelled) {
|
|
@@ -115,7 +112,28 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
115
112
|
return () => {
|
|
116
113
|
cancelled = true;
|
|
117
114
|
};
|
|
118
|
-
}, [client, bootstrap, environment]);
|
|
115
|
+
}, [client, bootstrap, environment, basePath, syncUrl]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!catalog || !selection)
|
|
118
|
+
return;
|
|
119
|
+
if (skipNextUrlPush.current) {
|
|
120
|
+
skipNextUrlPush.current = false;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
syncUrl(selection, catalog, false);
|
|
124
|
+
}, [selection, catalog, syncUrl]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const onPopState = () => {
|
|
127
|
+
if (!catalog)
|
|
128
|
+
return;
|
|
129
|
+
const fromUrl = selectionFromPath(window.location.pathname, basePath, catalog);
|
|
130
|
+
const resolved = coerceSelection(fromUrl ?? defaultSelection(catalog), catalog);
|
|
131
|
+
skipNextUrlPush.current = true;
|
|
132
|
+
setSelection((current) => selectionsEqual(current, resolved) ? current : resolved);
|
|
133
|
+
};
|
|
134
|
+
window.addEventListener("popstate", onPopState);
|
|
135
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
136
|
+
}, [catalog, basePath]);
|
|
119
137
|
useEffect(() => {
|
|
120
138
|
if (!selection || !catalog)
|
|
121
139
|
return;
|
|
@@ -131,6 +149,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
131
149
|
setResourceData(null);
|
|
132
150
|
setConfigData(null);
|
|
133
151
|
setGalleryData(null);
|
|
152
|
+
setFontLibraryData(null);
|
|
134
153
|
setEmailsData(null);
|
|
135
154
|
}
|
|
136
155
|
}
|
|
@@ -142,6 +161,19 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
142
161
|
setResourceData(null);
|
|
143
162
|
setConfigData(null);
|
|
144
163
|
setMetricsData(null);
|
|
164
|
+
setFontLibraryData(null);
|
|
165
|
+
setEmailsData(null);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (selection.kind === "fontLibrary") {
|
|
169
|
+
setFontLibraryLoading(true);
|
|
170
|
+
const data = await client.getFonts();
|
|
171
|
+
if (!cancelled) {
|
|
172
|
+
setFontLibraryData(data);
|
|
173
|
+
setResourceData(null);
|
|
174
|
+
setConfigData(null);
|
|
175
|
+
setMetricsData(null);
|
|
176
|
+
setGalleryData(null);
|
|
145
177
|
setEmailsData(null);
|
|
146
178
|
}
|
|
147
179
|
}
|
|
@@ -154,6 +186,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
154
186
|
setConfigData(null);
|
|
155
187
|
setMetricsData(null);
|
|
156
188
|
setGalleryData(null);
|
|
189
|
+
setFontLibraryData(null);
|
|
157
190
|
}
|
|
158
191
|
}
|
|
159
192
|
else if (selection.kind === "resource") {
|
|
@@ -163,6 +196,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
163
196
|
setConfigData(null);
|
|
164
197
|
setMetricsData(null);
|
|
165
198
|
setGalleryData(null);
|
|
199
|
+
setFontLibraryData(null);
|
|
166
200
|
setEmailsData(null);
|
|
167
201
|
}
|
|
168
202
|
}
|
|
@@ -174,6 +208,18 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
174
208
|
setMetricsData(null);
|
|
175
209
|
setGalleryData(null);
|
|
176
210
|
setEmailsData(null);
|
|
211
|
+
if (catalog.fontLibrary) {
|
|
212
|
+
setFontLibraryLoading(true);
|
|
213
|
+
try {
|
|
214
|
+
const fonts = await client.getFonts();
|
|
215
|
+
if (!cancelled)
|
|
216
|
+
setFontLibraryData(fonts);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
if (!cancelled)
|
|
220
|
+
setFontLibraryLoading(false);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
177
223
|
}
|
|
178
224
|
}
|
|
179
225
|
}
|
|
@@ -186,6 +232,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
186
232
|
if (!cancelled) {
|
|
187
233
|
setMetricsLoading(false);
|
|
188
234
|
setGalleryLoading(false);
|
|
235
|
+
setFontLibraryLoading(false);
|
|
189
236
|
setEmailsLoading(false);
|
|
190
237
|
}
|
|
191
238
|
}
|
|
@@ -224,6 +271,20 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
224
271
|
setGalleryLoading(false);
|
|
225
272
|
}
|
|
226
273
|
}, [client]);
|
|
274
|
+
const refreshFonts = useCallback(async () => {
|
|
275
|
+
setFontLibraryLoading(true);
|
|
276
|
+
try {
|
|
277
|
+
const data = await client.getFonts({ fresh: true });
|
|
278
|
+
setFontLibraryData(data);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
setFontLibraryLoading(false);
|
|
282
|
+
}
|
|
283
|
+
}, [client]);
|
|
284
|
+
const listFonts = useCallback(async () => {
|
|
285
|
+
const data = await client.getFonts();
|
|
286
|
+
return data.items;
|
|
287
|
+
}, [client]);
|
|
227
288
|
const refreshEmails = useCallback(async () => {
|
|
228
289
|
setEmailsLoading(true);
|
|
229
290
|
try {
|
|
@@ -256,6 +317,14 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
256
317
|
onUploadGallery: uploadGallery,
|
|
257
318
|
}
|
|
258
319
|
: {};
|
|
320
|
+
const fontLibraryPicker = catalog?.fontLibrary
|
|
321
|
+
? {
|
|
322
|
+
fonts: fontLibraryData?.items ?? [],
|
|
323
|
+
fontsLoading: fontLibraryLoading,
|
|
324
|
+
onListFonts: listFonts,
|
|
325
|
+
onRefreshFonts: refreshFonts,
|
|
326
|
+
}
|
|
327
|
+
: {};
|
|
259
328
|
const envList = catalog?.environments ?? bootstrap?.environments ?? [];
|
|
260
329
|
const activeEnv = envList.find((env) => env.id === environment);
|
|
261
330
|
const authRequired = Boolean(activeEnv?.auth?.required);
|
|
@@ -263,6 +332,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
263
332
|
? catalog?.configs.find((c) => c.id === selection.id)
|
|
264
333
|
: undefined;
|
|
265
334
|
const isPageSelection = activeConfig?.section === "page";
|
|
335
|
+
const hasPagePreview = Boolean(activeConfig?.previewPath);
|
|
266
336
|
const switchEnvironment = (nextEnv) => {
|
|
267
337
|
if (nextEnv === environment)
|
|
268
338
|
return;
|
|
@@ -277,6 +347,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
277
347
|
setResourceData(null);
|
|
278
348
|
setConfigData(null);
|
|
279
349
|
setGalleryData(null);
|
|
350
|
+
setFontLibraryData(null);
|
|
280
351
|
};
|
|
281
352
|
const saveToken = () => {
|
|
282
353
|
const next = tokenDraft.trim();
|
|
@@ -322,6 +393,15 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
322
393
|
onSelect: () => setSelection({ kind: "gallery" }),
|
|
323
394
|
});
|
|
324
395
|
}
|
|
396
|
+
if (catalog.fontLibrary) {
|
|
397
|
+
entries.push({
|
|
398
|
+
key: "fonts",
|
|
399
|
+
group: catalog.fontLibrary.group ?? "Resources",
|
|
400
|
+
label: catalog.fontLibrary.label,
|
|
401
|
+
active: selection?.kind === "fontLibrary",
|
|
402
|
+
onSelect: () => setSelection({ kind: "fontLibrary" }),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
325
405
|
if (catalog.emails) {
|
|
326
406
|
const emailsTab = selection?.kind === "emails" ? (selection.tab ?? "compose") : null;
|
|
327
407
|
entries.push({
|
|
@@ -374,12 +454,14 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
|
|
|
374
454
|
? (catalog?.metrics?.label ?? "Overview")
|
|
375
455
|
: selection?.kind === "gallery"
|
|
376
456
|
? (catalog?.gallery?.label ?? "Gallery")
|
|
377
|
-
: selection?.kind === "
|
|
378
|
-
? (catalog?.
|
|
379
|
-
: selection?.kind === "
|
|
380
|
-
? catalog?.
|
|
381
|
-
: selection?.kind === "
|
|
382
|
-
? catalog?.
|
|
383
|
-
:
|
|
384
|
-
|
|
457
|
+
: selection?.kind === "fontLibrary"
|
|
458
|
+
? (catalog?.fontLibrary?.label ?? "Font Library")
|
|
459
|
+
: selection?.kind === "emails"
|
|
460
|
+
? (catalog?.emails?.label ?? "Emails")
|
|
461
|
+
: selection?.kind === "resource"
|
|
462
|
+
? catalog?.resources.find((r) => r.id === selection.id)?.label
|
|
463
|
+
: selection?.kind === "config"
|
|
464
|
+
? catalog?.configs.find((c) => c.id === selection.id)?.label
|
|
465
|
+
: null;
|
|
466
|
+
return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsx(AdminSidebar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", title: title, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, navGroups: navGroups, getAccessToken: getAccessToken, authUserLabel: authUserLabel, onSignOut: onSignOut, authRequired: authRequired, activeEnv: activeEnv, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme }), _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 }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, 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 })] })] }));
|
|
385
467
|
}
|