@orion-studios/payload-studio 0.5.0-beta.7 → 0.5.0-beta.70

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 (67) hide show
  1. package/README.md +20 -0
  2. package/dist/admin/client.d.mts +2 -0
  3. package/dist/admin/client.d.ts +2 -0
  4. package/dist/admin/client.js +779 -137
  5. package/dist/admin/client.mjs +769 -129
  6. package/dist/admin/index.d.mts +1 -1
  7. package/dist/admin/index.d.ts +1 -1
  8. package/dist/admin/index.js +100 -8
  9. package/dist/admin/index.mjs +3 -1
  10. package/dist/admin-app/client.d.mts +7 -0
  11. package/dist/admin-app/client.d.ts +7 -0
  12. package/dist/admin-app/client.js +1262 -3
  13. package/dist/admin-app/client.mjs +1164 -2
  14. package/dist/admin-app/index.d.mts +1 -1
  15. package/dist/admin-app/index.d.ts +1 -1
  16. package/dist/admin-app/index.js +167 -0
  17. package/dist/admin-app/index.mjs +13 -1
  18. package/dist/admin-app/styles.css +229 -0
  19. package/dist/blocks/index.js +633 -8
  20. package/dist/blocks/index.mjs +2 -2
  21. package/dist/{chunk-ZLLNO5FM.mjs → chunk-CKX5Y2HU.mjs} +44 -13
  22. package/dist/{chunk-J7W5EE3B.mjs → chunk-HCEPGEAI.mjs} +100 -8
  23. package/dist/chunk-HXGAG6I7.mjs +325 -0
  24. package/dist/chunk-ROTPP5CU.mjs +99 -0
  25. package/dist/{chunk-ETRRXURT.mjs → chunk-SIL2J5MF.mjs} +14 -0
  26. package/dist/chunk-VDGSMD6H.mjs +1072 -0
  27. package/dist/{chunk-PC5622T7.mjs → chunk-XK3K5GRP.mjs} +620 -9
  28. package/dist/chunk-XVH5SCBD.mjs +234 -0
  29. package/dist/{index-CmR6NInu.d.ts → index-BIwu3qIH.d.mts} +29 -3
  30. package/dist/{index-CmR6NInu.d.mts → index-BIwu3qIH.d.ts} +29 -3
  31. package/dist/index-CdnUNrvX.d.mts +134 -0
  32. package/dist/{index-DbH0Ljwp.d.ts → index-CpG3UHcS.d.mts} +1 -0
  33. package/dist/{index-DbH0Ljwp.d.mts → index-CpG3UHcS.d.ts} +1 -0
  34. package/dist/index-DyHbWliW.d.ts +134 -0
  35. package/dist/index-ZbOx4OCF.d.mts +128 -0
  36. package/dist/index-ZbOx4OCF.d.ts +128 -0
  37. package/dist/{index-DJFhANvJ.d.mts → index-cDYkEj29.d.mts} +20 -2
  38. package/dist/{index-DJFhANvJ.d.ts → index-cDYkEj29.d.ts} +20 -2
  39. package/dist/index.d.mts +5 -5
  40. package/dist/index.d.ts +5 -5
  41. package/dist/index.js +2145 -305
  42. package/dist/index.mjs +9 -9
  43. package/dist/nextjs/index.d.mts +1 -1
  44. package/dist/nextjs/index.d.ts +1 -1
  45. package/dist/nextjs/index.js +996 -13
  46. package/dist/nextjs/index.mjs +4 -1
  47. package/dist/studio/index.d.mts +2 -1
  48. package/dist/studio/index.d.ts +2 -1
  49. package/dist/studio/index.js +171 -2
  50. package/dist/studio/index.mjs +7 -3
  51. package/dist/studio-pages/builder.css +358 -8
  52. package/dist/studio-pages/client.d.mts +17 -0
  53. package/dist/studio-pages/client.d.ts +17 -0
  54. package/dist/studio-pages/client.js +5657 -1478
  55. package/dist/studio-pages/client.mjs +5549 -1461
  56. package/dist/studio-pages/index.d.mts +3 -2
  57. package/dist/studio-pages/index.d.ts +3 -2
  58. package/dist/studio-pages/index.js +799 -29
  59. package/dist/studio-pages/index.mjs +6 -2
  60. package/package.json +26 -12
  61. package/dist/chunk-AAOHJDNS.mjs +0 -67
  62. package/dist/chunk-N67KVM2S.mjs +0 -156
  63. package/dist/chunk-UJFU323N.mjs +0 -301
  64. package/dist/index-B9N5MyjF.d.mts +0 -39
  65. package/dist/index-BallJs-K.d.mts +0 -43
  66. package/dist/index-BallJs-K.d.ts +0 -43
  67. package/dist/index-g8tBHLKD.d.ts +0 -39
@@ -1,4 +1,8 @@
1
1
  'use client';
2
+ import {
3
+ MAX_DIRECT_UPLOAD_BYTES,
4
+ optimizeImageForUpload
5
+ } from "../chunk-ROTPP5CU.mjs";
2
6
 
3
7
  // src/admin-app/components/AdminShellClient.tsx
4
8
  import { useEffect, useState } from "react";
@@ -22,6 +26,47 @@ var navItemIsActive = (pathname, item) => {
22
26
 
23
27
  // src/admin-app/components/AdminShellClient.tsx
24
28
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
29
+ var iconSize = 20;
30
+ var iconStyle = { display: "block", flexShrink: 0 };
31
+ function NavIcon({ name }) {
32
+ const props = { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: iconStyle };
33
+ switch (name) {
34
+ case "dashboard":
35
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
36
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "7", height: "9", rx: "1" }),
37
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "3", width: "7", height: "5", rx: "1" }),
38
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "12", width: "7", height: "9", rx: "1" }),
39
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "16", width: "7", height: "5", rx: "1" })
40
+ ] });
41
+ case "pages":
42
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
43
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" }),
44
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
45
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "13", x2: "16", y2: "13" }),
46
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "17", x2: "12", y2: "17" })
47
+ ] });
48
+ case "globals":
49
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
50
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
51
+ /* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
52
+ ] });
53
+ case "media":
54
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
55
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
56
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
57
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
58
+ ] });
59
+ case "tools":
60
+ return /* @__PURE__ */ jsx("svg", { ...props, children: /* @__PURE__ */ jsx("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76Z" }) });
61
+ case "account":
62
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
63
+ /* @__PURE__ */ jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
64
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7", r: "4" })
65
+ ] });
66
+ default:
67
+ return null;
68
+ }
69
+ }
25
70
  function AdminShellClient({
26
71
  children,
27
72
  brandName,
@@ -89,7 +134,10 @@ function AdminShellClient({
89
134
  className: `orion-admin-nav-link ${active ? "is-active" : ""}`,
90
135
  href: item.href,
91
136
  title: item.label,
92
- children: collapsed ? item.label.slice(0, 1) : item.label
137
+ children: item.icon ? collapsed ? /* @__PURE__ */ jsx(NavIcon, { name: item.icon }) : /* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: "0.6rem" }, children: [
138
+ /* @__PURE__ */ jsx(NavIcon, { name: item.icon }),
139
+ item.label
140
+ ] }) : collapsed ? item.label.slice(0, 1) : item.label
93
141
  },
