@orion-studios/payload-studio 0.5.0-beta.99 → 0.6.0-beta.1

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 (62) hide show
  1. package/README.md +58 -68
  2. package/dist/admin/client.d.mts +5 -0
  3. package/dist/admin/client.d.ts +5 -0
  4. package/dist/admin/client.js +4440 -806
  5. package/dist/admin/client.mjs +3279 -785
  6. package/dist/admin/index.d.mts +2 -1
  7. package/dist/admin/index.d.ts +2 -1
  8. package/dist/admin/index.js +380 -55
  9. package/dist/admin/index.mjs +2 -1
  10. package/dist/admin-app/client.d.mts +1 -0
  11. package/dist/admin-app/client.d.ts +1 -0
  12. package/dist/admin-app/client.js +285 -109
  13. package/dist/admin-app/client.mjs +59 -871
  14. package/dist/admin-app/index.d.mts +2 -1
  15. package/dist/admin-app/index.d.ts +2 -1
  16. package/dist/admin-app/index.mjs +5 -3
  17. package/dist/admin-app/styles.css +1708 -56
  18. package/dist/admin.css +158 -35
  19. package/dist/blocks/index.js +415 -200
  20. package/dist/blocks/index.mjs +2 -2
  21. package/dist/{chunk-XK3K5GRP.mjs → chunk-JQAHXYAM.mjs} +271 -67
  22. package/dist/chunk-KPIX7OSV.mjs +1051 -0
  23. package/dist/chunk-OQSEJXC4.mjs +166 -0
  24. package/dist/{chunk-XHWQJUX5.mjs → chunk-OTHERBGX.mjs} +3 -3
  25. package/dist/chunk-PF3EBZXF.mjs +326 -0
  26. package/dist/{chunk-74XFAVXU.mjs → chunk-Q2HGC67S.mjs} +371 -55
  27. package/dist/{chunk-XVH5SCBD.mjs → chunk-RKTIFEUY.mjs} +4 -19
  28. package/dist/chunk-W2UOCJDX.mjs +32 -0
  29. package/dist/{chunk-C4J35SPJ.mjs → chunk-XKUTZ7IU.mjs} +257 -452
  30. package/dist/{index-ZbOx4OCF.d.mts → index-52HdVLQq.d.ts} +12 -22
  31. package/dist/index-BMitiKK8.d.ts +435 -0
  32. package/dist/index-Crx_MtPw.d.ts +223 -0
  33. package/dist/index-Cv-6qnrw.d.mts +223 -0
  34. package/dist/{index-ZbOx4OCF.d.ts → index-DEQC3Dwj.d.mts} +12 -22
  35. package/dist/{index-BIwu3qIH.d.mts → index-DWmudwDm.d.mts} +2 -1
  36. package/dist/{index-BIwu3qIH.d.ts → index-DWmudwDm.d.ts} +2 -1
  37. package/dist/index-D_b24Gef.d.mts +435 -0
  38. package/dist/index.d.mts +5 -4
  39. package/dist/index.d.ts +5 -4
  40. package/dist/index.js +1855 -1205
  41. package/dist/index.mjs +10 -8
  42. package/dist/nextjs/index.js +5 -684
  43. package/dist/nextjs/index.mjs +2 -3
  44. package/dist/sitePreviewTypes-BkHCWxNW.d.mts +58 -0
  45. package/dist/sitePreviewTypes-BkHCWxNW.d.ts +58 -0
  46. package/dist/studio/index.d.mts +1 -1
  47. package/dist/studio/index.d.ts +1 -1
  48. package/dist/studio-pages/builder.css +125 -83
  49. package/dist/studio-pages/client.d.mts +58 -1
  50. package/dist/studio-pages/client.d.ts +58 -1
  51. package/dist/studio-pages/client.js +450 -241
  52. package/dist/studio-pages/client.mjs +455 -247
  53. package/dist/studio-pages/index.d.mts +3 -2
  54. package/dist/studio-pages/index.d.ts +3 -2
  55. package/dist/studio-pages/index.js +418 -183
  56. package/dist/studio-pages/index.mjs +15 -6
  57. package/package.json +10 -4
  58. package/dist/chunk-SIL2J5MF.mjs +0 -155
  59. package/dist/index-BnoqmQDP.d.mts +0 -219
  60. package/dist/index-CTpik6fR.d.ts +0 -219
  61. package/dist/index-R7hA134j.d.mts +0 -140
  62. package/dist/index-vjrjy0P4.d.ts +0 -140
package/dist/index.js CHANGED
@@ -79,8 +79,36 @@ var createThemePreferenceField = (defaultTheme = "brand-light") => ({
79
79
  });
80
80
  var themePreferenceField = createThemePreferenceField("brand-light");
81
81
 
82
+ // src/admin-app/routeRegistry.ts
83
+ var adminNavIcons = [
84
+ "dashboard",
85
+ "pages",
86
+ "forms",
87
+ "globals",
88
+ "media",
89
+ "tools",
90
+ "account",
91
+ "analytics"
92
+ ];
93
+ var roleCanAccessNav = (role, item) => {
94
+ if (!item.roles || item.roles.length === 0) {
95
+ return true;
96
+ }
97
+ if (!role) {
98
+ return false;
99
+ }
100
+ return item.roles.includes(role);
101
+ };
102
+ var navItemIsActive = (pathname, item) => {
103
+ if (item.href === "/admin") {
104
+ return pathname === "/admin";
105
+ }
106
+ return item.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
107
+ };
108
+
82
109
  // src/shared/studioSections.ts
83
110
  var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
111
+ var studioIcons = new Set(adminNavIcons);
84
112
  var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
85
113
  var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
86
114
  var normalizePathLikeValue = (value) => {
@@ -121,6 +149,7 @@ var normalizeCard = (value) => {
121
149
  ...typeof value.description === "string" && value.description.trim().length > 0 ? { description: value.description.trim() } : {}
122
150
  };
123
151
  };
