@orion-studios/payload-studio 0.5.0-beta.98 → 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 (63) 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 +4491 -736
  5. package/dist/admin/client.mjs +3367 -752
  6. package/dist/admin/index.d.mts +2 -1
  7. package/dist/admin/index.d.ts +2 -1
  8. package/dist/admin/index.js +498 -53
  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-Q2HGC67S.mjs +904 -0
  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.ts → 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.mts → 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 +1968 -1198
  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 +19 -5
  58. package/dist/chunk-2FO2ROW4.mjs +0 -468
  59. package/dist/chunk-SIL2J5MF.mjs +0 -155
  60. package/dist/index-BFXZue5i.d.ts +0 -178
  61. package/dist/index-CoYRBbf6.d.mts +0 -178
  62. package/dist/index-R7hA134j.d.mts +0 -140
  63. package/dist/index-vjrjy0P4.d.ts +0 -140
@@ -0,0 +1,1051 @@
1
+ 'use client';
2
+ import {
3
+ MAX_DIRECT_UPLOAD_BYTES,
4
+ optimizeImageForUpload
5
+ } from "./chunk-ROTPP5CU.mjs";
6
+
7
+ // src/admin-app/components/AdminShellClient.tsx
8
+ import { useEffect, useState } from "react";
9
+
10
+ // src/admin-app/routeRegistry.ts
11
+ var adminNavIcons = [
12
+ "dashboard",
13
+ "pages",
14
+ "forms",
15
+ "globals",
16
+ "media",
17
+ "tools",
18
+ "account",
19
+ "analytics"
20
+ ];
21
+ var roleCanAccessNav = (role, item) => {
22
+ if (!item.roles || item.roles.length === 0) {
23
+ return true;
24
+ }
25
+ if (!role) {
26
+ return false;
27
+ }
28
+ return item.roles.includes(role);
29
+ };
30
+ var navItemIsActive = (pathname, item) => {
31
+ if (item.href === "/admin") {
32
+ return pathname === "/admin";
33
+ }
34
+ return item.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
35
+ };
36
+
37
+ // src/admin-app/components/AdminShellClient.tsx
38
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
39
+ var iconSize = 20;
40
+ var iconStyle = { display: "block", flexShrink: 0 };
41
+ function NavIcon({ name }) {
42
+ const props = { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", style: iconStyle };
43
+ switch (name) {
44
+ case "dashboard":
45
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
46
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "7", height: "9", rx: "1" }),
47
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "3", width: "7", height: "5", rx: "1" }),
48
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "12", width: "7", height: "9", rx: "1" }),
49
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "16", width: "7", height: "5", rx: "1" })
50
+ ] });
51
+ case "pages":
52
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
53
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" }),
54
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
55
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "13", x2: "16", y2: "13" }),
56
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "17", x2: "12", y2: "17" })
57
+ ] });
58
+ case "forms":
59
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
60
+ /* @__PURE__ */ jsx("path", { d: "M9 3h6" }),
61
+ /* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
62
+ /* @__PURE__ */ jsx("path", { d: "M5 7h14a2 2 0 0 1 2 2v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a2 2 0 0 1 2-2Z" }),
63
+ /* @__PURE__ */ jsx("path", { d: "M7 11h10" }),
64
+ /* @__PURE__ */ jsx("path", { d: "M7 15h6" })
65
+ ] });
66
+ case "globals":
67
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
68
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
69
+ /* @__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" })
70
+ ] });
71
+ case "media":
72
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
73
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
74
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
75
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
76
+ ] });
77
+ case "tools":
78
+ 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" }) });
79
+ case "analytics":
80
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
81
+ /* @__PURE__ */ jsx("path", { d: "M4 19V5" }),
82
+ /* @__PURE__ */ jsx("path", { d: "M10 19V10" }),
83
+ /* @__PURE__ */ jsx("path", { d: "M16 19v-6" }),
84
+ /* @__PURE__ */ jsx("path", { d: "M22 19V3" })
85
+ ] });
86
+ case "account":
87
+ return /* @__PURE__ */ jsxs("svg", { ...props, children: [
88
+ /* @__PURE__ */ jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
89
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7", r: "4" })
90
+ ] });
91
+ default:
92
+ return null;
93
+ }
94
+ }
95
+ function AdminShellClient({
96
+ children,
97
+ brandName,
98
+ logoUrl,
99
+ userEmail,
100
+ userRole,
101
+ pathname,
102
+ navItems,
103
+ onLogout,
104
+ storageKey = "orion-admin-shell-collapsed"
105
+ }) {
106
+ const [collapsed, setCollapsed] = useState(false);
107
+ const [loggingOut, setLoggingOut] = useState(false);
108
+ useEffect(() => {
109
+ try {
110
+ const stored = window.localStorage.getItem(storageKey);
111
+ if (stored === "1") {
112
+ setCollapsed(true);
113
+ }
114
+ } catch {
115
+ }
116
+ }, [storageKey]);
117
+ const toggleSidebar = () => {
118
+ setCollapsed((current) => {
119
+ const next = !current;
120
+ try {
121
+ window.localStorage.setItem(storageKey, next ? "1" : "0");
122
+ } catch {
123
+ }
124
+ return next;
125
+ });
126
+ };
127
+ const handleLogout = async () => {
128
+ setLoggingOut(true);
129
+ try {
130
+ await onLogout();
131
+ } finally {
132
+ setLoggingOut(false);
133
+ }
134
+ };
135
+ return /* @__PURE__ */ jsxs("div", { className: `orion-admin-shell ${collapsed ? "is-collapsed" : ""}`, children: [
136
+ /* @__PURE__ */ jsxs("aside", { className: "orion-admin-sidebar", children: [
137
+ /* @__PURE__ */ jsx(
138
+ "button",
139
+ {
140
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
141
+ className: "orion-admin-sidebar-toggle",
142
+ onClick: toggleSidebar,
143
+ type: "button",
144
+ children: collapsed ? ">" : "<"
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsxs("div", { className: "orion-admin-brand-wrap", children: [
148
+ logoUrl ? /* @__PURE__ */ jsx("div", { className: "orion-admin-brand-logo", children: /* @__PURE__ */ jsx("img", { alt: `${brandName} logo`, src: logoUrl }) }) : null,
149
+ /* @__PURE__ */ jsxs("div", { className: "orion-admin-brand-text", children: [
150
+ /* @__PURE__ */ jsx("div", { className: "orion-admin-brand-name", title: brandName, children: collapsed ? brandName.slice(0, 1).toUpperCase() : brandName }),
151
+ !collapsed ? /* @__PURE__ */ jsx("div", { className: "orion-admin-brand-subtitle", children: "Studio" }) : null
152
+ ] })
153
+ ] }),
154
+ /* @__PURE__ */ jsx("nav", { className: "orion-admin-nav", children: navItems.filter((item) => roleCanAccessNav(userRole, item)).map((item) => {
155
+ const active = navItemIsActive(pathname, item);
156
+ return /* @__PURE__ */ jsx(
157
+ "a",
158
+ {
159
+ className: `orion-admin-nav-link ${active ? "is-active" : ""}`,
160
+ href: item.href,
161
+ title: item.label,
162
+ children: item.icon ? collapsed ? /* @__PURE__ */ jsx(NavIcon, { name: item.icon }) : /* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: "0.6rem" }, children: [
163
+ /* @__PURE__ */ jsx(NavIcon, { name: item.icon }),
164
+ item.label
165
+ ] }) : collapsed ? item.label.slice(0, 1) : item.label
166
+ },
167
+ item.href
168
+ );
169
+ }) }),
170
+ /* @__PURE__ */ jsxs("div", { className: "orion-admin-sidebar-footer", children: [
171
+ !collapsed ? /* @__PURE__ */ jsxs(Fragment, { children: [
172
+ /* @__PURE__ */ jsx("div", { className: "orion-admin-user-label", children: "Signed in as" }),
173
+ /* @__PURE__ */ jsx("div", { className: "orion-admin-user-email", children: userEmail })
174
+ ] }) : null,
175
+ /* @__PURE__ */ jsx("button", { className: "orion-admin-logout", disabled: loggingOut, onClick: handleLogout, type: "button", children: loggingOut ? "..." : "Log out" })
176
+ ] })
177
+ ] }),
178
+ /* @__PURE__ */ jsx("main", { className: "orion-admin-main", children })
179
+ ] });
180
+ }
181
+
182
+ // src/admin-app/components/HeaderNavItemsEditor.tsx
183
+ import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
184
+
185
+ // src/admin-app/navigationLinks.ts
186
+ var fallbackHomeOption = {
187
+ href: "/",
188
+ label: "Home",
189
+ title: "Home"
190
+ };
191
+ var buildAdminPageLinkOptions = (pages) => {
192
+ const options = pages.map((page) => {
193
+ const href = typeof page.path === "string" ? page.path.trim() : "";
194
+ const title = typeof page.title === "string" && page.title.trim().length > 0 ? page.title.trim() : "Untitled Page";
195
+ if (!href) {
196
+ return null;
197
+ }
198
+ const depth = href === "/" ? 0 : href.split("/").filter(Boolean).length;
199
+ const indentLevel = Math.max(depth - 1, 0);
200
+ const indent = indentLevel > 0 ? `${"\u21B3 ".repeat(indentLevel)}` : "";
201
+ return {
202
+ href,
203
+ label: `${indent}${title}`,
204
+ title
205
+ };
206
+ }).filter((option) => option !== null).sort((a, b) => {
207
+ if (a.href === "/" && b.href !== "/") return -1;
208
+ if (b.href === "/" && a.href !== "/") return 1;
209
+ return a.href.localeCompare(b.href);
210
+ });
211
+ if (options.length === 0) {
212
+ return [fallbackHomeOption];
213
+ }
214
+ return options;
215
+ };
216
+ var normalizeAdminNavInputs = (rows, pageOptions) => {
217
+ const allowedLinks = new Map(pageOptions.map((option) => [option.href, option.title]));
218
+ const deduped = [];
219
+ const seen = /* @__PURE__ */ new Set();
220
+ for (const row of rows) {
221
+ const href = typeof row.href === "string" ? row.href.trim() : "";
222
+ const defaultLabel = allowedLinks.get(href);
223
+ const explicitLabel = typeof row.label === "string" ? row.label.trim() : "";
224
+ const parentHref = typeof row.parentHref === "string" ? row.parentHref.trim() : "";
225
+ if (!href || !defaultLabel || seen.has(href)) {
226
+ continue;
227
+ }
228
+ seen.add(href);
229
+ deduped.push({
230
+ href,
231
+ label: explicitLabel.length > 0 ? explicitLabel : defaultLabel,
232
+ ...parentHref.length > 0 ? { parentHref } : {}
233
+ });
234
+ }
235
+ const hrefs = new Set(deduped.map((item) => item.href));
236
+ const parentByHref = new Map(deduped.map((item) => [item.href, item.parentHref || ""]));
237
+ const createsCycle = (href, parentHref) => {
238
+ const visited = /* @__PURE__ */ new Set([href]);
239
+ let current = parentHref;
240
+ while (current) {
241
+ if (visited.has(current)) {
242
+ return true;
243
+ }
244
+ visited.add(current);
245
+ current = parentByHref.get(current) || "";
246
+ }
247
+ return false;
248
+ };
249
+ return deduped.map((item) => {
250
+ const parentHref = item.parentHref;
251
+ if (!parentHref || parentHref === item.href || !hrefs.has(parentHref) || createsCycle(item.href, parentHref)) {
252
+ return {
253
+ href: item.href,
254
+ label: item.label
255
+ };
256
+ }
257
+ return item;
258
+ });
259
+ };
260
+
261
+ // src/admin-app/components/HeaderNavItemsEditor.tsx
262
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
263
+ var toRow = (item, index) => ({
264
+ id: `row-${index}-${item.href || "empty"}`,
265
+ href: item.href || "",
266
+ label: item.label || "",
267
+ parentHref: item.parentHref || ""
268
+ });
269
+ var moveRow = (rows, fromIndex, toIndex) => {
270
+ if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= rows.length || toIndex >= rows.length) {
271
+ return rows;
272
+ }
273
+ const next = [...rows];
274
+ const [moved] = next.splice(fromIndex, 1);
275
+ if (!moved) {
276
+ return rows;
277
+ }
278
+ next.splice(toIndex, 0, moved);
279
+ return next;
280
+ };
281
+ function HeaderNavItemsEditor({ initialItems, pageOptions, onItemsChange }) {
282
+ const [rows, setRows] = useState2(() => initialItems.map(toRow));
283
+ const [nextRowID, setNextRowID] = useState2(initialItems.length);
284
+ const [draggingRowID, setDraggingRowID] = useState2(null);
285
+ const [dragOverRowID, setDragOverRowID] = useState2(null);
286
+ const pageOptionByHref = useMemo(() => new Map(pageOptions.map((option) => [option.href, option])), [pageOptions]);
287
+ const serializedState = useMemo(
288
+ () => JSON.stringify(
289
+ rows.map((row) => ({
290
+ href: row.href.trim(),
291
+ label: row.label.trim(),
292
+ parentHref: row.parentHref.trim() || void 0
293
+ }))
294
+ ),
295
+ [rows]
296
+ );
297
+ const normalizedItems = useMemo(() => {
298
+ const inputs = rows.map((row) => ({
299
+ href: row.href,
300
+ label: row.label,
301
+ parentHref: row.parentHref
302
+ }));
303
+ return normalizeAdminNavInputs(inputs, pageOptions);
304
+ }, [rows, pageOptions]);
305
+ useEffect2(() => {
306
+ onItemsChange?.(normalizedItems);
307
+ }, [normalizedItems, onItemsChange]);
308
+ const setRowValue = (rowID, changes) => {
309
+ setRows(
310
+ (currentRows) => currentRows.map((row) => {
311
+ if (row.id !== rowID) {
312
+ return row;
313
+ }
314
+ const nextRow = { ...row, ...changes };
315
+ if (nextRow.parentHref === nextRow.href) {
316
+ nextRow.parentHref = "";
317
+ }
318
+ return nextRow;
319
+ })
320
+ );
321
+ };
322
+ const removeRow = (rowID) => {
323
+ setRows((currentRows) => {
324
+ const removedRow = currentRows.find((candidate) => candidate.id === rowID);
325
+ const removedHref = removedRow?.href || "";
326
+ return currentRows.filter((row) => row.id !== rowID).map((row) => removedHref && row.parentHref === removedHref ? { ...row, parentHref: "" } : row);
327
+ });
328
+ };
329
+ const addRow = () => {
330
+ setRows((currentRows) => [
331
+ ...currentRows,
332
+ {
333
+ id: `row-new-${nextRowID}`,
334
+ href: "",
335
+ label: "",
336
+ parentHref: ""
337
+ }
338
+ ]);
339
+ setNextRowID((current) => current + 1);
340
+ };
341
+ return /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor", children: [
342
+ /* @__PURE__ */ jsx2("input", { name: "navItemsState", readOnly: true, type: "hidden", value: serializedState }),
343
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-head", children: [
344
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-title", children: "Navigation Items" }),
345
+ /* @__PURE__ */ jsx2("button", { className: "orion-admin-nav-editor-add", onClick: addRow, type: "button", children: "Add Item" })
346
+ ] }),
347
+ /* @__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." }),
348
+ rows.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-empty", children: 'No navigation items yet. Click "Add Item" to start.' }) : null,
349
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-nav-editor-list", children: rows.map((row, rowIndex) => {
350
+ const parentCandidates = rows.filter((candidate) => candidate.id !== row.id && candidate.href.trim().length > 0).map((candidate) => {
351
+ const resolved = pageOptionByHref.get(candidate.href);
352
+ return {
353
+ href: candidate.href,
354
+ label: candidate.label.trim() || resolved?.title || candidate.href
355
+ };
356
+ });
357
+ const selectedPage = pageOptionByHref.get(row.href);
358
+ const labelPlaceholder = selectedPage?.title || "Navigation Label";
359
+ return /* @__PURE__ */ jsxs2(
360
+ "div",
361
+ {
362
+ className: `orion-admin-nav-editor-row${draggingRowID === row.id ? " is-dragging" : ""}${dragOverRowID === row.id && draggingRowID !== row.id ? " is-drop-target" : ""}`,
363
+ draggable: true,
364
+ onDragEnd: () => {
365
+ setDraggingRowID(null);
366
+ setDragOverRowID(null);
367
+ },
368
+ onDragEnter: () => {
369
+ if (draggingRowID && draggingRowID !== row.id) {
370
+ setDragOverRowID(row.id);
371
+ }
372
+ },
373
+ onDragLeave: () => {
374
+ if (dragOverRowID === row.id) {
375
+ setDragOverRowID(null);
376
+ }
377
+ },
378
+ onDragOver: (event) => {
379
+ event.preventDefault();
380
+ if (draggingRowID && draggingRowID !== row.id && dragOverRowID !== row.id) {
381
+ setDragOverRowID(row.id);
382
+ }
383
+ },
384
+ onDragStart: (event) => {
385
+ setDraggingRowID(row.id);
386
+ event.dataTransfer.effectAllowed = "move";
387
+ },
388
+ onDrop: (event) => {
389
+ event.preventDefault();
390
+ if (!draggingRowID || draggingRowID === row.id) {
391
+ return;
392
+ }
393
+ setRows((currentRows) => {
394
+ const fromIndex = currentRows.findIndex((entry) => entry.id === draggingRowID);
395
+ const toIndex = currentRows.findIndex((entry) => entry.id === row.id);
396
+ return moveRow(currentRows, fromIndex, toIndex);
397
+ });
398
+ setDraggingRowID(null);
399
+ setDragOverRowID(null);
400
+ },
401
+ children: [
402
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-row-head", children: [
403
+ /* @__PURE__ */ jsx2("span", { className: "orion-admin-nav-editor-drag", children: "Drag" }),
404
+ /* @__PURE__ */ jsxs2("span", { className: "orion-admin-nav-editor-row-index", children: [
405
+ "#",
406
+ rowIndex + 1
407
+ ] }),
408
+ /* @__PURE__ */ jsx2("button", { className: "orion-admin-nav-editor-remove", onClick: () => removeRow(row.id), type: "button", children: "Remove" })
409
+ ] }),
410
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-nav-editor-row-grid", children: [
411
+ /* @__PURE__ */ jsxs2("label", { children: [
412
+ "Label",
413
+ /* @__PURE__ */ jsx2(
414
+ "input",
415
+ {
416
+ name: `navLabel_${rowIndex}`,
417
+ onChange: (event) => setRowValue(row.id, { label: event.target.value }),
418
+ placeholder: labelPlaceholder,
419
+ type: "text",
420
+ value: row.label
421
+ }
422
+ )
423
+ ] }),
424
+ /* @__PURE__ */ jsxs2("label", { children: [
425
+ "Page",
426
+ /* @__PURE__ */ jsxs2(
427
+ "select",
428
+ {
429
+ name: `navPage_${rowIndex}`,
430
+ onChange: (event) => {
431
+ const nextHref = event.target.value;
432
+ const nextParent = row.parentHref && row.parentHref === nextHref ? "" : row.parentHref;
433
+ setRowValue(row.id, { href: nextHref, parentHref: nextParent });
434
+ },
435
+ value: row.href,
436
+ children: [
437
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Select page..." }),
438
+ pageOptions.map((pageOption) => /* @__PURE__ */ jsx2("option", { value: pageOption.href, children: pageOption.label }, `${pageOption.href}-${pageOption.title}`))
439
+ ]
440
+ }
441
+ )
442
+ ] }),
443
+ /* @__PURE__ */ jsxs2("label", { children: [
444
+ "Parent (dropdown under)",
445
+ /* @__PURE__ */ jsxs2(
446
+ "select",
447
+ {
448
+ name: `navParentHref_${rowIndex}`,
449
+ onChange: (event) => setRowValue(row.id, { parentHref: event.target.value }),
450
+ value: row.parentHref,
451
+ children: [
452
+ /* @__PURE__ */ jsx2("option", { value: "", children: "Top-level item" }),
453
+ parentCandidates.map((candidate) => /* @__PURE__ */ jsx2("option", { value: candidate.href, children: candidate.label }, `${row.id}-parent-${candidate.href}`))
454
+ ]
455
+ }
456
+ )
457
+ ] })
458
+ ] })
459
+ ]
460
+ },
461
+ row.id
462
+ );
463
+ }) })
464
+ ] });
465
+ }
466
+
467
+ // src/admin-app/components/SitePreview.tsx
468
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
469
+ var fallbackHeaderNav = [{ href: "/", label: "Home" }];
470
+ var socialGlyphByPlatform = {
471
+ facebook: "f",
472
+ instagram: "ig",
473
+ linkedin: "in",
474
+ pinterest: "p",
475
+ snapchat: "sc",
476
+ tiktok: "tt",
477
+ x: "x",
478
+ youtube: "yt"
479
+ };
480
+ var getInitials = (value) => {
481
+ const initials = value.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() || "").join("");
482
+ return initials || "OS";
483
+ };
484
+ var getSocialGlyph = (platform) => {
485
+ const normalized = platform.trim().toLowerCase();
486
+ return socialGlyphByPlatform[normalized] || normalized.slice(0, 2).toUpperCase() || "\u2022";
487
+ };
488
+ var getTaglineLines = (tagline) => {
489
+ const parts = (tagline || "").split(".").map((part) => part.trim()).filter(Boolean);
490
+ const first = parts[0] ? `${parts[0]}.` : "Warm Gifts.";
491
+ const second = parts[1] ? `${parts[1]}.` : "Local Heart.";
492
+ return [first, second];
493
+ };
494
+ function PreviewSocialLinks({
495
+ links,
496
+ variant
497
+ }) {
498
+ if (links.length === 0) {
499
+ return null;
500
+ }
501
+ return /* @__PURE__ */ jsx3(
502
+ "div",
503
+ {
504
+ className: variant === "header" ? "orion-admin-site-preview-social-list" : "orion-admin-site-preview-social-list orion-admin-site-preview-social-list--footer",
505
+ children: links.map((item) => /* @__PURE__ */ jsx3(
506
+ "a",
507
+ {
508
+ className: variant === "header" ? "orion-admin-site-preview-social-badge" : "orion-admin-site-preview-social-badge orion-admin-site-preview-social-badge--footer",
509
+ href: item.url,
510
+ tabIndex: -1,
511
+ children: getSocialGlyph(item.platform)
512
+ },
513
+ `${item.platform}-${item.url}`
514
+ ))
515
+ }
516
+ );
517
+ }
518
+ function PreviewLogo({
519
+ className,
520
+ logoUrl,
521
+ siteName
522
+ }) {
523
+ if (logoUrl && logoUrl.trim().length > 0) {
524
+ return /* @__PURE__ */ jsx3("img", { alt: `${siteName} logo`, className, src: logoUrl });
525
+ }
526
+ return /* @__PURE__ */ jsx3("div", { className: `${className} orion-admin-site-preview-logo-fallback`, children: getInitials(siteName) });
527
+ }
528
+ function SiteHeaderPreview({ site }) {
529
+ const navItems = site.navItems.length > 0 ? site.navItems : fallbackHeaderNav;
530
+ const [taglineLineOne, taglineLineTwo] = getTaglineLines(site.tagline);
531
+ const socialLinks = site.socialLinks?.slice(0, 4) || [];
532
+ const activePath = site.activePath || navItems[0]?.href || "/";
533
+ return /* @__PURE__ */ jsxs3("div", { "aria-hidden": "true", className: "orion-admin-site-preview orion-admin-site-preview--header", children: [
534
+ /* @__PURE__ */ jsx3("div", { className: "orion-admin-site-preview-header-bar", children: /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-shell orion-admin-site-preview-shell--utility", children: [
535
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-utility-copy", children: site.locationSummary?.address?.trim() || "Neighborhood gift shop" }),
536
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-utility-meta", children: [
537
+ site.locationSummary?.hours?.trim() ? /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-utility-copy", children: site.locationSummary.hours.trim() }) : null,
538
+ /* @__PURE__ */ jsx3(PreviewSocialLinks, { links: socialLinks, variant: "header" }),
539
+ /* @__PURE__ */ jsx3("a", { className: "orion-admin-site-preview-header-action", href: site.actionHref || "/contact", tabIndex: -1, children: site.actionLabel || "Visit Today" })
540
+ ] })
541
+ ] }) }),
542
+ /* @__PURE__ */ jsx3("div", { className: "orion-admin-site-preview-header-main", children: /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-shell orion-admin-site-preview-shell--main", children: [
543
+ /* @__PURE__ */ jsxs3("a", { className: "orion-admin-site-preview-brand", href: "/", tabIndex: -1, children: [
544
+ /* @__PURE__ */ jsx3(
545
+ PreviewLogo,
546
+ {
547
+ className: "orion-admin-site-preview-header-logo",
548
+ logoUrl: site.logoUrl,
549
+ siteName: site.siteName
550
+ }
551
+ ),
552
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-tagline", children: [
553
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-tagline-line", children: taglineLineOne }),
554
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-tagline-line", children: taglineLineTwo })
555
+ ] })
556
+ ] }),
557
+ /* @__PURE__ */ jsx3("nav", { className: "orion-admin-site-preview-nav", children: navItems.map((item) => /* @__PURE__ */ jsx3(
558
+ "a",
559
+ {
560
+ className: item.href === activePath ? "is-active" : void 0,
561
+ href: item.href,
562
+ tabIndex: -1,
563
+ children: item.label
564
+ },
565
+ `${item.href}-${item.label}`
566
+ )) }),
567
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-mobile-toggle", children: [
568
+ /* @__PURE__ */ jsx3("span", {}),
569
+ /* @__PURE__ */ jsx3("span", {}),
570
+ /* @__PURE__ */ jsx3("span", {})
571
+ ] })
572
+ ] }) })
573
+ ] });
574
+ }
575
+ function SiteFooterPreview({ site }) {
576
+ const description = site.description?.trim() || site.tagline?.trim() || "Thoughtful gifts, warm local service, and a beautifully stocked neighborhood shop.";
577
+ const socialLinks = site.socialLinks?.slice(0, 4) || [];
578
+ const builtByLabel = site.builtByLabel || "Built by Orion Studios";
579
+ const builtByHref = site.builtByHref || "https://orionstudios.dev";
580
+ return /* @__PURE__ */ jsx3("div", { "aria-hidden": "true", className: "orion-admin-site-preview orion-admin-site-preview--footer", children: /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-footer-shell", children: [
581
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-footer-grid", children: [
582
+ /* @__PURE__ */ jsxs3("div", { children: [
583
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-footer-brand", children: [
584
+ /* @__PURE__ */ jsx3(
585
+ PreviewLogo,
586
+ {
587
+ className: "orion-admin-site-preview-footer-logo",
588
+ logoUrl: site.logoUrl,
589
+ siteName: site.siteName
590
+ }
591
+ ),
592
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-footer-name", children: site.siteName })
593
+ ] }),
594
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-footer-description", children: description })
595
+ ] }),
596
+ /* @__PURE__ */ jsxs3("div", { children: [
597
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-footer-eyebrow", children: "Shop focus" }),
598
+ /* @__PURE__ */ jsx3("div", { className: "orion-admin-site-preview-footer-list", children: site.footerCategories.map((item) => /* @__PURE__ */ jsx3("p", { children: item }, item)) })
599
+ ] }),
600
+ /* @__PURE__ */ jsxs3("div", { children: [
601
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-footer-eyebrow", children: "Explore" }),
602
+ /* @__PURE__ */ jsx3("div", { className: "orion-admin-site-preview-footer-list", children: site.footerLinks.map((item) => /* @__PURE__ */ jsx3("a", { href: item.href, tabIndex: -1, children: item.label }, `${item.href}-${item.label}`)) })
603
+ ] }),
604
+ /* @__PURE__ */ jsxs3("div", { children: [
605
+ /* @__PURE__ */ jsx3("p", { className: "orion-admin-site-preview-footer-eyebrow", children: "Visit" }),
606
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-footer-contact", children: [
607
+ site.locationSummary.address?.trim() ? /* @__PURE__ */ jsx3("p", { children: site.locationSummary.address.trim() }) : null,
608
+ site.locationSummary.hours?.trim() ? /* @__PURE__ */ jsx3("p", { children: site.locationSummary.hours.trim() }) : null,
609
+ site.locationSummary.phone?.trim() ? /* @__PURE__ */ jsx3("a", { href: `tel:${site.locationSummary.phone.trim()}`, tabIndex: -1, children: site.locationSummary.phone.trim() }) : null,
610
+ /* @__PURE__ */ jsx3("a", { href: `mailto:${site.contactEmail}`, tabIndex: -1, children: site.contactEmail })
611
+ ] }),
612
+ /* @__PURE__ */ jsx3(PreviewSocialLinks, { links: socialLinks, variant: "footer" })
613
+ ] })
614
+ ] }),
615
+ /* @__PURE__ */ jsxs3("div", { className: "orion-admin-site-preview-footer-meta", children: [
616
+ /* @__PURE__ */ jsx3("span", { children: site.copyright }),
617
+ /* @__PURE__ */ jsx3("a", { href: builtByHref, tabIndex: -1, children: builtByLabel })
618
+ ] })
619
+ ] }) });
620
+ }
621
+
622
+ // src/admin-app/components/HeaderNavEditorWithPreview.tsx
623
+ import { useMemo as useMemo2, useState as useState3 } from "react";
624
+
625
+ // src/admin-app/nestedNavigation.ts
626
+ var normalizeNestedNavItems = (items) => {
627
+ const deduped = [];
628
+ const seen = /* @__PURE__ */ new Set();
629
+ for (const item of items) {
630
+ const href = typeof item.href === "string" ? item.href.trim() : "";
631
+ const label = typeof item.label === "string" ? item.label.trim() : "";
632
+ const parentHref = typeof item.parentHref === "string" ? item.parentHref.trim() : "";
633
+ if (!href || !label || seen.has(href)) {
634
+ continue;
635
+ }
636
+ seen.add(href);
637
+ deduped.push({
638
+ href,
639
+ label,
640
+ ...parentHref ? { parentHref } : {}
641
+ });
642
+ }
643
+ const hrefs = new Set(deduped.map((item) => item.href));
644
+ return deduped.map((item) => ({
645
+ href: item.href,
646
+ label: item.label,
647
+ ...item.parentHref && item.parentHref !== item.href && hrefs.has(item.parentHref) ? { parentHref: item.parentHref } : {}
648
+ }));
649
+ };
650
+ var buildNestedNavTree = (items) => {
651
+ const childrenByParent = /* @__PURE__ */ new Map();
652
+ const topLevel = [];
653
+ for (const item of items) {
654
+ if (!item.parentHref) {
655
+ topLevel.push(item);
656
+ continue;
657
+ }
658
+ const children = childrenByParent.get(item.parentHref) || [];
659
+ children.push(item);
660
+ childrenByParent.set(item.parentHref, children);
661
+ }
662
+ if (topLevel.length === 0 && items.length > 0) {
663
+ return {
664
+ topLevel: items.map((item) => ({ href: item.href, label: item.label })),
665
+ childrenByParent: /* @__PURE__ */ new Map()
666
+ };
667
+ }
668
+ return { childrenByParent, topLevel };
669
+ };
670
+
671
+ // src/admin-app/components/HeaderNavEditorWithPreview.tsx
672
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
673
+ var fallbackNav = [{ href: "/", label: "Home" }];
674
+ function HeaderNavEditorWithPreview({
675
+ activePath,
676
+ actionHref,
677
+ actionLabel,
678
+ brandName,
679
+ initialItems,
680
+ locationSummary,
681
+ logoUrl,
682
+ onItemsChange,
683
+ pageOptions,
684
+ socialLinks,
685
+ tagline = ""
686
+ }) {
687
+ const [liveItems, setLiveItems] = useState3(initialItems);
688
+ const previewItems = useMemo2(() => {
689
+ const normalized = normalizeNestedNavItems(liveItems);
690
+ const tree = buildNestedNavTree(normalized);
691
+ return tree.topLevel.length > 0 ? tree : buildNestedNavTree(fallbackNav);
692
+ }, [liveItems]);
693
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
694
+ /* @__PURE__ */ jsx4(
695
+ HeaderNavItemsEditor,
696
+ {
697
+ initialItems,
698
+ onItemsChange: (items) => {
699
+ setLiveItems(items);
700
+ onItemsChange?.(items);
701
+ },
702
+ pageOptions
703
+ }
704
+ ),
705
+ /* @__PURE__ */ jsx4("div", { className: "orion-admin-preview-label", children: "Header Preview" }),
706
+ /* @__PURE__ */ jsx4("div", { className: "orion-admin-preview-frame", children: /* @__PURE__ */ jsx4("div", { className: "orion-admin-preview-surface", children: /* @__PURE__ */ jsx4(
707
+ SiteHeaderPreview,
708
+ {
709
+ site: {
710
+ activePath,
711
+ actionHref,
712
+ actionLabel,
713
+ locationSummary,
714
+ logoUrl,
715
+ navItems: previewItems.topLevel.map((item) => ({
716
+ href: item.href,
717
+ label: item.label
718
+ })),
719
+ siteName: brandName,
720
+ socialLinks,
721
+ tagline
722
+ }
723
+ }
724
+ ) }) })
725
+ ] });
726
+ }
727
+
728
+ // src/admin-app/components/MediaListItem.tsx
729
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
730
+ function formatFileSize(bytes) {
731
+ if (bytes < 1024) return `${bytes} B`;
732
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
733
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
734
+ }
735
+ function MediaListItem({
736
+ id,
737
+ filename,
738
+ alt,
739
+ url,
740
+ filesize,
741
+ width,
742
+ height,
743
+ mimeType,
744
+ href
745
+ }) {
746
+ const label = filename || `Media ${id}`;
747
+ const altText = alt || "";
748
+ const metaParts = [];
749
+ if (typeof filesize === "number") metaParts.push(formatFileSize(filesize));
750
+ if (typeof width === "number" && typeof height === "number") metaParts.push(`${width}\xD7${height}`);
751
+ if (typeof mimeType === "string") metaParts.push(mimeType);
752
+ return /* @__PURE__ */ jsxs5("a", { className: "orion-admin-list-item", href, children: [
753
+ /* @__PURE__ */ jsxs5("div", { style: { alignItems: "center", display: "flex", gap: "0.8rem" }, children: [
754
+ url ? /* @__PURE__ */ jsx5("img", { alt: altText || label, className: "orion-admin-media-preview", src: url }) : null,
755
+ /* @__PURE__ */ jsxs5("div", { children: [
756
+ /* @__PURE__ */ jsx5("strong", { children: label }),
757
+ /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", children: altText || "No alt text" }),
758
+ metaParts.length > 0 ? /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", style: { marginTop: "0.15rem" }, children: metaParts.join(" \xB7 ") }) : null
759
+ ] })
760
+ ] }),
761
+ /* @__PURE__ */ jsx5("span", { className: "orion-admin-list-meta", children: "Edit" })
762
+ ] });
763
+ }
764
+
765
+ // src/admin-app/components/MediaUploadForm.tsx
766
+ import { useState as useState4, useRef, useCallback } from "react";
767
+ import { useRouter } from "next/navigation";
768
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
769
+ var MEDIA_LIBRARY_SYNC_EVENT = "orion-media-library-updated";
770
+ var notifyMediaLibraryUpdated = () => {
771
+ if (typeof window === "undefined") {
772
+ return;
773
+ }
774
+ const updatedAt = Date.now().toString();
775
+ window.dispatchEvent(
776
+ new CustomEvent(MEDIA_LIBRARY_SYNC_EVENT, {
777
+ detail: {
778
+ updatedAt
779
+ }
780
+ })
781
+ );
782
+ try {
783
+ window.localStorage.setItem(MEDIA_LIBRARY_SYNC_EVENT, updatedAt);
784
+ } catch {
785
+ }
786
+ };
787
+ var parseUploadError = async (response) => {
788
+ const fallback = "Upload failed. Please check the image and required fields, then try again.";
789
+ try {
790
+ const body = await response.json();
791
+ const nestedMessage = body.errors?.[0]?.data?.errors?.[0]?.message;
792
+ if (typeof nestedMessage === "string" && nestedMessage.trim().length > 0) {
793
+ return nestedMessage;
794
+ }
795
+ const topMessage = body.errors?.[0]?.message;
796
+ if (typeof topMessage === "string" && topMessage.trim().length > 0) {
797
+ return topMessage;
798
+ }
799
+ if (typeof body.message === "string" && body.message.trim().length > 0) {
800
+ return body.message;
801
+ }
802
+ } catch {
803
+ }
804
+ return fallback;
805
+ };
806
+ function MediaUploadForm() {
807
+ const router = useRouter();
808
+ const fileInputRef = useRef(null);
809
+ const [alt, setAlt] = useState4("");
810
+ const [file, setFile] = useState4(null);
811
+ const [preview, setPreview] = useState4(null);
812
+ const [dragging, setDragging] = useState4(false);
813
+ const [submitting, setSubmitting] = useState4(false);
814
+ const [error, setError] = useState4(null);
815
+ const handleFile = useCallback((selectedFile) => {
816
+ setFile(selectedFile);
817
+ if (preview) {
818
+ URL.revokeObjectURL(preview);
819
+ setPreview(null);
820
+ }
821
+ if (selectedFile && selectedFile.type.startsWith("image/")) {
822
+ setPreview(URL.createObjectURL(selectedFile));
823
+ }
824
+ }, [preview]);
825
+ const onDragOver = useCallback((e) => {
826
+ e.preventDefault();
827
+ setDragging(true);
828
+ }, []);
829
+ const onDragLeave = useCallback((e) => {
830
+ e.preventDefault();
831
+ setDragging(false);
832
+ }, []);
833
+ const onDrop = useCallback((e) => {
834
+ e.preventDefault();
835
+ setDragging(false);
836
+ const droppedFile = e.dataTransfer.files?.[0] || null;
837
+ if (droppedFile) {
838
+ handleFile(droppedFile);
839
+ }
840
+ }, [handleFile]);
841
+ const upload = async (event) => {
842
+ event.preventDefault();
843
+ if (!file) {
844
+ setError("Select an image to upload.");
845
+ return;
846
+ }
847
+ setSubmitting(true);
848
+ setError(null);
849
+ try {
850
+ const optimizedFile = await optimizeImageForUpload(file);
851
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
852
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
853
+ }
854
+ const formData = new FormData();
855
+ const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
856
+ const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
857
+ formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
858
+ formData.set("alt", resolvedAlt);
859
+ formData.set("file", optimizedFile);
860
+ const response = await fetch("/api/media", {
861
+ method: "POST",
862
+ credentials: "include",
863
+ body: formData
864
+ });
865
+ if (!response.ok) {
866
+ throw new Error(await parseUploadError(response));
867
+ }
868
+ setAlt("");
869
+ setFile(null);
870
+ if (preview) {
871
+ URL.revokeObjectURL(preview);
872
+ setPreview(null);
873
+ }
874
+ notifyMediaLibraryUpdated();
875
+ router.refresh();
876
+ } catch (err) {
877
+ setError(err instanceof Error ? err.message : "Upload failed");
878
+ } finally {
879
+ setSubmitting(false);
880
+ }
881
+ };
882
+ return /* @__PURE__ */ jsxs6("form", { className: "orion-admin-upload-form", onSubmit: upload, children: [
883
+ /* @__PURE__ */ jsxs6("label", { children: [
884
+ "Alt text",
885
+ /* @__PURE__ */ jsx6(
886
+ "input",
887
+ {
888
+ onChange: (event) => setAlt(event.target.value),
889
+ placeholder: "Describe the image",
890
+ type: "text",
891
+ value: alt
892
+ }
893
+ )
894
+ ] }),
895
+ /* @__PURE__ */ jsxs6(
896
+ "div",
897
+ {
898
+ className: `orion-admin-dropzone${dragging ? " is-dragging" : ""}${file ? " has-file" : ""}`,
899
+ onClick: () => fileInputRef.current?.click(),
900
+ onDragLeave,
901
+ onDragOver,
902
+ onDrop,
903
+ children: [
904
+ preview ? /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-preview", children: [
905
+ /* @__PURE__ */ jsx6("img", { alt: "Upload preview", src: preview }),
906
+ /* @__PURE__ */ jsx6("span", { children: file?.name })
907
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-label", children: [
908
+ /* @__PURE__ */ jsx6("strong", { children: "Drop an image here" }),
909
+ /* @__PURE__ */ jsx6("span", { children: "or click to browse" })
910
+ ] }),
911
+ /* @__PURE__ */ jsx6(
912
+ "input",
913
+ {
914
+ accept: "image/*",
915
+ onChange: (event) => handleFile(event.target.files?.[0] || null),
916
+ ref: fileInputRef,
917
+ style: { display: "none" },
918
+ type: "file"
919
+ }
920
+ )
921
+ ]
922
+ }
923
+ ),
924
+ error ? /* @__PURE__ */ jsx6("div", { className: "orion-admin-upload-error", children: error }) : null,
925
+ /* @__PURE__ */ jsx6("button", { disabled: submitting, type: "submit", children: submitting ? "Uploading..." : "Upload" })
926
+ ] });
927
+ }
928
+
929
+ // src/admin-app/components/MediaDetailPanel.tsx
930
+ import { useState as useState5 } from "react";
931
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
932
+ function formatFileSize2(bytes) {
933
+ if (bytes < 1024) return `${bytes} B`;
934
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
935
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
936
+ }
937
+ function MediaDetailPanel({
938
+ id,
939
+ filename,
940
+ alt,
941
+ url,
942
+ filesize,
943
+ width,
944
+ height,
945
+ mimeType,
946
+ createdAt,
947
+ updateAction,
948
+ deleteAction
949
+ }) {
950
+ const [copied, setCopied] = useState5(false);
951
+ const [confirmDelete, setConfirmDelete] = useState5(false);
952
+ const copyUrl = async () => {
953
+ if (!url) return;
954
+ try {
955
+ await navigator.clipboard.writeText(url);
956
+ setCopied(true);
957
+ setTimeout(() => setCopied(false), 2e3);
958
+ } catch {
959
+ const input = document.createElement("input");
960
+ input.value = url;
961
+ document.body.appendChild(input);
962
+ input.select();
963
+ document.execCommand("copy");
964
+ document.body.removeChild(input);
965
+ setCopied(true);
966
+ setTimeout(() => setCopied(false), 2e3);
967
+ }
968
+ };
969
+ const metaRows = [];
970
+ if (filename) metaRows.push({ label: "Filename", value: filename });
971
+ if (typeof filesize === "number") metaRows.push({ label: "File size", value: formatFileSize2(filesize) });
972
+ if (typeof width === "number" && typeof height === "number") metaRows.push({ label: "Dimensions", value: `${width} \xD7 ${height} px` });
973
+ if (mimeType) metaRows.push({ label: "Type", value: mimeType });
974
+ if (createdAt) {
975
+ try {
976
+ metaRows.push({ label: "Uploaded", value: new Date(createdAt).toLocaleDateString() });
977
+ } catch {
978
+ metaRows.push({ label: "Uploaded", value: createdAt });
979
+ }
980
+ }
981
+ return /* @__PURE__ */ jsxs7("div", { className: "orion-admin-grid", style: { alignItems: "start" }, children: [
982
+ /* @__PURE__ */ jsxs7("div", { children: [
983
+ /* @__PURE__ */ jsx7("div", { className: "orion-admin-card", children: url ? /* @__PURE__ */ jsx7("img", { alt: alt || filename || "Media", src: url, style: { borderRadius: 12, width: "100%" } }) : /* @__PURE__ */ jsx7("span", { children: "No preview available." }) }),
984
+ url ? /* @__PURE__ */ jsx7(
985
+ "button",
986
+ {
987
+ className: "orion-admin-action-button",
988
+ onClick: copyUrl,
989
+ style: { marginTop: "0.6rem", width: "100%" },
990
+ type: "button",
991
+ children: copied ? "Copied!" : "Copy URL"
992
+ }
993
+ ) : null
994
+ ] }),
995
+ /* @__PURE__ */ jsxs7("div", { style: { display: "grid", gap: "0.8rem" }, children: [
996
+ metaRows.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "orion-admin-card orion-admin-meta-table", children: metaRows.map((row) => /* @__PURE__ */ jsxs7("div", { className: "orion-admin-meta-row", children: [
997
+ /* @__PURE__ */ jsx7("span", { className: "orion-admin-meta-label", children: row.label }),
998
+ /* @__PURE__ */ jsx7("span", { className: "orion-admin-meta-value", children: row.value })
999
+ ] }, row.label)) }) : null,
1000
+ /* @__PURE__ */ jsxs7("form", { action: updateAction, className: "orion-admin-form", children: [
1001
+ /* @__PURE__ */ jsx7("input", { name: "id", type: "hidden", value: id }),
1002
+ /* @__PURE__ */ jsxs7("label", { children: [
1003
+ "Alt text",
1004
+ /* @__PURE__ */ jsx7("input", { defaultValue: alt || "", name: "alt", required: true, type: "text" })
1005
+ ] }),
1006
+ /* @__PURE__ */ jsx7("button", { type: "submit", children: "Save" })
1007
+ ] }),
1008
+ confirmDelete ? /* @__PURE__ */ jsxs7("div", { className: "orion-admin-form", style: { borderColor: "#b42318" }, children: [
1009
+ /* @__PURE__ */ jsx7("p", { style: { fontWeight: 700, margin: 0 }, children: "Are you sure you want to delete this asset?" }),
1010
+ /* @__PURE__ */ jsx7("p", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem", margin: 0 }, children: "This action cannot be undone." }),
1011
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "0.5rem" }, children: [
1012
+ /* @__PURE__ */ jsxs7("form", { action: deleteAction, style: { flex: 1 }, children: [
1013
+ /* @__PURE__ */ jsx7("input", { name: "id", type: "hidden", value: id }),
1014
+ /* @__PURE__ */ jsx7("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" })
1015
+ ] }),
1016
+ /* @__PURE__ */ jsx7(
1017
+ "button",
1018
+ {
1019
+ onClick: () => setConfirmDelete(false),
1020
+ style: { background: "transparent", border: "1px solid var(--orion-admin-border)", borderRadius: 10, cursor: "pointer", flex: 1, fontWeight: 700, padding: "0.55rem 0.8rem" },
1021
+ type: "button",
1022
+ children: "Cancel"
1023
+ }
1024
+ )
1025
+ ] })
1026
+ ] }) : /* @__PURE__ */ jsx7(
1027
+ "button",
1028
+ {
1029
+ className: "orion-admin-action-button",
1030
+ onClick: () => setConfirmDelete(true),
1031
+ style: { background: "#b42318" },
1032
+ type: "button",
1033
+ children: "Delete Asset"
1034
+ }
1035
+ )
1036
+ ] })
1037
+ ] });
1038
+ }
1039
+
1040
+ export {
1041
+ adminNavIcons,
1042
+ AdminShellClient,
1043
+ buildAdminPageLinkOptions,
1044
+ HeaderNavItemsEditor,
1045
+ SiteHeaderPreview,
1046
+ SiteFooterPreview,
1047
+ HeaderNavEditorWithPreview,
1048
+ MediaListItem,
1049
+ MediaUploadForm,
1050
+ MediaDetailPanel
1051
+ };