@orion-studios/payload-studio 0.6.0-beta.4 → 0.6.0-beta.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/admin/client.d.mts +1 -0
  2. package/dist/admin/client.d.ts +1 -0
  3. package/dist/admin/client.js +3924 -1355
  4. package/dist/admin/client.mjs +4090 -1533
  5. package/dist/admin/index.d.mts +2 -3
  6. package/dist/admin/index.d.ts +2 -3
  7. package/dist/admin/index.js +141 -1522
  8. package/dist/admin/index.mjs +2 -4
  9. package/dist/admin-app/client.js +11 -4
  10. package/dist/admin-app/client.mjs +1 -1
  11. package/dist/admin-app/index.d.mts +2 -2
  12. package/dist/admin-app/index.d.ts +2 -2
  13. package/dist/admin-app/index.mjs +4 -4
  14. package/dist/admin-app/styles.css +343 -41
  15. package/dist/admin.css +18 -2
  16. package/dist/{chunk-KPIX7OSV.mjs → chunk-2XH7X34N.mjs} +11 -4
  17. package/dist/{chunk-OTHERBGX.mjs → chunk-AWL7TIEI.mjs} +1 -1
  18. package/dist/{chunk-PF3EBZXF.mjs → chunk-CUPMTHGX.mjs} +19 -3
  19. package/dist/chunk-JC3UV74N.mjs +1033 -0
  20. package/dist/{chunk-XKUTZ7IU.mjs → chunk-LUF62ZWF.mjs} +31 -5
  21. package/dist/{chunk-EHUE4LCT.mjs → chunk-RKTIFEUY.mjs} +33 -3
  22. package/dist/chunk-W2UOCJDX.mjs +32 -0
  23. package/dist/{index-bbA3HSxa.d.ts → index-BV0vEGl6.d.ts} +6 -9
  24. package/dist/{index-52HdVLQq.d.ts → index-DAdN56fM.d.ts} +1 -1
  25. package/dist/{index-DEkV-sMs.d.mts → index-DLfPOqYA.d.mts} +6 -9
  26. package/dist/{index-Cv-6qnrw.d.mts → index-DRu4fKxf.d.mts} +1 -1
  27. package/dist/{index-DEQC3Dwj.d.mts → index-G_uTNffQ.d.mts} +1 -1
  28. package/dist/{index-Crx_MtPw.d.ts → index-vkEwwsoC.d.ts} +1 -1
  29. package/dist/index.d.mts +5 -5
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +249 -1542
  32. package/dist/index.mjs +10 -10
  33. package/dist/nextjs/index.js +19 -3
  34. package/dist/nextjs/index.mjs +2 -2
  35. package/dist/{sitePreviewTypes-BkHCWxNW.d.mts → sitePreviewTypes-BrJwGzJj.d.mts} +1 -1
  36. package/dist/{sitePreviewTypes-BkHCWxNW.d.ts → sitePreviewTypes-BrJwGzJj.d.ts} +1 -1
  37. package/dist/studio-pages/builder.css +24 -5
  38. package/dist/studio-pages/client.js +455 -63
  39. package/dist/studio-pages/client.mjs +455 -63
  40. package/dist/studio-pages/index.d.mts +1 -1
  41. package/dist/studio-pages/index.d.ts +1 -1
  42. package/dist/studio-pages/index.js +46 -4
  43. package/dist/studio-pages/index.mjs +3 -3
  44. package/package.json +1 -1
  45. package/dist/chunk-DYXSAVUQ.mjs +0 -2372
  46. package/dist/chunk-Z6L5K5MH.mjs +0 -64
package/dist/index.js CHANGED
@@ -42,7 +42,6 @@ module.exports = __toCommonJS(index_exports);
42
42
  // src/admin/index.ts
43
43
  var admin_exports = {};