152
+ var normalizeIcon = (value) => typeof value === "string" && studioIcons.has(value) ? value : void 0;
124
153
  var resolveStudioSections = (value) => {
125
154
  if (!Array.isArray(value)) {
126
155
  return [];
@@ -146,6 +175,7 @@ var resolveStudioSections = (value) => {
146
175
  label,
147
176
  href,
148
177
  matchPrefixes,
178
+ ...normalizeIcon(entry.icon) ? { icon: normalizeIcon(entry.icon) } : {},
149
179
  ...normalizeRoles(entry.roles) ? { roles: normalizeRoles(entry.roles) } : {},
150
180
  ...normalizeCard(entry.card) ? { card: normalizeCard(entry.card) } : {}
151
181
  });
@@ -211,17 +241,28 @@ function configureAdmin(config) {
211
241
  brandPrimary = "#3b82f6",
212
242
  brandSecondary = "#8b5cf6",
213
243
  defaultTheme = "brand-light",
214
- logoUrl
244
+ logoUrl,
245
+ allowThemePreference = false
215
246
  } = config;
216
247
  const studioEnabled = config.studio?.enabled ?? true;
248
+ const formsEnabled = config.studio?.forms?.enabled ?? false;
249
+ const formsCollectionSlug = config.studio?.forms?.collectionSlug || "forms";
250
+ const formSubmissionsCollectionSlug = config.studio?.forms?.submissionsCollectionSlug || "form-submissions";
251
+ const formUploadsCollectionSlug = config.studio?.forms?.uploadsCollectionSlug || "form-uploads";
217
252
  const pagesCollectionSlug = config.studio?.pages?.collectionSlug || "pages";
253
+ const builderBasePath = config.studio?.pages?.builderBasePath || "/builder";
218
254
  const mediaCollectionSlug = config.studio?.media?.collectionSlug || "media";
219
- const contactFormStudioPath = "/studio-contact-form";
255
+ const globalsBasePath = "/site-globals";
256
+ const pagesBasePath = "/pages";
257
+ const formsBasePath = "/forms";
258
+ const mediaBasePath = "/media";
259
+ const toolsBasePath = "/tools";
260
+ const contactFormStudioPath = "/contact-form";
220
261
  const configuredGlobals = config.studio?.globals || [
221
262
  { slug: "site-settings", label: "Website Settings" },
222
263
  { slug: "header", label: "Header & Navigation" },
223
264
  { slug: "footer", label: "Footer" },
224
- { slug: "contact-form", label: "Contact Form" }
265
+ { slug: "social-media", label: "Social Media" }
225
266
  ];
226
267
  const globals = configuredGlobals.map((global) => {
227
268
  if (global.slug !== "contact-form" || global.href) {
@@ -234,24 +275,78 @@ function configureAdmin(config) {
234
275
  });
235
276
  const studioSections = resolveStudioSections(config.studio?.sections || []);
236
277
  const studioSectionViews = resolveStudioSectionViews(config.studio?.sections || []);
278
+ const sitePreview = config.studio?.sitePreview;
237
279
  let cssPath;
238
280
  const pkgDist = getPkgDistDir();
239
281
  const sourceCssPath = import_path.default.resolve(pkgDist, "admin.css");
240
- if (config.basePath && import_fs.default.existsSync(sourceCssPath)) {
241
- let css = import_fs.default.readFileSync(sourceCssPath, "utf-8");
242
- css = css.replace("--brand-primary: #3b82f6;", `--brand-primary: ${brandPrimary};`);
243
- css = css.replace("--brand-secondary: #8b5cf6;", `--brand-secondary: ${brandSecondary};`);
244
- const genDir = import_path.default.resolve(config.basePath, ".generated");
282
+ const adminAppCssPath = import_path.default.resolve(pkgDist, "admin-app", "styles.css");
283
+ const cssSources = [sourceCssPath, adminAppCssPath].filter((filePath) => import_fs.default.existsSync(filePath));
284
+ if (cssSources.length === 0) {
285
+ cssPath = sourceCssPath;
286
+ } else {
287
+ let css = cssSources.map((filePath) => import_fs.default.readFileSync(filePath, "utf-8")).join("\n\n");
288
+ css = css.replace(
289
+ "--orion-cms-brand-primary-fallback: #3b82f6;",
290
+ `--orion-cms-brand-primary-fallback: ${brandPrimary};`
291
+ );
292
+ css = css.replace(
293
+ "--orion-cms-brand-secondary-fallback: #8b5cf6;",
294
+ `--orion-cms-brand-secondary-fallback: ${brandSecondary};`
295
+ );
296
+ const outputBasePath = config.basePath || process.cwd();
297
+ const genDir = import_path.default.resolve(outputBasePath, ".generated");
245
298
  if (!import_fs.default.existsSync(genDir)) {
246
299
  import_fs.default.mkdirSync(genDir, { recursive: true });
247
300
  }
248
301
  const genPath = import_path.default.resolve(genDir, "admin.css");
249
302
  import_fs.default.writeFileSync(genPath, css);
250
303
  cssPath = genPath;
251
- } else {
252
- cssPath = sourceCssPath;
253
304
  }
254
305
  const clientPath = "@orion-studios/payload-studio/admin/client";
306
+ const studioNavClientProps = {
307
+ brandName,
308
+ formSubmissionsCollectionSlug,
309
+ formsCollectionSlug,
310
+ formsEnabled,
311
+ formUploadsCollectionSlug,
312
+ globalsBasePath,
313
+ globalsExtraMatchPrefixes: [contactFormStudioPath],
314
+ logoUrl,
315
+ mediaCollectionSlug,
316
+ pagesCollectionSlug,
317
+ sections: studioSections
318
+ };
319
+ const studioBackBreadcrumbComponent = {
320
+ exportName: "StudioBackBreadcrumb",
321
+ path: clientPath
322
+ };
323
+ const hasMatchingComponent = (items, exportName) => Array.isArray(items) && items.some(
324
+ (item) => item && typeof item === "object" && item.exportName === exportName && item.path === clientPath
325
+ );
326
+ const appendComponent = (items, component, exportName) => hasMatchingComponent(items, exportName) ? items || [] : [...items || [], component];
327
+ const attachStudioBackBreadcrumbToCollection = (collection) => {
328
+ if (!studioEnabled) {
329
+ return collection;
330
+ }
331
+ const existingBeforeDocumentControls = collection.admin?.components?.edit?.beforeDocumentControls;
332
+ return {
333
+ ...collection,
334
+ admin: {
335
+ ...collection.admin,
336
+ components: {
337
+ ...collection.admin?.components,
338
+ edit: {
339
+ ...collection.admin?.components?.edit,
340
+ beforeDocumentControls: appendComponent(
341
+ existingBeforeDocumentControls,
342
+ studioBackBreadcrumbComponent,
343
+ "StudioBackBreadcrumb"
344
+ )
345
+ }
346
+ }
347
+ }
348
+ };
349
+ };
255
350
  return {
256
351
  admin: {
257
352
  css: cssPath,
@@ -260,15 +355,7 @@ function configureAdmin(config) {
260
355
  Nav: {
261
356
  exportName: "AdminStudioNav",
262
357
  path: clientPath,
263
- clientProps: {
264
- brandName,
265
- logoUrl,
266
- globalsBasePath: "/studio-globals",
267
- globalsExtraMatchPrefixes: [contactFormStudioPath],
268
- mediaCollectionSlug,
269
- pagesCollectionSlug,
270
- sections: studioSections
271
- }
358
+ clientProps: studioNavClientProps
272
359
  }
273
360
  } : {},
274
361
  graphics: {
@@ -294,37 +381,113 @@ function configureAdmin(config) {
294
381
  Component: {
295
382
  exportName: studioEnabled ? "AdminStudioDashboard" : "Dashboard",
296
383
  path: clientPath,
297
- clientProps: {
298
- brandName,
299
- logoUrl,
300
- globalsBasePath: "/studio-globals",
301
- globalsExtraMatchPrefixes: [contactFormStudioPath],
302
- mediaCollectionSlug,
303
- pagesCollectionSlug,
304
- sections: studioSections
305
- }
384
+ clientProps: studioNavClientProps
306
385
  }
307
386
  },
308
387
  ...studioEnabled ? {
309
388
  studioGlobals: {
310
- path: "/studio-globals",
389
+ path: globalsBasePath,
311
390
  Component: {
312
391
  exportName: "AdminStudioGlobalsView",
313
392
  path: clientPath,
314
393
  clientProps: {
394
+ ...studioNavClientProps,
315
395
  globals,
316
- globalsBasePath: "/studio-globals"
396
+ globalsBasePath
397
+ }
398
+ }
399
+ },
400
+ studioPages: {
401
+ path: pagesBasePath,
402
+ Component: {
403
+ exportName: "AdminStudioPagesListView",
404
+ path: clientPath,
405
+ clientProps: {
406
+ ...studioNavClientProps,
407
+ pagesCollectionSlug
408
+ }
409
+ }
410
+ },
411
+ studioPageEditor: {
412
+ path: `${pagesBasePath}/:id`,
413
+ Component: {
414
+ exportName: "AdminStudioPageEditView",
415
+ path: clientPath,
416
+ clientProps: {
417
+ ...studioNavClientProps,
418
+ builderBasePath
419
+ }
420
+ }
421
+ },
422
+ studioPageNew: {
423
+ path: `${pagesBasePath}/new`,
424
+ Component: {
425
+ exportName: "AdminStudioNewPageView",
426
+ path: clientPath,
427
+ clientProps: {
428
+ ...studioNavClientProps,
429
+ pagesCollectionSlug
317
430
  }
318
431
  }
319
432
  },
320
433
  studioContactForm: {
321
- path: "/studio-contact-form",
434
+ path: contactFormStudioPath,
322
435
  Component: {
323
436
  exportName: "AdminStudioContactFormView",
324
437
  path: clientPath,
325
438
  clientProps: {
439
+ ...studioNavClientProps,
326
440
  globalSlug: "contact-form",
327
- globalsBasePath: "/studio-globals"
441
+ globalsBasePath
442
+ }
443
+ }
444
+ },
445
+ ...formsEnabled ? {
446
+ studioForms: {
447
+ path: formsBasePath,
448
+ Component: {
449
+ exportName: "AdminStudioFormsView",
450
+ path: clientPath,
451
+ clientProps: {
452
+ ...studioNavClientProps,
453
+ formsCollectionSlug,
454
+ formSubmissionsCollectionSlug,
455
+ formUploadsCollectionSlug
456
+ }
457
+ }
458
+ }
459
+ } : {},
460
+ studioMedia: {
461
+ path: mediaBasePath,
462
+ Component: {
463
+ exportName: "AdminStudioMediaView",
464
+ path: clientPath,
465
+ clientProps: {
466
+ ...studioNavClientProps,
467
+ mediaCollectionSlug
468
+ }
469
+ }
470
+ },
471
+ studioMediaItem: {
472
+ path: `${mediaBasePath}/:id`,
473
+ Component: {
474
+ exportName: "AdminStudioMediaItemView",
475
+ path: clientPath,
476
+ clientProps: {
477
+ ...studioNavClientProps,
478
+ mediaCollectionSlug
479
+ }
480
+ }
481
+ },
482
+ studioTools: {
483
+ path: toolsBasePath,
484
+ Component: {
485
+ exportName: "AdminStudioToolsView",
486
+ path: clientPath,
487
+ clientProps: {
488
+ ...studioNavClientProps,
489
+ mediaCollectionSlug,
490
+ pagesCollectionSlug
328
491
  }
329
492
  }
330
493
  },
@@ -333,7 +496,13 @@ function configureAdmin(config) {
333
496
  id,
334
497
  {
335
498
  path: view.path,
336
- Component: view.Component
499
+ Component: {
500
+ ...view.Component,
501
+ clientProps: {
502
+ ...studioNavClientProps,
503
+ ...view.Component.clientProps || {}
504
+ }
505
+ }
337
506
  }
338
507
  ])
339
508
  )
@@ -344,19 +513,33 @@ function configureAdmin(config) {
344
513
  exportName: "ThemeProvider",
345
514
  path: clientPath,
346
515
  clientProps: {
516
+ allowThemePreference,
347
517
  defaultTheme
348
518
  }
349
519
  }
350
520
  ],
351
- afterNavLinks: [
521
+ beforeLogin: [
352
522
  {
353
- exportName: "ThemeSwitcher",
523
+ exportName: "AdminLoginIntro",
354
524
  path: clientPath,
355
525
  clientProps: {
356
- defaultTheme
526
+ brandName,
527
+ logoUrl
357
528
  }
358
529
  }
359
- ]
530
+ ],
531
+ ...allowThemePreference ? {
532
+ afterNavLinks: [
533
+ {
534
+ exportName: "ThemeSwitcher",
535
+ path: clientPath,
536
+ clientProps: {
537
+ allowThemePreference,
538
+ defaultTheme
539
+ }
540
+ }
541
+ ]
542
+ } : {}
360
543
  },
361
544
  meta: {
362
545
  titleSuffix: ` \u2014 ${brandName}`
@@ -371,11 +554,77 @@ function configureAdmin(config) {
371
554
  const hasThemePreference = existingFields.some(
372
555
  (field) => typeof field === "object" && field !== null && "name" in field && field.name === "themePreference"
373
556
  );
374
- return {
557
+ const nextCollection = {
375
558
  ...usersCollection,
376
- fields: hasThemePreference ? existingFields : [...existingFields, createThemePreferenceField(defaultTheme)]
559
+ fields: !allowThemePreference || hasThemePreference ? existingFields : [...existingFields, createThemePreferenceField(defaultTheme)]
560
+ };
561
+ return attachStudioBackBreadcrumbToCollection(nextCollection);
562
+ },
563
+ wrapPagesCollection(pagesCollection) {
564
+ if (!studioEnabled) {
565
+ return pagesCollection;
566
+ }
567
+ const collectionWithBreadcrumb = attachStudioBackBreadcrumbToCollection(pagesCollection);
568
+ const existingEditMenuItems = collectionWithBreadcrumb.admin?.components?.edit?.editMenuItems;
569
+ const existingViews = collectionWithBreadcrumb.admin?.components?.views;
570
+ const existingEditViews = existingViews?.edit;
571
+ const hasCustomEditView = Boolean(
572
+ existingEditViews?.root || existingEditViews?.default && typeof existingEditViews.default === "object" && existingEditViews.default.Component
573
+ );
574
+ return {
575
+ ...collectionWithBreadcrumb,
576
+ admin: {
577
+ ...collectionWithBreadcrumb.admin,
578
+ components: {
579
+ ...collectionWithBreadcrumb.admin?.components,
580
+ edit: {
581
+ ...collectionWithBreadcrumb.admin?.components?.edit,
582
+ editMenuItems: appendComponent(
583
+ existingEditMenuItems,
584
+ {
585
+ exportName: "OpenInStudioMenuItem",
586
+ path: clientPath,
587
+ clientProps: {
588
+ pagesPathBase: pagesBasePath
589
+ }
590
+ },
591
+ "OpenInStudioMenuItem"
592
+ )
593
+ },
594
+ views: {
595
+ ...existingViews,
596
+ ...hasCustomEditView ? {} : {
597
+ edit: {
598
+ ...existingEditViews,
599
+ default: {
600
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
601
+ Component: {
602
+ exportName: "PageEditRedirectToStudio",
603
+ path: clientPath,
604
+ clientProps: {
605
+ pagesPathBase: pagesBasePath
606
+ }
607
+ }
608
+ }
609
+ }
610
+ }
611
+ }
612
+ }
613
+ }
377
614
  };
378
615
  },
616
+ wrapMediaCollection(mediaCollection) {
617
+ return attachStudioBackBreadcrumbToCollection(mediaCollection);
618
+ },
619
+ wrapFormsCollection(formsCollection) {
620
+ return attachStudioBackBreadcrumbToCollection(formsCollection);
621
+ },
622
+ wrapFormSubmissionsCollection(formSubmissionsCollection) {
623
+ return attachStudioBackBreadcrumbToCollection(formSubmissionsCollection);
624
+ },
625
+ wrapFormUploadsCollection(formUploadsCollection) {
626
+ return attachStudioBackBreadcrumbToCollection(formUploadsCollection);
627
+ },
379
628
  wrapGlobals(globals2) {
380
629
  const labelMap = {
381
630
  header: { group: "Site Design", label: "Header & Navigation" },
@@ -387,25 +636,110 @@ function configureAdmin(config) {
387
636
  return globals2.map((global) => {
388
637
  const mapping = labelMap[global.slug];
389
638
  if (!mapping) return global;
639
+ const shouldAttachSiteSettingsEditView = studioEnabled && global.slug === "site-settings";
640
+ const shouldAttachSocialMediaEditView = studioEnabled && global.slug === "social-media";
390
641
  const shouldAttachContactFormRedirect = studioEnabled && global.slug === "contact-form";
642
+ const shouldAttachHeaderEditView = studioEnabled && global.slug === "header";
643
+ const shouldAttachFooterEditView = studioEnabled && global.slug === "footer";
391
644
  const existingViews = global.admin?.components?.views;
392
645
  const existingEditViews = existingViews?.edit;
393
- const hasCustomContactFormEditView = Boolean(
646
+ const hasCustomEditView = Boolean(
394
647
  existingEditViews?.root || existingEditViews?.default && typeof existingEditViews.default === "object" && existingEditViews.default.Component
395
648
  );
396
- const contactFormEditViews = shouldAttachContactFormRedirect && !hasCustomContactFormEditView ? {
397
- ...existingEditViews,
398
- default: {
399
- ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
400
- Component: {
401
- exportName: "StudioContactFormRedirect",
402
- path: clientPath,
403
- clientProps: {
404
- studioContactFormPath: contactFormStudioPath
649
+ const nextEditViews = (() => {
650
+ if (shouldAttachSiteSettingsEditView && !hasCustomEditView) {
651
+ return {
652
+ ...existingEditViews,
653
+ default: {
654
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
655
+ Component: {
656
+ exportName: "AdminStudioSiteSettingsGlobalView",
657
+ path: clientPath,
658
+ clientProps: {
659
+ ...studioNavClientProps,
660
+ globalSlug: global.slug,
661
+ mediaCollectionSlug
662
+ }
663
+ }
405
664
  }
406
- }
665
+ };
407
666
  }
408
- } : existingEditViews;
667
+ if (shouldAttachSocialMediaEditView && !hasCustomEditView) {
668
+ return {
669
+ ...existingEditViews,
670
+ default: {
671
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
672
+ Component: {
673
+ exportName: "AdminStudioSocialMediaGlobalView",
674
+ path: clientPath,
675
+ clientProps: {
676
+ ...studioNavClientProps,
677
+ globalSlug: global.slug
678
+ }
679
+ }
680
+ }
681
+ };
682
+ }
683
+ if (shouldAttachHeaderEditView && !hasCustomEditView) {
684
+ return {
685
+ ...existingEditViews,
686
+ default: {
687
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
688
+ Component: {
689
+ exportName: "AdminStudioHeaderGlobalView",
690
+ path: clientPath,
691
+ clientProps: {
692
+ ...studioNavClientProps,
693
+ actionHref: sitePreview?.header?.actionHref,
694
+ actionLabel: sitePreview?.header?.actionLabel,
695
+ globalSlug: global.slug,
696
+ locationSummary: sitePreview?.locationSummary,
697
+ pagesCollectionSlug
698
+ }
699
+ }
700
+ }
701
+ };
702
+ }
703
+ if (shouldAttachFooterEditView && !hasCustomEditView) {
704
+ return {
705
+ ...existingEditViews,
706
+ default: {
707
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
708
+ Component: {
709
+ exportName: "AdminStudioFooterGlobalView",
710
+ path: clientPath,
711
+ clientProps: {
712
+ ...studioNavClientProps,
713
+ builtByHref: sitePreview?.footer?.builtByHref,
714
+ builtByLabel: sitePreview?.footer?.builtByLabel,
715
+ description: sitePreview?.footer?.description,
716
+ footerCategories: sitePreview?.footer?.footerCategories,
717
+ footerLinks: sitePreview?.footer?.footerLinks,
718
+ globalSlug: global.slug,
719
+ locationSummary: sitePreview?.locationSummary
720
+ }
721
+ }
722
+ }
723
+ };
724
+ }
725
+ if (shouldAttachContactFormRedirect && !hasCustomEditView) {
726
+ return {
727
+ ...existingEditViews,
728
+ default: {
729
+ ...typeof existingEditViews?.default === "object" ? existingEditViews.default : {},
730
+ Component: {
731
+ exportName: "StudioContactFormRedirect",
732
+ path: clientPath,
733
+ clientProps: {
734
+ studioContactFormPath: contactFormStudioPath
735
+ }
736
+ }
737
+ }
738
+ };
739
+ }
740
+ return existingEditViews;
741
+ })();
742
+ const existingBeforeDocumentControls = global.admin?.components?.elements?.beforeDocumentControls;
409
743
  return {
410
744
  ...global,
411
745
  admin: {
@@ -413,12 +747,18 @@ function configureAdmin(config) {
413
747
  group: mapping.group,
414
748
  components: {
415
749
  ...global.admin?.components,
416
- ...shouldAttachContactFormRedirect ? {
750
+ elements: {
751
+ ...global.admin?.components?.elements,
752
+ beforeDocumentControls: studioEnabled ? appendComponent(
753
+ existingBeforeDocumentControls,
754
+ studioBackBreadcrumbComponent,
755
+ "StudioBackBreadcrumb"
756
+ ) : existingBeforeDocumentControls
757
+ },
758
+ ...nextEditViews ? {
417
759
  views: {
418
760
  ...existingViews,
419
- ...contactFormEditViews ? {
420
- edit: contactFormEditViews
421
- } : {}
761
+ edit: nextEditViews
422
762
  }
423
763
  } : {}
424
764
  }
@@ -885,23 +1225,6 @@ var parseAdminHeaderNavFromForm = (formData, pageOptions, maxRows = 24) => {
885
1225
  return [];
886
1226
  };
887
1227
 
888
- // src/admin-app/routeRegistry.ts
889
- var roleCanAccessNav = (role, item) => {
890
- if (!item.roles || item.roles.length === 0) {
891
- return true;
892
- }
893
- if (!role) {
894
- return false;
895
- }
896
- return item.roles.includes(role);
897
- };
898
- var navItemIsActive = (pathname, item) => {
899
- if (item.href === "/admin") {
900
- return pathname === "/admin";
901
- }
902
- return item.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
903
- };
904
-
905
1228
  // src/blocks/index.ts
906
1229
  var blocks_exports = {};
907
1230
  __export(blocks_exports, {
@@ -938,208 +1261,233 @@ var sectionStyleDefaults = {
938
1261
  contentGradientAngle: "135",
939
1262
  contentGradientFrom: "#ffffff",
940
1263
  contentGradientPreset: "none",
941
- contentGradientTo: "#f4f6f2",
1264
+ contentGradientTo: "#f4f6f8",
942
1265
  contentWidth: "inherit",
943
1266
  sectionPaddingX: "inherit",
944
1267
  sectionBackgroundColor: "#ffffff",
945
1268
  sectionBackgroundMode: "none",
946
1269
  sectionGradientAngle: "135",
947
- sectionGradientFrom: "#124a37",
948
- sectionGradientPreset: "forest",
949
- sectionGradientTo: "#1f684f",
1270
+ sectionGradientFrom: "#334b63",
1271
+ sectionGradientPreset: "slate",
1272
+ sectionGradientTo: "#496582",
950
1273
  sectionPaddingY: "md",
951
1274
  sectionWidth: "content"
952
1275
  };
953
- var sectionStyleFields = () => [
954
- {
955
- name: "sectionWidth",
956
- type: "select",
957
- defaultValue: sectionStyleDefaults.sectionWidth,
958
- options: [
959
- { label: "Content", value: "content" },
960
- { label: "Wide", value: "wide" },
961
- { label: "Full", value: "full" }
962
- ]
963
- },
964
- {
965
- name: "contentWidth",
966
- type: "select",
967
- defaultValue: sectionStyleDefaults.contentWidth,
968
- options: [
969
- { label: "Inherit", value: "inherit" },
970
- { label: "Narrow", value: "narrow" },
971
- { label: "Content", value: "content" },
972
- { label: "Wide", value: "wide" },
973
- { label: "Full", value: "full" }
974
- ]
975
- },
976
- {
977
- name: "sectionPaddingY",
978
- type: "select",
979
- defaultValue: sectionStyleDefaults.sectionPaddingY,
980
- options: [
981
- { label: "None", value: "none" },
982
- { label: "Small", value: "sm" },
983
- { label: "Medium", value: "md" },
984
- { label: "Large", value: "lg" }
985
- ]
986
- },
987
- {
988
- name: "sectionPaddingX",
989
- type: "select",
990
- defaultValue: sectionStyleDefaults.sectionPaddingX,
991
- options: [
992
- { label: "Inherit", value: "inherit" },
993
- { label: "None", value: "none" },
994
- { label: "Small", value: "sm" },
995
- { label: "Medium", value: "md" },
996
- { label: "Large", value: "lg" }
997
- ]
998
- },
999
- {
1000
- name: "sectionBackgroundMode",
1001
- type: "select",
1002
- defaultValue: sectionStyleDefaults.sectionBackgroundMode,
1003
- options: [
1004
- { label: "None", value: "none" },
1005
- { label: "Color", value: "color" },
1006
- { label: "Gradient", value: "gradient" }
1007
- ]
1008
- },
1009
- {
1010
- name: "sectionBackgroundColor",
1011
- type: "text",
1012
- defaultValue: sectionStyleDefaults.sectionBackgroundColor
1013
- },
1014
- {
1015
- name: "sectionGradientPreset",
1016
- type: "select",
1017
- defaultValue: sectionStyleDefaults.sectionGradientPreset,
1018
- options: [
1019
- { label: "None", value: "none" },
1020
- { label: "Forest", value: "forest" },
1021
- { label: "Moss", value: "moss" },
1022
- { label: "Cream", value: "cream" },
1023
- { label: "Slate", value: "slate" }
1024
- ]
1025
- },
1026
- {
1027
- name: "sectionGradientFrom",
1028
- type: "text",
1029
- defaultValue: sectionStyleDefaults.sectionGradientFrom
1030
- },
1031
- {
1032
- name: "sectionGradientTo",
1033
- type: "text",
1034
- defaultValue: sectionStyleDefaults.sectionGradientTo
1035
- },
1036
- {
1037
- name: "sectionGradientAngle",
1038
- type: "text",
1039
- defaultValue: sectionStyleDefaults.sectionGradientAngle
1040
- },
1041
- {
1042
- name: "contentBackgroundMode",
1043
- type: "select",
1044
- defaultValue: sectionStyleDefaults.contentBackgroundMode,
1045
- options: [
1046
- { label: "None", value: "none" },
1047
- { label: "Color", value: "color" },
1048
- { label: "Gradient", value: "gradient" }
1049
- ]
1050
- },
1051
- {
1052
- name: "contentBackgroundColor",
1053
- type: "text",
1054
- defaultValue: sectionStyleDefaults.contentBackgroundColor
1055
- },
1056
- {
1057
- name: "contentGradientPreset",
1058
- type: "select",
1059
- defaultValue: sectionStyleDefaults.contentGradientPreset,
1060
- options: [
1061
- { label: "None", value: "none" },
1062
- { label: "Cloud", value: "cloud" },
1063
- { label: "Sand", value: "sand" },
1064
- { label: "Mint", value: "mint" },
1065
- { label: "Night", value: "night" }
1066
- ]
1067
- },
1068
- {
1069
- name: "contentGradientFrom",
1070
- type: "text",
1071
- defaultValue: sectionStyleDefaults.contentGradientFrom
1072
- },
1073
- {
1074
- name: "contentGradientTo",
1075
- type: "text",
1076
- defaultValue: sectionStyleDefaults.contentGradientTo
1077
- },
1078
- {
1079
- name: "contentGradientAngle",
1080
- type: "text",
1081
- defaultValue: sectionStyleDefaults.contentGradientAngle
1082
- }
1083
- ];
1084
-
1085
- // src/blocks/blocks/BeforeAfter.ts
1086
- var BeforeAfterBlock = {
1087
- slug: "beforeAfter",
1088
- imageURL: "/images/project-before-2.svg",
1089
- imageAltText: "Before and after section preview",
1276
+ var hideFromCMS = (field) => ({
1277
+ ...field,
1090
1278
  admin: {
1091
- components: {
1092
- Label: builderBlockLabelComponent
1093
- }
1094
- },
1095
- labels: {
1096
- singular: "Before / After",
1097
- plural: "Before / After"
1098
- },
1099
- fields: [
1279
+ ...field.admin || {},
1280
+ hidden: true
1281
+ }
1282
+ });
1283
+ var sectionStyleFields = () => {
1284
+ const fields = [
1100
1285
  {
1101
- name: "title",
1102
- type: "text",
1103
- required: true
1286
+ name: "sectionWidth",
1287
+ type: "select",
1288
+ defaultValue: sectionStyleDefaults.sectionWidth,
1289
+ options: [
1290
+ { label: "Content", value: "content" },
1291
+ { label: "Wide", value: "wide" },
1292
+ { label: "Full", value: "full" }
1293
+ ]
1104
1294
  },
1105
1295
  {
1106
- name: "subtitle",
1107
- type: "textarea"
1296
+ name: "contentWidth",
1297
+ type: "select",
1298
+ defaultValue: sectionStyleDefaults.contentWidth,
1299
+ options: [
1300
+ { label: "Inherit", value: "inherit" },
1301
+ { label: "Narrow", value: "narrow" },
1302
+ { label: "Content", value: "content" },
1303
+ { label: "Wide", value: "wide" },
1304
+ { label: "Full", value: "full" }
1305
+ ]
1108
1306
  },
1109
1307
  {
1110
- name: "itemsPerRow",
1111
- type: "number",
1112
- defaultValue: 2,
1113
- min: 1,
1114
- max: 4,
1115
- admin: {
1116
- description: "How many project cards to show per row on desktop.",
1117
- step: 1
1118
- }
1308
+ name: "sectionPaddingY",
1309
+ type: "select",
1310
+ defaultValue: sectionStyleDefaults.sectionPaddingY,
1311
+ options: [
1312
+ { label: "None", value: "none" },
1313
+ { label: "Small", value: "sm" },
1314
+ { label: "Medium", value: "md" },
1315
+ { label: "Large", value: "lg" }
1316
+ ]
1119
1317
  },
1120
1318
  {
1121
- name: "items",
1122
- type: "array",
1123
- minRows: 1,
1124
- maxRows: 30,
1125
- fields: [
1126
- {
1127
- name: "label",
1128
- type: "text",
1129
- required: true
1130
- },
1131
- {
1132
- name: "beforeMedia",
1133
- type: "upload",
1134
- relationTo: "media",
1135
- required: false
1136
- },
1137
- {
1138
- name: "afterMedia",
1139
- type: "upload",
1319
+ name: "sectionPaddingX",
1320
+ type: "select",
1321
+ defaultValue: sectionStyleDefaults.sectionPaddingX,
1322
+ options: [
1323
+ { label: "Inherit", value: "inherit" },
1324
+ { label: "None", value: "none" },
1325
+ { label: "Small", value: "sm" },
1326
+ { label: "Medium", value: "md" },
1327
+ { label: "Large", value: "lg" }
1328
+ ]
1329
+ },
1330
+ {
1331
+ name: "sectionBackgroundMode",
1332
+ type: "select",
1333
+ defaultValue: sectionStyleDefaults.sectionBackgroundMode,
1334
+ options: [
1335
+ { label: "None", value: "none" },
1336
+ { label: "Color", value: "color" },
1337
+ { label: "Gradient", value: "gradient" }
1338
+ ]
1339
+ },
1340
+ {
1341
+ name: "sectionBackgroundColor",
1342
+ type: "text",
1343
+ defaultValue: sectionStyleDefaults.sectionBackgroundColor
1344
+ },
1345
+ {
1346
+ name: "sectionGradientPreset",
1347
+ type: "select",
1348
+ defaultValue: sectionStyleDefaults.sectionGradientPreset,
1349
+ options: [
1350
+ { label: "None", value: "none" },
1351
+ { label: "Brand", value: "brand" },
1352
+ { label: "Forest", value: "forest" },
1353
+ { label: "Moss", value: "moss" },
1354
+ { label: "Cream", value: "cream" },
1355
+ { label: "Slate", value: "slate" }
1356
+ ]
1357
+ },
1358
+ {
1359
+ name: "sectionGradientFrom",
1360
+ type: "text",
1361
+ defaultValue: sectionStyleDefaults.sectionGradientFrom
1362
+ },
1363
+ {
1364
+ name: "sectionGradientTo",
1365
+ type: "text",
1366
+ defaultValue: sectionStyleDefaults.sectionGradientTo
1367
+ },
1368
+ {
1369
+ name: "sectionGradientAngle",
1370
+ type: "text",
1371
+ defaultValue: sectionStyleDefaults.sectionGradientAngle
1372
+ },
1373
+ {
1374
+ name: "contentBackgroundMode",
1375
+ type: "select",
1376
+ defaultValue: sectionStyleDefaults.contentBackgroundMode,
1377
+ options: [
1378
+ { label: "None", value: "none" },
1379
+ { label: "Color", value: "color" },
1380
+ { label: "Gradient", value: "gradient" }
1381
+ ]
1382
+ },
1383
+ {
1384
+ name: "contentBackgroundColor",
1385
+ type: "text",
1386
+ defaultValue: sectionStyleDefaults.contentBackgroundColor
1387
+ },
1388
+ {
1389
+ name: "contentGradientPreset",
1390
+ type: "select",
1391
+ defaultValue: sectionStyleDefaults.contentGradientPreset,
1392
+ options: [
1393
+ { label: "None", value: "none" },
1394
+ { label: "Cloud", value: "cloud" },
1395
+ { label: "Sand", value: "sand" },
1396
+ { label: "Mint", value: "mint" },
1397
+ { label: "Night", value: "night" }
1398
+ ]
1399
+ },
1400
+ {
1401
+ name: "contentGradientFrom",
1402
+ type: "text",
1403
+ defaultValue: sectionStyleDefaults.contentGradientFrom
1404
+ },
1405
+ {
1406
+ name: "contentGradientTo",
1407
+ type: "text",
1408
+ defaultValue: sectionStyleDefaults.contentGradientTo
1409
+ },
1410
+ {
1411
+ name: "contentGradientAngle",
1412
+ type: "text",
1413
+ defaultValue: sectionStyleDefaults.contentGradientAngle
1414
+ }
1415
+ ];
1416
+ return fields.map(hideFromCMS);
1417
+ };
1418
+
1419
+ // src/blocks/blocks/BeforeAfter.ts
1420
+ var BeforeAfterBlock = {
1421
+ slug: "beforeAfter",
1422
+ imageURL: "/images/project-before-2.svg",
1423
+ imageAltText: "Before and after section preview",
1424
+ admin: {
1425
+ components: {
1426
+ Label: builderBlockLabelComponent
1427
+ }
1428
+ },
1429
+ labels: {
1430
+ singular: "Before / After",
1431
+ plural: "Before / After"
1432
+ },
1433
+ fields: [
1434
+ {
1435
+ name: "title",
1436
+ type: "text",
1437
+ required: true
1438
+ },
1439
+ {
1440
+ name: "subtitle",
1441
+ type: "textarea"
1442
+ },
1443
+ {
1444
+ name: "itemsPerRow",
1445
+ type: "number",
1446
+ defaultValue: 2,
1447
+ min: 1,
1448
+ max: 4,
1449
+ admin: {
1450
+ description: "How many project cards to show per row on desktop.",
1451
+ step: 1
1452
+ }
1453
+ },
1454
+ {
1455
+ name: "items",
1456
+ type: "array",
1457
+ minRows: 1,
1458
+ maxRows: 30,
1459
+ fields: [
1460
+ {
1461
+ name: "label",
1462
+ type: "text",
1463
+ required: true
1464
+ },
1465
+ {
1466
+ name: "beforeMedia",
1467
+ type: "upload",
1468
+ relationTo: "media",
1469
+ required: false
1470
+ },
1471
+ {
1472
+ name: "beforeImageURL",
1473
+ type: "text",
1474
+ admin: {
1475
+ description: "Optional direct URL for the before image when using an external asset."
1476
+ }
1477
+ },
1478
+ {
1479
+ name: "afterMedia",
1480
+ type: "upload",
1140
1481
  relationTo: "media",
1141
1482
  required: false
1142
1483
  },
1484
+ {
1485
+ name: "afterImageURL",
1486
+ type: "text",
1487
+ admin: {
1488
+ description: "Optional direct URL for the after image when using an external asset."
1489
+ }
1490
+ },
1143
1491
  {
1144
1492
  name: "imageHeight",
1145
1493
  type: "number",
@@ -1295,6 +1643,10 @@ var CtaBlock = {
1295
1643
  name: "description",
1296
1644
  type: "textarea"
1297
1645
  },
1646
+ {
1647
+ name: "eyebrow",
1648
+ type: "text"
1649
+ },
1298
1650
  {
1299
1651
  name: "buttonLabel",
1300
1652
  type: "text"
@@ -1325,9 +1677,33 @@ var CtaBlock = {
1325
1677
  name: "backgroundColor",
1326
1678
  type: "text",
1327
1679
  admin: {
1328
- description: "Optional background color override for the CTA strip (example: #124a37)."
1680
+ description: "Optional background color override for the CTA strip (example: #334b63)."
1681
+ }
1682
+ },
1683
+ {
1684
+ name: "media",
1685
+ type: "upload",
1686
+ relationTo: "media",
1687
+ required: false
1688
+ },
1689
+ {
1690
+ name: "imageURL",
1691
+ type: "text",
1692
+ admin: {
1693
+ description: "Optional direct image URL when this CTA should use an external image."
1329
1694
  }
1330
1695
  },
1696
+ {
1697
+ name: "bullets",
1698
+ type: "array",
1699
+ fields: [
1700
+ {
1701
+ name: "label",
1702
+ type: "text",
1703
+ required: true
1704
+ }
1705
+ ]
1706
+ },
1331
1707
  {
1332
1708
  name: "settings",
1333
1709
  type: "json",
@@ -1355,6 +1731,10 @@ var FaqBlock = {
1355
1731
  plural: "FAQs"
1356
1732
  },
1357
1733
  fields: [
1734
+ {
1735
+ name: "eyebrow",
1736
+ type: "text"
1737
+ },
1358
1738
  {
1359
1739
  name: "title",
1360
1740
  type: "text",
@@ -1412,10 +1792,17 @@ var FeatureGridBlock = {
1412
1792
  plural: "Feature Grids"
1413
1793
  },
1414
1794
  fields: [
1795
+ {
1796
+ name: "eyebrow",
1797
+ type: "text"
1798
+ },
1415
1799
  {
1416
1800
  name: "title",
1417
- type: "text",
1418
- required: true
1801
+ type: "text"
1802
+ },
1803
+ {
1804
+ name: "subtitle",
1805
+ type: "textarea"
1419
1806
  },
1420
1807
  {
1421
1808
  name: "itemsPerRow",
@@ -1443,6 +1830,16 @@ var FeatureGridBlock = {
1443
1830
  name: "description",
1444
1831
  type: "textarea"
1445
1832
  },
1833
+ {
1834
+ name: "tone",
1835
+ type: "select",
1836
+ defaultValue: "warm",
1837
+ options: [
1838
+ { label: "Warm", value: "warm" },
1839
+ { label: "Cool", value: "cool" },
1840
+ { label: "Neutral", value: "neutral" }
1841
+ ]
1842
+ },
1446
1843
  {
1447
1844
  name: "iconType",
1448
1845
  type: "select",
@@ -1488,6 +1885,39 @@ var FeatureGridBlock = {
1488
1885
  relationTo: "media",
1489
1886
  required: false
1490
1887
  },
1888
+ {
1889
+ name: "imageURL",
1890
+ type: "text",
1891
+ admin: {
1892
+ description: "Optional direct image URL when this item should use an external image."
1893
+ }
1894
+ },
1895
+ {
1896
+ name: "embedURL",
1897
+ type: "text",
1898
+ admin: {
1899
+ description: "Optional iframe/embed URL for items like maps or other embedded content."
1900
+ }
1901
+ },
1902
+ {
1903
+ name: "buttonLabel",
1904
+ type: "text"
1905
+ },
1906
+ {
1907
+ name: "buttonHref",
1908
+ type: "text"
1909
+ },
1910
+ {
1911
+ name: "bullets",
1912
+ type: "array",
1913
+ fields: [
1914
+ {
1915
+ name: "label",
1916
+ type: "text",
1917
+ required: true
1918
+ }
1919
+ ]
1920
+ },
1491
1921
  {
1492
1922
  name: "imageHeight",
1493
1923
  type: "number",
@@ -1570,6 +2000,22 @@ var FeatureGridBlock = {
1570
2000
  {
1571
2001
  label: "Highlight",
1572
2002
  value: "highlight"
2003
+ },
2004
+ {
2005
+ label: "Split List",
2006
+ value: "splitList"
2007
+ },
2008
+ {
2009
+ label: "Panels",
2010
+ value: "panels"
2011
+ },
2012
+ {
2013
+ label: "Catalog",
2014
+ value: "catalog"
2015
+ },
2016
+ {
2017
+ label: "Contact Split",
2018
+ value: "contact"
1573
2019
  }
1574
2020
  ]
1575
2021
  },
@@ -1577,7 +2023,7 @@ var FeatureGridBlock = {
1577
2023
  name: "backgroundColor",
1578
2024
  type: "text",
1579
2025
  admin: {
1580
- description: "Optional background color override when using the Highlight variant (example: #1f684f)."
2026
+ description: "Optional background color override when using the Highlight variant (example: #334b63)."
1581
2027
  }
1582
2028
  },
1583
2029
  {
@@ -1607,6 +2053,10 @@ var FormEmbedBlock = {
1607
2053
  plural: "Form Embeds"
1608
2054
  },
1609
2055
  fields: [
2056
+ {
2057
+ name: "eyebrow",
2058
+ type: "text"
2059
+ },
1610
2060
  {
1611
2061
  name: "title",
1612
2062
  type: "text"
@@ -1615,6 +2065,10 @@ var FormEmbedBlock = {
1615
2065
  name: "description",
1616
2066
  type: "textarea"
1617
2067
  },
2068
+ {
2069
+ name: "submitLabel",
2070
+ type: "text"
2071
+ },
1618
2072
  {
1619
2073
  name: "formType",
1620
2074
  type: "select",
@@ -1696,6 +2150,13 @@ var HeroBlock = {
1696
2150
  type: "upload",
1697
2151
  relationTo: "media"
1698
2152
  },
2153
+ {
2154
+ name: "backgroundImageURL",
2155
+ type: "text",
2156
+ admin: {
2157
+ description: "Optional direct image URL when the hero should use an external image source."
2158
+ }
2159
+ },
1699
2160
  {
1700
2161
  name: "backgroundImageFit",
1701
2162
  type: "select",
@@ -1741,7 +2202,7 @@ var HeroBlock = {
1741
2202
  name: "backgroundColor",
1742
2203
  type: "text",
1743
2204
  admin: {
1744
- description: "Optional background color override (example: #124a37)."
2205
+ description: "Optional background color override (example: #334b63)."
1745
2206
  }
1746
2207
  },
1747
2208
  {
@@ -1779,14 +2240,14 @@ var HeroBlock = {
1779
2240
  name: "backgroundOverlayGradientFrom",
1780
2241
  type: "text",
1781
2242
  admin: {
1782
- description: "Gradient overlay start color (example: #0d4a37). Used when Overlay Mode is Gradient."
2243
+ description: "Gradient overlay start color (example: #334b63). Used when Overlay Mode is Gradient."
1783
2244
  }
1784
2245
  },
1785
2246
  {
1786
2247
  name: "backgroundOverlayGradientTo",
1787
2248
  type: "text",
1788
2249
  admin: {
1789
- description: "Gradient overlay end color (example: #1f684f). Used when Overlay Mode is Gradient."
2250
+ description: "Gradient overlay end color (example: #496582). Used when Overlay Mode is Gradient."
1790
2251
  }
1791
2252
  },
1792
2253
  {
@@ -1941,6 +2402,13 @@ var LogoWallBlock = {
1941
2402
  relationTo: "media",
1942
2403
  required: false
1943
2404
  },
2405
+ {
2406
+ name: "imageURL",
2407
+ type: "text",
2408
+ admin: {
2409
+ description: "Optional direct image URL for this logo when using an external asset."
2410
+ }
2411
+ },
1944
2412
  {
1945
2413
  name: "imageHeight",
1946
2414
  type: "number",
@@ -2046,7 +2514,14 @@ var MediaBlock = {
2046
2514
  name: "image",
2047
2515
  type: "upload",
2048
2516
  relationTo: "media",
2049
- required: true
2517
+ required: false
2518
+ },
2519
+ {
2520
+ name: "imageURL",
2521
+ type: "text",
2522
+ admin: {
2523
+ description: "Optional direct image URL when this section should use an external image."
2524
+ }
2050
2525
  },
2051
2526
  {
2052
2527
  name: "caption",
@@ -2124,6 +2599,21 @@ var RichTextBlock = {
2124
2599
  plural: "Rich Text Sections"
2125
2600
  },
2126
2601
  fields: [
2602
+ {
2603
+ name: "variant",
2604
+ type: "select",
2605
+ defaultValue: "default",
2606
+ options: [
2607
+ {
2608
+ label: "Default",
2609
+ value: "default"
2610
+ },
2611
+ {
2612
+ label: "Quote Banner",
2613
+ value: "quoteBanner"
2614
+ }
2615
+ ]
2616
+ },
2127
2617
  {
2128
2618
  name: "title",
2129
2619
  type: "text"
@@ -2134,33 +2624,81 @@ var RichTextBlock = {
2134
2624
  required: true
2135
2625
  },
2136
2626
  {
2137
- name: "width",
2138
- type: "select",
2139
- defaultValue: "normal",
2140
- options: [
2627
+ name: "statsItems",
2628
+ type: "array",
2629
+ fields: [
2141
2630
  {
2142
- label: "Normal",
2143
- value: "normal"
2631
+ name: "value",
2632
+ type: "text",
2633
+ required: true
2144
2634
  },
2145
2635
  {
2146
- label: "Narrow",
2147
- value: "narrow"
2636
+ name: "label",
2637
+ type: "text",
2638
+ required: true
2148
2639
  }
2149
2640
  ]
2150
2641
  },
2151
2642
  {
2152
- name: "settings",
2153
- type: "json",
2154
- admin: {
2155
- description: "Internal builder settings schema v2.",
2156
- hidden: true
2157
- }
2158
- },
2159
- ...sectionStyleFields()
2160
- ]
2161
- };
2162
-
2163
- // src/blocks/blocks/Stats.ts
2643
+ name: "cards",
2644
+ type: "array",
2645
+ fields: [
2646
+ {
2647
+ name: "eyebrow",
2648
+ type: "text"
2649
+ },
2650
+ {
2651
+ name: "title",
2652
+ type: "text",
2653
+ required: true
2654
+ },
2655
+ {
2656
+ name: "description",
2657
+ type: "textarea"
2658
+ },
2659
+ {
2660
+ name: "media",
2661
+ type: "upload",
2662
+ relationTo: "media",
2663
+ required: false
2664
+ },
2665
+ {
2666
+ name: "imageURL",
2667
+ type: "text",
2668
+ admin: {
2669
+ description: "Optional direct image URL when this card should use an external image."
2670
+ }
2671
+ }
2672
+ ]
2673
+ },
2674
+ {
2675
+ name: "width",
2676
+ type: "select",
2677
+ defaultValue: "normal",
2678
+ options: [
2679
+ {
2680
+ label: "Normal",
2681
+ value: "normal"
2682
+ },
2683
+ {
2684
+ label: "Narrow",
2685
+ value: "narrow"
2686
+ }
2687
+ ]
2688
+ },
2689
+ {
2690
+ name: "settings",
2691
+ type: "json",
2692
+ admin: {
2693
+ description: "Internal builder settings schema v2.",
2694
+ hidden: true
2695
+ }
2696
+ },
2697
+ ...sectionStyleFields()
2698
+ ]
2699
+ };
2700
+
2701
+ // src/blocks/blocks/Stats.ts
2164
2702
  var StatsBlock = {
2165
2703
  slug: "stats",
2166
2704
  imageURL: "/images/service-removal.svg",
@@ -2387,18 +2925,18 @@ var sectionPresets = [
2387
2925
  blocks: [
2388
2926
  {
2389
2927
  blockType: "hero",
2390
- kicker: "Licensed + Insured",
2391
- headline: "Expert Tree Care for Central Texas",
2392
- subheadline: "Reliable trimming, safe removals, and stump grinding for residential and commercial properties.",
2393
- primaryLabel: "Get Your Free Quote",
2928
+ kicker: "Trusted Team",
2929
+ headline: "A clear headline for your primary offer",
2930
+ subheadline: "Explain what you offer, who it is for, and why it matters in one strong supporting sentence.",
2931
+ primaryLabel: "Get Started",
2394
2932
  primaryHref: "/contact",
2395
- secondaryLabel: "See Our Work",
2396
- secondaryHref: "/portfolio"
2933
+ secondaryLabel: "Learn More",
2934
+ secondaryHref: "/about"
2397
2935
  },
2398
2936
  {
2399
2937
  blockType: "cta",
2400
- headline: "Need a quote this week?",
2401
- description: "Call (512) 555-0149 or request an on-site estimate online.",
2938
+ headline: "Ready for the next step?",
2939
+ description: "Use a short call to action with a direct path to contact or purchase.",
2402
2940
  buttonLabel: "Contact Us",
2403
2941
  buttonHref: "/contact",
2404
2942
  style: "light"
@@ -2416,19 +2954,19 @@ var sectionPresets = [
2416
2954
  variant: "cards",
2417
2955
  items: [
2418
2956
  {
2419
- title: "Tree Trimming & Pruning",
2420
- description: "Canopy balancing, deadwood removal, and seasonal pruning.",
2421
- icon: "Trim"
2957
+ title: "Service One",
2958
+ description: "Briefly describe this offer or outcome.",
2959
+ icon: "01"
2422
2960
  },
2423
2961
  {
2424
- title: "Safe Tree Removal",
2425
- description: "Controlled removal for hazardous or unstable trees.",
2426
- icon: "Remove"
2962
+ title: "Service Two",
2963
+ description: "Briefly describe this offer or outcome.",
2964
+ icon: "02"
2427
2965
  },
2428
2966
  {
2429
- title: "Stump Grinding",
2430
- description: "Below-grade stump grinding and cleanup.",
2431
- icon: "Stump"
2967
+ title: "Service Three",
2968
+ description: "Briefly describe this offer or outcome.",
2969
+ icon: "03"
2432
2970
  }
2433
2971
  ]
2434
2972
  }
@@ -2444,15 +2982,15 @@ var sectionPresets = [
2444
2982
  title: "What Homeowners Say",
2445
2983
  items: [
2446
2984
  {
2447
- quote: "Great communication, fair pricing, and the cleanup was perfect. We will use them again.",
2448
- name: "Katie M.",
2449
- location: "Austin, TX",
2985
+ quote: "The experience was smooth, thoughtful, and exactly what we hoped for.",
2986
+ name: "Customer Name",
2987
+ location: "City, ST",
2450
2988
  rating: 5
2451
2989
  },
2452
2990
  {
2453
- quote: "They removed a dangerous limb over our driveway quickly and safely.",
2454
- name: "James R.",
2455
- location: "Round Rock, TX",
2991
+ quote: "Fast communication, strong service, and a result we would happily recommend.",
2992
+ name: "Customer Name",
2993
+ location: "City, ST",
2456
2994
  rating: 5
2457
2995
  }
2458
2996
  ]
@@ -2462,12 +3000,12 @@ var sectionPresets = [
2462
3000
  title: "Common Questions",
2463
3001
  items: [
2464
3002
  {
2465
- question: "How quickly can you schedule service?",
2466
- answer: "Most estimate requests are scheduled within 24 hours."
3003
+ question: "How quickly do you respond?",
3004
+ answer: "Replace this with a concise answer to a common customer question."
2467
3005
  },
2468
3006
  {
2469
- question: "Do you provide cleanup?",
2470
- answer: "Yes. Debris haul-off and cleanup are included in quoted scopes."
3007
+ question: "What is included?",
3008
+ answer: "Replace this with another concise answer that reduces buying friction."
2471
3009
  }
2472
3010
  ]
2473
3011
  }
@@ -2481,13 +3019,13 @@ var sectionPresets = [
2481
3019
  {
2482
3020
  blockType: "formEmbed",
2483
3021
  title: "Request a Quote",
2484
- description: "Share your project details and we will follow up quickly.",
3022
+ description: "Share a few details and your team can follow up with next steps.",
2485
3023
  formType: "quote"
2486
3024
  },
2487
3025
  {
2488
3026
  blockType: "bookingEmbed",
2489
3027
  title: "Prefer to book a consultation?",
2490
- description: "Choose a time window and we will confirm availability.",
3028
+ description: "Offer an alternative scheduling path for visitors who prefer to book directly.",
2491
3029
  buttonLabel: "Book Consultation",
2492
3030
  buttonHref: "/contact"
2493
3031
  }
@@ -2499,14 +3037,14 @@ var templateStarterPresets = {
2499
3037
  {
2500
3038
  blockType: "hero",
2501
3039
  headline: "Contact Us",
2502
- subheadline: "Request a quote, ask a question, or book a consultation window.",
2503
- primaryLabel: "Call (512) 555-0149",
2504
- primaryHref: "tel:+15125550149"
3040
+ subheadline: "Tell visitors exactly how to reach you and what to expect next.",
3041
+ primaryLabel: "Email Us",
3042
+ primaryHref: "mailto:hello@example.com"
2505
3043
  },
2506
3044
  {
2507
3045
  blockType: "formEmbed",
2508
3046
  title: "Request a Quote",
2509
- description: "Tell us about your project and we will follow up quickly.",
3047
+ description: "Use this space for a form embed or lead capture flow.",
2510
3048
  formType: "quote"
2511
3049
  },
2512
3050
  {
@@ -2514,8 +3052,8 @@ var templateStarterPresets = {
2514
3052
  title: "Common Questions",
2515
3053
  items: [
2516
3054
  {
2517
- question: "How quickly can you provide an estimate?",
2518
- answer: "Most estimates are scheduled within 24 hours."
3055
+ question: "How quickly will you respond?",
3056
+ answer: "Replace with the answer that best fits your operating process."
2519
3057
  }
2520
3058
  ]
2521
3059
  }
@@ -2524,27 +3062,27 @@ var templateStarterPresets = {
2524
3062
  {
2525
3063
  blockType: "hero",
2526
3064
  kicker: "Locally Owned",
2527
- headline: "Expert Tree Care for Central Texas",
2528
- subheadline: "Premium trimming, removal, and cleanup with safety-first execution.",
2529
- primaryLabel: "Get Your Free Quote",
3065
+ headline: "Lead with your strongest offer",
3066
+ subheadline: "Support the headline with a concise sentence that clarifies benefits and audience.",
3067
+ primaryLabel: "Get Started",
2530
3068
  primaryHref: "/contact",
2531
3069
  secondaryLabel: "View Services",
2532
3070
  secondaryHref: "/services"
2533
3071
  },
2534
3072
  {
2535
3073
  blockType: "featureGrid",
2536
- title: "Why Homeowners Choose Us",
3074
+ title: "Why clients choose us",
2537
3075
  variant: "highlight",
2538
3076
  items: [
2539
- { title: "Transparent Pricing", description: "Clear written estimates.", icon: "01" },
2540
- { title: "Safety-First Crew", description: "Property protection and planning.", icon: "02" },
2541
- { title: "Fast Scheduling", description: "Quick estimates and service windows.", icon: "03" }
3077
+ { title: "Clear Value", description: "Explain your first differentiator.", icon: "01" },
3078
+ { title: "Reliable Process", description: "Explain your second differentiator.", icon: "02" },
3079
+ { title: "Strong Results", description: "Explain your third differentiator.", icon: "03" }
2542
3080
  ]
2543
3081
  },
2544
3082
  {
2545
3083
  blockType: "cta",
2546
- headline: "Need a quote this week?",
2547
- description: "Call (512) 555-0149 or request an estimate online.",
3084
+ headline: "Ready to take the next step?",
3085
+ description: "Add a direct conversion prompt with a clear primary action.",
2548
3086
  buttonLabel: "Contact Us",
2549
3087
  buttonHref: "/contact",
2550
3088
  style: "light"
@@ -2553,9 +3091,9 @@ var templateStarterPresets = {
2553
3091
  services: [
2554
3092
  {
2555
3093
  blockType: "hero",
2556
- headline: "Tree Services Built for Safety and Curb Appeal",
2557
- subheadline: "Core offerings first, with clear scopes and scheduling.",
2558
- primaryLabel: "Schedule Estimate",
3094
+ headline: "Services Built Around Your Process",
3095
+ subheadline: "Summarize the core offerings with a short clarity-first introduction.",
3096
+ primaryLabel: "Request Info",
2559
3097
  primaryHref: "/contact"
2560
3098
  },
2561
3099
  {
@@ -2564,19 +3102,19 @@ var templateStarterPresets = {
2564
3102
  variant: "cards",
2565
3103
  items: [
2566
3104
  {
2567
- title: "Tree Trimming & Pruning",
2568
- description: "Selective pruning for structure, clearance, and health.",
2569
- icon: "Trim"
3105
+ title: "Service One",
3106
+ description: "Replace with a short description.",
3107
+ icon: "01"
2570
3108
  },
2571
3109
  {
2572
- title: "Tree Removal",
2573
- description: "Controlled removal for unstable or hazardous trees.",
2574
- icon: "Remove"
3110
+ title: "Service Two",
3111
+ description: "Replace with a short description.",
3112
+ icon: "02"
2575
3113
  },
2576
3114
  {
2577
- title: "Stump Grinding",
2578
- description: "Below-grade grinding and cleanup.",
2579
- icon: "Stump"
3115
+ title: "Service Three",
3116
+ description: "Replace with a short description.",
3117
+ icon: "03"
2580
3118
  }
2581
3119
  ]
2582
3120
  },
@@ -2585,8 +3123,8 @@ var templateStarterPresets = {
2585
3123
  title: "Frequently Asked Questions",
2586
3124
  items: [
2587
3125
  {
2588
- question: "Do you handle storm cleanup?",
2589
- answer: "Yes. We prioritize urgent hazards after severe weather."
3126
+ question: "Do you offer custom scopes?",
3127
+ answer: "Replace this with an answer that fits your business."
2590
3128
  }
2591
3129
  ]
2592
3130
  }
@@ -2984,20 +3522,6 @@ function migrateStudioDocument(value, migrations) {
2984
3522
  return assertStudioDocumentV1(current);
2985
3523
  }
2986
3524
 
2987
- // src/studio-pages/index.ts
2988
- var studio_pages_exports = {};
2989
- __export(studio_pages_exports, {
2990
- createDefaultStudioDocument: () => createDefaultStudioDocument,
2991
- defaultBuilderThemeTokens: () => defaultBuilderThemeTokens,
2992
- layoutToStudioDocument: () => layoutToStudioDocument,
2993
- pageInspectorPanels: () => pageInspectorPanels,
2994
- pageNodeTypes: () => pageNodeTypes,
2995
- pagePaletteGroups: () => pagePaletteGroups,
2996
- pageStudioModuleManifest: () => pageStudioModuleManifest,
2997
- resolveBuilderThemeTokens: () => resolveBuilderThemeTokens,
2998
- studioDocumentToLayout: () => studioDocumentToLayout
2999
- });
3000
-
3001
3525
  // src/studio-pages/builder/settings-v2/types.ts
3002
3526
  var defaultBuilderBlockSettingsV2 = {
3003
3527
  advanced: {
@@ -3011,13 +3535,13 @@ var defaultBuilderBlockSettingsV2 = {
3011
3535
  contentGradientAngle: "135",
3012
3536
  contentGradientFrom: "#ffffff",
3013
3537
  contentGradientPreset: "none",
3014
- contentGradientTo: "#f4f6f2",
3538
+ contentGradientTo: "#f4f6f8",
3015
3539
  sectionBackgroundColor: "#ffffff",
3016
3540
  sectionBackgroundMode: "none",
3017
3541
  sectionGradientAngle: "135",
3018
- sectionGradientFrom: "#124a37",
3019
- sectionGradientPreset: "forest",
3020
- sectionGradientTo: "#1f684f"
3542
+ sectionGradientFrom: "#334b63",
3543
+ sectionGradientPreset: "slate",
3544
+ sectionGradientTo: "#496582"
3021
3545
  },
3022
3546
  layout: {
3023
3547
  contentWidth: "inherit",
@@ -3078,9 +3602,9 @@ var defaultBuilderItemSettingsV2 = {
3078
3602
  };
3079
3603
  var defaultBuilderThemeTokens = {
3080
3604
  colors: {
3081
- accent: "#0d4a37",
3082
- bodyText: "#13211c",
3083
- headingText: "#13211c",
3605
+ accent: "#334b63",
3606
+ bodyText: "#425163",
3607
+ headingText: "#182332",
3084
3608
  surface: "#ffffff"
3085
3609
  },
3086
3610
  radii: {
@@ -3276,221 +3800,436 @@ var migrateBlockToSettingsV2 = (block) => {
3276
3800
  };
3277
3801
  };
3278
3802
 
3279
- // src/studio-pages/builder/settings-v2/themeTokens.ts
3280
- var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3281
- var merge = (base, next) => {
3282
- if (!next || !isRecord4(next)) {
3283
- return base;
3284
- }
3285
- const merged = { ...base };
3286
- for (const [key, value] of Object.entries(next)) {
3287
- if (isRecord4(value) && isRecord4(merged[key])) {
3288
- merged[key] = merge(merged[key], value);
3289
- continue;
3290
- }
3291
- if (typeof value !== "undefined") {
3292
- merged[key] = value;
3293
- }
3803
+ // src/studio-pages/document.ts
3804
+ var ensureNodeID = (value, index) => {
3805
+ if (typeof value === "string" && value.trim().length > 0) {
3806
+ return value.trim();
3294
3807
  }
3295
- return merged;
3808
+ return `node-${index + 1}`;
3296
3809
  };
3297
- var resolveBuilderThemeTokens = (layers) => {
3298
- const withSite = merge(defaultBuilderThemeTokens, layers.site);
3299
- const withPage = merge(withSite, layers.page);
3300
- return merge(withPage, layers.block);
3810
+ var layoutToStudioDocument = (layout, title, metadata) => {
3811
+ const nodes = layout.filter((block) => typeof block.blockType === "string").map((rawBlock, index) => {
3812
+ const block = migrateBlockToSettingsV2(rawBlock);
3813
+ const blockType = String(block.blockType);
3814
+ const { id, blockType: _ignoredBlockType, ...data } = block;
3815
+ return {
3816
+ id: ensureNodeID(id, index),
3817
+ type: blockType,
3818
+ data
3819
+ };
3820
+ });
3821
+ return {
3822
+ metadata,
3823
+ schemaVersion: 1,
3824
+ title,
3825
+ nodes,
3826
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3827
+ };
3301
3828
  };
3829
+ var studioDocumentToLayout = (document) => document.nodes.map(
3830
+ (node) => migrateBlockToSettingsV2({
3831
+ id: node.id,
3832
+ blockType: node.type,
3833
+ ...node.data
3834
+ })
3835
+ );
3836
+ var createDefaultStudioDocument = (title) => ({
3837
+ metadata: {},
3838
+ schemaVersion: 1,
3839
+ title,
3840
+ nodes: [],
3841
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3842
+ });
3302
3843
 
3303
- // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
3304
- var import_react2 = require("react");
3305
-
3306
- // src/studio-pages/builder/ui/Accordion.tsx
3307
- var import_react = require("react");
3308
- var import_jsx_runtime4 = require("react/jsx-runtime");
3309
-
3310
- // src/studio-pages/builder/ui/ImageControls.tsx
3311
- var import_jsx_runtime5 = require("react/jsx-runtime");
3312
-
3313
- // src/studio-pages/builder/settings-v2/inspectorSchema.ts
3314
- var alignOptions = [
3315
- { label: "Left", value: "left" },
3316
- { label: "Center", value: "center" },
3317
- { label: "Right", value: "right" },
3318
- { label: "Justify", value: "justify" }
3319
- ];
3320
- var layoutFieldSet = [
3321
- {
3322
- group: "layout",
3323
- key: "settings.layout.contentWidth",
3324
- label: "Content Width",
3325
- options: [
3326
- { label: "Inherit Page Default", value: "inherit" },
3327
- { label: "Narrow", value: "narrow" },
3328
- { label: "Content", value: "content" },
3329
- { label: "Wide", value: "wide" },
3330
- { label: "Full", value: "full" }
3331
- ],
3332
- tags: ["width", "container"],
3333
- type: "select"
3334
- },
3335
- {
3336
- group: "layout",
3337
- key: "settings.layout.linkVerticalPadding",
3338
- label: "Keep Top and Bottom Equal",
3339
- tags: ["spacing", "padding", "equal", "lock", "vertical"],
3340
- type: "checkbox"
3341
- },
3342
- {
3343
- group: "layout",
3344
- key: "settings.layout.paddingTopPt",
3345
- label: "Top Padding (pt)",
3346
- max: 240,
3347
- min: 0,
3348
- tags: ["spacing", "padding", "top", "vertical"],
3349
- type: "number"
3350
- },
3351
- {
3352
- group: "layout",
3353
- key: "settings.layout.paddingBottomPt",
3354
- label: "Bottom Padding (pt)",
3355
- max: 240,
3356
- min: 0,
3357
- tags: ["spacing", "padding", "bottom", "vertical"],
3358
- type: "number"
3359
- },
3360
- {
3361
- group: "layout",
3362
- key: "settings.layout.linkHorizontalPadding",
3363
- label: "Keep Left and Right Equal",
3364
- tags: ["spacing", "padding", "equal", "lock", "horizontal"],
3365
- type: "checkbox"
3366
- },
3367
- {
3368
- group: "layout",
3369
- key: "settings.layout.paddingLeftPt",
3370
- label: "Left Padding (pt)",
3371
- max: 240,
3372
- min: 0,
3373
- tags: ["spacing", "padding", "left", "horizontal"],
3374
- type: "number"
3375
- },
3376
- {
3377
- group: "layout",
3378
- key: "settings.layout.paddingRightPt",
3379
- label: "Right Padding (pt)",
3380
- max: 240,
3381
- min: 0,
3382
- tags: ["spacing", "padding", "right", "horizontal"],
3383
- type: "number"
3844
+ // src/nextjs/queries/pages.ts
3845
+ var PAGE_QUERY_CACHE_VERSION = "v4-published-only-public";
3846
+ function withStudioDocumentLayout(page) {
3847
+ if (!page) {
3848
+ return null;
3384
3849
  }
3385
- ];
3386
- var typographyFieldSet = [
3387
- {
3388
- group: "typography",
3389
- key: "settings.typography.headingAlign",
3390
- label: "Heading Alignment",
3391
- options: alignOptions,
3392
- tags: ["text", "align", "heading"],
3393
- type: "select"
3394
- },
3395
- {
3396
- group: "typography",
3397
- key: "settings.typography.bodyAlign",
3398
- label: "Body Alignment",
3399
- options: alignOptions,
3400
- tags: ["text", "align", "paragraph"],
3401
- type: "select"
3402
- },
3403
- {
3404
- group: "typography",
3405
- key: "settings.typography.maxTextWidth",
3406
- label: "Text Width",
3407
- options: [
3408
- { label: "Auto", value: "auto" },
3409
- { label: "Small", value: "sm" },
3410
- { label: "Medium", value: "md" },
3411
- { label: "Large", value: "lg" },
3412
- { label: "Full", value: "full" }
3413
- ],
3414
- tags: ["readability", "measure", "line length"],
3415
- type: "select"
3416
- },
3417
- {
3418
- advanced: true,
3419
- group: "typography",
3420
- key: "settings.typography.lineHeightPreset",
3421
- label: "Line Height",
3422
- options: [
3423
- { label: "Tight", value: "tight" },
3424
- { label: "Normal", value: "normal" },
3425
- { label: "Relaxed", value: "relaxed" }
3426
- ],
3427
- type: "select"
3428
- },
3429
- {
3430
- advanced: true,
3431
- group: "typography",
3432
- key: "settings.typography.letterSpacingPreset",
3433
- label: "Letter Spacing",
3434
- options: [
3435
- { label: "Tight", value: "tight" },
3436
- { label: "Normal", value: "normal" },
3437
- { label: "Relaxed", value: "relaxed" }
3438
- ],
3439
- type: "select"
3850
+ try {
3851
+ const studioDocument = assertStudioDocumentV1(page.studioDocument);
3852
+ const compiledLayout = studioDocumentToLayout(studioDocument);
3853
+ if (Array.isArray(compiledLayout) && compiledLayout.length > 0) {
3854
+ return {
3855
+ ...page,
3856
+ layout: compiledLayout
3857
+ };
3858
+ }
3859
+ } catch {
3440
3860
  }
3441
- ];
3442
- var styleFieldSet = [
3443
- {
3444
- group: "style",
3445
- key: "settings.appearance.sectionBackgroundMode",
3446
- label: "Section Background",
3447
- options: [
3448
- { label: "None", value: "none" },
3449
- { label: "Color", value: "color" },
3450
- { label: "Gradient", value: "gradient" }
3451
- ],
3452
- tags: ["background", "section"],
3453
- type: "select"
3454
- },
3455
- {
3456
- group: "style",
3457
- key: "settings.appearance.sectionBackgroundColor",
3458
- label: "Section Background Color",
3459
- type: "color"
3460
- },
3461
- {
3462
- group: "style",
3463
- key: "settings.appearance.contentBackgroundMode",
3464
- label: "Content Background",
3465
- options: [
3466
- { label: "None", value: "none" },
3467
- { label: "Color", value: "color" },
3468
- { label: "Gradient", value: "gradient" }
3469
- ],
3470
- tags: ["background", "content"],
3471
- type: "select"
3472
- },
3473
- {
3474
- group: "style",
3475
- key: "settings.appearance.contentBackgroundColor",
3476
- label: "Content Background Color",
3477
- type: "color"
3861
+ return page;
3862
+ }
3863
+ function normalizePath(segments) {
3864
+ if (!segments || segments.length === 0) {
3865
+ return "/";
3478
3866
  }
3479
- ];
3480
- var commonAdvanced = [
3481
- {
3482
- group: "advanced",
3483
- inlineEditable: false,
3484
- key: "settings.advanced.editCopyInPanel",
3485
- label: "Edit Copy In Panel",
3486
- tags: ["inline", "copy", "text"],
3487
- type: "checkbox"
3488
- },
3489
- {
3490
- advanced: true,
3491
- group: "advanced",
3492
- key: "settings.advanced.hideOnMobile",
3493
- label: "Hide On Mobile",
3867
+ const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
3868
+ return cleaned.length > 0 ? `/${cleaned}` : "/";
3869
+ }
3870
+ async function queryPageByPath(payload, path2, draft) {
3871
+ const pathWhere = {
3872
+ path: {
3873
+ equals: path2
3874
+ }
3875
+ };
3876
+ const publishedWhere = {
3877
+ _status: {
3878
+ equals: "published"
3879
+ }
3880
+ };
3881
+ const result = await payload.find({
3882
+ collection: "pages",
3883
+ depth: 2,
3884
+ draft,
3885
+ limit: 1,
3886
+ overrideAccess: false,
3887
+ where: draft ? pathWhere : { and: [pathWhere, publishedWhere] }
3888
+ });
3889
+ if (result.docs.length > 0) {
3890
+ return withStudioDocumentLayout(result.docs[0] || null);
3891
+ }
3892
+ if (path2 === "/") {
3893
+ const homeWhere = {
3894
+ slug: {
3895
+ equals: "home"
3896
+ }
3897
+ };
3898
+ const homeResult = await payload.find({
3899
+ collection: "pages",
3900
+ depth: 2,
3901
+ draft,
3902
+ limit: 1,
3903
+ overrideAccess: false,
3904
+ where: draft ? homeWhere : { and: [homeWhere, publishedWhere] }
3905
+ });
3906
+ return withStudioDocumentLayout(homeResult.docs[0] || null);
3907
+ }
3908
+ return null;
3909
+ }
3910
+ function createPageQueries(getPayloadClient, contentTag = "website-content") {
3911
+ const getPublishedPageByPathCached = (0, import_cache.unstable_cache)(
3912
+ async (path2) => {
3913
+ const payload = await getPayloadClient();
3914
+ return queryPageByPath(payload, path2, false);
3915
+ },
3916
+ ["page-by-path", PAGE_QUERY_CACHE_VERSION],
3917
+ { tags: [contentTag] }
3918
+ );
3919
+ async function getPageBySegments(segments, draft = false) {
3920
+ const path2 = normalizePath(segments);
3921
+ const payload = await getPayloadClient();
3922
+ if (draft) {
3923
+ return queryPageByPath(payload, path2, true);
3924
+ }
3925
+ return getPublishedPageByPathCached(path2);
3926
+ }
3927
+ async function listPublishedPagePaths() {
3928
+ const payload = await getPayloadClient();
3929
+ const pages = await payload.find({
3930
+ collection: "pages",
3931
+ depth: 0,
3932
+ draft: false,
3933
+ limit: 1e3,
3934
+ pagination: false,
3935
+ overrideAccess: false,
3936
+ where: {
3937
+ _status: {
3938
+ equals: "published"
3939
+ }
3940
+ }
3941
+ });
3942
+ return pages.docs.map((doc) => doc.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
3943
+ }
3944
+ function pathToSegments(path2) {
3945
+ if (!path2 || path2 === "/") {
3946
+ return [];
3947
+ }
3948
+ return path2.split("/").filter(Boolean);
3949
+ }
3950
+ return {
3951
+ getPageBySegments,
3952
+ listPublishedPagePaths,
3953
+ pathToSegments
3954
+ };
3955
+ }
3956
+
3957
+ // src/nextjs/queries/site.ts
3958
+ var import_cache2 = require("next/cache");
3959
+ function createSiteQueries(getPayloadClient, contentTag = "website-content") {
3960
+ const getSiteSettingsCached = (0, import_cache2.unstable_cache)(
3961
+ async () => {
3962
+ const payload = await getPayloadClient();
3963
+ const settings = await payload.findGlobal({
3964
+ slug: "site-settings",
3965
+ depth: 1
3966
+ });
3967
+ return settings;
3968
+ },
3969
+ ["site-settings-global"],
3970
+ { tags: [contentTag] }
3971
+ );
3972
+ const getHeaderCached = (0, import_cache2.unstable_cache)(
3973
+ async () => {
3974
+ const payload = await getPayloadClient();
3975
+ const header = await payload.findGlobal({
3976
+ slug: "header",
3977
+ depth: 1
3978
+ });
3979
+ return header;
3980
+ },
3981
+ ["header-global"],
3982
+ { tags: [contentTag] }
3983
+ );
3984
+ const getFooterCached = (0, import_cache2.unstable_cache)(
3985
+ async () => {
3986
+ const payload = await getPayloadClient();
3987
+ const footer = await payload.findGlobal({
3988
+ slug: "footer",
3989
+ depth: 1
3990
+ });
3991
+ return footer;
3992
+ },
3993
+ ["footer-global"],
3994
+ { tags: [contentTag] }
3995
+ );
3996
+ const getSocialMediaCached = (0, import_cache2.unstable_cache)(
3997
+ async () => {
3998
+ const payload = await getPayloadClient();
3999
+ const socialMedia = await payload.findGlobal({
4000
+ slug: "social-media",
4001
+ depth: 1
4002
+ });
4003
+ return socialMedia;
4004
+ },
4005
+ ["social-media-global"],
4006
+ { tags: [contentTag] }
4007
+ );
4008
+ async function getSiteSettings(draft = false) {
4009
+ if (draft) {
4010
+ const payload = await getPayloadClient();
4011
+ const settings = await payload.findGlobal({
4012
+ slug: "site-settings",
4013
+ depth: 1,
4014
+ draft: true
4015
+ });
4016
+ return settings;
4017
+ }
4018
+ return getSiteSettingsCached();
4019
+ }
4020
+ async function getHeader(draft = false) {
4021
+ if (draft) {
4022
+ const payload = await getPayloadClient();
4023
+ const header = await payload.findGlobal({
4024
+ slug: "header",
4025
+ depth: 1,
4026
+ draft: true
4027
+ });
4028
+ return header;
4029
+ }
4030
+ return getHeaderCached();
4031
+ }
4032
+ async function getFooter(draft = false) {
4033
+ if (draft) {
4034
+ const payload = await getPayloadClient();
4035
+ const footer = await payload.findGlobal({
4036
+ slug: "footer",
4037
+ depth: 1,
4038
+ draft: true
4039
+ });
4040
+ return footer;
4041
+ }
4042
+ return getFooterCached();
4043
+ }
4044
+ async function getSocialMedia(draft = false) {
4045
+ if (draft) {
4046
+ const payload = await getPayloadClient();
4047
+ const socialMedia = await payload.findGlobal({
4048
+ slug: "social-media",
4049
+ depth: 1,
4050
+ draft: true
4051
+ });
4052
+ return socialMedia;
4053
+ }
4054
+ return getSocialMediaCached();
4055
+ }
4056
+ return {
4057
+ getSiteSettings,
4058
+ getHeader,
4059
+ getFooter,
4060
+ getSocialMedia
4061
+ };
4062
+ }
4063
+
4064
+ // src/nextjs/utilities/media.ts
4065
+ function resolveMedia(media) {
4066
+ if (!media) {
4067
+ return null;
4068
+ }
4069
+ if (typeof media === "number" || typeof media === "string") {
4070
+ return null;
4071
+ }
4072
+ if (media.url) {
4073
+ return {
4074
+ url: media.url,
4075
+ alt: media.alt || ""
4076
+ };
4077
+ }
4078
+ if (media.filename) {
4079
+ return {
4080
+ url: `/media/${media.filename}`,
4081
+ alt: media.alt || ""
4082
+ };
4083
+ }
4084
+ return null;
4085
+ }
4086
+
4087
+ // src/nextjs/utilities/socialMedia.ts
4088
+ function resolveSocialMediaLinks(data) {
4089
+ const profiles = data?.profiles;
4090
+ if (!profiles || typeof profiles !== "object") {
4091
+ return [];
4092
+ }
4093
+ return SOCIAL_MEDIA_PLATFORMS.reduce((acc, platform) => {
4094
+ const profile = profiles[platform];
4095
+ if (!profile || typeof profile !== "object") {
4096
+ return acc;
4097
+ }
4098
+ const url = typeof profile.url === "string" ? profile.url.trim() : "";
4099
+ if (!url) {
4100
+ return acc;
4101
+ }
4102
+ const icon = typeof profile.icon === "string" && profile.icon.trim().length > 0 ? profile.icon.trim() : SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM[platform];
4103
+ acc.push({
4104
+ icon,
4105
+ label: SOCIAL_MEDIA_PLATFORM_LABELS[platform],
4106
+ platform,
4107
+ url
4108
+ });
4109
+ return acc;
4110
+ }, []);
4111
+ }
4112
+
4113
+ // src/studio-pages/index.ts
4114
+ var studio_pages_exports = {};
4115
+ __export(studio_pages_exports, {
4116
+ createDefaultStudioDocument: () => createDefaultStudioDocument,
4117
+ createStudioPageService: () => createStudioPageService,
4118
+ defaultBuilderThemeTokens: () => defaultBuilderThemeTokens,
4119
+ getStudioDocumentFromPage: () => getStudioDocumentFromPage,
4120
+ layoutToStudioDocument: () => layoutToStudioDocument,
4121
+ pageInspectorPanels: () => pageInspectorPanels,
4122
+ pageNodeTypes: () => pageNodeTypes,
4123
+ pagePaletteGroups: () => pagePaletteGroups,
4124
+ pageStudioModuleManifest: () => pageStudioModuleManifest,
4125
+ resolveBuilderThemeTokens: () => resolveBuilderThemeTokens,
4126
+ studioDocumentToLayout: () => studioDocumentToLayout,
4127
+ toEditorInitialDoc: () => toEditorInitialDoc
4128
+ });
4129
+
4130
+ // src/studio-pages/builder/settings-v2/themeTokens.ts
4131
+ var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
4132
+ var merge = (base, next) => {
4133
+ if (!next || !isRecord4(next)) {
4134
+ return base;
4135
+ }
4136
+ const merged = { ...base };
4137
+ for (const [key, value] of Object.entries(next)) {
4138
+ if (isRecord4(value) && isRecord4(merged[key])) {
4139
+ merged[key] = merge(merged[key], value);
4140
+ continue;
4141
+ }
4142
+ if (typeof value !== "undefined") {
4143
+ merged[key] = value;
4144
+ }
4145
+ }
4146
+ return merged;
4147
+ };
4148
+ var resolveBuilderThemeTokens = (layers) => {
4149
+ const withSite = merge(defaultBuilderThemeTokens, layers.site);
4150
+ const withPage = merge(withSite, layers.page);
4151
+ return merge(withPage, layers.block);
4152
+ };
4153
+
4154
+ // src/studio-pages/builder/settings-v2/inspectorSchema.ts
4155
+ var alignOptions = [
4156
+ { label: "Left", value: "left" },
4157
+ { label: "Center", value: "center" },
4158
+ { label: "Right", value: "right" },
4159
+ { label: "Justify", value: "justify" }
4160
+ ];
4161
+ var layoutFieldSet = [];
4162
+ var typographyFieldSet = [
4163
+ {
4164
+ group: "typography",
4165
+ key: "settings.typography.headingAlign",
4166
+ label: "Heading Alignment",
4167
+ options: alignOptions,
4168
+ tags: ["text", "align", "heading"],
4169
+ type: "select"
4170
+ },
4171
+ {
4172
+ group: "typography",
4173
+ key: "settings.typography.bodyAlign",
4174
+ label: "Body Alignment",
4175
+ options: alignOptions,
4176
+ tags: ["text", "align", "paragraph"],
4177
+ type: "select"
4178
+ },
4179
+ {
4180
+ group: "typography",
4181
+ key: "settings.typography.maxTextWidth",
4182
+ label: "Text Width",
4183
+ options: [
4184
+ { label: "Auto", value: "auto" },
4185
+ { label: "Small", value: "sm" },
4186
+ { label: "Medium", value: "md" },
4187
+ { label: "Large", value: "lg" },
4188
+ { label: "Full", value: "full" }
4189
+ ],
4190
+ tags: ["readability", "measure", "line length"],
4191
+ type: "select"
4192
+ },
4193
+ {
4194
+ advanced: true,
4195
+ group: "typography",
4196
+ key: "settings.typography.lineHeightPreset",
4197
+ label: "Line Height",
4198
+ options: [
4199
+ { label: "Tight", value: "tight" },
4200
+ { label: "Normal", value: "normal" },
4201
+ { label: "Relaxed", value: "relaxed" }
4202
+ ],
4203
+ type: "select"
4204
+ },
4205
+ {
4206
+ advanced: true,
4207
+ group: "typography",
4208
+ key: "settings.typography.letterSpacingPreset",
4209
+ label: "Letter Spacing",
4210
+ options: [
4211
+ { label: "Tight", value: "tight" },
4212
+ { label: "Normal", value: "normal" },
4213
+ { label: "Relaxed", value: "relaxed" }
4214
+ ],
4215
+ type: "select"
4216
+ }
4217
+ ];
4218
+ var styleFieldSet = [];
4219
+ var commonAdvanced = [
4220
+ {
4221
+ group: "advanced",
4222
+ inlineEditable: false,
4223
+ key: "settings.advanced.editCopyInPanel",
4224
+ label: "Edit Copy In Panel",
4225
+ tags: ["inline", "copy", "text"],
4226
+ type: "checkbox"
4227
+ },
4228
+ {
4229
+ advanced: true,
4230
+ group: "advanced",
4231
+ key: "settings.advanced.hideOnMobile",
4232
+ label: "Hide On Mobile",
3494
4233
  type: "checkbox"
3495
4234
  },
3496
4235
  {
@@ -3577,6 +4316,7 @@ var inspectorDefinitionByBlockType = {
3577
4316
  faq: {
3578
4317
  blockType: "faq",
3579
4318
  fields: [
4319
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
3580
4320
  { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3581
4321
  ...layoutFieldSet,
3582
4322
  ...typographyFieldSet,
@@ -3587,6 +4327,7 @@ var inspectorDefinitionByBlockType = {
3587
4327
  featureGrid: {
3588
4328
  blockType: "featureGrid",
3589
4329
  fields: [
4330
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
3590
4331
  { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3591
4332
  {
3592
4333
  group: "basics",
@@ -3594,7 +4335,11 @@ var inspectorDefinitionByBlockType = {
3594
4335
  label: "Variant",
3595
4336
  options: [
3596
4337
  { label: "Cards", value: "cards" },
3597
- { label: "Highlight", value: "highlight" }
4338
+ { label: "Highlight", value: "highlight" },
4339
+ { label: "Split List", value: "splitList" },
4340
+ { label: "Panels", value: "panels" },
4341
+ { label: "Catalog", value: "catalog" },
4342
+ { label: "Contact Split", value: "contact" }
3598
4343
  ],
3599
4344
  type: "select"
3600
4345
  },
@@ -3609,6 +4354,7 @@ var inspectorDefinitionByBlockType = {
3609
4354
  formEmbed: {
3610
4355
  blockType: "formEmbed",
3611
4356
  fields: [
4357
+ { group: "basics", inlineEditable: true, key: "eyebrow", label: "Eyebrow", type: "text" },
3612
4358
  { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3613
4359
  { group: "basics", inlineEditable: true, key: "description", label: "Description", type: "textarea" },
3614
4360
  {
@@ -3641,7 +4387,7 @@ var inspectorDefinitionByBlockType = {
3641
4387
  type: "select"
3642
4388
  },
3643
4389
  {
3644
- group: "layout",
4390
+ group: "basics",
3645
4391
  key: "heroHeight",
3646
4392
  label: "Hero Height",
3647
4393
  options: [
@@ -3694,6 +4440,16 @@ var inspectorDefinitionByBlockType = {
3694
4440
  richText: {
3695
4441
  blockType: "richText",
3696
4442
  fields: [
4443
+ {
4444
+ group: "basics",
4445
+ key: "variant",
4446
+ label: "Variant",
4447
+ options: [
4448
+ { label: "Default", value: "default" },
4449
+ { label: "Quote Banner", value: "quoteBanner" }
4450
+ ],
4451
+ type: "select"
4452
+ },
3697
4453
  { group: "basics", inlineEditable: true, key: "title", label: "Title", type: "text" },
3698
4454
  {
3699
4455
  group: "basics",
@@ -3729,677 +4485,571 @@ var inspectorDefinitionByBlockType = {
3729
4485
  { group: "basics", key: "visibleCount", label: "Visible At Once", max: 6, min: 1, type: "number" },
3730
4486
  { group: "basics", key: "autoRotate", label: "Auto Rotate", type: "checkbox" },
3731
4487
  {
3732
- advanced: true,
3733
- group: "advanced",
3734
- key: "rotateIntervalSeconds",
3735
- label: "Rotate Interval Seconds",
3736
- max: 30,
3737
- min: 2,
3738
- type: "number"
3739
- },
3740
- ...layoutFieldSet,
3741
- ...typographyFieldSet,
3742
- ...styleFieldSet,
3743
- ...commonAdvanced
3744
- ]
3745
- }
3746
- };
3747
-
3748
- // src/studio-pages/builder/settings-v2/BlockInspectorRenderer.tsx
3749
- var import_jsx_runtime6 = require("react/jsx-runtime");
3750
-
3751
- // src/studio-pages/migrations.ts
3752
- var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3753
- var assertPageStudioDocumentV1 = (value) => {
3754
- if (!isRecord5(value)) {
3755
- throw new Error("Studio document must be an object");
3756
- }
3757
- if (value.schemaVersion !== 1) {
3758
- throw new Error("Unsupported studio schemaVersion");
3759
- }
3760
- if (!Array.isArray(value.nodes)) {
3761
- throw new Error("Studio document nodes must be an array");
3762
- }
3763
- const nodes = value.nodes.map((node, index) => {
3764
- if (!isRecord5(node)) {
3765
- throw new Error(`Node at index ${index} must be an object`);
3766
- }
3767
- if (typeof node.id !== "string" || node.id.length === 0) {
3768
- throw new Error(`Node at index ${index} has invalid id`);
3769
- }
3770
- if (typeof node.type !== "string" || node.type.length === 0) {
3771
- throw new Error(`Node at index ${index} has invalid type`);
3772
- }
3773
- if (!isRecord5(node.data)) {
3774
- throw new Error(`Node at index ${index} has invalid data`);
3775
- }
3776
- return {
3777
- data: node.data,
3778
- id: node.id,
3779
- type: node.type
3780
- };
3781
- });
3782
- return {
3783
- metadata: isRecord5(value.metadata) ? value.metadata : void 0,
3784
- nodes,
3785
- schemaVersion: 1,
3786
- title: typeof value.title === "string" ? value.title : void 0,
3787
- updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : void 0
3788
- };
3789
- };
3790
- var migratePageNodeToSettingsV2 = (node) => {
3791
- const normalized = migrateBlockToSettingsV2({
3792
- blockType: node.type,
3793
- ...node.data
3794
- });
3795
- const { blockType: _ignoredBlockType, ...data } = normalized;
3796
- return {
3797
- ...node,
3798
- data
3799
- };
3800
- };
3801
- var migratePageDocumentSettingsToV2 = (value) => {
3802
- const current = assertPageStudioDocumentV1(value);
3803
- return {
3804
- ...current,
3805
- nodes: current.nodes.map(migratePageNodeToSettingsV2)
3806
- };
3807
- };
3808
- var pageStudioMigrations = [
3809
- {
3810
- fromVersion: 1,
3811
- migrate: migratePageDocumentSettingsToV2,
3812
- toVersion: 1
3813
- }
3814
- ];
3815
-
3816
- // src/studio-pages/index.ts
3817
- var withSectionStyleDefaults = (value) => ({
3818
- ...sectionStyleDefaults,
3819
- ...value
3820
- });
3821
- var defaultNodeData = {
3822
- bookingEmbed: {
3823
- ...withSectionStyleDefaults({}),
3824
- buttonHref: "/contact",
3825
- buttonLabel: "Book Consultation",
3826
- description: "Let visitors book a consultation.",
3827
- title: "Book a Time"
3828
- },
3829
- beforeAfter: withSectionStyleDefaults({
3830
- itemsPerRow: 2,
3831
- items: [
3832
- {
3833
- description: "Before and after result summary.",
3834
- imageCornerStyle: "rounded",
3835
- imageFit: "cover",
3836
- imagePosition: "center",
3837
- label: "Project One"
3838
- }
3839
- ],
3840
- subtitle: "Show visual proof from real projects.",
3841
- title: "Before & After Results"
3842
- }),
3843
- cta: {
3844
- ...withSectionStyleDefaults({}),
3845
- backgroundColor: "#1f684f",
3846
- buttonHref: "/contact",
3847
- buttonLabel: "Contact Us",
3848
- description: "Optional supporting copy.",
3849
- headline: "Ready to get started?",
3850
- style: "light"
3851
- },
3852
- faq: {
3853
- ...withSectionStyleDefaults({}),
3854
- items: [{ answer: "Answer goes here.", question: "Frequently asked question?" }],
3855
- title: "Frequently Asked Questions"
3856
- },
3857
- featureGrid: {
3858
- ...withSectionStyleDefaults({}),
3859
- itemsPerRow: 3,
3860
- items: [
3861
- { description: "Explain this point.", iconType: "badge", icon: "01", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature One" },
3862
- { description: "Explain this point.", iconType: "badge", icon: "02", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Two" },
3863
- { description: "Explain this point.", iconType: "badge", icon: "03", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Three" }
3864
- ],
3865
- title: "Section Title",
3866
- variant: "cards"
3867
- },
3868
- formEmbed: {
3869
- ...withSectionStyleDefaults({}),
3870
- description: "Collect lead details from visitors.",
3871
- formType: "quote",
3872
- title: "Request a Quote"
3873
- },
3874
- hero: {
3875
- ...withSectionStyleDefaults({}),
3876
- backgroundColor: "",
3877
- backgroundOverlayMode: "none",
3878
- backgroundOverlayOpacity: 45,
3879
- backgroundOverlayColor: "#000000",
3880
- backgroundOverlayGradientFrom: "#0d4a37",
3881
- backgroundOverlayGradientTo: "#1f684f",
3882
- backgroundOverlayGradientAngle: "135",
3883
- backgroundOverlayGradientFromStrength: 100,
3884
- backgroundOverlayGradientToStrength: 100,
3885
- backgroundOverlayGradientStart: 0,
3886
- backgroundOverlayGradientEnd: 100,
3887
- backgroundOverlayGradientFeather: 100,
3888
- backgroundImageCornerStyle: "rounded",
3889
- backgroundImageFit: "cover",
3890
- backgroundImagePosition: "center",
3891
- heroHeight: "sm",
3892
- headline: "New Hero Section",
3893
- kicker: "Optional kicker",
3894
- primaryHref: "/contact",
3895
- primaryLabel: "Primary Action",
3896
- secondaryHref: "/services",
3897
- secondaryLabel: "Secondary Action",
3898
- subheadline: "Describe your offer clearly for website visitors.",
3899
- variant: "default"
3900
- },
3901
- media: {
3902
- ...withSectionStyleDefaults({}),
3903
- caption: "Add a caption",
3904
- imageCornerStyle: "rounded",
3905
- imageFit: "cover",
3906
- imagePosition: "center",
3907
- size: "default"
3908
- },
3909
- logoWall: withSectionStyleDefaults({
3910
- items: [
3911
- { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 1" },
3912
- { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 2" },
3913
- { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 3" }
3914
- ],
3915
- subtitle: "Trusted by teams and homeowners across Central Texas.",
3916
- title: "Trusted by Local Organizations"
3917
- }),
3918
- richText: {
3919
- ...withSectionStyleDefaults({}),
3920
- content: {
3921
- root: {
3922
- children: [
3923
- {
3924
- children: [
3925
- {
3926
- detail: 0,
3927
- format: 0,
3928
- mode: "normal",
3929
- style: "",
3930
- text: "Write your content here.",
3931
- type: "text",
3932
- version: 1
3933
- }
3934
- ],
3935
- direction: "ltr",
3936
- format: "",
3937
- indent: 0,
3938
- type: "paragraph",
3939
- version: 1
3940
- }
3941
- ],
3942
- direction: "ltr",
3943
- format: "",
3944
- indent: 0,
3945
- type: "root",
3946
- version: 1
3947
- }
3948
- },
3949
- title: "Section Heading",
3950
- width: "normal"
3951
- },
3952
- testimonials: {
3953
- ...withSectionStyleDefaults({}),
3954
- autoRotate: true,
3955
- items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here.", rating: 5 }],
3956
- rotateIntervalSeconds: 7,
3957
- title: "What Customers Say",
3958
- visibleCount: 3
3959
- },
3960
- stats: withSectionStyleDefaults({
3961
- items: [
3962
- { description: "Average response time", label: "Same-Day Quotes", value: "24h" },
3963
- { description: "Projects completed", label: "Completed Jobs", value: "1,200+" },
3964
- { description: "Client satisfaction score", label: "Satisfaction", value: "4.9/5" }
3965
- ],
3966
- subtitle: "Highlight measurable outcomes to build trust quickly.",
3967
- title: "Performance Highlights"
3968
- })
4488
+ advanced: true,
4489
+ group: "advanced",
4490
+ key: "rotateIntervalSeconds",
4491
+ label: "Rotate Interval Seconds",
4492
+ max: 30,
4493
+ min: 2,
4494
+ type: "number"
4495
+ },
4496
+ ...layoutFieldSet,
4497
+ ...typographyFieldSet,
4498
+ ...styleFieldSet,
4499
+ ...commonAdvanced
4500
+ ]
4501
+ }
3969
4502
  };
3970
- var nodeTypeLabels = {
3971
- bookingEmbed: "Booking Embed",
3972
- beforeAfter: "Before / After",
3973
- cta: "Call To Action",
3974
- faq: "FAQ",
3975
- featureGrid: "Feature Grid",
3976
- formEmbed: "Form Embed",
3977
- hero: "Hero",
3978
- logoWall: "Logo Wall",
3979
- media: "Media",
3980
- richText: "Rich Text",
3981
- stats: "Stats",
3982
- testimonials: "Testimonials"
4503
+
4504
+ // src/studio-pages/pageService.ts
4505
+ var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
4506
+ var toRecordArray = (value) => Array.isArray(value) ? value.filter((item) => isRecord5(item)) : [];
4507
+ var getRelationID = (value) => {
4508
+ if (typeof value === "number" || typeof value === "string") {
4509
+ return value;
4510
+ }
4511
+ if (!isRecord5(value)) {
4512
+ return null;
4513
+ }
4514
+ const id = value.id;
4515
+ return typeof id === "number" || typeof id === "string" ? id : null;
3983
4516
  };
3984
- var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
3985
- type,
3986
- displayName: nodeTypeLabels[type] || type,
3987
- description: `Page node for ${nodeTypeLabels[type] || type}`,
3988
- getDefaultData: () => {
3989
- const migrated = migrateBlockToSettingsV2(structuredClone(defaultNodeData[type]));
3990
- const { blockType: _ignoredBlockType, ...data } = migrated;
3991
- return data;
4517
+ var asLayoutArray = (value) => toRecordArray(value);
4518
+ var hydrateRelationship = (valueFromStudio, valueFromLayout) => {
4519
+ if (isRecord5(valueFromStudio)) {
4520
+ return valueFromStudio;
3992
4521
  }
3993
- }));
3994
- var validatePageDocument = (document) => {
3995
- const issues = [];
3996
- if (!document.title || document.title.trim().length === 0) {
3997
- issues.push({
3998
- code: "pages.title.required",
3999
- message: "Page title is required before publishing.",
4000
- path: "title",
4001
- severity: "error"
4002
- });
4522
+ if (!isRecord5(valueFromLayout)) {
4523
+ return valueFromStudio;
4003
4524
  }
4004
- if (document.nodes.length === 0) {
4005
- issues.push({
4006
- code: "pages.nodes.required",
4007
- message: "At least one section is required.",
4008
- path: "nodes",
4009
- severity: "error"
4010
- });
4525
+ const studioID = getRelationID(valueFromStudio);
4526
+ const layoutID = getRelationID(valueFromLayout);
4527
+ if (studioID === null || layoutID === null || String(studioID) !== String(layoutID)) {
4528
+ return valueFromStudio;
4011
4529
  }
4012
- document.nodes.forEach((node, index) => {
4013
- if (node.type === "hero" && typeof node.data.headline !== "string") {
4014
- issues.push({
4015
- code: "pages.hero.headline",
4016
- message: "Hero section requires a headline.",
4017
- path: `nodes.${index}.data.headline`,
4018
- severity: "error"
4019
- });
4020
- }
4021
- });
4022
- return issues;
4530
+ return valueFromLayout;
4023
4531
  };
4024
- var pagePaletteGroups = [
4025
- {
4026
- id: "page-core",
4027
- label: "Core Sections",
4028
- items: [
4029
- { nodeType: "hero", title: "Hero", description: "Top-of-page headline and CTA" },
4030
- { nodeType: "featureGrid", title: "Feature Grid", description: "Service or value cards" },
4031
- { nodeType: "stats", title: "Stats", description: "Key performance highlights" },
4032
- { nodeType: "logoWall", title: "Logo Wall", description: "Trust logos and badges" },
4033
- { nodeType: "beforeAfter", title: "Before / After", description: "Visual before-and-after gallery" },
4034
- { nodeType: "richText", title: "Rich Text", description: "Long-form content area" },
4035
- { nodeType: "media", title: "Media", description: "Image/video section" },
4036
- { nodeType: "testimonials", title: "Testimonials", description: "Social proof quotes" },
4037
- { nodeType: "faq", title: "FAQ", description: "Question-and-answer section" },
4038
- { nodeType: "cta", title: "Call To Action", description: "Conversion strip with button" },
4039
- { nodeType: "formEmbed", title: "Form Embed", description: "Lead capture form" },
4040
- { nodeType: "bookingEmbed", title: "Booking Embed", description: "Scheduling panel" }
4041
- ]
4042
- }
4043
- ];
4044
- var pageInspectorPanelRegistry = Object.keys(defaultNodeData).map((nodeType) => ({
4045
- nodeType,
4046
- panelID: `${nodeType}-panel`,
4047
- panelLabel: `${nodeTypeLabels[nodeType] || nodeType} Settings`
4048
- }));
4049
- var resolvePanelFields = (nodeType) => inspectorDefinitionByBlockType[nodeType]?.fields.map((field) => ({
4050
- advanced: field.advanced,
4051
- group: field.group,
4052
- inlineEditable: field.inlineEditable,
4053
- key: field.key,
4054
- label: field.label,
4055
- type: field.type
4056
- })) || [];
4057
- var pageInspectorPanels = pageInspectorPanelRegistry.map((entry) => ({
4058
- fields: resolvePanelFields(entry.nodeType),
4059
- id: entry.panelID,
4060
- label: entry.panelLabel,
4061
- nodeType: entry.nodeType
4062
- }));
4063
- var pageStudioModuleManifest = {
4064
- id: "studio-pages",
4065
- version: "0.1.0-beta.0",
4066
- displayName: "Pages Studio Module",
4067
- nodeTypes: pageNodeTypes,
4068
- paletteGroups: pagePaletteGroups,
4069
- inspectorPanels: pageInspectorPanels,
4070
- validators: [validatePageDocument],
4071
- migrations: pageStudioMigrations,
4072
- compiler: {
4073
- compileNode: (node) => {
4074
- const normalized = migrateBlockToSettingsV2({
4075
- blockType: node.type,
4076
- ...node.data
4532
+ var hydrateDocumentWithLayoutRelations = (document, layout) => {
4533
+ const nextNodes = document.nodes.map((node, index) => {
4534
+ const layoutBlock = layout[index];
4535
+ if (!isRecord5(layoutBlock)) {
4536
+ return node;
4537
+ }
4538
+ if (typeof layoutBlock.blockType !== "string" || layoutBlock.blockType !== node.type) {
4539
+ return node;
4540
+ }
4541
+ const nextData = { ...node.data };
4542
+ if (node.type === "hero") {
4543
+ nextData.media = hydrateRelationship(nextData.media, layoutBlock.media);
4544
+ }
4545
+ if (node.type === "media") {
4546
+ nextData.image = hydrateRelationship(nextData.image, layoutBlock.image);
4547
+ }
4548
+ if (node.type === "featureGrid" || node.type === "logoWall" || node.type === "beforeAfter") {
4549
+ const studioItems = Array.isArray(nextData.items) ? nextData.items : [];
4550
+ const layoutItems = Array.isArray(layoutBlock.items) ? layoutBlock.items : [];
4551
+ nextData.items = studioItems.map((rawStudioItem, itemIndex) => {
4552
+ if (!isRecord5(rawStudioItem)) {
4553
+ return rawStudioItem;
4554
+ }
4555
+ const layoutItem = layoutItems[itemIndex];
4556
+ if (!isRecord5(layoutItem)) {
4557
+ return rawStudioItem;
4558
+ }
4559
+ const nextItem = { ...rawStudioItem };
4560
+ nextItem.media = hydrateRelationship(nextItem.media, layoutItem.media);
4561
+ nextItem.beforeMedia = hydrateRelationship(nextItem.beforeMedia, layoutItem.beforeMedia);
4562
+ nextItem.afterMedia = hydrateRelationship(nextItem.afterMedia, layoutItem.afterMedia);
4563
+ return nextItem;
4077
4564
  });
4078
- return {
4079
- id: node.id,
4080
- ...normalized
4081
- };
4082
4565
  }
4083
- },
4084
- permissions: [
4085
- { action: "read", role: "admin" },
4086
- { action: "read", role: "editor" },
4087
- { action: "read", role: "client" },
4088
- { action: "update", role: "admin" },
4089
- { action: "update", role: "editor" },
4090
- { action: "update", role: "client" },
4091
- { action: "publish", role: "admin" },
4092
- { action: "publish", role: "editor" }
4093
- ]
4094
- };
4095
- var ensureNodeID = (inputID, index) => {
4096
- if (typeof inputID === "string" && inputID.length > 0) {
4097
- return inputID;
4098
- }
4099
- return `node-${index + 1}`;
4100
- };
4101
- var layoutToStudioDocument = (layout, title, metadata) => {
4102
- const nodes = layout.filter((block) => typeof block.blockType === "string").map((rawBlock, index) => {
4103
- const block = migrateBlockToSettingsV2(rawBlock);
4104
- const blockType = String(block.blockType);
4105
- const { id, blockType: _ignoredBlockType, ...data } = block;
4106
4566
  return {
4107
- id: ensureNodeID(id, index),
4108
- type: blockType,
4109
- data
4567
+ ...node,
4568
+ data: nextData
4110
4569
  };
4111
4570
  });
4112
4571
  return {
4113
- metadata,
4114
- schemaVersion: 1,
4115
- title,
4116
- nodes,
4117
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4572
+ ...document,
4573
+ nodes: nextNodes
4118
4574
  };
4119
4575
  };
4120
- var studioDocumentToLayout = (document) => document.nodes.map(
4121
- (node) => migrateBlockToSettingsV2({
4122
- id: node.id,
4123
- blockType: node.type,
4124
- ...node.data
4125
- })
4126
- );
4127
- var createDefaultStudioDocument = (title) => ({
4128
- metadata: {},
4576
+ var normalizeDocument = (document) => ({
4577
+ ...document,
4129
4578
  schemaVersion: 1,
4130
- title,
4131
- nodes: [],
4132
4579
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4133
4580
  });
4134
-
4135
- // src/nextjs/queries/pages.ts
4136
- var PAGE_QUERY_CACHE_VERSION = "v4-published-only-public";
4137
- function withStudioDocumentLayout(page) {
4138
- if (!page) {
4139
- return null;
4140
- }
4141
- try {
4142
- const studioDocument = assertStudioDocumentV1(page.studioDocument);
4143
- const compiledLayout = studioDocumentToLayout(studioDocument);
4144
- if (Array.isArray(compiledLayout) && compiledLayout.length > 0) {
4145
- return {
4146
- ...page,
4147
- layout: compiledLayout
4148
- };
4149
- }
4150
- } catch {
4151
- }
4152
- return page;
4153
- }
4154
- function normalizePath(segments) {
4155
- if (!segments || segments.length === 0) {
4156
- return "/";
4157
- }
4158
- const cleaned = segments.map((segment) => segment.trim()).filter(Boolean).join("/");
4159
- return cleaned.length > 0 ? `/${cleaned}` : "/";
4160
- }
4161
- async function queryPageByPath(payload, path2, draft) {
4162
- const pathWhere = {
4163
- path: {
4164
- equals: path2
4581
+ var normalizeLegacyHeroDefaults = (document) => {
4582
+ const nodes = document.nodes.map((node) => {
4583
+ if (node.type !== "hero" || !isRecord5(node.data)) {
4584
+ return node;
4165
4585
  }
4166
- };
4167
- const publishedWhere = {
4168
- _status: {
4169
- equals: "published"
4586
+ const nextData = { ...node.data };
4587
+ const heroBackgroundColor = typeof nextData.backgroundColor === "string" ? nextData.backgroundColor.trim().toLowerCase() : "";
4588
+ const sectionBackgroundMode = typeof nextData.sectionBackgroundMode === "string" ? nextData.sectionBackgroundMode : "none";
4589
+ const hasBackgroundImageURL = typeof nextData.backgroundImageURL === "string" && nextData.backgroundImageURL.trim().length > 0;
4590
+ const mediaRelation = getRelationID(nextData.media);
4591
+ if (heroBackgroundColor === "#124a37" && sectionBackgroundMode === "none" && !hasBackgroundImageURL && mediaRelation === null) {
4592
+ nextData.backgroundColor = "";
4170
4593
  }
4171
- };
4172
- const result = await payload.find({
4173
- collection: "pages",
4174
- depth: 2,
4175
- draft,
4176
- limit: 1,
4177
- overrideAccess: false,
4178
- where: draft ? pathWhere : { and: [pathWhere, publishedWhere] }
4179
- });
4180
- if (result.docs.length > 0) {
4181
- return withStudioDocumentLayout(result.docs[0] || null);
4182
- }
4183
- if (path2 === "/") {
4184
- const homeWhere = {
4185
- slug: {
4186
- equals: "home"
4187
- }
4594
+ return {
4595
+ ...node,
4596
+ data: nextData
4188
4597
  };
4189
- const homeResult = await payload.find({
4190
- collection: "pages",
4191
- depth: 2,
4192
- draft,
4193
- limit: 1,
4194
- overrideAccess: false,
4195
- where: draft ? homeWhere : { and: [homeWhere, publishedWhere] }
4196
- });
4197
- return withStudioDocumentLayout(homeResult.docs[0] || null);
4598
+ });
4599
+ return {
4600
+ ...document,
4601
+ nodes
4602
+ };
4603
+ };
4604
+ var assertCanPublish = (issues) => {
4605
+ const publishErrors = issues.filter((issue) => issue.severity === "error");
4606
+ if (publishErrors.length > 0) {
4607
+ throw new Error(`Cannot publish page: ${publishErrors[0].message}`);
4198
4608
  }
4199
- return null;
4200
- }
4201
- function createPageQueries(getPayloadClient, contentTag = "website-content") {
4202
- const getPublishedPageByPathCached = (0, import_cache.unstable_cache)(
4203
- async (path2) => {
4204
- const payload = await getPayloadClient();
4205
- return queryPageByPath(payload, path2, false);
4206
- },
4207
- ["page-by-path", PAGE_QUERY_CACHE_VERSION],
4208
- { tags: [contentTag] }
4209
- );
4210
- async function getPageBySegments(segments, draft = false) {
4211
- const path2 = normalizePath(segments);
4212
- const payload = await getPayloadClient();
4213
- if (draft) {
4214
- return queryPageByPath(payload, path2, true);
4609
+ };
4610
+ var getStudioDocumentFromPage = (doc) => {
4611
+ const title = typeof doc.title === "string" ? doc.title : void 0;
4612
+ const layout = asLayoutArray(doc.layout);
4613
+ try {
4614
+ const studioDocument = assertStudioDocumentV1(doc.studioDocument);
4615
+ if (layout.length > 0) {
4616
+ return hydrateDocumentWithLayoutRelations(studioDocument, layout);
4215
4617
  }
4216
- return getPublishedPageByPathCached(path2);
4618
+ return studioDocument;
4619
+ } catch {
4620
+ if (layout.length > 0) {
4621
+ return layoutToStudioDocument(layout, title);
4622
+ }
4623
+ return createEmptyStudioDocument(title);
4217
4624
  }
4218
- async function listPublishedPagePaths() {
4219
- const payload = await getPayloadClient();
4220
- const pages = await payload.find({
4221
- collection: "pages",
4222
- depth: 0,
4223
- draft: false,
4224
- limit: 1e3,
4225
- pagination: false,
4625
+ };
4626
+ var createStudioPageService = ({
4627
+ collectionSlug = "pages",
4628
+ modules,
4629
+ payload,
4630
+ user
4631
+ }) => ({
4632
+ getPageForStudio: async (pageID) => {
4633
+ const page = await payload.findByID({
4634
+ collection: collectionSlug,
4635
+ depth: 2,
4636
+ draft: true,
4637
+ id: pageID,
4226
4638
  overrideAccess: false,
4227
- where: {
4228
- _status: {
4229
- equals: "published"
4230
- }
4231
- }
4639
+ user
4232
4640
  });
4233
- return pages.docs.map((doc) => doc.path).filter((path2) => typeof path2 === "string" && path2.length > 0);
4641
+ const studioDocument = getStudioDocumentFromPage(page);
4642
+ return {
4643
+ id: String(page.id),
4644
+ slug: typeof page.slug === "string" ? page.slug : "",
4645
+ studioDocument,
4646
+ title: typeof page.title === "string" ? page.title : "Untitled Page"
4647
+ };
4648
+ },
4649
+ validateStudioDocument: (document) => validateStudioDocument(document, modules),
4650
+ saveDraft: async (pageID, document, _metadata) => {
4651
+ const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document));
4652
+ const compileResult = compileStudioDocument(normalizedDocument, modules);
4653
+ await payload.update({
4654
+ collection: collectionSlug,
4655
+ data: {
4656
+ _status: "draft",
4657
+ layout: compileResult.layout,
4658
+ studioDocument: normalizedDocument,
4659
+ studioValidationIssues: compileResult.issues,
4660
+ title: normalizedDocument.title
4661
+ },
4662
+ id: pageID,
4663
+ overrideAccess: false,
4664
+ user
4665
+ });
4666
+ return {
4667
+ id: pageID,
4668
+ status: "draft"
4669
+ };
4670
+ },
4671
+ publish: async (pageID, document, _metadata) => {
4672
+ const normalizedDocument = normalizeLegacyHeroDefaults(normalizeDocument(document));
4673
+ const compileResult = compileStudioDocument(normalizedDocument, modules);
4674
+ assertCanPublish(compileResult.issues);
4675
+ await payload.update({
4676
+ collection: collectionSlug,
4677
+ data: {
4678
+ _status: "published",
4679
+ layout: compileResult.layout,
4680
+ studioDocument: normalizedDocument,
4681
+ studioValidationIssues: compileResult.issues,
4682
+ title: normalizedDocument.title
4683
+ },
4684
+ id: pageID,
4685
+ overrideAccess: false,
4686
+ user
4687
+ });
4688
+ return {
4689
+ id: pageID,
4690
+ status: "published"
4691
+ };
4234
4692
  }
4235
- function pathToSegments(path2) {
4236
- if (!path2 || path2 === "/") {
4237
- return [];
4238
- }
4239
- return path2.split("/").filter(Boolean);
4693
+ });
4694
+ var toEditorInitialDoc = (payloadPage) => ({
4695
+ layout: studioDocumentToLayout(payloadPage.studioDocument),
4696
+ slug: typeof payloadPage.slug === "string" ? payloadPage.slug : "",
4697
+ studioDocument: payloadPage.studioDocument,
4698
+ title: payloadPage.title
4699
+ });
4700
+
4701
+ // src/studio-pages/migrations.ts
4702
+ var isRecord6 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
4703
+ var assertPageStudioDocumentV1 = (value) => {
4704
+ if (!isRecord6(value)) {
4705
+ throw new Error("Studio document must be an object");
4706
+ }
4707
+ if (value.schemaVersion !== 1) {
4708
+ throw new Error("Unsupported studio schemaVersion");
4709
+ }
4710
+ if (!Array.isArray(value.nodes)) {
4711
+ throw new Error("Studio document nodes must be an array");
4240
4712
  }
4713
+ const nodes = value.nodes.map((node, index) => {
4714
+ if (!isRecord6(node)) {
4715
+ throw new Error(`Node at index ${index} must be an object`);
4716
+ }
4717
+ if (typeof node.id !== "string" || node.id.length === 0) {
4718
+ throw new Error(`Node at index ${index} has invalid id`);
4719
+ }
4720
+ if (typeof node.type !== "string" || node.type.length === 0) {
4721
+ throw new Error(`Node at index ${index} has invalid type`);
4722
+ }
4723
+ if (!isRecord6(node.data)) {
4724
+ throw new Error(`Node at index ${index} has invalid data`);
4725
+ }
4726
+ return {
4727
+ data: node.data,
4728
+ id: node.id,
4729
+ type: node.type
4730
+ };
4731
+ });
4241
4732
  return {
4242
- getPageBySegments,
4243
- listPublishedPagePaths,
4244
- pathToSegments
4733
+ metadata: isRecord6(value.metadata) ? value.metadata : void 0,
4734
+ nodes,
4735
+ schemaVersion: 1,
4736
+ title: typeof value.title === "string" ? value.title : void 0,
4737
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : void 0
4245
4738
  };
4246
- }
4739
+ };
4740
+ var migratePageNodeToSettingsV2 = (node) => {
4741
+ const normalized = migrateBlockToSettingsV2({
4742
+ blockType: node.type,
4743
+ ...node.data
4744
+ });
4745
+ const { blockType: _ignoredBlockType, ...data } = normalized;
4746
+ return {
4747
+ ...node,
4748
+ data
4749
+ };
4750
+ };
4751
+ var migratePageDocumentSettingsToV2 = (value) => {
4752
+ const current = assertPageStudioDocumentV1(value);
4753
+ return {
4754
+ ...current,
4755
+ nodes: current.nodes.map(migratePageNodeToSettingsV2)
4756
+ };
4757
+ };
4758
+ var pageStudioMigrations = [
4759
+ {
4760
+ fromVersion: 1,
4761
+ migrate: migratePageDocumentSettingsToV2,
4762
+ toVersion: 1
4763
+ }
4764
+ ];
4247
4765
 
4248
- // src/nextjs/queries/site.ts
4249
- var import_cache2 = require("next/cache");
4250
- function createSiteQueries(getPayloadClient, contentTag = "website-content") {
4251
- const getSiteSettingsCached = (0, import_cache2.unstable_cache)(
4252
- async () => {
4253
- const payload = await getPayloadClient();
4254
- const settings = await payload.findGlobal({
4255
- slug: "site-settings",
4256
- depth: 1
4257
- });
4258
- return settings;
4259
- },
4260
- ["site-settings-global"],
4261
- { tags: [contentTag] }
4262
- );
4263
- const getHeaderCached = (0, import_cache2.unstable_cache)(
4264
- async () => {
4265
- const payload = await getPayloadClient();
4266
- const header = await payload.findGlobal({
4267
- slug: "header",
4268
- depth: 1
4269
- });
4270
- return header;
4271
- },
4272
- ["header-global"],
4273
- { tags: [contentTag] }
4274
- );
4275
- const getFooterCached = (0, import_cache2.unstable_cache)(
4276
- async () => {
4277
- const payload = await getPayloadClient();
4278
- const footer = await payload.findGlobal({
4279
- slug: "footer",
4280
- depth: 1
4281
- });
4282
- return footer;
4283
- },
4284
- ["footer-global"],
4285
- { tags: [contentTag] }
4286
- );
4287
- const getSocialMediaCached = (0, import_cache2.unstable_cache)(
4288
- async () => {
4289
- const payload = await getPayloadClient();
4290
- const socialMedia = await payload.findGlobal({
4291
- slug: "social-media",
4292
- depth: 1
4293
- });
4294
- return socialMedia;
4766
+ // src/studio-pages/index.ts
4767
+ var withSectionStyleDefaults = (value) => ({
4768
+ ...sectionStyleDefaults,
4769
+ ...value
4770
+ });
4771
+ var defaultNodeData = {
4772
+ bookingEmbed: {
4773
+ ...withSectionStyleDefaults({}),
4774
+ buttonHref: "/contact",
4775
+ buttonLabel: "Book Consultation",
4776
+ description: "Let visitors book a consultation.",
4777
+ title: "Book a Time"
4778
+ },
4779
+ beforeAfter: withSectionStyleDefaults({
4780
+ itemsPerRow: 2,
4781
+ items: [
4782
+ {
4783
+ description: "Before and after result summary.",
4784
+ imageCornerStyle: "rounded",
4785
+ imageFit: "cover",
4786
+ imagePosition: "center",
4787
+ label: "Project One"
4788
+ }
4789
+ ],
4790
+ subtitle: "Show visual proof from real projects.",
4791
+ title: "Before & After Results"
4792
+ }),
4793
+ cta: {
4794
+ ...withSectionStyleDefaults({}),
4795
+ backgroundColor: "",
4796
+ bullets: [],
4797
+ buttonHref: "/contact",
4798
+ buttonLabel: "Contact Us",
4799
+ description: "Optional supporting copy.",
4800
+ eyebrow: "",
4801
+ headline: "Ready to get started?",
4802
+ imageURL: "",
4803
+ style: "light"
4804
+ },
4805
+ faq: {
4806
+ ...withSectionStyleDefaults({}),
4807
+ items: [{ answer: "Answer goes here.", question: "Frequently asked question?" }],
4808
+ title: "Frequently Asked Questions"
4809
+ },
4810
+ featureGrid: {
4811
+ ...withSectionStyleDefaults({}),
4812
+ itemsPerRow: 3,
4813
+ items: [
4814
+ { description: "Explain this point.", iconType: "badge", icon: "01", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature One" },
4815
+ { description: "Explain this point.", iconType: "badge", icon: "02", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Two" },
4816
+ { description: "Explain this point.", iconType: "badge", icon: "03", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Three" }
4817
+ ],
4818
+ subtitle: "",
4819
+ title: "Section Title",
4820
+ variant: "cards"
4821
+ },
4822
+ formEmbed: {
4823
+ ...withSectionStyleDefaults({}),
4824
+ description: "Collect lead details from visitors.",
4825
+ formType: "quote",
4826
+ submitLabel: "Submit",
4827
+ title: "Request a Quote"
4828
+ },
4829
+ hero: {
4830
+ ...withSectionStyleDefaults({}),
4831
+ backgroundColor: "",
4832
+ backgroundImageURL: "",
4833
+ backgroundOverlayMode: "none",
4834
+ backgroundOverlayOpacity: 45,
4835
+ backgroundOverlayColor: "#000000",
4836
+ backgroundOverlayGradientFrom: "#334b63",
4837
+ backgroundOverlayGradientTo: "#496582",
4838
+ backgroundOverlayGradientAngle: "135",
4839
+ backgroundOverlayGradientFromStrength: 100,
4840
+ backgroundOverlayGradientToStrength: 100,
4841
+ backgroundOverlayGradientStart: 0,
4842
+ backgroundOverlayGradientEnd: 100,
4843
+ backgroundOverlayGradientFeather: 100,
4844
+ backgroundImageCornerStyle: "rounded",
4845
+ backgroundImageFit: "cover",
4846
+ backgroundImagePosition: "center",
4847
+ heroHeight: "sm",
4848
+ headline: "New Hero Section",
4849
+ kicker: "Optional kicker",
4850
+ primaryHref: "/contact",
4851
+ primaryLabel: "Primary Action",
4852
+ secondaryHref: "/services",
4853
+ secondaryLabel: "Secondary Action",
4854
+ subheadline: "Describe your offer clearly for website visitors.",
4855
+ variant: "default"
4856
+ },
4857
+ media: {
4858
+ ...withSectionStyleDefaults({}),
4859
+ caption: "Add a caption",
4860
+ imageCornerStyle: "rounded",
4861
+ imageFit: "cover",
4862
+ imagePosition: "center",
4863
+ size: "default"
4864
+ },
4865
+ logoWall: withSectionStyleDefaults({
4866
+ items: [
4867
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 1" },
4868
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 2" },
4869
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 3" }
4870
+ ],
4871
+ subtitle: "Show logos from trusted associations, partners, or collaborators.",
4872
+ title: "Trusted by Great Partners"
4873
+ }),
4874
+ richText: {
4875
+ ...withSectionStyleDefaults({}),
4876
+ cards: [],
4877
+ content: {
4878
+ root: {
4879
+ children: [
4880
+ {
4881
+ children: [
4882
+ {
4883
+ detail: 0,
4884
+ format: 0,
4885
+ mode: "normal",
4886
+ style: "",
4887
+ text: "Write your content here.",
4888
+ type: "text",
4889
+ version: 1
4890
+ }
4891
+ ],
4892
+ direction: "ltr",
4893
+ format: "",
4894
+ indent: 0,
4895
+ type: "paragraph",
4896
+ version: 1
4897
+ }
4898
+ ],
4899
+ direction: "ltr",
4900
+ format: "",
4901
+ indent: 0,
4902
+ type: "root",
4903
+ version: 1
4904
+ }
4295
4905
  },
4296
- ["social-media-global"],
4297
- { tags: [contentTag] }
4298
- );
4299
- async function getSiteSettings(draft = false) {
4300
- if (draft) {
4301
- const payload = await getPayloadClient();
4302
- const settings = await payload.findGlobal({
4303
- slug: "site-settings",
4304
- depth: 1,
4305
- draft: true
4306
- });
4307
- return settings;
4308
- }
4309
- return getSiteSettingsCached();
4906
+ statsItems: [],
4907
+ title: "Section Heading",
4908
+ width: "normal"
4909
+ },
4910
+ testimonials: {
4911
+ ...withSectionStyleDefaults({}),
4912
+ autoRotate: true,
4913
+ items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here.", rating: 5 }],
4914
+ rotateIntervalSeconds: 7,
4915
+ title: "What Customers Say",
4916
+ visibleCount: 3
4917
+ },
4918
+ stats: withSectionStyleDefaults({
4919
+ items: [
4920
+ { description: "Average first response", label: "Response Time", value: "24h" },
4921
+ { description: "Client satisfaction score", label: "Satisfaction", value: "4.9/5" },
4922
+ { description: "Recent projects or clients served", label: "Recent Work", value: "150+" }
4923
+ ],
4924
+ subtitle: "Highlight measurable outcomes to build trust quickly.",
4925
+ title: "Performance Highlights"
4926
+ })
4927
+ };
4928
+ var nodeTypeLabels = {
4929
+ bookingEmbed: "Booking Embed",
4930
+ beforeAfter: "Before / After",
4931
+ cta: "Call To Action",
4932
+ faq: "FAQ",
4933
+ featureGrid: "Feature Grid",
4934
+ formEmbed: "Form Embed",
4935
+ hero: "Hero",
4936
+ logoWall: "Logo Wall",
4937
+ media: "Media",
4938
+ richText: "Rich Text",
4939
+ stats: "Stats",
4940
+ testimonials: "Testimonials"
4941
+ };
4942
+ var pageNodeTypes = Object.keys(defaultNodeData).map((type) => ({
4943
+ type,
4944
+ displayName: nodeTypeLabels[type] || type,
4945
+ description: `Page node for ${nodeTypeLabels[type] || type}`,
4946
+ getDefaultData: () => {
4947
+ const migrated = migrateBlockToSettingsV2(structuredClone(defaultNodeData[type]));
4948
+ const { blockType: _ignoredBlockType, ...data } = migrated;
4949
+ return data;
4310
4950
  }
4311
- async function getHeader(draft = false) {
4312
- if (draft) {
4313
- const payload = await getPayloadClient();
4314
- const header = await payload.findGlobal({
4315
- slug: "header",
4316
- depth: 1,
4317
- draft: true
4318
- });
4319
- return header;
4320
- }
4321
- return getHeaderCached();
4951
+ }));
4952
+ var validatePageDocument = (document) => {
4953
+ const issues = [];
4954
+ if (!document.title || document.title.trim().length === 0) {
4955
+ issues.push({
4956
+ code: "pages.title.required",
4957
+ message: "Page title is required before publishing.",
4958
+ path: "title",
4959
+ severity: "error"
4960
+ });
4322
4961
  }
4323
- async function getFooter(draft = false) {
4324
- if (draft) {
4325
- const payload = await getPayloadClient();
4326
- const footer = await payload.findGlobal({
4327
- slug: "footer",
4328
- depth: 1,
4329
- draft: true
4330
- });
4331
- return footer;
4332
- }
4333
- return getFooterCached();
4962
+ if (document.nodes.length === 0) {
4963
+ issues.push({
4964
+ code: "pages.nodes.required",
4965
+ message: "At least one section is required.",
4966
+ path: "nodes",
4967
+ severity: "error"
4968
+ });
4334
4969
  }
4335
- async function getSocialMedia(draft = false) {
4336
- if (draft) {
4337
- const payload = await getPayloadClient();
4338
- const socialMedia = await payload.findGlobal({
4339
- slug: "social-media",
4340
- depth: 1,
4341
- draft: true
4970
+ document.nodes.forEach((node, index) => {
4971
+ if (node.type === "hero" && typeof node.data.headline !== "string") {
4972
+ issues.push({
4973
+ code: "pages.hero.headline",
4974
+ message: "Hero section requires a headline.",
4975
+ path: `nodes.${index}.data.headline`,
4976
+ severity: "error"
4342
4977
  });
4343
- return socialMedia;
4344
4978
  }
4345
- return getSocialMediaCached();
4346
- }
4347
- return {
4348
- getSiteSettings,
4349
- getHeader,
4350
- getFooter,
4351
- getSocialMedia
4352
- };
4353
- }
4354
-
4355
- // src/nextjs/utilities/media.ts
4356
- function resolveMedia(media) {
4357
- if (!media) {
4358
- return null;
4359
- }
4360
- if (typeof media === "number" || typeof media === "string") {
4361
- return null;
4362
- }
4363
- if (media.url) {
4364
- return {
4365
- url: media.url,
4366
- alt: media.alt || ""
4367
- };
4368
- }
4369
- if (media.filename) {
4370
- return {
4371
- url: `/media/${media.filename}`,
4372
- alt: media.alt || ""
4373
- };
4374
- }
4375
- return null;
4376
- }
4377
-
4378
- // src/nextjs/utilities/socialMedia.ts
4379
- function resolveSocialMediaLinks(data) {
4380
- const profiles = data?.profiles;
4381
- if (!profiles || typeof profiles !== "object") {
4382
- return [];
4979
+ });
4980
+ return issues;
4981
+ };
4982
+ var pagePaletteGroups = [
4983
+ {
4984
+ id: "page-core",
4985
+ label: "Core Sections",
4986
+ items: [
4987
+ { nodeType: "hero", title: "Hero", description: "Top-of-page headline and CTA" },
4988
+ { nodeType: "featureGrid", title: "Feature Grid", description: "Service or value cards" },
4989
+ { nodeType: "stats", title: "Stats", description: "Key performance highlights" },
4990
+ { nodeType: "logoWall", title: "Logo Wall", description: "Trust logos and badges" },
4991
+ { nodeType: "beforeAfter", title: "Before / After", description: "Visual before-and-after gallery" },
4992
+ { nodeType: "richText", title: "Rich Text", description: "Long-form content area" },
4993
+ { nodeType: "media", title: "Media", description: "Image/video section" },
4994
+ { nodeType: "testimonials", title: "Testimonials", description: "Social proof quotes" },
4995
+ { nodeType: "faq", title: "FAQ", description: "Question-and-answer section" },
4996
+ { nodeType: "cta", title: "Call To Action", description: "Conversion strip with button" },
4997
+ { nodeType: "formEmbed", title: "Form Embed", description: "Lead capture form" },
4998
+ { nodeType: "bookingEmbed", title: "Booking Embed", description: "Scheduling panel" }
4999
+ ]
4383
5000
  }
4384
- return SOCIAL_MEDIA_PLATFORMS.reduce((acc, platform) => {
4385
- const profile = profiles[platform];
4386
- if (!profile || typeof profile !== "object") {
4387
- return acc;
4388
- }
4389
- const url = typeof profile.url === "string" ? profile.url.trim() : "";
4390
- if (!url) {
4391
- return acc;
5001
+ ];
5002
+ var pageInspectorPanelRegistry = Object.keys(defaultNodeData).map((nodeType) => ({
5003
+ nodeType,
5004
+ panelID: `${nodeType}-panel`,
5005
+ panelLabel: `${nodeTypeLabels[nodeType] || nodeType} Settings`
5006
+ }));
5007
+ var resolvePanelFields = (nodeType) => inspectorDefinitionByBlockType[nodeType]?.fields.map((field) => ({
5008
+ advanced: field.advanced,
5009
+ group: field.group,
5010
+ inlineEditable: field.inlineEditable,
5011
+ key: field.key,
5012
+ label: field.label,
5013
+ type: field.type
5014
+ })) || [];
5015
+ var pageInspectorPanels = pageInspectorPanelRegistry.map((entry) => ({
5016
+ fields: resolvePanelFields(entry.nodeType),
5017
+ id: entry.panelID,
5018
+ label: entry.panelLabel,
5019
+ nodeType: entry.nodeType
5020
+ }));
5021
+ var pageStudioModuleManifest = {
5022
+ id: "studio-pages",
5023
+ version: "0.1.0-beta.0",
5024
+ displayName: "Pages Studio Module",
5025
+ nodeTypes: pageNodeTypes,
5026
+ paletteGroups: pagePaletteGroups,
5027
+ inspectorPanels: pageInspectorPanels,
5028
+ validators: [validatePageDocument],
5029
+ migrations: pageStudioMigrations,
5030
+ compiler: {
5031
+ compileNode: (node) => {
5032
+ const normalized = migrateBlockToSettingsV2({
5033
+ blockType: node.type,
5034
+ ...node.data
5035
+ });
5036
+ return {
5037
+ id: node.id,
5038
+ ...normalized
5039
+ };
4392
5040
  }
4393
- const icon = typeof profile.icon === "string" && profile.icon.trim().length > 0 ? profile.icon.trim() : SOCIAL_MEDIA_DEFAULT_ICON_BY_PLATFORM[platform];
4394
- acc.push({
4395
- icon,
4396
- label: SOCIAL_MEDIA_PLATFORM_LABELS[platform],
4397
- platform,
4398
- url
4399
- });
4400
- return acc;
4401
- }, []);
4402
- }
5041
+ },
5042
+ permissions: [
5043
+ { action: "read", role: "admin" },
5044
+ { action: "read", role: "editor" },
5045
+ { action: "read", role: "client" },
5046
+ { action: "update", role: "admin" },
5047
+ { action: "update", role: "editor" },
5048
+ { action: "update", role: "client" },
5049
+ { action: "publish", role: "admin" },
5050
+ { action: "publish", role: "editor" }
5051
+ ]
5052
+ };
4403
5053
  // Annotate the CommonJS export names for ESM import in node:
4404
5054
  0 && (module.exports = {
4405
5055
  admin,