@orion-studios/payload-studio 0.6.0-beta.2 → 0.6.0-beta.4
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/admin/client.d.mts +0 -1
- package/dist/admin/client.d.ts +0 -1
- package/dist/admin/client.js +896 -982
- package/dist/admin/client.mjs +609 -696
- package/dist/admin/index.d.mts +2 -1
- package/dist/admin/index.d.ts +2 -1
- package/dist/admin/index.js +1505 -1
- package/dist/admin/index.mjs +4 -2
- package/dist/admin-app/index.mjs +4 -4
- package/dist/admin-app/styles.css +362 -0
- package/dist/chunk-DYXSAVUQ.mjs +2372 -0
- package/dist/{chunk-RKTIFEUY.mjs → chunk-EHUE4LCT.mjs} +3 -33
- package/dist/chunk-Z6L5K5MH.mjs +64 -0
- package/dist/index-DEkV-sMs.d.mts +191 -0
- package/dist/index-bbA3HSxa.d.ts +191 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1521 -63
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-QJAWO6K3.mjs +0 -910
- package/dist/chunk-W2UOCJDX.mjs +0 -32
- package/dist/index-B6_D4Hm4.d.ts +0 -439
- package/dist/index-CYaWadBl.d.mts +0 -439
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
42
|
// src/admin/index.ts
|
|
43
43
|
var admin_exports = {};
|
|
44
44
|
__export(admin_exports, {
|
|
45
|
+
AdminStudioDashboard: () => AdminStudioDashboard,
|
|
45
46
|
SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM: () => SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
|
|
46
47
|
SOCIAL_MEDIA_ICON_OPTIONS: () => SOCIAL_MEDIA_ICON_OPTIONS,
|
|
47
48
|
SOCIAL_MEDIA_PLATFORMS: () => SOCIAL_MEDIA_PLATFORMS,
|
|
@@ -109,6 +110,7 @@ var navItemIsActive = (pathname, item) => {
|
|
|
109
110
|
// src/shared/studioSections.ts
|
|
110
111
|
var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
|
|
111
112
|
var studioIcons = new Set(adminNavIcons);
|
|
113
|
+
var dashboardSpans = /* @__PURE__ */ new Set(["full", "half"]);
|
|
112
114
|
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
113
115
|
var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
|
|
114
116
|
var normalizePathLikeValue = (value) => {
|
|
@@ -150,6 +152,22 @@ var normalizeCard = (value) => {
|
|
|
150
152
|
};
|
|
151
153
|
};
|
|
152
154
|
var normalizeIcon = (value) => typeof value === "string" && studioIcons.has(value) ? value : void 0;
|
|
155
|
+
var normalizeComponent = (value) => {
|
|
156
|
+
if (!isRecord(value)) {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
const componentPath = typeof value.path === "string" ? value.path.trim() : "";
|
|
160
|
+
const exportName = typeof value.exportName === "string" ? value.exportName.trim() : "";
|
|
161
|
+
if (!componentPath || !exportName) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
exportName,
|
|
166
|
+
path: componentPath,
|
|
167
|
+
...isRecord(value.clientProps) ? { clientProps: value.clientProps } : {}
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
var normalizeDashboardSpan = (value) => typeof value === "string" && dashboardSpans.has(value) ? value : "half";
|
|
153
171
|
var resolveStudioSections = (value) => {
|
|
154
172
|
if (!Array.isArray(value)) {
|
|
155
173
|
return [];
|
|
@@ -215,6 +233,38 @@ var resolveStudioSectionViews = (value) => {
|
|
|
215
233
|
}
|
|
216
234
|
return views;
|
|
217
235
|
};
|
|
236
|
+
var resolveStudioSectionDashboards = (value) => {
|
|
237
|
+
if (!Array.isArray(value)) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const panels = [];
|
|
241
|
+
const seen = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const entry of value) {
|
|
243
|
+
if (!isRecord(entry) || typeof entry.id !== "string" || typeof entry.label !== "string") {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const id = entry.id.trim();
|
|
247
|
+
const label = entry.label.trim();
|
|
248
|
+
const dashboard = isRecord(entry.dashboard) ? entry.dashboard : null;
|
|
249
|
+
const component = dashboard ? normalizeComponent(dashboard.Component) : void 0;
|
|
250
|
+
const href = typeof entry.href === "string" && entry.href.trim().length > 0 ? normalizePathLikeValue(entry.href) : isRecord(entry.view) && typeof entry.view.path === "string" ? normalizePathLikeValue(entry.view.path) : "";
|
|
251
|
+
if (!id || !label || !dashboard || !component || !href || seen.has(id)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const priority = typeof dashboard.priority === "number" && Number.isFinite(dashboard.priority) ? dashboard.priority : 100;
|
|
255
|
+
panels.push({
|
|
256
|
+
Component: component,
|
|
257
|
+
href,
|
|
258
|
+
id,
|
|
259
|
+
label,
|
|
260
|
+
priority,
|
|
261
|
+
...normalizeRoles(dashboard.roles ?? entry.roles) ? { roles: normalizeRoles(dashboard.roles ?? entry.roles) } : {},
|
|
262
|
+
span: normalizeDashboardSpan(dashboard.span)
|
|
263
|
+
});
|
|
264
|
+
seen.add(id);
|
|
265
|
+
}
|
|
266
|
+
return panels.sort((a, b) => a.priority === b.priority ? a.label.localeCompare(b.label) : a.priority - b.priority);
|
|
267
|
+
};
|
|
218
268
|
|
|
219
269
|
// src/admin/helpers/configureAdmin.ts
|
|
220
270
|
var import_meta = {};
|
|
@@ -274,6 +324,7 @@ function configureAdmin(config) {
|
|
|
274
324
|
};
|
|
275
325
|
});
|
|
276
326
|
const studioSections = resolveStudioSections(config.studio?.sections || []);
|
|
327
|
+
const studioDashboardPanels = resolveStudioSectionDashboards(config.studio?.sections || []);
|
|
277
328
|
const studioSectionViews = resolveStudioSectionViews(config.studio?.sections || []);
|
|
278
329
|
const sitePreview = config.studio?.sitePreview;
|
|
279
330
|
let cssPath;
|
|
@@ -303,6 +354,7 @@ function configureAdmin(config) {
|
|
|
303
354
|
cssPath = genPath;
|
|
304
355
|
}
|
|
305
356
|
const clientPath = "@orion-studios/payload-studio/admin/client";
|
|
357
|
+
const adminPath = "@orion-studios/payload-studio/admin";
|
|
306
358
|
const studioNavClientProps = {
|
|
307
359
|
brandName,
|
|
308
360
|
formSubmissionsCollectionSlug,
|
|
@@ -314,8 +366,14 @@ function configureAdmin(config) {
|
|
|
314
366
|
logoUrl,
|
|
315
367
|
mediaCollectionSlug,
|
|
316
368
|
pagesCollectionSlug,
|
|
369
|
+
dashboardPanels: studioDashboardPanels,
|
|
317
370
|
sections: studioSections
|
|
318
371
|
};
|
|
372
|
+
const dashboardImportMapGenerator = studioDashboardPanels.length > 0 ? ({ addToImportMap }) => {
|
|
373
|
+
addToImportMap(
|
|
374
|
+
studioDashboardPanels.map((panel) => panel.Component)
|
|
375
|
+
);
|
|
376
|
+
} : void 0;
|
|
319
377
|
const studioBackBreadcrumbComponent = {
|
|
320
378
|
exportName: "StudioBackBreadcrumb",
|
|
321
379
|
path: clientPath
|
|
@@ -350,6 +408,11 @@ function configureAdmin(config) {
|
|
|
350
408
|
return {
|
|
351
409
|
admin: {
|
|
352
410
|
css: cssPath,
|
|
411
|
+
...dashboardImportMapGenerator ? {
|
|
412
|
+
importMap: {
|
|
413
|
+
generators: [dashboardImportMapGenerator]
|
|
414
|
+
}
|
|
415
|
+
} : {},
|
|
353
416
|
components: {
|
|
354
417
|
...studioEnabled ? {
|
|
355
418
|
Nav: {
|
|
@@ -380,7 +443,7 @@ function configureAdmin(config) {
|
|
|
380
443
|
dashboard: {
|
|
381
444
|
Component: {
|
|
382
445
|
exportName: studioEnabled ? "AdminStudioDashboard" : "Dashboard",
|
|
383
|
-
path: clientPath,
|
|
446
|
+
path: studioEnabled ? adminPath : clientPath,
|
|
384
447
|
clientProps: studioNavClientProps
|
|
385
448
|
}
|
|
386
449
|
},
|
|
@@ -776,6 +839,1431 @@ function configureAdmin(config) {
|
|
|
776
839
|
};
|
|
777
840
|
}
|
|
778
841
|
|
|
842
|
+
// src/admin/components/studio/AdminStudioDashboard.tsx
|
|
843
|
+
var import_RenderServerComponent = require("@payloadcms/ui/elements/RenderServerComponent");
|
|
844
|
+
|
|
845
|
+
// src/admin-app/components/AdminBreadcrumbs.tsx
|
|
846
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
847
|
+
function AdminBreadcrumbs({ items }) {
|
|
848
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Breadcrumb", className: "orion-admin-breadcrumbs", children: items.map((item, index) => {
|
|
849
|
+
const isLast = index === items.length - 1;
|
|
850
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
851
|
+
item.href && !isLast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: item.href, children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.label }),
|
|
852
|
+
!isLast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "orion-admin-breadcrumb-sep", children: "/" }) : null
|
|
853
|
+
] }, `${item.label}-${index}`);
|
|
854
|
+
}) });
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/admin-app/components/AdminPage.tsx
|
|
858
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
859
|
+
function AdminPage({ title, description, breadcrumbs, actions, children }) {
|
|
860
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page", children: [
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-header", children: [
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AdminBreadcrumbs, { items: breadcrumbs }),
|
|
863
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-title-row", children: [
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { children: title }),
|
|
866
|
+
description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: description }) : null
|
|
867
|
+
] }),
|
|
868
|
+
actions ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-actions", children: actions }) : null
|
|
869
|
+
] })
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-content", children })
|
|
872
|
+
] });
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/admin/components/studio/AdminStudioDashboardClient.tsx
|
|
876
|
+
var import_react = require("react");
|
|
877
|
+
var import_link = __toESM(require("next/link"));
|
|
878
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
879
|
+
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
880
|
+
var isRole = (value) => value === "admin" || value === "editor" || value === "client";
|
|
881
|
+
var canReviewForms = (role) => role === "admin" || role === "editor";
|
|
882
|
+
var canCreatePages = (role) => role === "admin" || role === "editor";
|
|
883
|
+
var canAccess = (role, roles) => {
|
|
884
|
+
if (!roles || roles.length === 0) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (!role) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
return roles.includes(role);
|
|
891
|
+
};
|
|
892
|
+
var asText = (value, fallback) => typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
893
|
+
var asID = (value) => {
|
|
894
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
895
|
+
return "";
|
|
896
|
+
};
|
|
897
|
+
var toTimestamp = (value) => {
|
|
898
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
899
|
+
return Number.NaN;
|
|
900
|
+
}
|
|
901
|
+
const timestamp = Date.parse(value);
|
|
902
|
+
return Number.isFinite(timestamp) ? timestamp : Number.NaN;
|
|
903
|
+
};
|
|
904
|
+
var isRecent = (value) => {
|
|
905
|
+
const timestamp = toTimestamp(value);
|
|
906
|
+
return Number.isFinite(timestamp) && Date.now() - timestamp <= SEVEN_DAYS_MS;
|
|
907
|
+
};
|
|
908
|
+
var formatDateTime = (value) => {
|
|
909
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
910
|
+
return "Unknown time";
|
|
911
|
+
}
|
|
912
|
+
const date = new Date(value);
|
|
913
|
+
if (Number.isNaN(date.getTime())) {
|
|
914
|
+
return value;
|
|
915
|
+
}
|
|
916
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
917
|
+
dateStyle: "medium",
|
|
918
|
+
timeStyle: "short"
|
|
919
|
+
}).format(date);
|
|
920
|
+
};
|
|
921
|
+
var formatRelativeTime = (timestamp) => {
|
|
922
|
+
if (!Number.isFinite(timestamp)) {
|
|
923
|
+
return "Unknown time";
|
|
924
|
+
}
|
|
925
|
+
const diffMs = timestamp - Date.now();
|
|
926
|
+
const diffMinutes = Math.round(diffMs / (60 * 1e3));
|
|
927
|
+
const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
|
|
928
|
+
if (Math.abs(diffMinutes) < 60) {
|
|
929
|
+
return rtf.format(diffMinutes, "minute");
|
|
930
|
+
}
|
|
931
|
+
const diffHours = Math.round(diffMinutes / 60);
|
|
932
|
+
if (Math.abs(diffHours) < 24) {
|
|
933
|
+
return rtf.format(diffHours, "hour");
|
|
934
|
+
}
|
|
935
|
+
const diffDays = Math.round(diffHours / 24);
|
|
936
|
+
return rtf.format(diffDays, "day");
|
|
937
|
+
};
|
|
938
|
+
var readSubmissionIdentity = (value) => {
|
|
939
|
+
if (!value || typeof value !== "object") {
|
|
940
|
+
return "New submission";
|
|
941
|
+
}
|
|
942
|
+
const data = value;
|
|
943
|
+
const firstName = typeof data.firstName === "string" ? data.firstName.trim() : "";
|
|
944
|
+
const lastName = typeof data.lastName === "string" ? data.lastName.trim() : "";
|
|
945
|
+
const name = typeof data.name === "string" ? data.name.trim() : "";
|
|
946
|
+
const email = typeof data.email === "string" ? data.email.trim() : typeof data.contactEmail === "string" ? data.contactEmail.trim() : "";
|
|
947
|
+
const fullName = [firstName, lastName].filter(Boolean).join(" ").trim();
|
|
948
|
+
return fullName || name || email || "New submission";
|
|
949
|
+
};
|
|
950
|
+
var getFormID = (value) => {
|
|
951
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
952
|
+
if (value && typeof value === "object") {
|
|
953
|
+
const nestedID = value.id;
|
|
954
|
+
if (typeof nestedID === "string" || typeof nestedID === "number") return String(nestedID);
|
|
955
|
+
}
|
|
956
|
+
return "";
|
|
957
|
+
};
|
|
958
|
+
var getFormTitle = (value) => {
|
|
959
|
+
if (!value || typeof value !== "object") {
|
|
960
|
+
return "";
|
|
961
|
+
}
|
|
962
|
+
const title = value.title;
|
|
963
|
+
if (typeof title === "string" && title.trim().length > 0) {
|
|
964
|
+
return title.trim();
|
|
965
|
+
}
|
|
966
|
+
const slug = value.slug;
|
|
967
|
+
return typeof slug === "string" && slug.trim().length > 0 ? slug.trim() : "";
|
|
968
|
+
};
|
|
969
|
+
var buildSearchParams = (params) => new URLSearchParams(
|
|
970
|
+
Object.entries(params).filter(([, value]) => typeof value === "string" && value.length > 0)
|
|
971
|
+
).toString();
|
|
972
|
+
async function loadCollection(path2) {
|
|
973
|
+
const response = await fetch(path2, {
|
|
974
|
+
cache: "no-store",
|
|
975
|
+
credentials: "include"
|
|
976
|
+
});
|
|
977
|
+
if (!response.ok) {
|
|
978
|
+
const body = await response.text();
|
|
979
|
+
throw new Error(body || `Request failed: ${response.status}`);
|
|
980
|
+
}
|
|
981
|
+
return await response.json();
|
|
982
|
+
}
|
|
983
|
+
async function loadPages(collectionSlug) {
|
|
984
|
+
const params = buildSearchParams({
|
|
985
|
+
depth: "0",
|
|
986
|
+
draft: "true",
|
|
987
|
+
limit: "200",
|
|
988
|
+
sort: "-updatedAt"
|
|
989
|
+
});
|
|
990
|
+
const result = await loadCollection(`/api/${collectionSlug}?${params}`);
|
|
991
|
+
const docs = Array.isArray(result.docs) ? result.docs : [];
|
|
992
|
+
return {
|
|
993
|
+
draftCount: docs.filter((doc) => doc._status === "draft").length,
|
|
994
|
+
recent: docs.slice(0, 8),
|
|
995
|
+
total: typeof result.totalDocs === "number" ? result.totalDocs : docs.length,
|
|
996
|
+
updatedThisWeek: docs.filter((doc) => isRecent(doc.updatedAt)).length
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
async function loadForms(formsCollectionSlug, submissionsCollectionSlug) {
|
|
1000
|
+
const [formsResult, submissionsResult] = await Promise.all([
|
|
1001
|
+
loadCollection(
|
|
1002
|
+
`/api/${formsCollectionSlug}?${buildSearchParams({
|
|
1003
|
+
depth: "0",
|
|
1004
|
+
draft: "true",
|
|
1005
|
+
limit: "80",
|
|
1006
|
+
sort: "-updatedAt"
|
|
1007
|
+
})}`
|
|
1008
|
+
),
|
|
1009
|
+
loadCollection(
|
|
1010
|
+
`/api/${submissionsCollectionSlug}?${buildSearchParams({
|
|
1011
|
+
depth: "1",
|
|
1012
|
+
limit: "40",
|
|
1013
|
+
sort: "-submittedAt"
|
|
1014
|
+
})}`
|
|
1015
|
+
)
|
|
1016
|
+
]);
|
|
1017
|
+
const forms = Array.isArray(formsResult.docs) ? formsResult.docs : [];
|
|
1018
|
+
const recentSubmissions = Array.isArray(submissionsResult.docs) ? submissionsResult.docs : [];
|
|
1019
|
+
const submissionsByForm = /* @__PURE__ */ new Map();
|
|
1020
|
+
for (const submission of recentSubmissions) {
|
|
1021
|
+
const formID = getFormID(submission.form);
|
|
1022
|
+
if (!formID) continue;
|
|
1023
|
+
submissionsByForm.set(formID, (submissionsByForm.get(formID) || 0) + 1);
|
|
1024
|
+
}
|
|
1025
|
+
let busiestFormTitle = null;
|
|
1026
|
+
let busiestFormCount = 0;
|
|
1027
|
+
for (const form of forms) {
|
|
1028
|
+
const formID = asID(form.id);
|
|
1029
|
+
if (!formID) continue;
|
|
1030
|
+
const submissionCount = submissionsByForm.get(formID) || 0;
|
|
1031
|
+
if (submissionCount > busiestFormCount) {
|
|
1032
|
+
busiestFormCount = submissionCount;
|
|
1033
|
+
busiestFormTitle = asText(form.title, "Untitled form");
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
busiestFormTitle,
|
|
1038
|
+
forms,
|
|
1039
|
+
recentSubmissions,
|
|
1040
|
+
submissionsThisWeek: recentSubmissions.filter((submission) => isRecent(submission.submittedAt)).length,
|
|
1041
|
+
totalForms: typeof formsResult.totalDocs === "number" ? formsResult.totalDocs : forms.length
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function loadMedia(collectionSlug) {
|
|
1045
|
+
const params = buildSearchParams({
|
|
1046
|
+
depth: "0",
|
|
1047
|
+
limit: "24",
|
|
1048
|
+
sort: "-updatedAt"
|
|
1049
|
+
});
|
|
1050
|
+
const result = await loadCollection(`/api/${collectionSlug}?${params}`);
|
|
1051
|
+
const docs = Array.isArray(result.docs) ? result.docs : [];
|
|
1052
|
+
return {
|
|
1053
|
+
recent: docs.slice(0, 8),
|
|
1054
|
+
total: typeof result.totalDocs === "number" ? result.totalDocs : docs.length,
|
|
1055
|
+
uploadsThisWeek: docs.filter((doc) => isRecent(doc.updatedAt)).length
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
var loadingState = {
|
|
1059
|
+
forms: null,
|
|
1060
|
+
media: { status: "loading" },
|
|
1061
|
+
pages: { status: "loading" }
|
|
1062
|
+
};
|
|
1063
|
+
function ModuleStatus({ message }) {
|
|
1064
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-inline-note", children: message });
|
|
1065
|
+
}
|
|
1066
|
+
function EmptyState({
|
|
1067
|
+
body,
|
|
1068
|
+
title
|
|
1069
|
+
}) {
|
|
1070
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-empty-state", children: [
|
|
1071
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: title }),
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: body })
|
|
1073
|
+
] });
|
|
1074
|
+
}
|
|
1075
|
+
function SnapshotMetric({ card }) {
|
|
1076
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "orion-dashboard-snapshot-card", children: [
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-snapshot-kicker", children: card.kicker }),
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: card.value }),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: card.detail })
|
|
1080
|
+
] });
|
|
1081
|
+
}
|
|
1082
|
+
function AdminStudioDashboardClient({
|
|
1083
|
+
extensionPanels = [],
|
|
1084
|
+
formSubmissionsCollectionSlug,
|
|
1085
|
+
formsCollectionSlug,
|
|
1086
|
+
formsEnabled,
|
|
1087
|
+
formsPath,
|
|
1088
|
+
globalsBasePath,
|
|
1089
|
+
mediaCollectionSlug,
|
|
1090
|
+
mediaPath,
|
|
1091
|
+
pagesCollectionSlug,
|
|
1092
|
+
pagesPath,
|
|
1093
|
+
sectionLinks,
|
|
1094
|
+
toolsPath,
|
|
1095
|
+
userRole
|
|
1096
|
+
}) {
|
|
1097
|
+
const role = isRole(userRole) ? userRole : void 0;
|
|
1098
|
+
const [state, setState] = (0, import_react.useState)(
|
|
1099
|
+
() => formsEnabled && canReviewForms(role) ? {
|
|
1100
|
+
forms: { status: "loading" },
|
|
1101
|
+
media: { status: "loading" },
|
|
1102
|
+
pages: { status: "loading" }
|
|
1103
|
+
} : loadingState
|
|
1104
|
+
);
|
|
1105
|
+
(0, import_react.useEffect)(() => {
|
|
1106
|
+
let cancelled = false;
|
|
1107
|
+
const run = async () => {
|
|
1108
|
+
const includeForms = formsEnabled && canReviewForms(role);
|
|
1109
|
+
(0, import_react.startTransition)(() => {
|
|
1110
|
+
setState({
|
|
1111
|
+
forms: includeForms ? { status: "loading" } : null,
|
|
1112
|
+
media: { status: "loading" },
|
|
1113
|
+
pages: { status: "loading" }
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1116
|
+
const [pagesResult, formsResult, mediaResult] = await Promise.allSettled([
|
|
1117
|
+
loadPages(pagesCollectionSlug),
|
|
1118
|
+
includeForms ? loadForms(formsCollectionSlug, formSubmissionsCollectionSlug) : Promise.resolve(null),
|
|
1119
|
+
loadMedia(mediaCollectionSlug)
|
|
1120
|
+
]);
|
|
1121
|
+
if (cancelled) return;
|
|
1122
|
+
(0, import_react.startTransition)(() => {
|
|
1123
|
+
setState({
|
|
1124
|
+
forms: includeForms && formsResult.status === "rejected" ? {
|
|
1125
|
+
error: formsResult.reason instanceof Error ? formsResult.reason.message : "Unable to load form activity.",
|
|
1126
|
+
status: "error"
|
|
1127
|
+
} : includeForms && formsResult.status === "fulfilled" && formsResult.value ? {
|
|
1128
|
+
data: formsResult.value,
|
|
1129
|
+
status: "success"
|
|
1130
|
+
} : null,
|
|
1131
|
+
media: mediaResult.status === "rejected" ? {
|
|
1132
|
+
error: mediaResult.reason instanceof Error ? mediaResult.reason.message : "Unable to load media activity.",
|
|
1133
|
+
status: "error"
|
|
1134
|
+
} : {
|
|
1135
|
+
data: mediaResult.value,
|
|
1136
|
+
status: "success"
|
|
1137
|
+
},
|
|
1138
|
+
pages: pagesResult.status === "rejected" ? {
|
|
1139
|
+
error: pagesResult.reason instanceof Error ? pagesResult.reason.message : "Unable to load page activity.",
|
|
1140
|
+
status: "error"
|
|
1141
|
+
} : {
|
|
1142
|
+
data: pagesResult.value,
|
|
1143
|
+
status: "success"
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
};
|
|
1148
|
+
void run();
|
|
1149
|
+
return () => {
|
|
1150
|
+
cancelled = true;
|
|
1151
|
+
};
|
|
1152
|
+
}, [
|
|
1153
|
+
formSubmissionsCollectionSlug,
|
|
1154
|
+
formsCollectionSlug,
|
|
1155
|
+
formsEnabled,
|
|
1156
|
+
mediaCollectionSlug,
|
|
1157
|
+
pagesCollectionSlug,
|
|
1158
|
+
role
|
|
1159
|
+
]);
|
|
1160
|
+
const visibleWorkspaceLinks = (0, import_react.useMemo)(
|
|
1161
|
+
() => sectionLinks.filter((section) => canAccess(role, section.roles)),
|
|
1162
|
+
[role, sectionLinks]
|
|
1163
|
+
);
|
|
1164
|
+
const activityItems = (0, import_react.useMemo)(() => {
|
|
1165
|
+
const items = [];
|
|
1166
|
+
if (state.pages.status === "success") {
|
|
1167
|
+
for (const doc of state.pages.data.recent) {
|
|
1168
|
+
const id = asID(doc.id);
|
|
1169
|
+
if (!id) continue;
|
|
1170
|
+
const timestamp = toTimestamp(doc.updatedAt);
|
|
1171
|
+
items.push({
|
|
1172
|
+
href: `${pagesPath}/${id}`,
|
|
1173
|
+
id: `page-${id}`,
|
|
1174
|
+
kind: "page",
|
|
1175
|
+
label: typeof doc._status === "string" ? doc._status : "page",
|
|
1176
|
+
meta: Number.isFinite(timestamp) ? formatDateTime(doc.updatedAt) : "Update time unavailable",
|
|
1177
|
+
timestamp,
|
|
1178
|
+
title: asText(doc.title, "Untitled page")
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (state.forms?.status === "success") {
|
|
1183
|
+
for (const submission of state.forms.data.recentSubmissions) {
|
|
1184
|
+
const id = asID(submission.id);
|
|
1185
|
+
const formID = getFormID(submission.form);
|
|
1186
|
+
if (!id || !formID) continue;
|
|
1187
|
+
const timestamp = toTimestamp(submission.submittedAt);
|
|
1188
|
+
const formTitle = getFormTitle(submission.form) || "Form response";
|
|
1189
|
+
items.push({
|
|
1190
|
+
href: `${formsPath}?form=${encodeURIComponent(formID)}`,
|
|
1191
|
+
id: `submission-${id}`,
|
|
1192
|
+
kind: "submission",
|
|
1193
|
+
label: formTitle,
|
|
1194
|
+
meta: Number.isFinite(timestamp) ? `${readSubmissionIdentity(submission.data)} \xB7 ${formatDateTime(submission.submittedAt)}` : readSubmissionIdentity(submission.data),
|
|
1195
|
+
timestamp,
|
|
1196
|
+
title: "New submission"
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (state.media.status === "success") {
|
|
1201
|
+
for (const doc of state.media.data.recent) {
|
|
1202
|
+
const id = asID(doc.id);
|
|
1203
|
+
if (!id) continue;
|
|
1204
|
+
const timestamp = toTimestamp(doc.updatedAt);
|
|
1205
|
+
items.push({
|
|
1206
|
+
href: `${mediaPath}/${id}`,
|
|
1207
|
+
id: `media-${id}`,
|
|
1208
|
+
kind: "media",
|
|
1209
|
+
label: asText(doc.mimeType, "Media asset"),
|
|
1210
|
+
meta: Number.isFinite(timestamp) ? formatDateTime(doc.updatedAt) : "Update time unavailable",
|
|
1211
|
+
timestamp,
|
|
1212
|
+
title: asText(doc.filename, "Untitled asset")
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return items.filter((item) => Number.isFinite(item.timestamp)).sort((a, b) => b.timestamp - a.timestamp).slice(0, 10);
|
|
1217
|
+
}, [formsPath, mediaPath, pagesPath, state.forms, state.media, state.pages]);
|
|
1218
|
+
const attentionItems = (0, import_react.useMemo)(() => {
|
|
1219
|
+
if (role === "client") {
|
|
1220
|
+
const items2 = [];
|
|
1221
|
+
if (state.pages.status === "success" && state.pages.data.updatedThisWeek > 0) {
|
|
1222
|
+
items2.push({
|
|
1223
|
+
href: pagesPath,
|
|
1224
|
+
id: "pages-updated",
|
|
1225
|
+
label: `${state.pages.data.updatedThisWeek} page${state.pages.data.updatedThisWeek === 1 ? "" : "s"} changed in the last 7 days.`
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (state.media.status === "success" && state.media.data.uploadsThisWeek > 0) {
|
|
1229
|
+
items2.push({
|
|
1230
|
+
href: mediaPath,
|
|
1231
|
+
id: "media-updated",
|
|
1232
|
+
label: `${state.media.data.uploadsThisWeek} new media asset${state.media.data.uploadsThisWeek === 1 ? "" : "s"} landed this week.`
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
return items2;
|
|
1236
|
+
}
|
|
1237
|
+
const items = [];
|
|
1238
|
+
if (state.pages.status === "success" && state.pages.data.draftCount > 0) {
|
|
1239
|
+
items.push({
|
|
1240
|
+
href: pagesPath,
|
|
1241
|
+
id: "draft-pages",
|
|
1242
|
+
label: `${state.pages.data.draftCount} page${state.pages.data.draftCount === 1 ? "" : "s"} still need publishing review.`,
|
|
1243
|
+
tone: "accent"
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (state.forms?.status === "success" && state.forms.data.submissionsThisWeek > 0) {
|
|
1247
|
+
items.push({
|
|
1248
|
+
href: formsPath,
|
|
1249
|
+
id: "recent-submissions",
|
|
1250
|
+
label: `${state.forms.data.submissionsThisWeek} form submission${state.forms.data.submissionsThisWeek === 1 ? "" : "s"} arrived in the last 7 days.`,
|
|
1251
|
+
tone: "accent"
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (state.media.status === "success" && state.media.data.total === 0) {
|
|
1255
|
+
items.push({
|
|
1256
|
+
href: mediaPath,
|
|
1257
|
+
id: "empty-media",
|
|
1258
|
+
label: "The media library is still empty. Add brand assets before content expands."
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
if (state.pages.status === "error") {
|
|
1262
|
+
items.push({
|
|
1263
|
+
href: pagesPath,
|
|
1264
|
+
id: "pages-error",
|
|
1265
|
+
label: "Page activity could not be loaded for the dashboard.",
|
|
1266
|
+
tone: "muted"
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
if (state.forms?.status === "error") {
|
|
1270
|
+
items.push({
|
|
1271
|
+
href: formsPath,
|
|
1272
|
+
id: "forms-error",
|
|
1273
|
+
label: "Form activity could not be loaded for the dashboard.",
|
|
1274
|
+
tone: "muted"
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
if (state.media.status === "error") {
|
|
1278
|
+
items.push({
|
|
1279
|
+
href: mediaPath,
|
|
1280
|
+
id: "media-error",
|
|
1281
|
+
label: "Media activity could not be loaded for the dashboard.",
|
|
1282
|
+
tone: "muted"
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
return items;
|
|
1286
|
+
}, [formsPath, mediaPath, pagesPath, role, state.forms, state.media, state.pages]);
|
|
1287
|
+
const snapshotCards = (0, import_react.useMemo)(() => {
|
|
1288
|
+
const cards = [];
|
|
1289
|
+
if (state.pages.status === "success") {
|
|
1290
|
+
cards.push(
|
|
1291
|
+
role === "client" ? {
|
|
1292
|
+
detail: `${state.pages.data.updatedThisWeek} updated this week`,
|
|
1293
|
+
kicker: "Pages",
|
|
1294
|
+
value: `${state.pages.data.total}`
|
|
1295
|
+
} : {
|
|
1296
|
+
detail: `${state.pages.data.draftCount} draft${state.pages.data.draftCount === 1 ? "" : "s"} waiting`,
|
|
1297
|
+
kicker: "Pages",
|
|
1298
|
+
value: `${state.pages.data.total}`
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
if (state.forms?.status === "success") {
|
|
1303
|
+
cards.push({
|
|
1304
|
+
detail: state.forms.data.busiestFormTitle ? `Most active: ${state.forms.data.busiestFormTitle}` : "Waiting on the first submission",
|
|
1305
|
+
kicker: "Forms",
|
|
1306
|
+
value: `${state.forms.data.submissionsThisWeek}`
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
if (state.media.status === "success") {
|
|
1310
|
+
cards.push({
|
|
1311
|
+
detail: `${state.media.data.uploadsThisWeek} uploaded this week`,
|
|
1312
|
+
kicker: "Media",
|
|
1313
|
+
value: `${state.media.data.total}`
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
cards.push({
|
|
1317
|
+
detail: `${visibleWorkspaceLinks.length} section${visibleWorkspaceLinks.length === 1 ? "" : "s"} available`,
|
|
1318
|
+
kicker: "Workspace",
|
|
1319
|
+
value: role ? role.toUpperCase() : "CMS"
|
|
1320
|
+
});
|
|
1321
|
+
return cards.slice(0, 4);
|
|
1322
|
+
}, [role, state.forms, state.media, state.pages, visibleWorkspaceLinks.length]);
|
|
1323
|
+
const primaryActions = (0, import_react.useMemo)(() => {
|
|
1324
|
+
const actions = [];
|
|
1325
|
+
if (canCreatePages(role)) {
|
|
1326
|
+
actions.push({
|
|
1327
|
+
description: "Create or revise content in the visual builder.",
|
|
1328
|
+
href: `${pagesPath}/new`,
|
|
1329
|
+
label: "New Page"
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
actions.push({
|
|
1333
|
+
description: "Review live pages and recent edits.",
|
|
1334
|
+
href: pagesPath,
|
|
1335
|
+
label: "Open Pages",
|
|
1336
|
+
tone: "ghost"
|
|
1337
|
+
});
|
|
1338
|
+
if (formsEnabled && canReviewForms(role)) {
|
|
1339
|
+
actions.push({
|
|
1340
|
+
description: "Check submissions and form performance.",
|
|
1341
|
+
href: formsPath,
|
|
1342
|
+
label: "Review Forms",
|
|
1343
|
+
tone: "soft"
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
actions.push({
|
|
1347
|
+
description: "Update site settings, navigation, and footer content.",
|
|
1348
|
+
href: globalsBasePath,
|
|
1349
|
+
label: "Open Globals",
|
|
1350
|
+
tone: "ghost"
|
|
1351
|
+
});
|
|
1352
|
+
actions.push({
|
|
1353
|
+
description: "Upload or organize brand and campaign assets.",
|
|
1354
|
+
href: mediaPath,
|
|
1355
|
+
label: "Manage Media",
|
|
1356
|
+
tone: "ghost"
|
|
1357
|
+
});
|
|
1358
|
+
if (role === "admin") {
|
|
1359
|
+
actions.push({
|
|
1360
|
+
description: "Manage users, roles, and fallback tools.",
|
|
1361
|
+
href: toolsPath,
|
|
1362
|
+
label: "Admin Tools",
|
|
1363
|
+
tone: "ghost"
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return actions;
|
|
1367
|
+
}, [formsEnabled, formsPath, globalsBasePath, mediaPath, pagesPath, role, toolsPath]);
|
|
1368
|
+
const attentionTitle = role === "client" ? "What Changed" : "Needs Attention";
|
|
1369
|
+
const attentionDescription = role === "client" ? "A quick read on recent content and asset changes." : "The highest-signal items that still need review.";
|
|
1370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-layout", children: [
|
|
1371
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "orion-dashboard-panel orion-dashboard-panel--attention", children: [
|
|
1372
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-panel-header", children: [
|
|
1373
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-kicker", children: attentionTitle }),
|
|
1375
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { children: attentionDescription })
|
|
1376
|
+
] }),
|
|
1377
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-meta", children: "Last 7 days" })
|
|
1378
|
+
] }),
|
|
1379
|
+
attentionItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-attention-list", children: attentionItems.map(
|
|
1380
|
+
(item) => item.href ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1381
|
+
import_link.default,
|
|
1382
|
+
{
|
|
1383
|
+
className: ["orion-dashboard-attention-item", item.tone ? `is-${item.tone}` : ""].filter(Boolean).join(" "),
|
|
1384
|
+
href: item.href,
|
|
1385
|
+
children: [
|
|
1386
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: item.label }),
|
|
1387
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Open" })
|
|
1388
|
+
]
|
|
1389
|
+
},
|
|
1390
|
+
item.id
|
|
1391
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1392
|
+
"div",
|
|
1393
|
+
{
|
|
1394
|
+
className: ["orion-dashboard-attention-item", item.tone ? `is-${item.tone}` : ""].filter(Boolean).join(" "),
|
|
1395
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: item.label })
|
|
1396
|
+
},
|
|
1397
|
+
item.id
|
|
1398
|
+
)
|
|
1399
|
+
) }) : state.pages.status === "loading" || state.media.status === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ModuleStatus, { message: "Loading attention items..." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1400
|
+
EmptyState,
|
|
1401
|
+
{
|
|
1402
|
+
body: role === "client" ? "No major changes landed during the current review window." : "No urgent follow-up surfaced from content, forms, or media activity.",
|
|
1403
|
+
title: "Everything looks steady"
|
|
1404
|
+
}
|
|
1405
|
+
)
|
|
1406
|
+
] }),
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "orion-dashboard-panel orion-dashboard-panel--actions", children: [
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-panel-header", children: [
|
|
1409
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1410
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-kicker", children: "Quick Actions" }),
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { children: "Jump into the work that matters most." })
|
|
1412
|
+
] }),
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "orion-dashboard-panel-meta", children: [
|
|
1414
|
+
role || "studio",
|
|
1415
|
+
" mode"
|
|
1416
|
+
] })
|
|
1417
|
+
] }),
|
|
1418
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-action-list", children: primaryActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1419
|
+
import_link.default,
|
|
1420
|
+
{
|
|
1421
|
+
className: [
|
|
1422
|
+
"orion-dashboard-action",
|
|
1423
|
+
action.tone === "ghost" ? "is-ghost" : action.tone === "soft" ? "is-soft" : ""
|
|
1424
|
+
].filter(Boolean).join(" "),
|
|
1425
|
+
href: action.href,
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: action.label }),
|
|
1428
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: action.description })
|
|
1429
|
+
]
|
|
1430
|
+
},
|
|
1431
|
+
action.label
|
|
1432
|
+
)) }),
|
|
1433
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-workspace-strip", children: [
|
|
1434
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-workspace-label", children: "Workspace" }),
|
|
1435
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-workspace-pills", children: visibleWorkspaceLinks.map((section) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_link.default, { className: "orion-dashboard-workspace-pill", href: section.href, children: section.label }, section.id)) })
|
|
1436
|
+
] })
|
|
1437
|
+
] }),
|
|
1438
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "orion-dashboard-panel orion-dashboard-panel--activity", children: [
|
|
1439
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-panel-header", children: [
|
|
1440
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1441
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-kicker", children: "Recent Activity" }),
|
|
1442
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { children: "The freshest edits, responses, and uploads across the studio." })
|
|
1443
|
+
] }),
|
|
1444
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "orion-dashboard-panel-meta", children: [
|
|
1445
|
+
activityItems.length,
|
|
1446
|
+
" items"
|
|
1447
|
+
] })
|
|
1448
|
+
] }),
|
|
1449
|
+
activityItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-activity-list", children: activityItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_link.default, { className: `orion-dashboard-activity-item is-${item.kind}`, href: item.href, children: [
|
|
1450
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-activity-copy", children: [
|
|
1451
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-activity-topline", children: [
|
|
1452
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: item.title }),
|
|
1453
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: formatRelativeTime(item.timestamp) })
|
|
1454
|
+
] }),
|
|
1455
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: item.meta })
|
|
1456
|
+
] }),
|
|
1457
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-activity-pill", children: item.label })
|
|
1458
|
+
] }, item.id)) }) : state.pages.status === "loading" || state.media.status === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ModuleStatus, { message: "Loading activity feed..." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1459
|
+
EmptyState,
|
|
1460
|
+
{
|
|
1461
|
+
body: "Recent page edits, submissions, and uploads will start stacking here as soon as the team gets moving.",
|
|
1462
|
+
title: "No recent activity yet"
|
|
1463
|
+
}
|
|
1464
|
+
)
|
|
1465
|
+
] }),
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "orion-dashboard-panel orion-dashboard-panel--snapshot", children: [
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-panel-header", children: [
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1469
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-kicker", children: "Business Snapshot" }),
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { children: "A compact read on content momentum and performance." })
|
|
1471
|
+
] }),
|
|
1472
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "orion-dashboard-panel-meta", children: "30-day lens" })
|
|
1473
|
+
] }),
|
|
1474
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-dashboard-snapshot-grid", children: [
|
|
1475
|
+
snapshotCards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SnapshotMetric, { card }, card.kicker)),
|
|
1476
|
+
extensionPanels.map((panel) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1477
|
+
"div",
|
|
1478
|
+
{
|
|
1479
|
+
className: [
|
|
1480
|
+
"orion-dashboard-extension-panel",
|
|
1481
|
+
panel.span === "full" ? "is-full" : "is-half"
|
|
1482
|
+
].filter(Boolean).join(" "),
|
|
1483
|
+
children: panel.node
|
|
1484
|
+
},
|
|
1485
|
+
panel.id
|
|
1486
|
+
))
|
|
1487
|
+
] }),
|
|
1488
|
+
state.pages.status === "error" || state.media.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-dashboard-inline-note", children: "Some dashboard modules could not be loaded. The rest of the workspace remains available." }) : null
|
|
1489
|
+
] })
|
|
1490
|
+
] });
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/admin/components/studio/StudioSectionLayout.tsx
|
|
1494
|
+
var import_react5 = require("react");
|
|
1495
|
+
var import_navigation = require("next/navigation");
|
|
1496
|
+
var import_ui = require("@payloadcms/ui");
|
|
1497
|
+
|
|
1498
|
+
// src/admin-app/components/AdminShellClient.tsx
|
|
1499
|
+
var import_react2 = require("react");
|
|
1500
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1501
|
+
var iconSize = 20;
|
|
1502
|
+
var iconStyle = { display: "block", flexShrink: 0 };
|
|
1503
|
+
function NavIcon({ name }) {
|
|
1504
|
+
const props = { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: iconStyle };
|
|
1505
|
+
switch (name) {
|
|
1506
|
+
case "dashboard":
|
|
1507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1508
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3", y: "3", width: "7", height: "9", rx: "1" }),
|
|
1509
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "14", y: "3", width: "7", height: "5", rx: "1" }),
|
|
1510
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "14", y: "12", width: "7", height: "9", rx: "1" }),
|
|
1511
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3", y: "16", width: "7", height: "5", rx: "1" })
|
|
1512
|
+
] });
|
|
1513
|
+
case "pages":
|
|
1514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" }),
|
|
1516
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("polyline", { points: "14 2 14 8 20 8" }),
|
|
1517
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "8", y1: "13", x2: "16", y2: "13" }),
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "8", y1: "17", x2: "12", y2: "17" })
|
|
1519
|
+
] });
|
|
1520
|
+
case "forms":
|
|
1521
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1522
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M9 3h6" }),
|
|
1523
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 3v18" }),
|
|
1524
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M5 7h14a2 2 0 0 1 2 2v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a2 2 0 0 1 2-2Z" }),
|
|
1525
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M7 11h10" }),
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M7 15h6" })
|
|
1527
|
+
] });
|
|
1528
|
+
case "globals":
|
|
1529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1530
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
|
|
1531
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
|
|
1532
|
+
] });
|
|
1533
|
+
case "media":
|
|
1534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1535
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
1537
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("polyline", { points: "21 15 16 10 5 21" })
|
|
1538
|
+
] });
|
|
1539
|
+
case "tools":
|
|
1540
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76Z" }) });
|
|
1541
|
+
case "analytics":
|
|
1542
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1543
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 19V5" }),
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M10 19V10" }),
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M16 19v-6" }),
|
|
1546
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M22 19V3" })
|
|
1547
|
+
] });
|
|
1548
|
+
case "account":
|
|
1549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { ...props, children: [
|
|
1550
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
|
|
1551
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "12", cy: "7", r: "4" })
|
|
1552
|
+
] });
|
|
1553
|
+
default:
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function AdminShellClient({
|
|
1558
|
+
children,
|
|
1559
|
+
brandName,
|
|
1560
|
+
logoUrl,
|
|
1561
|
+
userEmail,
|
|
1562
|
+
userRole,
|
|
1563
|
+
pathname,
|
|
1564
|
+
navItems,
|
|
1565
|
+
onLogout,
|
|
1566
|
+
storageKey = "orion-admin-shell-collapsed"
|
|
1567
|
+
}) {
|
|
1568
|
+
const [collapsed, setCollapsed] = (0, import_react2.useState)(false);
|
|
1569
|
+
const [loggingOut, setLoggingOut] = (0, import_react2.useState)(false);
|
|
1570
|
+
(0, import_react2.useEffect)(() => {
|
|
1571
|
+
try {
|
|
1572
|
+
const stored = window.localStorage.getItem(storageKey);
|
|
1573
|
+
if (stored === "1") {
|
|
1574
|
+
setCollapsed(true);
|
|
1575
|
+
}
|
|
1576
|
+
} catch {
|
|
1577
|
+
}
|
|
1578
|
+
}, [storageKey]);
|
|
1579
|
+
const toggleSidebar = () => {
|
|
1580
|
+
setCollapsed((current) => {
|
|
1581
|
+
const next = !current;
|
|
1582
|
+
try {
|
|
1583
|
+
window.localStorage.setItem(storageKey, next ? "1" : "0");
|
|
1584
|
+
} catch {
|
|
1585
|
+
}
|
|
1586
|
+
return next;
|
|
1587
|
+
});
|
|
1588
|
+
};
|
|
1589
|
+
const handleLogout = async () => {
|
|
1590
|
+
setLoggingOut(true);
|
|
1591
|
+
try {
|
|
1592
|
+
await onLogout();
|
|
1593
|
+
} finally {
|
|
1594
|
+
setLoggingOut(false);
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `orion-admin-shell ${collapsed ? "is-collapsed" : ""}`, children: [
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("aside", { className: "orion-admin-sidebar", children: [
|
|
1599
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1600
|
+
"button",
|
|
1601
|
+
{
|
|
1602
|
+
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1603
|
+
className: "orion-admin-sidebar-toggle",
|
|
1604
|
+
onClick: toggleSidebar,
|
|
1605
|
+
type: "button",
|
|
1606
|
+
children: collapsed ? ">" : "<"
|
|
1607
|
+
}
|
|
1608
|
+
),
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-brand-wrap", children: [
|
|
1610
|
+
logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-brand-logo", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { alt: `${brandName} logo`, src: logoUrl }) }) : null,
|
|
1611
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-brand-text", children: [
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-brand-name", title: brandName, children: collapsed ? brandName.slice(0, 1).toUpperCase() : brandName }),
|
|
1613
|
+
!collapsed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-brand-subtitle", children: "Studio" }) : null
|
|
1614
|
+
] })
|
|
1615
|
+
] }),
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: "orion-admin-nav", children: navItems.filter((item) => roleCanAccessNav(userRole, item)).map((item) => {
|
|
1617
|
+
const active = navItemIsActive(pathname, item);
|
|
1618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1619
|
+
"a",
|
|
1620
|
+
{
|
|
1621
|
+
className: `orion-admin-nav-link ${active ? "is-active" : ""}`,
|
|
1622
|
+
href: item.href,
|
|
1623
|
+
title: item.label,
|
|
1624
|
+
children: item.icon ? collapsed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavIcon, { name: item.icon }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { alignItems: "center", display: "flex", gap: "0.6rem" }, children: [
|
|
1625
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavIcon, { name: item.icon }),
|
|
1626
|
+
item.label
|
|
1627
|
+
] }) : collapsed ? item.label.slice(0, 1) : item.label
|
|
1628
|
+
},
|
|
1629
|
+
item.href
|
|
1630
|
+
);
|
|
1631
|
+
}) }),
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-sidebar-footer", children: [
|
|
1633
|
+
!collapsed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1634
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-user-label", children: "Signed in as" }),
|
|
1635
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-user-email", children: userEmail })
|
|
1636
|
+
] }) : null,
|
|
1637
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "orion-admin-logout", disabled: loggingOut, onClick: handleLogout, type: "button", children: loggingOut ? "..." : "Log out" })
|
|
1638
|
+
] })
|
|
1639
|
+
] }),
|
|
1640
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("main", { className: "orion-admin-main", children })
|
|
1641
|
+
] });
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/admin/hooks/useSiteBranding.ts
|
|
1645
|
+
var import_react3 = require("react");
|
|
1646
|
+
var asRecord = (value) => {
|
|
1647
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1648
|
+
return value;
|
|
1649
|
+
};
|
|
1650
|
+
var pickString = (value) => {
|
|
1651
|
+
if (typeof value !== "string") return null;
|
|
1652
|
+
const next = value.trim();
|
|
1653
|
+
return next.length > 0 ? next : null;
|
|
1654
|
+
};
|
|
1655
|
+
var resolveUploadUrl = (value) => {
|
|
1656
|
+
const direct = pickString(value);
|
|
1657
|
+
if (direct) return direct;
|
|
1658
|
+
const record = asRecord(value);
|
|
1659
|
+
if (!record) return null;
|
|
1660
|
+
const fromUrl = pickString(record.url);
|
|
1661
|
+
if (fromUrl) return fromUrl;
|
|
1662
|
+
const fromFilename = pickString(record.filename);
|
|
1663
|
+
if (fromFilename) return `/api/media/file/${fromFilename}`;
|
|
1664
|
+
return null;
|
|
1665
|
+
};
|
|
1666
|
+
var resolveLogoUrl = (settings) => {
|
|
1667
|
+
return resolveUploadUrl(settings.logo) || resolveUploadUrl(settings.adminLogo) || resolveUploadUrl(settings.brandLogo) || null;
|
|
1668
|
+
};
|
|
1669
|
+
var cachedBranding = null;
|
|
1670
|
+
function useSiteBranding(defaultName, defaultLogoUrl) {
|
|
1671
|
+
const [branding, setBranding] = (0, import_react3.useState)(() => {
|
|
1672
|
+
if (cachedBranding) {
|
|
1673
|
+
return {
|
|
1674
|
+
logoUrl: cachedBranding.logoUrl || defaultLogoUrl || null,
|
|
1675
|
+
siteName: cachedBranding.siteName || defaultName || null
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
return {
|
|
1679
|
+
logoUrl: defaultLogoUrl || null,
|
|
1680
|
+
siteName: defaultName || null
|
|
1681
|
+
};
|
|
1682
|
+
});
|
|
1683
|
+
(0, import_react3.useEffect)(() => {
|
|
1684
|
+
let cancelled = false;
|
|
1685
|
+
const run = async () => {
|
|
1686
|
+
try {
|
|
1687
|
+
const res = await fetch("/api/globals/site-settings?depth=1&draft=true", {
|
|
1688
|
+
credentials: "include"
|
|
1689
|
+
});
|
|
1690
|
+
if (!res.ok) return;
|
|
1691
|
+
const data = await res.json();
|
|
1692
|
+
const record = asRecord(data);
|
|
1693
|
+
if (!record) return;
|
|
1694
|
+
const siteName = pickString(record.siteName);
|
|
1695
|
+
const logoUrl = resolveLogoUrl(record);
|
|
1696
|
+
cachedBranding = { logoUrl, siteName };
|
|
1697
|
+
if (!cancelled) {
|
|
1698
|
+
setBranding({
|
|
1699
|
+
logoUrl: logoUrl || defaultLogoUrl || null,
|
|
1700
|
+
siteName: siteName || defaultName || null
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
void run();
|
|
1707
|
+
return () => {
|
|
1708
|
+
cancelled = true;
|
|
1709
|
+
};
|
|
1710
|
+
}, [defaultLogoUrl, defaultName]);
|
|
1711
|
+
return branding;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/admin/components/studio/adminPathUtils.ts
|
|
1715
|
+
var import_react4 = require("react");
|
|
1716
|
+
var DEFAULT_ADMIN_BASE_PATH = "/admin";
|
|
1717
|
+
var normalizePath = (value) => {
|
|
1718
|
+
if (!value || value === "/") return "/";
|
|
1719
|
+
const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
|
|
1720
|
+
const trimmed = withLeadingSlash.replace(/\/+$/, "");
|
|
1721
|
+
return trimmed.length > 0 ? trimmed : "/";
|
|
1722
|
+
};
|
|
1723
|
+
var normalizeAdminBasePath = (value) => {
|
|
1724
|
+
const normalized = normalizePath(value);
|
|
1725
|
+
return normalized === "/" ? DEFAULT_ADMIN_BASE_PATH : normalized;
|
|
1726
|
+
};
|
|
1727
|
+
var detectAdminBasePath = (pathname, fallback = DEFAULT_ADMIN_BASE_PATH) => {
|
|
1728
|
+
const normalizedPathname = normalizePath(pathname);
|
|
1729
|
+
const normalizedFallback = normalizeAdminBasePath(fallback);
|
|
1730
|
+
const markers = [
|
|
1731
|
+
"/contact-form",
|
|
1732
|
+
"/collections/",
|
|
1733
|
+
"/globals/",
|
|
1734
|
+
"/forms",
|
|
1735
|
+
"/pages/",
|
|
1736
|
+
"/tools",
|
|
1737
|
+
"/media",
|
|
1738
|
+
"/logout",
|
|
1739
|
+
"/login"
|
|
1740
|
+
];
|
|
1741
|
+
for (const marker of markers) {
|
|
1742
|
+
const index = normalizedPathname.indexOf(marker);
|
|
1743
|
+
if (index > 0) {
|
|
1744
|
+
return normalizeAdminBasePath(normalizedPathname.slice(0, index));
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (normalizedPathname === normalizedFallback || normalizedPathname.startsWith(`${normalizedFallback}/`)) {
|
|
1748
|
+
return normalizedFallback;
|
|
1749
|
+
}
|
|
1750
|
+
const segments = normalizedPathname.split("/").filter(Boolean);
|
|
1751
|
+
if (segments.length > 0) {
|
|
1752
|
+
return normalizeAdminBasePath(`/${segments[0]}`);
|
|
1753
|
+
}
|
|
1754
|
+
return normalizedFallback;
|
|
1755
|
+
};
|
|
1756
|
+
var isAbsoluteExternalURL2 = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
|
|
1757
|
+
var resolveAdminPath = (adminBasePath, targetPath) => {
|
|
1758
|
+
if (!targetPath) return adminBasePath;
|
|
1759
|
+
if (isAbsoluteExternalURL2(targetPath)) return targetPath;
|
|
1760
|
+
const normalizedBasePath = normalizeAdminBasePath(adminBasePath);
|
|
1761
|
+
const normalizedTargetPath = normalizePath(targetPath);
|
|
1762
|
+
if (normalizedTargetPath === "/admin") {
|
|
1763
|
+
return normalizedBasePath;
|
|
1764
|
+
}
|
|
1765
|
+
if (normalizedTargetPath.startsWith("/admin/")) {
|
|
1766
|
+
return `${normalizedBasePath}${normalizedTargetPath.slice("/admin".length)}`;
|
|
1767
|
+
}
|
|
1768
|
+
if (normalizedTargetPath === normalizedBasePath || normalizedTargetPath.startsWith(`${normalizedBasePath}/`)) {
|
|
1769
|
+
return normalizedTargetPath;
|
|
1770
|
+
}
|
|
1771
|
+
if (normalizedTargetPath === "/") {
|
|
1772
|
+
return normalizedBasePath;
|
|
1773
|
+
}
|
|
1774
|
+
return `${normalizedBasePath}${normalizedTargetPath}`;
|
|
1775
|
+
};
|
|
1776
|
+
var useAdminBasePath = (fallback = DEFAULT_ADMIN_BASE_PATH) => {
|
|
1777
|
+
const [adminBasePath, setAdminBasePath] = (0, import_react4.useState)(normalizeAdminBasePath(fallback));
|
|
1778
|
+
(0, import_react4.useEffect)(() => {
|
|
1779
|
+
const update = () => {
|
|
1780
|
+
setAdminBasePath(detectAdminBasePath(window.location.pathname, fallback));
|
|
1781
|
+
};
|
|
1782
|
+
update();
|
|
1783
|
+
window.addEventListener("popstate", update);
|
|
1784
|
+
return () => window.removeEventListener("popstate", update);
|
|
1785
|
+
}, [fallback]);
|
|
1786
|
+
return adminBasePath;
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// src/admin/components/studio/studioNavModel.ts
|
|
1790
|
+
var getPropString = (props, key, fallback) => {
|
|
1791
|
+
if (!props || typeof props !== "object") return fallback;
|
|
1792
|
+
const direct = props[key];
|
|
1793
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
1794
|
+
const clientProps = props.clientProps;
|
|
1795
|
+
if (clientProps && typeof clientProps === "object") {
|
|
1796
|
+
const nested = clientProps[key];
|
|
1797
|
+
if (typeof nested === "string" && nested.length > 0) return nested;
|
|
1798
|
+
}
|
|
1799
|
+
return fallback;
|
|
1800
|
+
};
|
|
1801
|
+
var getPropBoolean = (props, key, fallback) => {
|
|
1802
|
+
if (!props || typeof props !== "object") return fallback;
|
|
1803
|
+
const direct = props[key];
|
|
1804
|
+
if (typeof direct === "boolean") return direct;
|
|
1805
|
+
const clientProps = props.clientProps;
|
|
1806
|
+
if (clientProps && typeof clientProps === "object") {
|
|
1807
|
+
const nested = clientProps[key];
|
|
1808
|
+
if (typeof nested === "boolean") return nested;
|
|
1809
|
+
}
|
|
1810
|
+
return fallback;
|
|
1811
|
+
};
|
|
1812
|
+
var getPropStringArray = (props, key, fallback) => {
|
|
1813
|
+
if (!props || typeof props !== "object") return fallback;
|
|
1814
|
+
const read = (candidate) => Array.isArray(candidate) ? candidate.filter((value) => typeof value === "string" && value.length > 0) : null;
|
|
1815
|
+
const direct = read(props[key]);
|
|
1816
|
+
if (direct) return direct;
|
|
1817
|
+
const clientProps = props.clientProps;
|
|
1818
|
+
if (clientProps && typeof clientProps === "object") {
|
|
1819
|
+
const nested = read(clientProps[key]);
|
|
1820
|
+
if (nested) return nested;
|
|
1821
|
+
}
|
|
1822
|
+
return fallback;
|
|
1823
|
+
};
|
|
1824
|
+
var getPropSections = (props) => {
|
|
1825
|
+
if (!props || typeof props !== "object") return [];
|
|
1826
|
+
const direct = resolveStudioSections(props.sections);
|
|
1827
|
+
if (direct.length > 0) return direct;
|
|
1828
|
+
const clientProps = props.clientProps;
|
|
1829
|
+
if (clientProps && typeof clientProps === "object") {
|
|
1830
|
+
return resolveStudioSections(clientProps.sections);
|
|
1831
|
+
}
|
|
1832
|
+
return [];
|
|
1833
|
+
};
|
|
1834
|
+
var readUserRole = (user) => {
|
|
1835
|
+
if (!user || typeof user !== "object") {
|
|
1836
|
+
return void 0;
|
|
1837
|
+
}
|
|
1838
|
+
const role = user.role;
|
|
1839
|
+
return typeof role === "string" ? role : void 0;
|
|
1840
|
+
};
|
|
1841
|
+
var buildStudioNavItems = (props, adminBasePath) => {
|
|
1842
|
+
const formsEnabled = getPropBoolean(props, "formsEnabled", false);
|
|
1843
|
+
const formsCollectionSlug = getPropString(props, "formsCollectionSlug", "forms");
|
|
1844
|
+
const formSubmissionsCollectionSlug = getPropString(
|
|
1845
|
+
props,
|
|
1846
|
+
"formSubmissionsCollectionSlug",
|
|
1847
|
+
"form-submissions"
|
|
1848
|
+
);
|
|
1849
|
+
const formUploadsCollectionSlug = getPropString(props, "formUploadsCollectionSlug", "form-uploads");
|
|
1850
|
+
const mediaCollectionSlug = getPropString(props, "mediaCollectionSlug", "media");
|
|
1851
|
+
const globalsBasePath = getPropString(props, "globalsBasePath", "/globals");
|
|
1852
|
+
const globalsExtraMatchPrefixes = getPropStringArray(props, "globalsExtraMatchPrefixes", []);
|
|
1853
|
+
const sections = getPropSections(props);
|
|
1854
|
+
const pagesPath = resolveAdminPath(adminBasePath, "/pages");
|
|
1855
|
+
const formsPath = resolveAdminPath(adminBasePath, "/forms");
|
|
1856
|
+
const mediaPath = resolveAdminPath(adminBasePath, "/media");
|
|
1857
|
+
const toolsPath = resolveAdminPath(adminBasePath, "/tools");
|
|
1858
|
+
const resolvedGlobalsBasePath = resolveAdminPath(adminBasePath, globalsBasePath);
|
|
1859
|
+
const resolvedGlobalsExtraMatchPrefixes = globalsExtraMatchPrefixes.map(
|
|
1860
|
+
(prefix) => resolveAdminPath(adminBasePath, prefix)
|
|
1861
|
+
);
|
|
1862
|
+
const baseItemsBeforeTools = [
|
|
1863
|
+
{
|
|
1864
|
+
href: adminBasePath,
|
|
1865
|
+
icon: "dashboard",
|
|
1866
|
+
label: "Dashboard",
|
|
1867
|
+
matchPrefixes: [adminBasePath]
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
href: pagesPath,
|
|
1871
|
+
icon: "pages",
|
|
1872
|
+
label: "Pages",
|
|
1873
|
+
matchPrefixes: [pagesPath]
|
|
1874
|
+
},
|
|
1875
|
+
...formsEnabled ? [
|
|
1876
|
+
{
|
|
1877
|
+
href: formsPath,
|
|
1878
|
+
icon: "forms",
|
|
1879
|
+
label: "Forms",
|
|
1880
|
+
matchPrefixes: [
|
|
1881
|
+
formsPath,
|
|
1882
|
+
resolveAdminPath(adminBasePath, `/collections/${formsCollectionSlug}`),
|
|
1883
|
+
resolveAdminPath(adminBasePath, `/collections/${formSubmissionsCollectionSlug}`),
|
|
1884
|
+
resolveAdminPath(adminBasePath, `/collections/${formUploadsCollectionSlug}`)
|
|
1885
|
+
]
|
|
1886
|
+
}
|
|
1887
|
+
] : [],
|
|
1888
|
+
{
|
|
1889
|
+
href: resolvedGlobalsBasePath,
|
|
1890
|
+
icon: "globals",
|
|
1891
|
+
label: "Globals",
|
|
1892
|
+
matchPrefixes: [
|
|
1893
|
+
resolvedGlobalsBasePath,
|
|
1894
|
+
resolveAdminPath(adminBasePath, "/globals"),
|
|
1895
|
+
...resolvedGlobalsExtraMatchPrefixes
|
|
1896
|
+
]
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
href: mediaPath,
|
|
1900
|
+
icon: "media",
|
|
1901
|
+
label: "Media",
|
|
1902
|
+
matchPrefixes: [mediaPath, resolveAdminPath(adminBasePath, `/collections/${mediaCollectionSlug}`)]
|
|
1903
|
+
}
|
|
1904
|
+
];
|
|
1905
|
+
const adminToolsItem = {
|
|
1906
|
+
href: toolsPath,
|
|
1907
|
+
icon: "tools",
|
|
1908
|
+
label: "Admin Tools",
|
|
1909
|
+
matchPrefixes: [toolsPath, resolveAdminPath(adminBasePath, "/collections/users")],
|
|
1910
|
+
roles: ["admin"]
|
|
1911
|
+
};
|
|
1912
|
+
const extensionItems = sections.map((section) => ({
|
|
1913
|
+
href: resolveAdminPath(adminBasePath, section.href),
|
|
1914
|
+
...section.icon ? { icon: section.icon } : {},
|
|
1915
|
+
label: section.label,
|
|
1916
|
+
matchPrefixes: section.matchPrefixes.map((prefix) => resolveAdminPath(adminBasePath, prefix)),
|
|
1917
|
+
roles: section.roles
|
|
1918
|
+
}));
|
|
1919
|
+
return [...baseItemsBeforeTools, ...extensionItems, adminToolsItem];
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1922
|
+
// src/admin/components/studio/StudioSectionLayout.tsx
|
|
1923
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1924
|
+
function StudioSectionLayout({ children, navProps }) {
|
|
1925
|
+
const { user } = (0, import_ui.useAuth)();
|
|
1926
|
+
const pathname = (0, import_navigation.usePathname)() || "";
|
|
1927
|
+
const router = (0, import_navigation.useRouter)();
|
|
1928
|
+
const adminBasePath = useAdminBasePath();
|
|
1929
|
+
const defaultBrandName = getPropString(navProps, "brandName", "Orion Studio");
|
|
1930
|
+
const defaultLogoUrl = getPropString(navProps, "logoUrl", "");
|
|
1931
|
+
const navItems = (0, import_react5.useMemo)(
|
|
1932
|
+
() => buildStudioNavItems(navProps, adminBasePath),
|
|
1933
|
+
[adminBasePath, navProps]
|
|
1934
|
+
);
|
|
1935
|
+
const branding = useSiteBranding(defaultBrandName, defaultLogoUrl || void 0);
|
|
1936
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
1937
|
+
document.body.classList.add("orion-studio-shell-active");
|
|
1938
|
+
return () => {
|
|
1939
|
+
document.body.classList.remove("orion-studio-shell-active");
|
|
1940
|
+
};
|
|
1941
|
+
}, []);
|
|
1942
|
+
const logout = (0, import_react5.useMemo)(
|
|
1943
|
+
() => async () => {
|
|
1944
|
+
await fetch("/api/users/logout", {
|
|
1945
|
+
credentials: "include",
|
|
1946
|
+
method: "POST"
|
|
1947
|
+
});
|
|
1948
|
+
router.push(resolveAdminPath(adminBasePath, "/login"));
|
|
1949
|
+
router.refresh();
|
|
1950
|
+
},
|
|
1951
|
+
[adminBasePath, router]
|
|
1952
|
+
);
|
|
1953
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1954
|
+
AdminShellClient,
|
|
1955
|
+
{
|
|
1956
|
+
brandName: branding.siteName || defaultBrandName,
|
|
1957
|
+
logoUrl: branding.logoUrl || defaultLogoUrl || void 0,
|
|
1958
|
+
navItems,
|
|
1959
|
+
onLogout: logout,
|
|
1960
|
+
pathname,
|
|
1961
|
+
storageKey: "orion-admin-sidebar-collapsed-v1",
|
|
1962
|
+
userEmail: typeof user?.email === "string" ? user.email : "user",
|
|
1963
|
+
userRole: readUserRole(user),
|
|
1964
|
+
children
|
|
1965
|
+
}
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// src/admin/components/studio/AdminStudioDashboard.tsx
|
|
1970
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1971
|
+
var DEFAULT_ADMIN_BASE_PATH2 = "/admin";
|
|
1972
|
+
var normalizePath2 = (value) => {
|
|
1973
|
+
if (!value || value === "/") return "/";
|
|
1974
|
+
const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
|
|
1975
|
+
const trimmed = withLeadingSlash.replace(/\/+$/, "");
|
|
1976
|
+
return trimmed.length > 0 ? trimmed : "/";
|
|
1977
|
+
};
|
|
1978
|
+
var normalizeAdminBasePath2 = (value) => {
|
|
1979
|
+
const normalized = normalizePath2(value);
|
|
1980
|
+
return normalized === "/" ? DEFAULT_ADMIN_BASE_PATH2 : normalized;
|
|
1981
|
+
};
|
|
1982
|
+
var isAbsoluteExternalURL3 = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
|
|
1983
|
+
var resolveAdminPath2 = (adminBasePath, targetPath) => {
|
|
1984
|
+
if (!targetPath) return adminBasePath;
|
|
1985
|
+
if (isAbsoluteExternalURL3(targetPath)) return targetPath;
|
|
1986
|
+
const normalizedBasePath = normalizeAdminBasePath2(adminBasePath);
|
|
1987
|
+
const normalizedTargetPath = normalizePath2(targetPath);
|
|
1988
|
+
if (normalizedTargetPath === "/admin") {
|
|
1989
|
+
return normalizedBasePath;
|
|
1990
|
+
}
|
|
1991
|
+
if (normalizedTargetPath.startsWith("/admin/")) {
|
|
1992
|
+
return `${normalizedBasePath}${normalizedTargetPath.slice("/admin".length)}`;
|
|
1993
|
+
}
|
|
1994
|
+
if (normalizedTargetPath === normalizedBasePath || normalizedTargetPath.startsWith(`${normalizedBasePath}/`)) {
|
|
1995
|
+
return normalizedTargetPath;
|
|
1996
|
+
}
|
|
1997
|
+
if (normalizedTargetPath === "/") {
|
|
1998
|
+
return normalizedBasePath;
|
|
1999
|
+
}
|
|
2000
|
+
return `${normalizedBasePath}${normalizedTargetPath}`;
|
|
2001
|
+
};
|
|
2002
|
+
var getPropString2 = (props, key, fallback) => {
|
|
2003
|
+
if (!props || typeof props !== "object") return fallback;
|
|
2004
|
+
const direct = props[key];
|
|
2005
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
2006
|
+
const clientProps = props.clientProps;
|
|
2007
|
+
if (clientProps && typeof clientProps === "object") {
|
|
2008
|
+
const nested = clientProps[key];
|
|
2009
|
+
if (typeof nested === "string" && nested.length > 0) return nested;
|
|
2010
|
+
}
|
|
2011
|
+
return fallback;
|
|
2012
|
+
};
|
|
2013
|
+
var getPropBoolean2 = (props, key, fallback) => {
|
|
2014
|
+
if (!props || typeof props !== "object") return fallback;
|
|
2015
|
+
const direct = props[key];
|
|
2016
|
+
if (typeof direct === "boolean") return direct;
|
|
2017
|
+
const clientProps = props.clientProps;
|
|
2018
|
+
if (clientProps && typeof clientProps === "object") {
|
|
2019
|
+
const nested = clientProps[key];
|
|
2020
|
+
if (typeof nested === "boolean") return nested;
|
|
2021
|
+
}
|
|
2022
|
+
return fallback;
|
|
2023
|
+
};
|
|
2024
|
+
var getPropSections2 = (props) => {
|
|
2025
|
+
if (!props || typeof props !== "object") return [];
|
|
2026
|
+
const direct = resolveStudioSections(props.sections);
|
|
2027
|
+
if (direct.length > 0) return direct;
|
|
2028
|
+
const clientProps = props.clientProps;
|
|
2029
|
+
if (clientProps && typeof clientProps === "object") {
|
|
2030
|
+
return resolveStudioSections(clientProps.sections);
|
|
2031
|
+
}
|
|
2032
|
+
return [];
|
|
2033
|
+
};
|
|
2034
|
+
var getPropDashboardPanels = (props) => {
|
|
2035
|
+
if (!props || typeof props !== "object") return [];
|
|
2036
|
+
const direct = resolveStudioSectionDashboards(props.dashboardPanels);
|
|
2037
|
+
if (direct.length > 0) return direct;
|
|
2038
|
+
const clientProps = props.clientProps;
|
|
2039
|
+
if (clientProps && typeof clientProps === "object") {
|
|
2040
|
+
return resolveStudioSectionDashboards(clientProps.dashboardPanels);
|
|
2041
|
+
}
|
|
2042
|
+
return [];
|
|
2043
|
+
};
|
|
2044
|
+
var readImportMap = (props) => {
|
|
2045
|
+
if (!props || typeof props !== "object") return void 0;
|
|
2046
|
+
const direct = props.importMap;
|
|
2047
|
+
if (direct && typeof direct === "object") {
|
|
2048
|
+
return direct;
|
|
2049
|
+
}
|
|
2050
|
+
const payloadLike = props.payload;
|
|
2051
|
+
if (payloadLike && typeof payloadLike === "object") {
|
|
2052
|
+
const nested = payloadLike.importMap;
|
|
2053
|
+
if (nested && typeof nested === "object") {
|
|
2054
|
+
return nested;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
const initPageResult = props.initPageResult;
|
|
2058
|
+
if (initPageResult && typeof initPageResult === "object") {
|
|
2059
|
+
const req = initPageResult.req;
|
|
2060
|
+
if (req && typeof req === "object") {
|
|
2061
|
+
const payload = req.payload;
|
|
2062
|
+
if (payload && typeof payload === "object") {
|
|
2063
|
+
const nested = payload.importMap;
|
|
2064
|
+
if (nested && typeof nested === "object") {
|
|
2065
|
+
return nested;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
return void 0;
|
|
2071
|
+
};
|
|
2072
|
+
var readUserRole2 = (props) => {
|
|
2073
|
+
if (!props || typeof props !== "object") return void 0;
|
|
2074
|
+
const user = props.user;
|
|
2075
|
+
if (user && typeof user === "object") {
|
|
2076
|
+
const role = user.role;
|
|
2077
|
+
if (typeof role === "string") {
|
|
2078
|
+
return role;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const initPageResult = props.initPageResult;
|
|
2082
|
+
if (initPageResult && typeof initPageResult === "object") {
|
|
2083
|
+
const req = initPageResult.req;
|
|
2084
|
+
if (req && typeof req === "object") {
|
|
2085
|
+
const nestedUser = req.user;
|
|
2086
|
+
if (nestedUser && typeof nestedUser === "object") {
|
|
2087
|
+
const role = nestedUser.role;
|
|
2088
|
+
if (typeof role === "string") {
|
|
2089
|
+
return role;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return void 0;
|
|
2095
|
+
};
|
|
2096
|
+
var readAdminBasePath = (props) => {
|
|
2097
|
+
if (!props || typeof props !== "object") {
|
|
2098
|
+
return DEFAULT_ADMIN_BASE_PATH2;
|
|
2099
|
+
}
|
|
2100
|
+
const payloadLike = props.payload;
|
|
2101
|
+
if (payloadLike && typeof payloadLike === "object") {
|
|
2102
|
+
const config = payloadLike.config;
|
|
2103
|
+
const routes = config && typeof config === "object" ? config.routes : null;
|
|
2104
|
+
const admin = routes && typeof routes === "object" ? routes.admin : null;
|
|
2105
|
+
if (typeof admin === "string" && admin.length > 0) {
|
|
2106
|
+
return normalizeAdminBasePath2(admin);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
const initPageResult = props.initPageResult;
|
|
2110
|
+
if (initPageResult && typeof initPageResult === "object") {
|
|
2111
|
+
const req = initPageResult.req;
|
|
2112
|
+
if (req && typeof req === "object") {
|
|
2113
|
+
const nestedPayload = req.payload;
|
|
2114
|
+
if (nestedPayload && typeof nestedPayload === "object") {
|
|
2115
|
+
const config = nestedPayload.config;
|
|
2116
|
+
const routes = config && typeof config === "object" ? config.routes : null;
|
|
2117
|
+
const admin = routes && typeof routes === "object" ? routes.admin : null;
|
|
2118
|
+
if (typeof admin === "string" && admin.length > 0) {
|
|
2119
|
+
return normalizeAdminBasePath2(admin);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return DEFAULT_ADMIN_BASE_PATH2;
|
|
2125
|
+
};
|
|
2126
|
+
var canAccess2 = (role, roles) => {
|
|
2127
|
+
if (!roles || roles.length === 0) {
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
if (!role) {
|
|
2131
|
+
return false;
|
|
2132
|
+
}
|
|
2133
|
+
return roles.includes(role);
|
|
2134
|
+
};
|
|
2135
|
+
var buildSectionLinks = (adminBasePath, sections, formsEnabled, globalsBasePath) => {
|
|
2136
|
+
const links = [
|
|
2137
|
+
{
|
|
2138
|
+
href: resolveAdminPath2(adminBasePath, "/pages"),
|
|
2139
|
+
id: "pages",
|
|
2140
|
+
label: "Pages"
|
|
2141
|
+
},
|
|
2142
|
+
...formsEnabled ? [
|
|
2143
|
+
{
|
|
2144
|
+
href: resolveAdminPath2(adminBasePath, "/forms"),
|
|
2145
|
+
id: "forms",
|
|
2146
|
+
label: "Forms",
|
|
2147
|
+
roles: ["admin", "editor"]
|
|
2148
|
+
}
|
|
2149
|
+
] : [],
|
|
2150
|
+
{
|
|
2151
|
+
href: resolveAdminPath2(adminBasePath, globalsBasePath),
|
|
2152
|
+
id: "globals",
|
|
2153
|
+
label: "Globals"
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
href: resolveAdminPath2(adminBasePath, "/media"),
|
|
2157
|
+
id: "media",
|
|
2158
|
+
label: "Media"
|
|
2159
|
+
},
|
|
2160
|
+
...sections.map((section) => ({
|
|
2161
|
+
href: resolveAdminPath2(adminBasePath, section.href),
|
|
2162
|
+
id: section.id,
|
|
2163
|
+
label: section.label,
|
|
2164
|
+
...section.roles ? { roles: section.roles } : {}
|
|
2165
|
+
})),
|
|
2166
|
+
{
|
|
2167
|
+
href: resolveAdminPath2(adminBasePath, "/tools"),
|
|
2168
|
+
id: "admin-tools",
|
|
2169
|
+
label: "Admin Tools",
|
|
2170
|
+
roles: ["admin"]
|
|
2171
|
+
}
|
|
2172
|
+
];
|
|
2173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2174
|
+
return links.filter((link) => {
|
|
2175
|
+
if (seen.has(link.id)) {
|
|
2176
|
+
return false;
|
|
2177
|
+
}
|
|
2178
|
+
seen.add(link.id);
|
|
2179
|
+
return true;
|
|
2180
|
+
});
|
|
2181
|
+
};
|
|
2182
|
+
function DashboardPanelFallback({ label }) {
|
|
2183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "orion-dashboard-extension-fallback", children: [
|
|
2184
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "orion-dashboard-panel-kicker", children: "Extension Unavailable" }),
|
|
2185
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: label }),
|
|
2186
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "This dashboard panel could not be rendered, but the rest of the dashboard is still available." })
|
|
2187
|
+
] });
|
|
2188
|
+
}
|
|
2189
|
+
function buildExtensionPanels(adminBasePath, importMap, panels, userRole) {
|
|
2190
|
+
const visiblePanels = panels.filter((panel) => canAccess2(userRole, panel.roles));
|
|
2191
|
+
return visiblePanels.map((panel) => {
|
|
2192
|
+
const href = resolveAdminPath2(adminBasePath, panel.href);
|
|
2193
|
+
return {
|
|
2194
|
+
id: panel.id,
|
|
2195
|
+
node: importMap ? (0, import_RenderServerComponent.RenderServerComponent)({
|
|
2196
|
+
Component: panel.Component,
|
|
2197
|
+
Fallback: () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DashboardPanelFallback, { label: panel.label }),
|
|
2198
|
+
importMap,
|
|
2199
|
+
serverProps: {
|
|
2200
|
+
adminBasePath,
|
|
2201
|
+
href,
|
|
2202
|
+
label: panel.label
|
|
2203
|
+
}
|
|
2204
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DashboardPanelFallback, { label: panel.label }),
|
|
2205
|
+
span: panel.span
|
|
2206
|
+
};
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
function AdminStudioDashboard(rawProps) {
|
|
2210
|
+
const props = rawProps || {};
|
|
2211
|
+
const formsEnabled = getPropBoolean2(props, "formsEnabled", false);
|
|
2212
|
+
const globalsBasePath = getPropString2(props, "globalsBasePath", "/globals");
|
|
2213
|
+
const sections = getPropSections2(props);
|
|
2214
|
+
const dashboardPanels = getPropDashboardPanels(props);
|
|
2215
|
+
const pagesCollectionSlug = getPropString2(props, "pagesCollectionSlug", "pages");
|
|
2216
|
+
const formsCollectionSlug = getPropString2(props, "formsCollectionSlug", "forms");
|
|
2217
|
+
const formSubmissionsCollectionSlug = getPropString2(
|
|
2218
|
+
props,
|
|
2219
|
+
"formSubmissionsCollectionSlug",
|
|
2220
|
+
"form-submissions"
|
|
2221
|
+
);
|
|
2222
|
+
const mediaCollectionSlug = getPropString2(props, "mediaCollectionSlug", "media");
|
|
2223
|
+
const adminBasePath = readAdminBasePath(props);
|
|
2224
|
+
const importMap = readImportMap(props);
|
|
2225
|
+
const userRole = readUserRole2(props);
|
|
2226
|
+
const navProps = {
|
|
2227
|
+
brandName: getPropString2(props, "brandName", "Orion Studio"),
|
|
2228
|
+
dashboardPanels,
|
|
2229
|
+
formSubmissionsCollectionSlug,
|
|
2230
|
+
formsCollectionSlug,
|
|
2231
|
+
formsEnabled,
|
|
2232
|
+
globalsBasePath,
|
|
2233
|
+
logoUrl: getPropString2(props, "logoUrl", ""),
|
|
2234
|
+
mediaCollectionSlug,
|
|
2235
|
+
pagesCollectionSlug,
|
|
2236
|
+
sections
|
|
2237
|
+
};
|
|
2238
|
+
const extensionPanels = buildExtensionPanels(adminBasePath, importMap, dashboardPanels, userRole);
|
|
2239
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StudioSectionLayout, { navProps, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2240
|
+
AdminPage,
|
|
2241
|
+
{
|
|
2242
|
+
breadcrumbs: [{ label: "Dashboard" }],
|
|
2243
|
+
description: "What needs attention, what changed recently, and how the site is performing.",
|
|
2244
|
+
title: "Studio",
|
|
2245
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2246
|
+
AdminStudioDashboardClient,
|
|
2247
|
+
{
|
|
2248
|
+
extensionPanels,
|
|
2249
|
+
formSubmissionsCollectionSlug,
|
|
2250
|
+
formsCollectionSlug,
|
|
2251
|
+
formsEnabled,
|
|
2252
|
+
formsPath: resolveAdminPath2(adminBasePath, "/forms"),
|
|
2253
|
+
globalsBasePath: resolveAdminPath2(adminBasePath, globalsBasePath),
|
|
2254
|
+
mediaCollectionSlug,
|
|
2255
|
+
mediaPath: resolveAdminPath2(adminBasePath, "/media"),
|
|
2256
|
+
pagesCollectionSlug,
|
|
2257
|
+
pagesPath: resolveAdminPath2(adminBasePath, "/pages"),
|
|
2258
|
+
sectionLinks: buildSectionLinks(adminBasePath, sections, formsEnabled, globalsBasePath),
|
|
2259
|
+
toolsPath: resolveAdminPath2(adminBasePath, "/tools"),
|
|
2260
|
+
userRole
|
|
2261
|
+
}
|
|
2262
|
+
)
|
|
2263
|
+
}
|
|
2264
|
+
) });
|
|
2265
|
+
}
|
|
2266
|
+
|
|
779
2267
|
// src/admin/helpers/withTooltips.ts
|
|
780
2268
|
var defaultTooltips = {
|
|
781
2269
|
title: "The main title displayed on this page.",
|
|
@@ -1046,36 +2534,6 @@ __export(admin_app_exports, {
|
|
|
1046
2534
|
roleCanAccessNav: () => roleCanAccessNav
|
|
1047
2535
|
});
|
|
1048
2536
|
|
|
1049
|
-
// src/admin-app/components/AdminBreadcrumbs.tsx
|
|
1050
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1051
|
-
function AdminBreadcrumbs({ items }) {
|
|
1052
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Breadcrumb", className: "orion-admin-breadcrumbs", children: items.map((item, index) => {
|
|
1053
|
-
const isLast = index === items.length - 1;
|
|
1054
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1055
|
-
item.href && !isLast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: item.href, children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.label }),
|
|
1056
|
-
!isLast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "orion-admin-breadcrumb-sep", children: "/" }) : null
|
|
1057
|
-
] }, `${item.label}-${index}`);
|
|
1058
|
-
}) });
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// src/admin-app/components/AdminPage.tsx
|
|
1062
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1063
|
-
function AdminPage({ title, description, breadcrumbs, actions, children }) {
|
|
1064
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page", children: [
|
|
1065
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-header", children: [
|
|
1066
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AdminBreadcrumbs, { items: breadcrumbs }),
|
|
1067
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-title-row", children: [
|
|
1068
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
1069
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { children: title }),
|
|
1070
|
-
description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: description }) : null
|
|
1071
|
-
] }),
|
|
1072
|
-
actions ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-actions", children: actions }) : null
|
|
1073
|
-
] })
|
|
1074
|
-
] }),
|
|
1075
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-content", children })
|
|
1076
|
-
] });
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
2537
|
// src/admin-app/nestedNavigation.ts
|
|
1080
2538
|
var normalizeNestedNavItems = (items) => {
|
|
1081
2539
|
const deduped = [];
|
|
@@ -2872,7 +4330,7 @@ var TestimonialsBlock = {
|
|
|
2872
4330
|
};
|
|
2873
4331
|
|
|
2874
4332
|
// src/blocks/components/BuilderBlockLabel.tsx
|
|
2875
|
-
var
|
|
4333
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2876
4334
|
var blockTitles = {
|
|
2877
4335
|
bookingEmbed: "Booking Embed",
|
|
2878
4336
|
cta: "Call to Action",
|
|
@@ -2899,8 +4357,8 @@ function BuilderBlockLabel({ blockType, rowLabel, rowNumber }) {
|
|
|
2899
4357
|
const fallbackTitle = blockTitles[blockType] || blockType;
|
|
2900
4358
|
const title = rowLabel && rowLabel.trim().length > 0 ? rowLabel : fallbackTitle;
|
|
2901
4359
|
const tag = blockEmojis[blockType] || "Section";
|
|
2902
|
-
return /* @__PURE__ */ (0,
|
|
2903
|
-
/* @__PURE__ */ (0,
|
|
4360
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
4361
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2904
4362
|
"span",
|
|
2905
4363
|
{
|
|
2906
4364
|
style: {
|
|
@@ -2915,7 +4373,7 @@ function BuilderBlockLabel({ blockType, rowLabel, rowNumber }) {
|
|
|
2915
4373
|
children: tag
|
|
2916
4374
|
}
|
|
2917
4375
|
),
|
|
2918
|
-
/* @__PURE__ */ (0,
|
|
4376
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: { fontWeight: 700 }, children: [
|
|
2919
4377
|
typeof rowNumber === "number" ? `${rowNumber + 1}. ` : "",
|
|
2920
4378
|
title
|
|
2921
4379
|
] })
|
|
@@ -3462,18 +4920,18 @@ function createStudioRegistry(modules) {
|
|
|
3462
4920
|
listNodeTypes: () => [...nodeTypes]
|
|
3463
4921
|
};
|
|
3464
4922
|
}
|
|
3465
|
-
function validateStudioDocument(
|
|
4923
|
+
function validateStudioDocument(document2, modules) {
|
|
3466
4924
|
const issues = [];
|
|
3467
|
-
if (
|
|
4925
|
+
if (document2.schemaVersion !== 1) {
|
|
3468
4926
|
issues.push(makeIssue("Unsupported schema version", "schemaVersion", "studio.schemaVersion"));
|
|
3469
4927
|
}
|
|
3470
|
-
if (!Array.isArray(
|
|
4928
|
+
if (!Array.isArray(document2.nodes)) {
|
|
3471
4929
|
issues.push(makeIssue("Nodes must be an array", "nodes", "studio.nodes"));
|
|
3472
4930
|
return issues;
|
|
3473
4931
|
}
|
|
3474
4932
|
const registry = createStudioRegistry(modules);
|
|
3475
4933
|
const nodeIDs = /* @__PURE__ */ new Set();
|
|
3476
|
-
|
|
4934
|
+
document2.nodes.forEach((node, index) => {
|
|
3477
4935
|
if (!node.id) {
|
|
3478
4936
|
issues.push(makeIssue("Node id is required", `nodes.${index}.id`, "studio.node.id"));
|
|
3479
4937
|
}
|
|
@@ -3487,15 +4945,15 @@ function validateStudioDocument(document, modules) {
|
|
|
3487
4945
|
});
|
|
3488
4946
|
for (const module2 of modules) {
|
|
3489
4947
|
for (const validate of module2.validators) {
|
|
3490
|
-
issues.push(...validate(
|
|
4948
|
+
issues.push(...validate(document2));
|
|
3491
4949
|
}
|
|
3492
4950
|
}
|
|
3493
4951
|
return issues;
|
|
3494
4952
|
}
|
|
3495
|
-
function compileStudioDocument(
|
|
3496
|
-
const issues = validateStudioDocument(
|
|
4953
|
+
function compileStudioDocument(document2, modules) {
|
|
4954
|
+
const issues = validateStudioDocument(document2, modules);
|
|
3497
4955
|
const compilerEntries = modules.filter((mod) => typeof mod.compiler?.compileNode === "function").map((mod) => mod.compiler?.compileNode);
|
|
3498
|
-
const layout =
|
|
4956
|
+
const layout = document2.nodes.map((node) => {
|
|
3499
4957
|
for (const compileNode of compilerEntries) {
|
|
3500
4958
|
if (!compileNode) {
|
|
3501
4959
|
continue;
|
|
@@ -3832,7 +5290,7 @@ var layoutToStudioDocument = (layout, title, metadata) => {
|
|
|
3832
5290
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3833
5291
|
};
|
|
3834
5292
|
};
|
|
3835
|
-
var studioDocumentToLayout = (
|
|
5293
|
+
var studioDocumentToLayout = (document2) => document2.nodes.map(
|
|
3836
5294
|
(node) => migrateBlockToSettingsV2({
|
|
3837
5295
|
id: node.id,
|
|
3838
5296
|
blockType: node.type,
|
|
@@ -3866,7 +5324,7 @@ function withStudioDocumentLayout(page) {
|
|
|
3866
5324
|
}
|
|
3867
5325
|
return page;
|
|
3868
5326
|
}
|
|
3869
|
-
function
|
|
5327
|
+
function normalizePath3(segments) {
|
|
3870
5328
|
if (!segments || segments.length === 0) {
|
|
3871
5329
|
return "/";
|
|
3872
5330
|
}
|
|
@@ -3923,7 +5381,7 @@ function createPageQueries(getPayloadClient, contentTag = "website-content") {
|
|
|
3923
5381
|
{ tags: [contentTag] }
|
|
3924
5382
|
);
|
|
3925
5383
|
async function getPageBySegments(segments, draft = false) {
|
|
3926
|
-
const path2 =
|
|
5384
|
+
const path2 = normalizePath3(segments);
|
|
3927
5385
|
const payload = await getPayloadClient();
|
|
3928
5386
|
if (draft) {
|
|
3929
5387
|
return queryPageByPath(payload, path2, true);
|
|
@@ -4535,8 +5993,8 @@ var hydrateRelationship = (valueFromStudio, valueFromLayout) => {
|
|
|
4535
5993
|
}
|
|
4536
5994
|
return valueFromLayout;
|
|
4537
5995
|
};
|
|
4538
|
-
var hydrateDocumentWithLayoutRelations = (
|
|
4539
|
-
const nextNodes =
|
|
5996
|
+
var hydrateDocumentWithLayoutRelations = (document2, layout) => {
|
|
5997
|
+
const nextNodes = document2.nodes.map((node, index) => {
|
|
4540
5998
|
const layoutBlock = layout[index];
|
|
4541
5999
|
if (!isRecord5(layoutBlock)) {
|
|
4542
6000
|
return node;
|
|
@@ -4575,17 +6033,17 @@ var hydrateDocumentWithLayoutRelations = (document, layout) => {
|
|
|
4575
6033
|
};
|
|
4576
6034
|
});
|
|
4577
6035
|
return {
|
|
4578
|
-
...
|
|
6036
|
+
...document2,
|
|
4579
6037
|
nodes: nextNodes
|
|
4580
6038
|
};
|
|
4581
6039
|
};
|
|
4582
|
-
var normalizeDocument = (
|
|
4583
|
-
...
|
|
6040
|
+
var normalizeDocument = (document2) => ({
|
|
6041
|
+
...document2,
|
|
4584
6042
|
schemaVersion: 1,
|
|
4585
6043
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4586
6044
|
});
|
|
4587
|
-
var normalizeLegacyHeroDefaults = (
|
|
4588
|
-
const nodes =
|
|
6045
|
+
var normalizeLegacyHeroDefaults = (document2) => {
|
|
6046
|
+
const nodes = document2.nodes.map((node) => {
|
|
4589
6047
|
if (node.type !== "hero" || !isRecord5(node.data)) {
|
|
4590
6048
|
return node;
|
|
4591
6049
|
}
|
|
@@ -4603,7 +6061,7 @@ var normalizeLegacyHeroDefaults = (document) => {
|
|
|
4603
6061
|
};
|
|
4604
6062
|
});
|
|
4605
6063
|
return {
|
|
4606
|
-
...
|
|
6064
|
+
...document2,
|
|
4607
6065
|
nodes
|
|
4608
6066
|
};
|
|
4609
6067
|
};
|
|
@@ -4652,9 +6110,9 @@ var createStudioPageService = ({
|
|
|
4652
6110
|
title: typeof page.title === "string" ? page.title : "Untitled Page"
|
|
4653
6111
|
};
|
|
4654
6112
|
},
|
|
4655
|
-
validateStudioDocument: (
|
|
4656
|
-
saveDraft: async (pageID,
|
|
4657
|
-
const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(
|
|
6113
|
+
validateStudioDocument: (document2) => validateStudioDocument(document2, modules),
|
|
6114
|
+
saveDraft: async (pageID, document2, _metadata) => {
|
|
6115
|
+
const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document2));
|
|
4658
6116
|
const compileResult = compileStudioDocument(normalizedDocument, modules);
|
|
4659
6117
|
await payload.update({
|
|
4660
6118
|
collection: collectionSlug,
|
|
@@ -4674,8 +6132,8 @@ var createStudioPageService = ({
|
|
|
4674
6132
|
status: "draft"
|
|
4675
6133
|
};
|
|
4676
6134
|
},
|
|
4677
|
-
publish: async (pageID,
|
|
4678
|
-
const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(
|
|
6135
|
+
publish: async (pageID, document2, _metadata) => {
|
|
6136
|
+
const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document2));
|
|
4679
6137
|
const compileResult = compileStudioDocument(normalizedDocument, modules);
|
|
4680
6138
|
assertCanPublish(compileResult.issues);
|
|
4681
6139
|
await payload.update({
|
|
@@ -4955,9 +6413,9 @@ var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
|
|
|
4955
6413
|
return data;
|
|
4956
6414
|
}
|
|
4957
6415
|
}));
|
|
4958
|
-
var validatePageDocument = (
|
|
6416
|
+
var validatePageDocument = (document2) => {
|
|
4959
6417
|
const issues = [];
|
|
4960
|
-
if (!
|
|
6418
|
+
if (!document2.title || document2.title.trim().length === 0) {
|
|
4961
6419
|
issues.push({
|
|
4962
6420
|
code: "pages.title.required",
|
|
4963
6421
|
message: "Page title is required before publishing.",
|
|
@@ -4965,7 +6423,7 @@ var validatePageDocument = (document) => {
|
|
|
4965
6423
|
severity: "error"
|
|
4966
6424
|
});
|
|
4967
6425
|
}
|
|
4968
|
-
if (
|
|
6426
|
+
if (document2.nodes.length === 0) {
|
|
4969
6427
|
issues.push({
|
|
4970
6428
|
code: "pages.nodes.required",
|
|
4971
6429
|
message: "At least one section is required.",
|
|
@@ -4973,7 +6431,7 @@ var validatePageDocument = (document) => {
|
|
|
4973
6431
|
severity: "error"
|
|
4974
6432
|
});
|
|
4975
6433
|
}
|
|
4976
|
-
|
|
6434
|
+
document2.nodes.forEach((node, index) => {
|
|
4977
6435
|
if (node.type === "hero" && typeof node.data.headline !== "string") {
|
|
4978
6436
|
issues.push({
|
|
4979
6437
|
code: "pages.hero.headline",
|