44
44
  __export(admin_exports, {
45
- AdminStudioDashboard: () => AdminStudioDashboard,
46
45
  SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM: () => SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM,
47
46
  SOCIAL_MEDIA_ICON_OPTIONS: () => SOCIAL_MEDIA_ICON_OPTIONS,
48
47
  SOCIAL_MEDIA_PLATFORMS: () => SOCIAL_MEDIA_PLATFORMS,
@@ -108,9 +107,8 @@ var navItemIsActive = (pathname, item) => {
108
107
  };
109
108
 
110
109
  // src/shared/studioSections.ts
111
- var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
110
+ var studioRoles = /* @__PURE__ */ new Set(["admin", "developer", "editor", "client"]);
112
111
  var studioIcons = new Set(adminNavIcons);
113
- var dashboardSpans = /* @__PURE__ */ new Set(["full", "half"]);
114
112
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
115
113
  var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
116
114
  var normalizePathLikeValue = (value) => {
@@ -152,22 +150,6 @@ var normalizeCard = (value) => {
152
150
  };
153
151
  };
154
152
  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";
171
153
  var resolveStudioSections = (value) => {
172
154
  if (!Array.isArray(value)) {
173
155
  return [];
@@ -233,38 +215,6 @@ var resolveStudioSectionViews = (value) => {
233
215
  }
234
216
  return views;
235
217
  };
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
- };
268
218
 
269
219
  // src/admin/helpers/configureAdmin.ts
270
220
  var import_meta = {};
@@ -291,8 +241,10 @@ function configureAdmin(config) {
291
241
  brandPrimary = "#3b82f6",
292
242
  brandSecondary = "#8b5cf6",
293
243
  defaultTheme = "brand-light",
244
+ logoOnDarkUrl,
294
245
  logoUrl,
295
- allowThemePreference = false
246
+ allowThemePreference = false,
247
+ userSessionDurationSeconds = 60 * 60 * 24
296
248
  } = config;
297
249
  const studioEnabled = config.studio?.enabled ?? true;
298
250
  const formsEnabled = config.studio?.forms?.enabled ?? false;
@@ -324,7 +276,6 @@ function configureAdmin(config) {
324
276
  };
325
277
  });
326
278
  const studioSections = resolveStudioSections(config.studio?.sections || []);
327
- const studioDashboardPanels = resolveStudioSectionDashboards(config.studio?.sections || []);
328
279
  const studioSectionViews = resolveStudioSectionViews(config.studio?.sections || []);
329
280
  const sitePreview = config.studio?.sitePreview;
330
281
  let cssPath;
@@ -354,7 +305,6 @@ function configureAdmin(config) {
354
305
  cssPath = genPath;
355
306
  }
356
307
  const clientPath = "@orion-studios/payload-studio/admin/client";
357
- const adminPath = "@orion-studios/payload-studio/admin";
358
308
  const studioNavClientProps = {
359
309
  brandName,
360
310
  formSubmissionsCollectionSlug,
@@ -366,14 +316,8 @@ function configureAdmin(config) {
366
316
  logoUrl,
367
317
  mediaCollectionSlug,
368
318
  pagesCollectionSlug,
369
- dashboardPanels: studioDashboardPanels,
370
319
  sections: studioSections
371
320
  };
372
- const dashboardImportMapGenerator = studioDashboardPanels.length > 0 ? ({ addToImportMap }) => {
373
- addToImportMap(
374
- studioDashboardPanels.map((panel) => panel.Component)
375
- );
376
- } : void 0;
377
321
  const studioBackBreadcrumbComponent = {
378
322
  exportName: "StudioBackBreadcrumb",
379
323
  path: clientPath
@@ -405,14 +349,52 @@ function configureAdmin(config) {
405
349
  }
406
350
  };
407
351
  };