94
142
  item.href
95
143
  );
@@ -105,6 +153,1120 @@ function AdminShellClient({
105
153
  /* @__PURE__ */ jsx("main", { className: "orion-admin-main", children })
106
154
  ] });
107
155
  }
156
+
157
+ // src/admin-app/components/HeaderNavItemsEditor.tsx
158
+ import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
159
+
160
+ // src/admin-app/navigationLinks.ts
161
+ var normalizeAdminNavInputs = (rows, pageOptions) => {
162
+ const allowedLinks = new Map(pageOptions.map((option) => [option.href, option.title]));
163
+ const deduped = [];
164
+ const seen = /* @__PURE__ */ new Set();
165
+ for (const row of rows) {
166
+ const href = typeof row.href === "string" ? row.href.trim() : "";
167
+ const defaultLabel = allowedLinks.get(href);
168
+ const explicitLabel = typeof row.label === "string" ? row.label.trim() : "";
169
+ const parentHref = typeof row.parentHref === "string" ? row.parentHref.trim() : "";
170
+ if (!href || !defaultLabel || seen.has(href)) {
171
+ continue;
172
+ }
173
+ seen.add(href);
174
+ deduped.push({
175
+ href,
176
+ label: explicitLabel.length > 0 ? explicitLabel : defaultLabel,
177
+ ...parentHref.length > 0 ? { parentHref } : {}
178
+ });
179
+ }
180
+ const hrefs = new Set(deduped.map((item) => item.href));
181
+ const parentByHref = new Map(deduped.map((item) => [item.href, item.parentHref || ""]));
182
+ const createsCycle = (href, parentHref) => {
183
+ const visited = /* @__PURE__ */ new Set([href]);
184
+ let current = parentHref;
185
+ while (current) {
186
+ if (visited.has(current)) {
187
+ return true;
188
+ }
189
+ visited.add(current);
190
+ current = parentByHref.get(current) || "";
191
+ }
192
+ return false;
193
+ };
194
+ return deduped.map((item) => {
195
+ const parentHref = item.parentHref;
196
+ if (!parentHref || parentHref === item.href || !hrefs.has(parentHref) || createsCycle(item.href, parentHref)) {
197
+ return {
198
+ href: item.href,
199
+ label: item.label
200
+ };
201
+ }
202
+ return item;
203
+ });
204
+ };
205
+
206
+ // src/admin-app/components/HeaderNavItemsEditor.tsx
207
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
208
+ var toRow = (item, index) => ({
209
+ id: `row-${index}-${item.href || "empty"}`,
210
+ href: item.href || "",
211
+ label: item.label || "",
212
+ parentHref: item.parentHref || ""
213
+ });
214
+ var moveRow = (rows, fromIndex, toIndex) => {
215
+ if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= rows.length || toIndex >= rows.length) {
216
+ return rows;
217
+ }
218
+ const next = [...rows];
219
+ const [moved] = next.splice(fromIndex, 1);
220
+ if (!moved) {
221
+ return rows;
222
+ }
223
+ next.splice(toIndex, 0, moved);
224
+ return next;
225
+ };
226
+ function HeaderNavItemsEditor({ initialItems, pageOptions, onItemsChange }) {
227
+ const [rows, setRows] = useState2(() => initialItems.map(toRow));
228
+ const [nextRowID, setNextRowID] = useState2(initialItems.length);
229
+ const [draggingRowID, setDraggingRowID] = useState2(null);
230
+ const [dragOverRowID, setDragOverRowID] = useState2(null);
231
+ const pageOptionByHref = useMemo(() => new Map(pageOptions.map((option) => [option.href, option])), [pageOptions]);
232
+ const serializedState = useMemo(
233
+ () => JSON.stringify(
234
+ rows.map((row) => ({
235
+ href: row.href.trim(),
236
+ label: row.label.trim(),
237
+ parentHref: row.parentHref.trim() || void 0
238
+ }))
239
+ ),
240
+ [rows]
241
+ );
242
+ const normalizedItems = useMemo(() => {
243
+ const inputs = rows.map((row) => ({
244
+ href: row.href,
245
+ label: row.label,
246
+ parentHref: row.parentHref
247
+ }));
248
+ return normalizeAdminNavInputs(inputs, pageOptions);
249
+ }, [rows, pageOptions]);
250
+ useEffect2(() => {
251
+ onItemsChange?.(normalizedItems);
252
+ }, [normalizedItems, onItemsChange]);
253
+ const setRowValue = (rowID, changes) => {
254
+ setRows(
255
+ (currentRows) => currentRows.map((row) => {
256
+ if (row.id !== rowID) {
257
+ return row;
258
+ }
259
+ const nextRow = { ...row, ...changes };
260
+ if (nextRow.parentHref === nextRow.href) {
261
+ nextRow.parentHref = "";
262
+ }
263
+ return nextRow;
264
+ })
265
+ );
266
+ };
267
+ const removeRow = (rowID) => {
268
+ setRows((currentRows) => {
269
+ const removedRow = currentRows.find((candidate) => candidate.id === rowID);
270
+ const removedHref = removedRow?.href || "";
271
+ return currentRows.filter((row) => row.id !== rowID).map((row) => removedHref && row.parentHref === removedHref ? { ...row, parentHref: "" } : row);
272
+ });
273
+ };
274
+ const addRow = () => {
275
+ setRows((currentRows) => [
276
+ ...currentRows,
277
+ {
278
+ id: `row-new-${nextRowID}`,
279
+ href: "",
280
+ label: "",
281
+ parentHref: ""
282
+ }
283
+ ]);
284
+ setNextRowID((current) => current + 1);
285
+ };
286
+ return /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor", children: [
287
+ /* @__PURE__ */ jsx2("input", { name: "navItemsState", readOnly: true, type: "hidden", value: serializedState }),
288
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-head", children: [
289
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-title", children: "Navigation Items" }),
290
+ /* @__PURE__ */ jsx2("button", { className: "orion-admin-nav-editor-add", onClick: addRow, type: "button", children: "Add Item" })
291
+ ] }),
292
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-help", children: "Add only links you want in the menu. Drag rows to reorder. Set a parent to create dropdown items." }),
293
+ rows.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-empty", children: 'No navigation items yet. Click "Add Item" to start.' }) : null,
294
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-list", children: rows.map((row, rowIndex) => {
295
+ const parentCandidates = rows.filter((candidate) => candidate.id !== row.id && candidate.href.trim().length > 0).map((candidate) => {
296
+ const resolved = pageOptionByHref.get(candidate.href);
297
+ return {
298
+ href: candidate.href,
299
+ label: candidate.label.trim() || resolved?.title || candidate.href
300
+ };
301
+ });
302
+ const selectedPage = pageOptionByHref.get(row.href);
303
+ const labelPlaceholder = selectedPage?.title || "Navigation Label";
304
+ return /* @__PURE__ */ jsxs2(
305
+ "div",
306
+ {
307
+ className: `orion-admin-nav-editor-row${draggingRowID === row.id ? " is-dragging" : ""}${dragOverRowID === row.id && draggingRowID !== row.id ? " is-drop-target" : ""}`,
308
+ draggable: true,
309
+ onDragEnd: () => {
310
+ setDraggingRowID(null);
311
+ setDragOverRowID(null);
312
+ },
313
+ onDragEnter: () => {
314
+ if (draggingRowID && draggingRowID !== row.id) {
315
+ setDragOverRowID(row.id);
316
+ }
317
+ },
318
+ onDragLeave: () => {
319
+ if (dragOverRowID === row.id) {
320
+ setDragOverRowID(null);
321
+ }
322
+ },
323
+ onDragOver: (event) => {
324
+ event.preventDefault();
325
+ if (draggingRowID && draggingRowID !== row.id && dragOverRowID !== row.id) {
326
+ setDragOverRowID(row.id);
327
+ }
328
+ },
329
+ onDragStart: (event) => {
330
+ setDraggingRowID(row.id);
331
+ event.dataTransfer.effectAllowed = "move";
332
+ },
333
+ onDrop: (event) => {
334
+ event.preventDefault();
335
+ if (!draggingRowID || draggingRowID === row.id) {
336
+ return;
337
+ }
338
+ setRows((currentRows) => {
339
+ const fromIndex = currentRows.findIndex((entry) => entry.id === draggingRowID);
340
+ const toIndex = currentRows.findIndex((entry) => entry.id === row.id);
341
+ return moveRow(currentRows, fromIndex, toIndex);
342
+ });
343
+ setDraggingRowID(null);
344
+ setDragOverRowID(null);
345
+ },
346
+ children: [
347
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-row-head", children: [
348
+ /* @__PURE__ */ jsx2("span", { className: "orion-admin-nav-editor-drag", children: "Drag" }),
349
+ /* @__PURE__ */ jsxs2("span", { className: "orion-admin-nav-editor-row-index", children: [
350
+ "#",
351
+ rowIndex + 1
352
+ ] }),
353
+ /* @__PURE__ */ jsx2("button", { className: "orion-admin-nav-editor-remove", onClick: () => removeRow(row.id), type: "button", children: "Remove" })
354
+ ] }),
355
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-row-grid", children: [
356
+ /* @__PURE__ */ jsxs2("label", { children: [
357
+ "Label",
358
+ /* @__PURE__ */ jsx2(
359
+ "input",
360
+ {
361
+ name: `navLabel_${rowIndex}`,
362
+ onChange: (event) => setRowValue(row.id, { label: event.target.value }),
363
+ placeholder: labelPlaceholder,
364
+ type: "text",
365
+ value: row.label
366
+ }
367
+ )
368
+ ] }),
369
+ /* @__PURE__ */ jsxs2("label", { children: [
370
+ "Page",
371
+ /* @__PURE__ */ jsxs2(
372
+ "select",
373
+ {
374
+ name: `navPage_${rowIndex}`,
375
+ onChange: (event) => {
376
+ const nextHref = event.target.value;
377
+ const nextParent = row.parentHref && row.parentHref === nextHref ? "" : row.parentHref;
378
+ setRowValue(row.id, { href: nextHref, parentHref: nextParent });
379
+ },
380
+ value: row.href,
381
+ children: [
382
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Select page..." }),
383
+ pageOptions.map((pageOption) => /* @__PURE__ */ jsx2("option", { value: pageOption.href, children: pageOption.label }, `${pageOption.href}-${pageOption.title}`))
384
+ ]
385
+ }
386
+ )
387
+ ] }),
388
+ /* @__PURE__ */ jsxs2("label", { children: [
389
+ "Parent (dropdown under)",
390
+ /* @__PURE__ */ jsxs2(
391
+ "select",
392
+ {
393
+ name: `navParentHref_${rowIndex}`,
394
+ onChange: (event) => setRowValue(row.id, { parentHref: event.target.value }),
395
+ value: row.parentHref,
396
+ children: [
397
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Top-level item" }),
398
+ parentCandidates.map((candidate) => /* @__PURE__ */ jsx2("option", { value: candidate.href, children: candidate.label }, `${row.id}-parent-${candidate.href}`))
399
+ ]
400
+ }
401
+ )
402
+ ] })
403
+ ] })
404
+ ]
405
+ },
406
+ row.id
407
+ );
408
+ }) })
409
+ ] });
410
+ }
411
+
412
+ // src/admin-app/components/HeaderNavEditorWithPreview.tsx
413
+ import { useMemo as useMemo2, useState as useState3 } from "react";
414
+
415
+ // src/admin-app/nestedNavigation.ts
416
+ var normalizeNestedNavItems = (items) => {
417
+ const deduped = [];
418
+ const seen = /* @__PURE__ */ new Set();
419
+ for (const item of items) {
420
+ const href = typeof item.href === "string" ? item.href.trim() : "";
421
+ const label = typeof item.label === "string" ? item.label.trim() : "";
422
+ const parentHref = typeof item.parentHref === "string" ? item.parentHref.trim() : "";
423
+ if (!href || !label || seen.has(href)) {
424
+ continue;
425
+ }
426
+ seen.add(href);
427
+ deduped.push({
428
+ href,
429
+ label,
430
+ ...parentHref ? { parentHref } : {}
431
+ });
432
+ }
433
+ const hrefs = new Set(deduped.map((item) => item.href));
434
+ return deduped.map((item) => ({
435
+ href: item.href,
436
+ label: item.label,
437
+ ...item.parentHref && item.parentHref !== item.href && hrefs.has(item.parentHref) ? { parentHref: item.parentHref } : {}
438
+ }));
439
+ };
440
+ var buildNestedNavTree = (items) => {
441
+ const childrenByParent = /* @__PURE__ */ new Map();
442
+ const topLevel = [];
443
+ for (const item of items) {
444
+ if (!item.parentHref) {
445
+ topLevel.push(item);
446
+ continue;
447
+ }
448
+ const children = childrenByParent.get(item.parentHref) || [];
449
+ children.push(item);
450
+ childrenByParent.set(item.parentHref, children);
451
+ }
452
+ if (topLevel.length === 0 && items.length > 0) {
453
+ return {
454
+ topLevel: items.map((item) => ({ href: item.href, label: item.label })),
455
+ childrenByParent: /* @__PURE__ */ new Map()
456
+ };
457
+ }
458
+ return { childrenByParent, topLevel };
459
+ };
460
+
461
+ // src/admin-app/components/HeaderNavEditorWithPreview.tsx
462
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
463
+ var fallbackNav = [{ href: "/", label: "Home" }];
464
+ function HeaderNavEditorWithPreview({
465
+ brandName,
466
+ initialItems,
467
+ logoUrl,
468
+ pageOptions,
469
+ tagline = ""
470
+ }) {
471
+ const [liveItems, setLiveItems] = useState3(initialItems);
472
+ const previewItems = useMemo2(() => {
473
+ const normalized = normalizeNestedNavItems(liveItems);
474
+ const tree = buildNestedNavTree(normalized);
475
+ return tree.topLevel.length > 0 ? tree : buildNestedNavTree(fallbackNav);
476
+ }, [liveItems]);
477
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
478
+ /* @__PURE__ */ jsx3(HeaderNavItemsEditor, { initialItems, onItemsChange: setLiveItems, pageOptions }),
479
+ /* @__PURE__ */ jsx3("div", { style: { color: "#5d6664", fontSize: "0.88rem", fontWeight: 700 }, children: "Header Preview" }),
480
+ /* @__PURE__ */ jsx3(
481
+ "div",
482
+ {
483
+ style: {
484
+ border: "1px solid #dce2e0",
485
+ borderRadius: 14,
486
+ overflow: "hidden"
487
+ },
488
+ children: /* @__PURE__ */ jsx3("header", { className: "site-header", style: { position: "relative", top: "auto" }, children: /* @__PURE__ */ jsxs3("div", { className: "container container-content header-wrap", children: [
489
+ /* @__PURE__ */ jsxs3("a", { className: "brand", href: "#", children: [
490
+ logoUrl ? /* @__PURE__ */ jsx3("img", { alt: `${brandName} logo`, src: logoUrl }) : null,
491
+ /* @__PURE__ */ jsxs3("div", { children: [
492
+ /* @__PURE__ */ jsx3("div", { className: "brand-title", children: brandName }),
493
+ tagline ? /* @__PURE__ */ jsx3("div", { className: "brand-subtitle", children: tagline }) : null
494
+ ] })
495
+ ] }),
496
+ /* @__PURE__ */ jsx3("nav", { className: "site-nav open", children: previewItems.topLevel.map((item, index) => {
497
+ const children = previewItems.childrenByParent.get(item.href) || [];
498
+ return /* @__PURE__ */ jsxs3(
499
+ "div",
500
+ {
501
+ className: children.length > 0 ? "site-nav-item has-children" : "site-nav-item",
502
+ children: [
503
+ /* @__PURE__ */ jsx3("a", { className: index === 0 ? "active" : "", href: item.href, children: item.label }),
504
+ children.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "site-subnav", children: children.map((child) => /* @__PURE__ */ jsx3("a", { href: child.href, children: child.label }, `${item.href}-${child.href}`)) }) : null
505
+ ]
506
+ },
507
+ `${item.href}-${index}`
508
+ );
509
+ }) })
510
+ ] }) })
511
+ }
512
+ )
513
+ ] });
514
+ }
515
+
516
+ // src/admin-app/components/MediaDetailPanel.tsx
517
+ import { useState as useState4 } from "react";
518
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
519
+ function formatFileSize(bytes) {
520
+ if (bytes < 1024) return `${bytes} B`;
521
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
522
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
523
+ }
524
+ function MediaDetailPanel({
525
+ id,
526
+ filename,
527
+ alt,
528
+ url,
529
+ filesize,
530
+ width,
531
+ height,
532
+ mimeType,
533
+ createdAt,
534
+ updateAction,
535
+ deleteAction
536
+ }) {
537
+ const [copied, setCopied] = useState4(false);
538
+ const [confirmDelete, setConfirmDelete] = useState4(false);
539
+ const copyUrl = async () => {
540
+ if (!url) return;
541
+ try {
542
+ await navigator.clipboard.writeText(url);
543
+ setCopied(true);
544
+ setTimeout(() => setCopied(false), 2e3);
545
+ } catch {
546
+ const input = document.createElement("input");
547
+ input.value = url;
548
+ document.body.appendChild(input);
549
+ input.select();
550
+ document.execCommand("copy");
551
+ document.body.removeChild(input);
552
+ setCopied(true);
553
+ setTimeout(() => setCopied(false), 2e3);
554
+ }
555
+ };
556
+ const metaRows = [];
557
+ if (filename) metaRows.push({ label: "Filename", value: filename });
558
+ if (typeof filesize === "number") metaRows.push({ label: "File size", value: formatFileSize(filesize) });
559
+ if (typeof width === "number" && typeof height === "number") metaRows.push({ label: "Dimensions", value: `${width} \xD7 ${height} px` });
560
+ if (mimeType) metaRows.push({ label: "Type", value: mimeType });
561
+ if (createdAt) {
562
+ try {
563
+ metaRows.push({ label: "Uploaded", value: new Date(createdAt).toLocaleDateString() });
564
+ } catch {
565
+ metaRows.push({ label: "Uploaded", value: createdAt });
566
+ }
567
+ }
568
+ return /* @__PURE__ */ jsxs4("div", { className: "orion-admin-grid", style: { alignItems: "start" }, children: [
569
+ /* @__PURE__ */ jsxs4("div", { children: [
570
+ /* @__PURE__ */ jsx4("div", { className: "orion-admin-card", children: url ? /* @__PURE__ */ jsx4("img", { alt: alt || filename || "Media", src: url, style: { borderRadius: 12, width: "100%" } }) : /* @__PURE__ */ jsx4("span", { children: "No preview available." }) }),
571
+ url ? /* @__PURE__ */ jsx4(
572
+ "button",
573
+ {
574
+ className: "orion-admin-action-button",
575
+ onClick: copyUrl,
576
+ style: { marginTop: "0.6rem", width: "100%" },
577
+ type: "button",
578
+ children: copied ? "Copied!" : "Copy URL"
579
+ }
580
+ ) : null
581
+ ] }),
582
+ /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gap: "0.8rem" }, children: [
583
+ metaRows.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "orion-admin-card orion-admin-meta-table", children: metaRows.map((row) => /* @__PURE__ */ jsxs4("div", { className: "orion-admin-meta-row", children: [
584
+ /* @__PURE__ */ jsx4("span", { className: "orion-admin-meta-label", children: row.label }),
585
+ /* @__PURE__ */ jsx4("span", { className: "orion-admin-meta-value", children: row.value })
586
+ ] }, row.label)) }) : null,
587
+ /* @__PURE__ */ jsxs4("form", { action: updateAction, className: "orion-admin-form", children: [
588
+ /* @__PURE__ */ jsx4("input", { name: "id", type: "hidden", value: id }),
589
+ /* @__PURE__ */ jsxs4("label", { children: [
590
+ "Alt text",
591
+ /* @__PURE__ */ jsx4("input", { defaultValue: alt || "", name: "alt", required: true, type: "text" })
592
+ ] }),
593
+ /* @__PURE__ */ jsx4("button", { type: "submit", children: "Save" })
594
+ ] }),
595
+ confirmDelete ? /* @__PURE__ */ jsxs4("div", { className: "orion-admin-form", style: { borderColor: "#b42318" }, children: [
596
+ /* @__PURE__ */ jsx4("p", { style: { fontWeight: 700, margin: 0 }, children: "Are you sure you want to delete this asset?" }),
597
+ /* @__PURE__ */ jsx4("p", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem", margin: 0 }, children: "This action cannot be undone." }),
598
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "0.5rem" }, children: [
599
+ /* @__PURE__ */ jsxs4("form", { action: deleteAction, style: { flex: 1 }, children: [
600
+ /* @__PURE__ */ jsx4("input", { name: "id", type: "hidden", value: id }),
601
+ /* @__PURE__ */ jsx4("button", { style: { background: "#b42318", border: 0, borderRadius: 10, color: "#fff", cursor: "pointer", fontWeight: 800, padding: "0.55rem 0.8rem", width: "100%" }, type: "submit", children: "Yes, Delete" })
602
+ ] }),
603
+ /* @__PURE__ */ jsx4(
604
+ "button",
605
+ {
606
+ onClick: () => setConfirmDelete(false),
607
+ style: { background: "transparent", border: "1px solid var(--orion-admin-border)", borderRadius: 10, cursor: "pointer", flex: 1, fontWeight: 700, padding: "0.55rem 0.8rem" },
608
+ type: "button",
609
+ children: "Cancel"
610
+ }
611
+ )
612
+ ] })
613
+ ] }) : /* @__PURE__ */ jsx4(
614
+ "button",
615
+ {
616
+ className: "orion-admin-action-button",
617
+ onClick: () => setConfirmDelete(true),
618
+ style: { background: "#b42318" },
619
+ type: "button",
620
+ children: "Delete Asset"
621
+ }
622
+ )
623
+ ] })
624
+ ] });
625
+ }
626
+
627
+ // src/admin-app/components/MediaListItem.tsx
628
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
629
+ function formatFileSize2(bytes) {
630
+ if (bytes < 1024) return `${bytes} B`;
631
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
632
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
633
+ }
634
+ function MediaListItem({
635
+ id,
636
+ filename,
637
+ alt,
638
+ url,
639
+ filesize,
640
+ width,
641
+ height,
642
+ mimeType,
643
+ href
644
+ }) {
645
+ const label = filename || `Media ${id}`;
646
+ const altText = alt || "";
647
+ const metaParts = [];
648
+ if (typeof filesize === "number") metaParts.push(formatFileSize2(filesize));
649
+ if (typeof width === "number" && typeof height === "number") metaParts.push(`${width}\xD7${height}`);
650
+ if (typeof mimeType === "string") metaParts.push(mimeType);
651
+ return /* @__PURE__ */ jsxs5("a", { className: "orion-admin-list-item", href, children: [
652
+ /* @__PURE__ */ jsxs5("div", { style: { alignItems: "center", display: "flex", gap: "0.8rem" }, children: [
653
+ url ? /* @__PURE__ */ jsx5("img", { alt: altText || label, className: "orion-admin-media-preview", src: url }) : null,
654
+ /* @__PURE__ */ jsxs5("div", { children: [
655
+ /* @__PURE__ */ jsx5("strong", { children: label }),
656
+ /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", children: altText || "No alt text" }),
657
+ metaParts.length > 0 ? /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", style: { marginTop: "0.15rem" }, children: metaParts.join(" \xB7 ") }) : null
658
+ ] })
659
+ ] }),
660
+ /* @__PURE__ */ jsx5("span", { className: "orion-admin-list-meta", children: "Edit" })
661
+ ] });
662
+ }
663
+
664
+ // src/admin-app/components/MediaUploadForm.tsx
665
+ import { useState as useState5, useRef, useCallback } from "react";
666
+ import { useRouter } from "next/navigation";
667
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
668
+ var MEDIA_LIBRARY_SYNC_EVENT = "orion-media-library-updated";
669
+ var notifyMediaLibraryUpdated = () => {
670
+ if (typeof window === "undefined") {
671
+ return;
672
+ }
673
+ const updatedAt = Date.now().toString();
674
+ window.dispatchEvent(
675
+ new CustomEvent(MEDIA_LIBRARY_SYNC_EVENT, {
676
+ detail: {
677
+ updatedAt
678
+ }
679
+ })
680
+ );
681
+ try {
682
+ window.localStorage.setItem(MEDIA_LIBRARY_SYNC_EVENT, updatedAt);
683
+ } catch {
684
+ }
685
+ };
686
+ var parseUploadError = async (response) => {
687
+ const fallback = "Upload failed. Please check the image and required fields, then try again.";
688
+ try {
689
+ const body = await response.json();
690
+ const nestedMessage = body.errors?.[0]?.data?.errors?.[0]?.message;
691
+ if (typeof nestedMessage === "string" && nestedMessage.trim().length > 0) {
692
+ return nestedMessage;
693
+ }
694
+ const topMessage = body.errors?.[0]?.message;
695
+ if (typeof topMessage === "string" && topMessage.trim().length > 0) {
696
+ return topMessage;
697
+ }
698
+ if (typeof body.message === "string" && body.message.trim().length > 0) {
699
+ return body.message;
700
+ }
701
+ } catch {
702
+ }
703
+ return fallback;
704
+ };
705
+ function MediaUploadForm() {
706
+ const router = useRouter();
707
+ const fileInputRef = useRef(null);
708
+ const [alt, setAlt] = useState5("");
709
+ const [file, setFile] = useState5(null);
710
+ const [preview, setPreview] = useState5(null);
711
+ const [dragging, setDragging] = useState5(false);
712
+ const [submitting, setSubmitting] = useState5(false);
713
+ const [error, setError] = useState5(null);
714
+ const handleFile = useCallback((selectedFile) => {
715
+ setFile(selectedFile);
716
+ if (preview) {
717
+ URL.revokeObjectURL(preview);
718
+ setPreview(null);
719
+ }
720
+ if (selectedFile && selectedFile.type.startsWith("image/")) {
721
+ setPreview(URL.createObjectURL(selectedFile));
722
+ }
723
+ }, [preview]);
724
+ const onDragOver = useCallback((e) => {
725
+ e.preventDefault();
726
+ setDragging(true);
727
+ }, []);
728
+ const onDragLeave = useCallback((e) => {
729
+ e.preventDefault();
730
+ setDragging(false);
731
+ }, []);
732
+ const onDrop = useCallback((e) => {
733
+ e.preventDefault();
734
+ setDragging(false);
735
+ const droppedFile = e.dataTransfer.files?.[0] || null;
736
+ if (droppedFile) {
737
+ handleFile(droppedFile);
738
+ }
739
+ }, [handleFile]);
740
+ const upload = async (event) => {
741
+ event.preventDefault();
742
+ if (!file) {
743
+ setError("Select an image to upload.");
744
+ return;
745
+ }
746
+ setSubmitting(true);
747
+ setError(null);
748
+ try {
749
+ const optimizedFile = await optimizeImageForUpload(file);
750
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
751
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
752
+ }
753
+ const formData = new FormData();
754
+ const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
755
+ const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
756
+ formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
757
+ formData.set("alt", resolvedAlt);
758
+ formData.set("file", optimizedFile);
759
+ const response = await fetch("/api/media", {
760
+ method: "POST",
761
+ credentials: "include",
762
+ body: formData
763
+ });
764
+ if (!response.ok) {
765
+ throw new Error(await parseUploadError(response));
766
+ }
767
+ setAlt("");
768
+ setFile(null);
769
+ if (preview) {
770
+ URL.revokeObjectURL(preview);
771
+ setPreview(null);
772
+ }
773
+ notifyMediaLibraryUpdated();
774
+ router.refresh();
775
+ } catch (err) {
776
+ setError(err instanceof Error ? err.message : "Upload failed");
777
+ } finally {
778
+ setSubmitting(false);
779
+ }
780
+ };
781
+ return /* @__PURE__ */ jsxs6("form", { className: "orion-admin-upload-form", onSubmit: upload, children: [
782
+ /* @__PURE__ */ jsxs6("label", { children: [
783
+ "Alt text",
784
+ /* @__PURE__ */ jsx6(
785
+ "input",
786
+ {
787
+ onChange: (event) => setAlt(event.target.value),
788
+ placeholder: "Describe the image",
789
+ type: "text",
790
+ value: alt
791
+ }
792
+ )
793
+ ] }),
794
+ /* @__PURE__ */ jsxs6(
795
+ "div",
796
+ {
797
+ className: `orion-admin-dropzone${dragging ? " is-dragging" : ""}${file ? " has-file" : ""}`,
798
+ onClick: () => fileInputRef.current?.click(),
799
+ onDragLeave,
800
+ onDragOver,
801
+ onDrop,
802
+ children: [
803
+ preview ? /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-preview", children: [
804
+ /* @__PURE__ */ jsx6("img", { alt: "Upload preview", src: preview }),
805
+ /* @__PURE__ */ jsx6("span", { children: file?.name })
806
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-label", children: [
807
+ /* @__PURE__ */ jsx6("strong", { children: "Drop an image here" }),
808
+ /* @__PURE__ */ jsx6("span", { children: "or click to browse" })
809
+ ] }),
810
+ /* @__PURE__ */ jsx6(
811
+ "input",
812
+ {
813
+ accept: "image/*",
814
+ onChange: (event) => handleFile(event.target.files?.[0] || null),
815
+ ref: fileInputRef,
816
+ style: { display: "none" },
817
+ type: "file"
818
+ }
819
+ )
820
+ ]
821
+ }
822
+ ),
823
+ error ? /* @__PURE__ */ jsx6("div", { className: "orion-admin-upload-error", children: error }) : null,
824
+ /* @__PURE__ */ jsx6("button", { disabled: submitting, type: "submit", children: submitting ? "Uploading..." : "Upload" })
825
+ ] });
826
+ }
827
+
828
+ // src/admin-app/components/PageEditorFrame.tsx
829
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState6 } from "react";
830
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
831
+ var extractPageIDFromBuilderSrc = (value) => {
832
+ if (!value || typeof value !== "string") {
833
+ return null;
834
+ }
835
+ try {
836
+ const url = new URL(value, "http://localhost");
837
+ const segments = url.pathname.split("/").filter(Boolean);
838
+ return segments.length > 0 ? segments[segments.length - 1] || null : null;
839
+ } catch {
840
+ return null;
841
+ }
842
+ };
843
+ function PageEditorFrame({ src }) {
844
+ const iframeRef = useRef2(null);
845
+ const dirtyCheckTimerRef = useRef2(null);
846
+ const [saving, setSaving] = useState6(null);
847
+ const [message, setMessage] = useState6("");
848
+ const [error, setError] = useState6("");
849
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState6(false);
850
+ const [awaitingDirtyCheck, setAwaitingDirtyCheck] = useState6(false);
851
+ const [pendingNavigationURL, setPendingNavigationURL] = useState6(null);
852
+ const [showUnsavedDialog, setShowUnsavedDialog] = useState6(false);
853
+ const [canUndo, setCanUndo] = useState6(false);
854
+ const [canRedo, setCanRedo] = useState6(false);
855
+ const [sessionExpired, setSessionExpired] = useState6(false);
856
+ const [hasUnpublishedChanges, setHasUnpublishedChanges] = useState6(false);
857
+ const pageID = extractPageIDFromBuilderSrc(src);
858
+ const refreshUnpublishedChanges = async () => {
859
+ if (!pageID) {
860
+ return;
861
+ }
862
+ try {
863
+ const response = await fetch(
864
+ `/api/pages/versions?depth=0&limit=25&sort=-updatedAt&where[parent][equals]=${encodeURIComponent(pageID)}`,
865
+ {
866
+ credentials: "include"
867
+ }
868
+ );
869
+ if (!response.ok) {
870
+ return;
871
+ }
872
+ const payload = await response.json();
873
+ const docs = Array.isArray(payload.docs) ? payload.docs : [];
874
+ let latestDraft = 0;
875
+ let latestPublished = 0;
876
+ docs.forEach((doc) => {
877
+ const status = doc.version?._status;
878
+ const millis = typeof doc.updatedAt === "string" ? Date.parse(doc.updatedAt) : Number.NaN;
879
+ if (!Number.isFinite(millis)) {
880
+ return;
881
+ }
882
+ if (status === "draft") {
883
+ latestDraft = Math.max(latestDraft, millis);
884
+ } else if (status === "published") {
885
+ latestPublished = Math.max(latestPublished, millis);
886
+ }
887
+ });
888
+ setHasUnpublishedChanges(latestDraft > 0 && latestDraft >= latestPublished);
889
+ } catch {
890
+ }
891
+ };
892
+ const clearDirtyCheckTimer = () => {
893
+ if (dirtyCheckTimerRef.current) {
894
+ window.clearTimeout(dirtyCheckTimerRef.current);
895
+ dirtyCheckTimerRef.current = null;
896
+ }
897
+ };
898
+ const continueNavigation = (nextURL) => {
899
+ setPendingNavigationURL(null);
900
+ setShowUnsavedDialog(false);
901
+ setAwaitingDirtyCheck(false);
902
+ clearDirtyCheckTimer();
903
+ window.location.assign(nextURL);
904
+ };
905
+ const sendSave = (status) => {
906
+ const iframe = iframeRef.current;
907
+ if (!iframe?.contentWindow) {
908
+ setError("Editor is still loading. Try again in a moment.");
909
+ return;
910
+ }
911
+ setSaving(status);
912
+ setError("");
913
+ setMessage("");
914
+ iframe.contentWindow.postMessage(
915
+ {
916
+ source: "payload-visual-builder-parent",
917
+ type: "save",
918
+ status
919
+ },
920
+ "*"
921
+ );
922
+ };
923
+ const sendHistory = (type) => {
924
+ const iframe = iframeRef.current;
925
+ if (!iframe?.contentWindow) {
926
+ setError("Editor is still loading. Try again in a moment.");
927
+ return;
928
+ }
929
+ iframe.contentWindow.postMessage(
930
+ {
931
+ source: "payload-visual-builder-parent",
932
+ type
933
+ },
934
+ "*"
935
+ );
936
+ };
937
+ useEffect3(() => {
938
+ const onMessage = (event) => {
939
+ const data = event.data;
940
+ if (!data || data.source !== "payload-visual-builder-child") {
941
+ return;
942
+ }
943
+ if (data.type === "dirty-state") {
944
+ const dirty = Boolean(data.dirty);
945
+ setHasUnsavedChanges(dirty);
946
+ if (awaitingDirtyCheck && pendingNavigationURL) {
947
+ clearDirtyCheckTimer();
948
+ setAwaitingDirtyCheck(false);
949
+ if (dirty) {
950
+ setShowUnsavedDialog(true);
951
+ } else {
952
+ continueNavigation(pendingNavigationURL);
953
+ }
954
+ }
955
+ return;
956
+ }
957
+ if (data.type === "history-state") {
958
+ setCanUndo(Boolean(data.canUndo));
959
+ setCanRedo(Boolean(data.canRedo));
960
+ return;
961
+ }
962
+ if (data.type === "session-expired") {
963
+ setSessionExpired(true);
964
+ return;
965
+ }
966
+ if (data.type !== "save-result") {
967
+ return;
968
+ }
969
+ setSaving(null);
970
+ if (data.ok) {
971
+ if (data.status === "draft") {
972
+ setHasUnpublishedChanges(true);
973
+ } else if (data.status === "published") {
974
+ setHasUnpublishedChanges(false);
975
+ } else {
976
+ void refreshUnpublishedChanges();
977
+ }
978
+ setMessage(typeof data.message === "string" ? data.message : "Saved.");
979
+ setError("");
980
+ setHasUnsavedChanges(false);
981
+ } else {
982
+ setError(typeof data.message === "string" ? data.message : "Could not save changes.");
983
+ setMessage("");
984
+ }
985
+ };
986
+ window.addEventListener("message", onMessage);
987
+ return () => window.removeEventListener("message", onMessage);
988
+ }, [awaitingDirtyCheck, pendingNavigationURL]);
989
+ useEffect3(() => {
990
+ void refreshUnpublishedChanges();
991
+ }, [pageID]);
992
+ useEffect3(() => {
993
+ const onDocumentClick = (event) => {
994
+ if (!hasUnsavedChanges) {
995
+ return;
996
+ }
997
+ const target = event.target;
998
+ if (target?.closest('[data-orion-unsaved-dialog="true"]')) {
999
+ return;
1000
+ }
1001
+ const link = target?.closest("a[href]");
1002
+ if (!link) {
1003
+ return;
1004
+ }
1005
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
1006
+ return;
1007
+ }
1008
+ if (link.target && link.target !== "_self") {
1009
+ return;
1010
+ }
1011
+ const href = link.getAttribute("href");
1012
+ if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("tel:")) {
1013
+ return;
1014
+ }
1015
+ const destination = new URL(href, window.location.href);
1016
+ const current = new URL(window.location.href);
1017
+ const isSameDestination = destination.pathname === current.pathname && destination.search === current.search && destination.hash === current.hash;
1018
+ if (destination.origin !== current.origin || isSameDestination) {
1019
+ return;
1020
+ }
1021
+ event.preventDefault();
1022
+ const nextURL = `${destination.pathname}${destination.search}${destination.hash}`;
1023
+ setPendingNavigationURL(nextURL);
1024
+ const iframe = iframeRef.current;
1025
+ if (!iframe?.contentWindow) {
1026
+ if (hasUnsavedChanges) {
1027
+ setShowUnsavedDialog(true);
1028
+ } else {
1029
+ continueNavigation(nextURL);
1030
+ }
1031
+ return;
1032
+ }
1033
+ setAwaitingDirtyCheck(true);
1034
+ setShowUnsavedDialog(false);
1035
+ clearDirtyCheckTimer();
1036
+ iframe.contentWindow.postMessage(
1037
+ {
1038
+ source: "payload-visual-builder-parent",
1039
+ type: "dirty-check-request"
1040
+ },
1041
+ "*"
1042
+ );
1043
+ dirtyCheckTimerRef.current = window.setTimeout(() => {
1044
+ setAwaitingDirtyCheck(false);
1045
+ if (hasUnsavedChanges) {
1046
+ setShowUnsavedDialog(true);
1047
+ } else {
1048
+ continueNavigation(nextURL);
1049
+ }
1050
+ }, 250);
1051
+ };
1052
+ document.addEventListener("click", onDocumentClick, true);
1053
+ return () => document.removeEventListener("click", onDocumentClick, true);
1054
+ }, [hasUnsavedChanges]);
1055
+ useEffect3(() => {
1056
+ if (!hasUnsavedChanges) {
1057
+ return;
1058
+ }
1059
+ const onBeforeUnload = (event) => {
1060
+ event.preventDefault();
1061
+ event.returnValue = "";
1062
+ };
1063
+ window.addEventListener("beforeunload", onBeforeUnload);
1064
+ return () => window.removeEventListener("beforeunload", onBeforeUnload);
1065
+ }, [hasUnsavedChanges]);
1066
+ useEffect3(
1067
+ () => () => {
1068
+ clearDirtyCheckTimer();
1069
+ },
1070
+ []
1071
+ );
1072
+ return /* @__PURE__ */ jsxs7("div", { style: { display: "grid", gap: "0.8rem" }, children: [
1073
+ /* @__PURE__ */ jsxs7(
1074
+ "div",
1075
+ {
1076
+ style: {
1077
+ alignItems: "center",
1078
+ background: "var(--orion-admin-card-bg)",
1079
+ border: "1px solid var(--orion-admin-card-border)",
1080
+ borderRadius: 12,
1081
+ display: "flex",
1082
+ flexWrap: "wrap",
1083
+ gap: "0.6rem",
1084
+ justifyContent: "space-between",
1085
+ padding: "0.7rem 0.8rem"
1086
+ },
1087
+ children: [
1088
+ /* @__PURE__ */ jsxs7("div", { style: { display: "grid", gap: "0.2rem" }, children: [
1089
+ /* @__PURE__ */ jsx7("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem" }, children: "Save changes to update the page layout and content." }),
1090
+ /* @__PURE__ */ jsx7(
1091
+ "div",
1092
+ {
1093
+ style: {
1094
+ alignItems: "center",
1095
+ background: hasUnpublishedChanges ? "#fff3cd" : "#e7f7ee",
1096
+ border: `1px solid ${hasUnpublishedChanges ? "#f0c36d" : "#87c79e"}`,
1097
+ borderRadius: 999,
1098
+ color: hasUnpublishedChanges ? "#6a4a00" : "#0e5a2a",
1099
+ display: "inline-flex",
1100
+ fontSize: "0.75rem",
1101
+ fontWeight: 800,
1102
+ padding: "0.2rem 0.55rem",
1103
+ width: "fit-content"
1104
+ },
1105
+ title: hasUnpublishedChanges ? "This page has saved draft changes not published yet." : "The live page matches the latest published version.",
1106
+ children: hasUnpublishedChanges ? "Unpublished draft changes" : "Live is up to date"
1107
+ }
1108
+ )
1109
+ ] }),
1110
+ /* @__PURE__ */ jsxs7("div", { style: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
1111
+ /* @__PURE__ */ jsx7(
1112
+ "button",
1113
+ {
1114
+ className: "orion-admin-action-button",
1115
+ disabled: !canUndo,
1116
+ onClick: () => sendHistory("undo"),
1117
+ style: { background: "transparent", border: "1px solid var(--orion-admin-card-border)", color: "var(--orion-admin-text)" },
1118
+ type: "button",
1119
+ children: "Undo"
1120
+ }
1121
+ ),
1122
+ /* @__PURE__ */ jsx7(
1123
+ "button",
1124
+ {
1125
+ className: "orion-admin-action-button",
1126
+ disabled: !canRedo,
1127
+ onClick: () => sendHistory("redo"),
1128
+ style: { background: "transparent", border: "1px solid var(--orion-admin-card-border)", color: "var(--orion-admin-text)" },
1129
+ type: "button",
1130
+ children: "Redo"
1131
+ }
1132
+ ),
1133
+ /* @__PURE__ */ jsx7(
1134
+ "button",
1135
+ {
1136
+ className: "orion-admin-action-button",
1137
+ disabled: saving !== null,
1138
+ onClick: () => sendSave("draft"),
1139
+ type: "button",
1140
+ children: saving === "draft" ? "Saving..." : "Save Draft"
1141
+ }
1142
+ ),
1143
+ /* @__PURE__ */ jsx7(
1144
+ "button",
1145
+ {
1146
+ className: "orion-admin-action-button",
1147
+ disabled: saving !== null,
1148
+ onClick: () => sendSave("published"),
1149
+ style: { background: "#0f7d52" },
1150
+ type: "button",
1151
+ children: saving === "published" ? "Publishing..." : "Publish"
1152
+ }
1153
+ )
1154
+ ] })
1155
+ ]
1156
+ }
1157
+ ),
1158
+ message ? /* @__PURE__ */ jsx7("div", { style: { color: "#0f7d52", fontSize: "0.9rem", fontWeight: 700 }, children: message }) : null,
1159
+ error ? /* @__PURE__ */ jsx7("div", { className: "orion-admin-error", children: error }) : null,
1160
+ sessionExpired ? /* @__PURE__ */ jsx7(
1161
+ "div",
1162
+ {
1163
+ style: {
1164
+ background: "#fef2f2",
1165
+ borderLeft: "4px solid #8d1d1d",
1166
+ borderRadius: 8,
1167
+ color: "#8d1d1d",
1168
+ fontSize: "0.9rem",
1169
+ fontWeight: 600,
1170
+ padding: "0.6rem 0.8rem"
1171
+ },
1172
+ children: "Session expired. Log in again in a new tab, then save your changes."
1173
+ }
1174
+ ) : null,
1175
+ /* @__PURE__ */ jsx7(
1176
+ "iframe",
1177
+ {
1178
+ ref: iframeRef,
1179
+ src,
1180
+ style: {
1181
+ border: "1px solid var(--orion-admin-card-border)",
1182
+ borderRadius: 12,
1183
+ height: "calc(100vh - 280px)",
1184
+ width: "100%"
1185
+ },
1186
+ title: "Page Builder",
1187
+ onLoad: () => {
1188
+ const iframe = iframeRef.current;
1189
+ if (!iframe?.contentWindow) return;
1190
+ iframe.contentWindow.postMessage({ source: "payload-visual-builder-parent", type: "dirty-check-request" }, "*");
1191
+ iframe.contentWindow.postMessage({ source: "payload-visual-builder-parent", type: "history-check-request" }, "*");
1192
+ }
1193
+ }
1194
+ ),
1195
+ pendingNavigationURL && showUnsavedDialog ? /* @__PURE__ */ jsx7(
1196
+ "div",
1197
+ {
1198
+ "data-orion-unsaved-dialog": "true",
1199
+ style: {
1200
+ alignItems: "center",
1201
+ background: "rgba(0, 0, 0, 0.35)",
1202
+ display: "grid",
1203
+ inset: 0,
1204
+ justifyItems: "center",
1205
+ padding: "1rem",
1206
+ position: "fixed",
1207
+ zIndex: 2e3
1208
+ },
1209
+ children: /* @__PURE__ */ jsxs7(
1210
+ "div",
1211
+ {
1212
+ style: {
1213
+ background: "#ffffff",
1214
+ border: "1px solid var(--orion-admin-card-border)",
1215
+ borderRadius: 14,
1216
+ boxShadow: "0 20px 42px rgba(13, 74, 55, 0.28)",
1217
+ display: "grid",
1218
+ gap: "0.9rem",
1219
+ maxWidth: 420,
1220
+ padding: "1rem",
1221
+ width: "100%"
1222
+ },
1223
+ children: [
1224
+ /* @__PURE__ */ jsxs7("div", { children: [
1225
+ /* @__PURE__ */ jsx7("div", { style: { color: "var(--orion-admin-text)", fontSize: "1rem", fontWeight: 700 }, children: "Unsaved changes" }),
1226
+ /* @__PURE__ */ jsx7("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem", marginTop: "0.4rem" }, children: "You have unsaved edits in the page builder. Save before leaving this page?" })
1227
+ ] }),
1228
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "0.6rem", justifyContent: "flex-end" }, children: [
1229
+ /* @__PURE__ */ jsx7(
1230
+ "button",
1231
+ {
1232
+ className: "orion-admin-action-button",
1233
+ onClick: () => {
1234
+ setPendingNavigationURL(null);
1235
+ setShowUnsavedDialog(false);
1236
+ },
1237
+ type: "button",
1238
+ children: "Stay"
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsx7(
1242
+ "button",
1243
+ {
1244
+ className: "orion-admin-action-button",
1245
+ onClick: () => {
1246
+ const next = pendingNavigationURL;
1247
+ if (next) {
1248
+ continueNavigation(next);
1249
+ }
1250
+ },
1251
+ style: { background: "#8d1d1d" },
1252
+ type: "button",
1253
+ children: "Leave Without Saving"
1254
+ }
1255
+ )
1256
+ ] })
1257
+ ]
1258
+ }
1259
+ )
1260
+ }
1261
+ ) : null
1262
+ ] });
1263
+ }
108
1264
  export {
109
- AdminShellClient
1265
+ AdminShellClient,
1266
+ HeaderNavEditorWithPreview,
1267
+ HeaderNavItemsEditor,
1268
+ MediaDetailPanel,
1269
+ MediaListItem,
1270
+ MediaUploadForm,
1271
+ PageEditorFrame
110
1272
  };