352
+ const attachStudioEditRedirectToCollection = (collection, options) => {
353
+ if (!studioEnabled) {
354
+ return collection;
355
+ }
356
+ const collectionWithBreadcrumb = attachStudioBackBreadcrumbToCollection(collection);
357
+ const existingViews = collectionWithBreadcrumb.admin?.components?.views;
358
+ const existingEditViews = existingViews?.edit;
359
+ const hasCustomEditView = Boolean(
360
+ existingEditViews?.root || existingEditViews?.default && typeof existingEditViews.default === "object" && existingEditViews.default.Component
361
+ );
362
+ if (hasCustomEditView) {
363
+ return collectionWithBreadcrumb;
364
+ }
365
+ return {
366
+ ...collectionWithBreadcrumb,
367
+ admin: {
368
+ ...collectionWithBreadcrumb.admin,
369
+ components: {
370
+ ...collectionWithBreadcrumb.admin?.components,
371
+ views: {
372
+ ...existingViews,
373
+ edit: {
374
+ ...existingEditViews,
375
+ default: {
376
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
377
+ Component: {
378
+ exportName: "StudioDocumentRedirect",
379
+ path: clientPath,
380
+ clientProps: {
381
+ description: options.description,
382
+ ...options.emptyHref ? { emptyHref: options.emptyHref } : {},
383
+ ...options.emptyLabel ? { emptyLabel: options.emptyLabel } : {},
384
+ pathBase: options.pathBase,
385
+ title: options.title
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ };
394
+ };
408
395
  return {
409
396
  admin: {
410
397
  css: cssPath,
411
- ...dashboardImportMapGenerator ? {
412
- importMap: {
413
- generators: [dashboardImportMapGenerator]
414
- }
415
- } : {},
416
398
  components: {
417
399
  ...studioEnabled ? {
418
400
  Nav: {
@@ -427,6 +409,7 @@ function configureAdmin(config) {
427
409
  path: clientPath,
428
410
  clientProps: {
429
411
  brandName,
412
+ logoOnDarkUrl,
430
413
  logoUrl
431
414
  }
432
415
  },
@@ -435,6 +418,7 @@ function configureAdmin(config) {
435
418
  path: clientPath,
436
419
  clientProps: {
437
420
  brandName,
421
+ logoOnDarkUrl,
438
422
  logoUrl
439
423
  }
440
424
  }
@@ -443,12 +427,13 @@ function configureAdmin(config) {
443
427
  dashboard: {
444
428
  Component: {
445
429
  exportName: studioEnabled ? "AdminStudioDashboard" : "Dashboard",
446
- path: studioEnabled ? adminPath : clientPath,
430
+ path: clientPath,
447
431
  clientProps: studioNavClientProps
448
432
  }
449
433
  },
450
434
  ...studioEnabled ? {
451
435
  studioGlobals: {
436
+ exact: true,
452
437
  path: globalsBasePath,
453
438
  Component: {
454
439
  exportName: "AdminStudioGlobalsView",
@@ -460,10 +445,11 @@ function configureAdmin(config) {
460
445
  }
461
446
  }
462
447
  },
463
- studioPages: {
464
- path: pagesBasePath,
448
+ studioPageNew: {
449
+ exact: true,
450
+ path: `${pagesBasePath}/new`,
465
451
  Component: {
466
- exportName: "AdminStudioPagesListView",
452
+ exportName: "AdminStudioNewPageView",
467
453
  path: clientPath,
468
454
  clientProps: {
469
455
  ...studioNavClientProps,
@@ -472,6 +458,7 @@ function configureAdmin(config) {
472
458
  }
473
459
  },
474
460
  studioPageEditor: {
461
+ exact: true,
475
462
  path: `${pagesBasePath}/:id`,
476
463
  Component: {
477
464
  exportName: "AdminStudioPageEditView",
@@ -482,10 +469,11 @@ function configureAdmin(config) {
482
469
  }
483
470
  }
484
471
  },
485
- studioPageNew: {
486
- path: `${pagesBasePath}/new`,
472
+ studioPages: {
473
+ exact: true,
474
+ path: pagesBasePath,
487
475
  Component: {
488
- exportName: "AdminStudioNewPageView",
476
+ exportName: "AdminStudioPagesListView",
489
477
  path: clientPath,
490
478
  clientProps: {
491
479
  ...studioNavClientProps,
@@ -494,6 +482,7 @@ function configureAdmin(config) {
494
482
  }
495
483
  },
496
484
  studioContactForm: {
485
+ exact: true,
497
486
  path: contactFormStudioPath,
498
487
  Component: {
499
488
  exportName: "AdminStudioContactFormView",
@@ -506,7 +495,47 @@ function configureAdmin(config) {
506
495
  }
507
496
  },
508
497
  ...formsEnabled ? {
498
+ studioFormSubmission: {
499
+ exact: true,
500
+ path: `${formsBasePath}/submissions/:id`,
501
+ Component: {
502
+ exportName: "AdminStudioFormSubmissionView",
503
+ path: clientPath,
504
+ clientProps: {
505
+ ...studioNavClientProps,
506
+ formsCollectionSlug,
507
+ formSubmissionsCollectionSlug,
508
+ formUploadsCollectionSlug
509
+ }
510
+ }
511
+ },
512
+ studioFormUpload: {
513
+ exact: true,
514
+ path: `${formsBasePath}/uploads/:id`,
515
+ Component: {
516
+ exportName: "AdminStudioFormUploadView",
517
+ path: clientPath,
518
+ clientProps: {
519
+ ...studioNavClientProps,
520
+ formUploadsCollectionSlug
521
+ }
522
+ }
523
+ },
524
+ studioFormDetail: {
525
+ exact: true,
526
+ path: `${formsBasePath}/:id`,
527
+ Component: {
528
+ exportName: "AdminStudioFormDetailView",
529
+ path: clientPath,
530
+ clientProps: {
531
+ ...studioNavClientProps,
532
+ formsCollectionSlug,
533
+ formSubmissionsCollectionSlug
534
+ }
535
+ }
536
+ },
509
537
  studioForms: {
538
+ exact: true,
510
539
  path: formsBasePath,
511
540
  Component: {
512
541
  exportName: "AdminStudioFormsView",
@@ -520,10 +549,11 @@ function configureAdmin(config) {
520
549
  }
521
550
  }
522
551
  } : {},
523
- studioMedia: {
524
- path: mediaBasePath,
552
+ studioMediaItem: {
553
+ exact: true,
554
+ path: `${mediaBasePath}/:id`,
525
555
  Component: {
526
- exportName: "AdminStudioMediaView",
556
+ exportName: "AdminStudioMediaItemView",
527
557
  path: clientPath,
528
558
  clientProps: {
529
559
  ...studioNavClientProps,
@@ -531,10 +561,11 @@ function configureAdmin(config) {
531
561
  }
532
562
  }
533
563
  },
534
- studioMediaItem: {
535
- path: `${mediaBasePath}/:id`,
564
+ studioMedia: {
565
+ exact: true,
566
+ path: mediaBasePath,
536
567
  Component: {
537
- exportName: "AdminStudioMediaItemView",
568
+ exportName: "AdminStudioMediaView",
538
569
  path: clientPath,
539
570
  clientProps: {
540
571
  ...studioNavClientProps,
@@ -543,6 +574,7 @@ function configureAdmin(config) {
543
574
  }
544
575
  },
545
576
  studioTools: {
577
+ exact: true,
546
578
  path: toolsBasePath,
547
579
  Component: {
548
580
  exportName: "AdminStudioToolsView",
@@ -587,6 +619,7 @@ function configureAdmin(config) {
587
619
  path: clientPath,
588
620
  clientProps: {
589
621
  brandName,
622
+ logoOnDarkUrl,
590
623
  logoUrl
591
624
  }
592
625
  }
@@ -623,8 +656,17 @@ function configureAdmin(config) {
623
656
  const hasThemePreference = existingFields.some(
624
657
  (field) => typeof field === "object" && field !== null && "name" in field && field.name === "themePreference"
625
658
  );
659
+ const normalizedAuth = usersCollection.auth === true ? {
660
+ tokenExpiration: userSessionDurationSeconds,
661
+ useSessions: true
662
+ } : usersCollection.auth && typeof usersCollection.auth === "object" ? {
663
+ ...usersCollection.auth,
664
+ tokenExpiration: usersCollection.auth.tokenExpiration ?? userSessionDurationSeconds,
665
+ useSessions: usersCollection.auth.useSessions ?? true
666
+ } : usersCollection.auth;
626
667
  const nextCollection = {
627
668
  ...usersCollection,
669
+ auth: normalizedAuth,
628
670
  fields: !allowThemePreference || hasThemePreference ? existingFields : [...existingFields, createThemePreferenceField(defaultTheme)]
629
671
  };
630
672
  return attachStudioBackBreadcrumbToCollection(nextCollection);
@@ -686,13 +728,31 @@ function configureAdmin(config) {
686
728
  return attachStudioBackBreadcrumbToCollection(mediaCollection);
687
729
  },
688
730
  wrapFormsCollection(formsCollection) {
689
- return attachStudioBackBreadcrumbToCollection(formsCollection);
731
+ return attachStudioEditRedirectToCollection(formsCollection, {
732
+ description: "Redirecting to the Studio form workspace.",
733
+ emptyHref: formsBasePath,
734
+ emptyLabel: "Open Forms",
735
+ pathBase: formsBasePath,
736
+ title: "Opening Form..."
737
+ });
690
738
  },
691
739
  wrapFormSubmissionsCollection(formSubmissionsCollection) {
692
- return attachStudioBackBreadcrumbToCollection(formSubmissionsCollection);
740
+ return attachStudioEditRedirectToCollection(formSubmissionsCollection, {
741
+ description: "Redirecting to the Studio submission workspace.",
742
+ emptyHref: formsBasePath,
743
+ emptyLabel: "Open Forms",
744
+ pathBase: `${formsBasePath}/submissions`,
745
+ title: "Opening Submission..."
746
+ });
693
747
  },
694
748
  wrapFormUploadsCollection(formUploadsCollection) {
695
- return attachStudioBackBreadcrumbToCollection(formUploadsCollection);
749
+ return attachStudioEditRedirectToCollection(formUploadsCollection, {
750
+ description: "Redirecting to the Studio upload workspace.",
751
+ emptyHref: formsBasePath,
752
+ emptyLabel: "Open Forms",
753
+ pathBase: `${formsBasePath}/uploads`,
754
+ title: "Opening Upload..."
755
+ });
696
756
  },
697
757
  wrapGlobals(globals2) {
698
758
  const labelMap = {
@@ -839,1431 +899,6 @@ function configureAdmin(config) {
839
899
  };
840
900
  }
841
901
 
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
-
2267
902
  // src/admin/helpers/withTooltips.ts
2268
903
  var defaultTooltips = {
2269
904
  title: "The main title displayed on this page.",
@@ -2534,6 +1169,36 @@ __export(admin_app_exports, {
2534
1169
  roleCanAccessNav: () => roleCanAccessNav
2535
1170
  });
2536
1171
 
1172
+ // src/admin-app/components/AdminBreadcrumbs.tsx
1173
+ var import_jsx_runtime = require("react/jsx-runtime");
1174
+ function AdminBreadcrumbs({ items }) {
1175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Breadcrumb", className: "orion-admin-breadcrumbs", children: items.map((item, index) => {
1176
+ const isLast = index === items.length - 1;
1177
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1178
+ 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 }),
1179
+ !isLast ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "orion-admin-breadcrumb-sep", children: "/" }) : null
1180
+ ] }, `${item.label}-${index}`);
1181
+ }) });
1182
+ }
1183
+
1184
+ // src/admin-app/components/AdminPage.tsx
1185
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1186
+ function AdminPage({ title, description, breadcrumbs, actions, children }) {
1187
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page", children: [
1188
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-header", children: [
1189
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AdminBreadcrumbs, { items: breadcrumbs }),
1190
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "orion-admin-page-title-row", children: [
1191
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
1192
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { children: title }),
1193
+ description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: description }) : null
1194
+ ] }),
1195
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-actions", children: actions }) : null
1196
+ ] })
1197
+ ] }),
1198
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "orion-admin-page-content", children })
1199
+ ] });
1200
+ }
1201
+
2537
1202
  // src/admin-app/nestedNavigation.ts
2538
1203
  var normalizeNestedNavItems = (items) => {
2539
1204
  const deduped = [];
@@ -4330,7 +2995,7 @@ var TestimonialsBlock = {
4330
2995
  };
4331
2996
 
4332
2997
  // src/blocks/components/BuilderBlockLabel.tsx
4333
- var import_jsx_runtime7 = require("react/jsx-runtime");
2998
+ var import_jsx_runtime3 = require("react/jsx-runtime");
4334
2999
  var blockTitles = {
4335
3000
  bookingEmbed: "Booking Embed",
4336
3001
  cta: "Call to Action",
@@ -4357,8 +3022,8 @@ function BuilderBlockLabel({ blockType, rowLabel, rowNumber }) {
4357
3022
  const fallbackTitle = blockTitles[blockType] || blockType;
4358
3023
  const title = rowLabel && rowLabel.trim().length > 0 ? rowLabel : fallbackTitle;
4359
3024
  const tag = blockEmojis[blockType] || "Section";
4360
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
4361
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3025
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
3026
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4362
3027
  "span",
4363
3028
  {
4364
3029
  style: {
@@ -4373,7 +3038,7 @@ function BuilderBlockLabel({ blockType, rowLabel, rowNumber }) {
4373
3038
  children: tag
4374
3039
  }
4375
3040
  ),
4376
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: { fontWeight: 700 }, children: [
3041
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontWeight: 700 }, children: [
4377
3042
  typeof rowNumber === "number" ? `${rowNumber + 1}. ` : "",
4378
3043
  title
4379
3044
  ] })
@@ -4920,18 +3585,18 @@ function createStudioRegistry(modules) {
4920
3585
  listNodeTypes: () => [...nodeTypes]
4921
3586
  };
4922
3587
  }
4923
- function validateStudioDocument(document2, modules) {
3588
+ function validateStudioDocument(document, modules) {
4924
3589
  const issues = [];
4925
- if (document2.schemaVersion !== 1) {
3590
+ if (document.schemaVersion !== 1) {
4926
3591
  issues.push(makeIssue("Unsupported schema version", "schemaVersion", "studio.schemaVersion"));
4927
3592
  }
4928
- if (!Array.isArray(document2.nodes)) {
3593
+ if (!Array.isArray(document.nodes)) {
4929
3594
  issues.push(makeIssue("Nodes must be an array", "nodes", "studio.nodes"));
4930
3595
  return issues;
4931
3596
  }
4932
3597
  const registry = createStudioRegistry(modules);
4933
3598
  const nodeIDs = /* @__PURE__ */ new Set();
4934
- document2.nodes.forEach((node, index) => {
3599
+ document.nodes.forEach((node, index) => {
4935
3600
  if (!node.id) {
4936
3601
  issues.push(makeIssue("Node id is required", `nodes.${index}.id`, "studio.node.id"));
4937
3602
  }
@@ -4945,15 +3610,15 @@ function validateStudioDocument(document2, modules) {
4945
3610
  });
4946
3611
  for (const module2 of modules) {
4947
3612
  for (const validate of module2.validators) {
4948
- issues.push(...validate(document2));
3613
+ issues.push(...validate(document));
4949
3614
  }
4950
3615
  }
4951
3616
  return issues;
4952
3617
  }
4953
- function compileStudioDocument(document2, modules) {
4954
- const issues = validateStudioDocument(document2, modules);
3618
+ function compileStudioDocument(document, modules) {
3619
+ const issues = validateStudioDocument(document, modules);
4955
3620
  const compilerEntries = modules.filter((mod) => typeof mod.compiler?.compileNode === "function").map((mod) => mod.compiler?.compileNode);
4956
- const layout = document2.nodes.map((node) => {
3621
+ const layout = document.nodes.map((node) => {
4957
3622
  for (const compileNode of compilerEntries) {
4958
3623
  if (!compileNode) {
4959
3624
  continue;
@@ -5087,6 +3752,7 @@ var defaultBuilderThemeTokens = {
5087
3752
 
5088
3753
  // src/studio-pages/builder/adapters/settingsV2.ts
5089
3754
  var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3755
+ var isTextAlign = (value) => value === "left" || value === "center" || value === "right" || value === "justify";
5090
3756
  var parsePercent = (value) => {
5091
3757
  if (typeof value === "number" && Number.isFinite(value)) {
5092
3758
  return Math.max(0, Math.min(100, value));
@@ -5129,6 +3795,10 @@ var mergeSettings = (defaults, input) => {
5129
3795
  };
5130
3796
  var legacyBlockToV2Settings = (block) => {
5131
3797
  const current = structuredClone(defaultBuilderBlockSettingsV2);
3798
+ if (block.blockType === "hero" && block.variant === "centered") {
3799
+ current.typography.headingAlign = "center";
3800
+ current.typography.bodyAlign = "center";
3801
+ }
5132
3802
  current.layout.contentWidth = block.contentWidth === "narrow" || block.contentWidth === "content" || block.contentWidth === "wide" || block.contentWidth === "full" || block.contentWidth === "inherit" ? block.contentWidth : current.layout.contentWidth;
5133
3803
  current.layout.sectionPaddingX = block.sectionPaddingX === "none" || block.sectionPaddingX === "sm" || block.sectionPaddingX === "md" || block.sectionPaddingX === "lg" || block.sectionPaddingX === "inherit" ? block.sectionPaddingX : current.layout.sectionPaddingX;
5134
3804
  current.layout.sectionPaddingY = block.sectionPaddingY === "none" || block.sectionPaddingY === "sm" || block.sectionPaddingY === "lg" ? block.sectionPaddingY : current.layout.sectionPaddingY;
@@ -5165,15 +3835,26 @@ var legacyBlockToV2Settings = (block) => {
5165
3835
  current.media.positionX = parsePercent(block.imagePositionX ?? block.backgroundImagePositionX);
5166
3836
  current.media.positionY = parsePercent(block.imagePositionY ?? block.backgroundImagePositionY);
5167
3837
  current.media.height = parsePixel(block.imageHeight);
5168
- current.typography.headingAlign = block.textHeadingAlign === "left" || block.textHeadingAlign === "center" || block.textHeadingAlign === "right" || block.textHeadingAlign === "justify" ? block.textHeadingAlign : current.typography.headingAlign;
5169
- current.typography.bodyAlign = block.textBodyAlign === "left" || block.textBodyAlign === "center" || block.textBodyAlign === "right" || block.textBodyAlign === "justify" ? block.textBodyAlign : current.typography.bodyAlign;
3838
+ current.typography.headingAlign = isTextAlign(block.textHeadingAlign) ? block.textHeadingAlign : current.typography.headingAlign;
3839
+ current.typography.bodyAlign = isTextAlign(block.textBodyAlign) ? block.textBodyAlign : current.typography.bodyAlign;
5170
3840
  current.typography.maxTextWidth = block.textMaxWidth === "auto" || block.textMaxWidth === "sm" || block.textMaxWidth === "md" || block.textMaxWidth === "lg" || block.textMaxWidth === "full" ? block.textMaxWidth : current.typography.maxTextWidth;
5171
3841
  current.typography.lineHeightPreset = block.textLineHeightPreset === "tight" || block.textLineHeightPreset === "normal" || block.textLineHeightPreset === "relaxed" ? block.textLineHeightPreset : current.typography.lineHeightPreset;
5172
3842
  current.typography.letterSpacingPreset = block.textLetterSpacingPreset === "tight" || block.textLetterSpacingPreset === "normal" || block.textLetterSpacingPreset === "relaxed" ? block.textLetterSpacingPreset : current.typography.letterSpacingPreset;
5173
3843
  current.advanced.editCopyInPanel = Boolean(block.editCopyInPanel ?? current.advanced.editCopyInPanel);
5174
3844
  current.advanced.customClassName = typeof block.customClassName === "string" ? block.customClassName : current.advanced.customClassName;
5175
3845
  current.advanced.hideOnMobile = Boolean(block.hideOnMobile ?? current.advanced.hideOnMobile);
5176
- return mergeSettings(current, block.settings);
3846
+ const settings = mergeSettings(current, block.settings);
3847
+ if (block.blockType === "hero") {
3848
+ const top = settings.layout.paddingTopPt;
3849
+ const bottom = settings.layout.paddingBottomPt;
3850
+ const hasLegacyLinkedHeroPadding = settings.layout.linkVerticalPadding !== false && (top === null || typeof top === "undefined" || top === 30) && (bottom === null || typeof bottom === "undefined" || bottom === 30 || bottom === 20);
3851
+ if (hasLegacyLinkedHeroPadding) {
3852
+ settings.layout.linkVerticalPadding = false;
3853
+ settings.layout.paddingTopPt = 30;
3854
+ settings.layout.paddingBottomPt = 20;
3855
+ }
3856
+ }
3857
+ return settings;
5177
3858
  };
5178
3859
  var v2SettingsToLegacyBlock = (blockWithSettings) => {
5179
3860
  const settings = legacyBlockToV2Settings(blockWithSettings);
@@ -5290,7 +3971,7 @@ var layoutToStudioDocument = (layout, title, metadata) => {
5290
3971
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5291
3972
  };
5292
3973
  };
5293
- var studioDocumentToLayout = (document2) => document2.nodes.map(
3974
+ var studioDocumentToLayout = (document) => document.nodes.map(
5294
3975
  (node) => migrateBlockToSettingsV2({
5295
3976
  id: node.id,
5296
3977
  blockType: node.type,
@@ -5324,7 +4005,7 @@ function withStudioDocumentLayout(page) {
5324
4005
  }
5325
4006
  return page;
5326
4007
  }
5327
- function normalizePath3(segments) {
4008
+ function normalizePath(segments) {
5328
4009
  if (!segments || segments.length === 0) {
5329
4010
  return "/";
5330
4011
  }
@@ -5381,7 +4062,7 @@ function createPageQueries(getPayloadClient, contentTag = "website-content") {
5381
4062
  { tags: [contentTag] }
5382
4063
  );
5383
4064
  async function getPageBySegments(segments, draft = false) {
5384
- const path2 = normalizePath3(segments);
4065
+ const path2 = normalizePath(segments);
5385
4066
  const payload = await getPayloadClient();
5386
4067
  if (draft) {
5387
4068
  return queryPageByPath(payload, path2, true);
@@ -5622,7 +4303,33 @@ var alignOptions = [
5622
4303
  { label: "Right", value: "right" },
5623
4304
  { label: "Justify", value: "justify" }
5624
4305
  ];
5625
- var layoutFieldSet = [];
4306
+ var layoutFieldSet = [
4307
+ {
4308
+ group: "layout",
4309
+ key: "settings.layout.linkVerticalPadding",
4310
+ label: "Link Top/Bottom Padding",
4311
+ tags: ["spacing", "padding", "vertical"],
4312
+ type: "checkbox"
4313
+ },
4314
+ {
4315
+ group: "layout",
4316
+ key: "settings.layout.paddingTopPt",
4317
+ label: "Top Padding (pt)",
4318
+ max: 240,
4319
+ min: 0,
4320
+ tags: ["spacing", "padding", "top"],
4321
+ type: "number"
4322
+ },
4323
+ {
4324
+ group: "layout",
4325
+ key: "settings.layout.paddingBottomPt",
4326
+ label: "Bottom Padding (pt)",
4327
+ max: 240,
4328
+ min: 0,
4329
+ tags: ["spacing", "padding", "bottom"],
4330
+ type: "number"
4331
+ }
4332
+ ];
5626
4333
  var typographyFieldSet = [
5627
4334
  {
5628
4335
  group: "typography",
@@ -5993,8 +4700,8 @@ var hydrateRelationship = (valueFromStudio, valueFromLayout) => {
5993
4700
  }
5994
4701
  return valueFromLayout;
5995
4702
  };
5996
- var hydrateDocumentWithLayoutRelations = (document2, layout) => {
5997
- const nextNodes = document2.nodes.map((node, index) => {
4703
+ var hydrateDocumentWithLayoutRelations = (document, layout) => {
4704
+ const nextNodes = document.nodes.map((node, index) => {
5998
4705
  const layoutBlock = layout[index];
5999
4706
  if (!isRecord5(layoutBlock)) {
6000
4707
  return node;
@@ -6033,17 +4740,17 @@ var hydrateDocumentWithLayoutRelations = (document2, layout) => {
6033
4740
  };
6034
4741
  });
6035
4742
  return {
6036
- ...document2,
4743
+ ...document,
6037
4744
  nodes: nextNodes
6038
4745
  };
6039
4746
  };
6040
- var normalizeDocument = (document2) => ({
6041
- ...document2,
4747
+ var normalizeDocument = (document) => ({
4748
+ ...document,
6042
4749
  schemaVersion: 1,
6043
4750
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6044
4751
  });
6045
- var normalizeLegacyHeroDefaults = (document2) => {
6046
- const nodes = document2.nodes.map((node) => {
4752
+ var normalizeLegacyHeroDefaults = (document) => {
4753
+ const nodes = document.nodes.map((node) => {
6047
4754
  if (node.type !== "hero" || !isRecord5(node.data)) {
6048
4755
  return node;
6049
4756
  }
@@ -6061,7 +4768,7 @@ var normalizeLegacyHeroDefaults = (document2) => {
6061
4768
  };
6062
4769
  });
6063
4770
  return {
6064
- ...document2,
4771
+ ...document,
6065
4772
  nodes
6066
4773
  };
6067
4774
  };
@@ -6110,9 +4817,9 @@ var createStudioPageService = ({
6110
4817
  title: typeof page.title === "string" ? page.title : "Untitled Page"
6111
4818
  };
6112
4819
  },
6113
- validateStudioDocument: (document2) => validateStudioDocument(document2, modules),
6114
- saveDraft: async (pageID, document2, _metadata) => {
6115
- const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document2));
4820
+ validateStudioDocument: (document) => validateStudioDocument(document, modules),
4821
+ saveDraft: async (pageID, document, _metadata) => {
4822
+ const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document));
6116
4823
  const compileResult = compileStudioDocument(normalizedDocument, modules);
6117
4824
  await payload.update({
6118
4825
  collection: collectionSlug,
@@ -6132,8 +4839,8 @@ var createStudioPageService = ({
6132
4839
  status: "draft"
6133
4840
  };
6134
4841
  },
6135
- publish: async (pageID, document2, _metadata) => {
6136
- const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document2));
4842
+ publish: async (pageID, document, _metadata) => {
4843
+ const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document));
6137
4844
  const compileResult = compileStudioDocument(normalizedDocument, modules);
6138
4845
  assertCanPublish(compileResult.issues);
6139
4846
  await payload.update({
@@ -6413,9 +5120,9 @@ var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
6413
5120
  return data;
6414
5121
  }
6415
5122
  }));
6416
- var validatePageDocument = (document2) => {
5123
+ var validatePageDocument = (document) => {
6417
5124
  const issues = [];
6418
- if (!document2.title || document2.title.trim().length === 0) {
5125
+ if (!document.title || document.title.trim().length === 0) {
6419
5126
  issues.push({
6420
5127
  code: "pages.title.required",
6421
5128
  message: "Page title is required before publishing.",
@@ -6423,7 +5130,7 @@ var validatePageDocument = (document2) => {
6423
5130
  severity: "error"
6424
5131
  });
6425
5132
  }
6426
- if (document2.nodes.length === 0) {
5133
+ if (document.nodes.length === 0) {
6427
5134
  issues.push({
6428
5135
  code: "pages.nodes.required",
6429
5136
  message: "At least one section is required.",
@@ -6431,7 +5138,7 @@ var validatePageDocument = (document2) => {
6431
5138
  severity: "error"
6432
5139
  });
6433
5140
  }
6434
- document2.nodes.forEach((node, index) => {
5141
+ document.nodes.forEach((node, index) => {
6435
5142
  if (node.type === "hero" && typeof node.data.headline !== "string") {
6436
5143
  issues.push({
6437
5144
  code: "pages.hero.headline",