@orion-studios/payload-studio 0.5.0-beta.51 → 0.5.0-beta.53

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.
@@ -1,4 +1,7 @@
1
1
  export { AdminShellClient } from './components/AdminShellClient'
2
2
  export { HeaderNavItemsEditor } from './components/HeaderNavItemsEditor'
3
3
  export { HeaderNavEditorWithPreview } from './components/HeaderNavEditorWithPreview'
4
+ export { MediaDetailPanel } from './components/MediaDetailPanel'
5
+ export { MediaListItem } from './components/MediaListItem'
6
+ export { MediaUploadForm } from './components/MediaUploadForm'
4
7
  export { PageEditorFrame } from './components/PageEditorFrame'
@@ -1,4 +1,7 @@
1
1
  export { AdminShellClient } from './components/AdminShellClient'
2
2
  export { HeaderNavItemsEditor } from './components/HeaderNavItemsEditor'
3
3
  export { HeaderNavEditorWithPreview } from './components/HeaderNavEditorWithPreview'
4
+ export { MediaDetailPanel } from './components/MediaDetailPanel'
5
+ export { MediaListItem } from './components/MediaListItem'
6
+ export { MediaUploadForm } from './components/MediaUploadForm'
4
7
  export { PageEditorFrame } from './components/PageEditorFrame'
@@ -24,6 +24,9 @@ __export(client_exports, {
24
24
  AdminShellClient: () => AdminShellClient,
25
25
  HeaderNavEditorWithPreview: () => HeaderNavEditorWithPreview,
26
26
  HeaderNavItemsEditor: () => HeaderNavItemsEditor,
27
+ MediaDetailPanel: () => MediaDetailPanel,
28
+ MediaListItem: () => MediaListItem,
29
+ MediaUploadForm: () => MediaUploadForm,
27
30
  PageEditorFrame: () => PageEditorFrame
28
31
  });
29
32
  module.exports = __toCommonJS(client_exports);
@@ -537,22 +540,311 @@ function HeaderNavEditorWithPreview({
537
540
  ] });
538
541
  }
539
542
 
540
- // src/admin-app/components/PageEditorFrame.tsx
543
+ // src/admin-app/components/MediaDetailPanel.tsx
541
544
  var import_react4 = require("react");
542
545
  var import_jsx_runtime4 = require("react/jsx-runtime");
546
+ function formatFileSize(bytes) {
547
+ if (bytes < 1024) return `${bytes} B`;
548
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
549
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
550
+ }
551
+ function MediaDetailPanel({
552
+ id,
553
+ filename,
554
+ alt,
555
+ url,
556
+ filesize,
557
+ width,
558
+ height,
559
+ mimeType,
560
+ createdAt,
561
+ updateAction,
562
+ deleteAction
563
+ }) {
564
+ const [copied, setCopied] = (0, import_react4.useState)(false);
565
+ const [confirmDelete, setConfirmDelete] = (0, import_react4.useState)(false);
566
+ const copyUrl = async () => {
567
+ if (!url) return;
568
+ try {
569
+ await navigator.clipboard.writeText(url);
570
+ setCopied(true);
571
+ setTimeout(() => setCopied(false), 2e3);
572
+ } catch {
573
+ const input = document.createElement("input");
574
+ input.value = url;
575
+ document.body.appendChild(input);
576
+ input.select();
577
+ document.execCommand("copy");
578
+ document.body.removeChild(input);
579
+ setCopied(true);
580
+ setTimeout(() => setCopied(false), 2e3);
581
+ }
582
+ };
583
+ const metaRows = [];
584
+ if (filename) metaRows.push({ label: "Filename", value: filename });
585
+ if (typeof filesize === "number") metaRows.push({ label: "File size", value: formatFileSize(filesize) });
586
+ if (typeof width === "number" && typeof height === "number") metaRows.push({ label: "Dimensions", value: `${width} \xD7 ${height} px` });
587
+ if (mimeType) metaRows.push({ label: "Type", value: mimeType });
588
+ if (createdAt) {
589
+ try {
590
+ metaRows.push({ label: "Uploaded", value: new Date(createdAt).toLocaleDateString() });
591
+ } catch {
592
+ metaRows.push({ label: "Uploaded", value: createdAt });
593
+ }
594
+ }
595
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-grid", style: { alignItems: "start" }, children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-card", children: url ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { alt: alt || filename || "Media", src: url, style: { borderRadius: 12, width: "100%" } }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "No preview available." }) }),
598
+ url ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
599
+ "button",
600
+ {
601
+ className: "orion-admin-action-button",
602
+ onClick: copyUrl,
603
+ style: { marginTop: "0.6rem", width: "100%" },
604
+ type: "button",
605
+ children: copied ? "Copied!" : "Copy URL"
606
+ }
607
+ ) : null
608
+ ] }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "grid", gap: "0.8rem" }, children: [
610
+ metaRows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-card orion-admin-meta-table", children: metaRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-meta-row", children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "orion-admin-meta-label", children: row.label }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "orion-admin-meta-value", children: row.value })
613
+ ] }, row.label)) }) : null,
614
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { action: updateAction, className: "orion-admin-form", children: [
615
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { name: "id", type: "hidden", value: id }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [
617
+ "Alt text",
618
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { defaultValue: alt || "", name: "alt", required: true, type: "text" })
619
+ ] }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "submit", children: "Save" })
621
+ ] }),
622
+ confirmDelete ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "orion-admin-form", style: { borderColor: "#b42318" }, children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontWeight: 700, margin: 0 }, children: "Are you sure you want to delete this asset?" }),
624
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem", margin: 0 }, children: "This action cannot be undone." }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
626
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { action: deleteAction, style: { flex: 1 }, children: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { name: "id", type: "hidden", value: id }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" })
629
+ ] }),
630
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
631
+ "button",
632
+ {
633
+ onClick: () => setConfirmDelete(false),
634
+ style: { background: "transparent", border: "1px solid var(--orion-admin-border)", borderRadius: 10, cursor: "pointer", flex: 1, fontWeight: 700, padding: "0.55rem 0.8rem" },
635
+ type: "button",
636
+ children: "Cancel"
637
+ }
638
+ )
639
+ ] })
640
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
641
+ "button",
642
+ {
643
+ className: "orion-admin-action-button",
644
+ onClick: () => setConfirmDelete(true),
645
+ style: { background: "#b42318" },
646
+ type: "button",
647
+ children: "Delete Asset"
648
+ }
649
+ )
650
+ ] })
651
+ ] });
652
+ }
653
+
654
+ // src/admin-app/components/MediaListItem.tsx
655
+ var import_jsx_runtime5 = require("react/jsx-runtime");
656
+ function formatFileSize2(bytes) {
657
+ if (bytes < 1024) return `${bytes} B`;
658
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
659
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
660
+ }
661
+ function MediaListItem({
662
+ id,
663
+ filename,
664
+ alt,
665
+ url,
666
+ filesize,
667
+ width,
668
+ height,
669
+ mimeType,
670
+ href
671
+ }) {
672
+ const label = filename || `Media ${id}`;
673
+ const altText = alt || "";
674
+ const metaParts = [];
675
+ if (typeof filesize === "number") metaParts.push(formatFileSize2(filesize));
676
+ if (typeof width === "number" && typeof height === "number") metaParts.push(`${width}\xD7${height}`);
677
+ if (typeof mimeType === "string") metaParts.push(mimeType);
678
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("a", { className: "orion-admin-list-item", href, children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { alignItems: "center", display: "flex", gap: "0.8rem" }, children: [
680
+ url ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { alt: altText || label, className: "orion-admin-media-preview", src: url }) : null,
681
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
682
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: label }),
683
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "orion-admin-list-meta", children: altText || "No alt text" }),
684
+ metaParts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "orion-admin-list-meta", style: { marginTop: "0.15rem" }, children: metaParts.join(" \xB7 ") }) : null
685
+ ] })
686
+ ] }),
687
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "orion-admin-list-meta", children: "Edit" })
688
+ ] });
689
+ }
690
+
691
+ // src/admin-app/components/MediaUploadForm.tsx
692
+ var import_react5 = require("react");
693
+ var import_navigation = require("next/navigation");
694
+ var import_jsx_runtime6 = require("react/jsx-runtime");
695
+ var parseUploadError = async (response) => {
696
+ const fallback = "Upload failed. Please check the image and required fields, then try again.";
697
+ try {
698
+ const body = await response.json();
699
+ const nestedMessage = body.errors?.[0]?.data?.errors?.[0]?.message;
700
+ if (typeof nestedMessage === "string" && nestedMessage.trim().length > 0) {
701
+ return nestedMessage;
702
+ }
703
+ const topMessage = body.errors?.[0]?.message;
704
+ if (typeof topMessage === "string" && topMessage.trim().length > 0) {
705
+ return topMessage;
706
+ }
707
+ if (typeof body.message === "string" && body.message.trim().length > 0) {
708
+ return body.message;
709
+ }
710
+ } catch {
711
+ }
712
+ return fallback;
713
+ };
714
+ function MediaUploadForm() {
715
+ const router = (0, import_navigation.useRouter)();
716
+ const fileInputRef = (0, import_react5.useRef)(null);
717
+ const [alt, setAlt] = (0, import_react5.useState)("");
718
+ const [file, setFile] = (0, import_react5.useState)(null);
719
+ const [preview, setPreview] = (0, import_react5.useState)(null);
720
+ const [dragging, setDragging] = (0, import_react5.useState)(false);
721
+ const [submitting, setSubmitting] = (0, import_react5.useState)(false);
722
+ const [error, setError] = (0, import_react5.useState)(null);
723
+ const handleFile = (0, import_react5.useCallback)((selectedFile) => {
724
+ setFile(selectedFile);
725
+ if (preview) {
726
+ URL.revokeObjectURL(preview);
727
+ setPreview(null);
728
+ }
729
+ if (selectedFile && selectedFile.type.startsWith("image/")) {
730
+ setPreview(URL.createObjectURL(selectedFile));
731
+ }
732
+ }, [preview]);
733
+ const onDragOver = (0, import_react5.useCallback)((e) => {
734
+ e.preventDefault();
735
+ setDragging(true);
736
+ }, []);
737
+ const onDragLeave = (0, import_react5.useCallback)((e) => {
738
+ e.preventDefault();
739
+ setDragging(false);
740
+ }, []);
741
+ const onDrop = (0, import_react5.useCallback)((e) => {
742
+ e.preventDefault();
743
+ setDragging(false);
744
+ const droppedFile = e.dataTransfer.files?.[0] || null;
745
+ if (droppedFile) {
746
+ handleFile(droppedFile);
747
+ }
748
+ }, [handleFile]);
749
+ const upload = async (event) => {
750
+ event.preventDefault();
751
+ if (!file) {
752
+ setError("Select an image to upload.");
753
+ return;
754
+ }
755
+ setSubmitting(true);
756
+ setError(null);
757
+ try {
758
+ const formData = new FormData();
759
+ const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
760
+ const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
761
+ formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
762
+ formData.set("alt", resolvedAlt);
763
+ formData.set("file", file);
764
+ const response = await fetch("/api/media", {
765
+ method: "POST",
766
+ credentials: "include",
767
+ body: formData
768
+ });
769
+ if (!response.ok) {
770
+ throw new Error(await parseUploadError(response));
771
+ }
772
+ setAlt("");
773
+ setFile(null);
774
+ if (preview) {
775
+ URL.revokeObjectURL(preview);
776
+ setPreview(null);
777
+ }
778
+ router.refresh();
779
+ } catch (err) {
780
+ setError(err instanceof Error ? err.message : "Upload failed");
781
+ } finally {
782
+ setSubmitting(false);
783
+ }
784
+ };
785
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { className: "orion-admin-upload-form", onSubmit: upload, children: [
786
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { children: [
787
+ "Alt text",
788
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
789
+ "input",
790
+ {
791
+ onChange: (event) => setAlt(event.target.value),
792
+ placeholder: "Describe the image",
793
+ type: "text",
794
+ value: alt
795
+ }
796
+ )
797
+ ] }),
798
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
799
+ "div",
800
+ {
801
+ className: `orion-admin-dropzone${dragging ? " is-dragging" : ""}${file ? " has-file" : ""}`,
802
+ onClick: () => fileInputRef.current?.click(),
803
+ onDragLeave,
804
+ onDragOver,
805
+ onDrop,
806
+ children: [
807
+ preview ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "orion-admin-dropzone-preview", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { alt: "Upload preview", src: preview }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: file?.name })
810
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "orion-admin-dropzone-label", children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Drop an image here" }),
812
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "or click to browse" })
813
+ ] }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
815
+ "input",
816
+ {
817
+ accept: "image/*",
818
+ onChange: (event) => handleFile(event.target.files?.[0] || null),
819
+ ref: fileInputRef,
820
+ style: { display: "none" },
821
+ type: "file"
822
+ }
823
+ )
824
+ ]
825
+ }
826
+ ),
827
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "orion-admin-upload-error", children: error }) : null,
828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { disabled: submitting, type: "submit", children: submitting ? "Uploading..." : "Upload" })
829
+ ] });
830
+ }
831
+
832
+ // src/admin-app/components/PageEditorFrame.tsx
833
+ var import_react6 = require("react");
834
+ var import_jsx_runtime7 = require("react/jsx-runtime");
543
835
  function PageEditorFrame({ src }) {
544
- const iframeRef = (0, import_react4.useRef)(null);
545
- const dirtyCheckTimerRef = (0, import_react4.useRef)(null);
546
- const [saving, setSaving] = (0, import_react4.useState)(null);
547
- const [message, setMessage] = (0, import_react4.useState)("");
548
- const [error, setError] = (0, import_react4.useState)("");
549
- const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react4.useState)(false);
550
- const [awaitingDirtyCheck, setAwaitingDirtyCheck] = (0, import_react4.useState)(false);
551
- const [pendingNavigationURL, setPendingNavigationURL] = (0, import_react4.useState)(null);
552
- const [showUnsavedDialog, setShowUnsavedDialog] = (0, import_react4.useState)(false);
553
- const [canUndo, setCanUndo] = (0, import_react4.useState)(false);
554
- const [canRedo, setCanRedo] = (0, import_react4.useState)(false);
555
- const [sessionExpired, setSessionExpired] = (0, import_react4.useState)(false);
836
+ const iframeRef = (0, import_react6.useRef)(null);
837
+ const dirtyCheckTimerRef = (0, import_react6.useRef)(null);
838
+ const [saving, setSaving] = (0, import_react6.useState)(null);
839
+ const [message, setMessage] = (0, import_react6.useState)("");
840
+ const [error, setError] = (0, import_react6.useState)("");
841
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react6.useState)(false);
842
+ const [awaitingDirtyCheck, setAwaitingDirtyCheck] = (0, import_react6.useState)(false);
843
+ const [pendingNavigationURL, setPendingNavigationURL] = (0, import_react6.useState)(null);
844
+ const [showUnsavedDialog, setShowUnsavedDialog] = (0, import_react6.useState)(false);
845
+ const [canUndo, setCanUndo] = (0, import_react6.useState)(false);
846
+ const [canRedo, setCanRedo] = (0, import_react6.useState)(false);
847
+ const [sessionExpired, setSessionExpired] = (0, import_react6.useState)(false);
556
848
  const clearDirtyCheckTimer = () => {
557
849
  if (dirtyCheckTimerRef.current) {
558
850
  window.clearTimeout(dirtyCheckTimerRef.current);
@@ -598,7 +890,7 @@ function PageEditorFrame({ src }) {
598
890
  "*"
599
891
  );
600
892
  };
601
- (0, import_react4.useEffect)(() => {
893
+ (0, import_react6.useEffect)(() => {
602
894
  const onMessage = (event) => {
603
895
  const data = event.data;
604
896
  if (!data || data.source !== "payload-visual-builder-child") {
@@ -643,7 +935,7 @@ function PageEditorFrame({ src }) {
643
935
  window.addEventListener("message", onMessage);
644
936
  return () => window.removeEventListener("message", onMessage);
645
937
  }, [awaitingDirtyCheck, pendingNavigationURL]);
646
- (0, import_react4.useEffect)(() => {
938
+ (0, import_react6.useEffect)(() => {
647
939
  const onDocumentClick = (event) => {
648
940
  if (!hasUnsavedChanges) {
649
941
  return;
@@ -706,7 +998,7 @@ function PageEditorFrame({ src }) {
706
998
  document.addEventListener("click", onDocumentClick, true);
707
999
  return () => document.removeEventListener("click", onDocumentClick, true);
708
1000
  }, [hasUnsavedChanges]);
709
- (0, import_react4.useEffect)(() => {
1001
+ (0, import_react6.useEffect)(() => {
710
1002
  if (!hasUnsavedChanges) {
711
1003
  return;
712
1004
  }
@@ -717,14 +1009,14 @@ function PageEditorFrame({ src }) {
717
1009
  window.addEventListener("beforeunload", onBeforeUnload);
718
1010
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
719
1011
  }, [hasUnsavedChanges]);
720
- (0, import_react4.useEffect)(
1012
+ (0, import_react6.useEffect)(
721
1013
  () => () => {
722
1014
  clearDirtyCheckTimer();
723
1015
  },
724
1016
  []
725
1017
  );
726
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "grid", gap: "0.8rem" }, children: [
727
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "grid", gap: "0.8rem" }, children: [
1019
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
728
1020
  "div",
729
1021
  {
730
1022
  style: {
@@ -739,9 +1031,9 @@ function PageEditorFrame({ src }) {
739
1031
  padding: "0.7rem 0.8rem"
740
1032
  },
741
1033
  children: [
742
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem" }, children: "Save changes to update the page layout and content." }),
743
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
744
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1034
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem" }, children: "Save changes to update the page layout and content." }),
1035
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
745
1037
  "button",
746
1038
  {
747
1039
  className: "orion-admin-action-button",
@@ -752,7 +1044,7 @@ function PageEditorFrame({ src }) {
752
1044
  children: "Undo"
753
1045
  }
754
1046
  ),
755
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1047
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
756
1048
  "button",
757
1049
  {
758
1050
  className: "orion-admin-action-button",
@@ -763,7 +1055,7 @@ function PageEditorFrame({ src }) {
763
1055
  children: "Redo"
764
1056
  }
765
1057
  ),
766
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1058
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
767
1059
  "button",
768
1060
  {
769
1061
  className: "orion-admin-action-button",
@@ -773,7 +1065,7 @@ function PageEditorFrame({ src }) {
773
1065
  children: saving === "draft" ? "Saving..." : "Save Draft"
774
1066
  }
775
1067
  ),
776
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1068
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
777
1069
  "button",
778
1070
  {
779
1071
  className: "orion-admin-action-button",
@@ -788,9 +1080,9 @@ function PageEditorFrame({ src }) {
788
1080
  ]
789
1081
  }
790
1082
  ),
791
- message ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#0f7d52", fontSize: "0.9rem", fontWeight: 700 }, children: message }) : null,
792
- error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "orion-admin-error", children: error }) : null,
793
- sessionExpired ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1083
+ message ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { color: "#0f7d52", fontSize: "0.9rem", fontWeight: 700 }, children: message }) : null,
1084
+ error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "orion-admin-error", children: error }) : null,
1085
+ sessionExpired ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
794
1086
  "div",
795
1087
  {
796
1088
  style: {
@@ -805,7 +1097,7 @@ function PageEditorFrame({ src }) {
805
1097
  children: "Session expired. Log in again in a new tab, then save your changes."
806
1098
  }
807
1099
  ) : null,
808
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1100
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
809
1101
  "iframe",
810
1102
  {
811
1103
  ref: iframeRef,
@@ -825,7 +1117,7 @@ function PageEditorFrame({ src }) {
825
1117
  }
826
1118
  }
827
1119
  ),
828
- pendingNavigationURL && showUnsavedDialog ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1120
+ pendingNavigationURL && showUnsavedDialog ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
829
1121
  "div",
830
1122
  {
831
1123
  "data-orion-unsaved-dialog": "true",
@@ -839,7 +1131,7 @@ function PageEditorFrame({ src }) {
839
1131
  position: "fixed",
840
1132
  zIndex: 2e3
841
1133
  },
842
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1134
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
843
1135
  "div",
844
1136
  {
845
1137
  style: {
@@ -854,12 +1146,12 @@ function PageEditorFrame({ src }) {
854
1146
  width: "100%"
855
1147
  },
856
1148
  children: [
857
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
858
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "var(--orion-admin-text)", fontSize: "1rem", fontWeight: 700 }, children: "Unsaved changes" }),
859
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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?" })
1149
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { color: "var(--orion-admin-text)", fontSize: "1rem", fontWeight: 700 }, children: "Unsaved changes" }),
1151
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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?" })
860
1152
  ] }),
861
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "0.6rem", justifyContent: "flex-end" }, children: [
862
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1153
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: "0.6rem", justifyContent: "flex-end" }, children: [
1154
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
863
1155
  "button",
864
1156
  {
865
1157
  className: "orion-admin-action-button",
@@ -871,7 +1163,7 @@ function PageEditorFrame({ src }) {
871
1163
  children: "Stay"
872
1164
  }
873
1165
  ),
874
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1166
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
875
1167
  "button",
876
1168
  {
877
1169
  className: "orion-admin-action-button",
@@ -899,5 +1191,8 @@ function PageEditorFrame({ src }) {
899
1191
  AdminShellClient,
900
1192
  HeaderNavEditorWithPreview,
901
1193
  HeaderNavItemsEditor,
1194
+ MediaDetailPanel,
1195
+ MediaListItem,
1196
+ MediaUploadForm,
902
1197
  PageEditorFrame
903
1198
  });
@@ -509,22 +509,311 @@ function HeaderNavEditorWithPreview({
509
509
  ] });
510
510
  }
511
511
 
512
- // src/admin-app/components/PageEditorFrame.tsx
513
- import { useEffect as useEffect3, useRef, useState as useState4 } from "react";
512
+ // src/admin-app/components/MediaDetailPanel.tsx
513
+ import { useState as useState4 } from "react";
514
514
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
515
+ function formatFileSize(bytes) {
516
+ if (bytes < 1024) return `${bytes} B`;
517
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
518
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
519
+ }
520
+ function MediaDetailPanel({
521
+ id,
522
+ filename,
523
+ alt,
524
+ url,
525
+ filesize,
526
+ width,
527
+ height,
528
+ mimeType,
529
+ createdAt,
530
+ updateAction,
531
+ deleteAction
532
+ }) {
533
+ const [copied, setCopied] = useState4(false);
534
+ const [confirmDelete, setConfirmDelete] = useState4(false);
535
+ const copyUrl = async () => {
536
+ if (!url) return;
537
+ try {
538
+ await navigator.clipboard.writeText(url);
539
+ setCopied(true);
540
+ setTimeout(() => setCopied(false), 2e3);
541
+ } catch {
542
+ const input = document.createElement("input");
543
+ input.value = url;
544
+ document.body.appendChild(input);
545
+ input.select();
546
+ document.execCommand("copy");
547
+ document.body.removeChild(input);
548
+ setCopied(true);
549
+ setTimeout(() => setCopied(false), 2e3);
550
+ }
551
+ };
552
+ const metaRows = [];
553
+ if (filename) metaRows.push({ label: "Filename", value: filename });
554
+ if (typeof filesize === "number") metaRows.push({ label: "File size", value: formatFileSize(filesize) });
555
+ if (typeof width === "number" && typeof height === "number") metaRows.push({ label: "Dimensions", value: `${width} \xD7 ${height} px` });
556
+ if (mimeType) metaRows.push({ label: "Type", value: mimeType });
557
+ if (createdAt) {
558
+ try {
559
+ metaRows.push({ label: "Uploaded", value: new Date(createdAt).toLocaleDateString() });
560
+ } catch {
561
+ metaRows.push({ label: "Uploaded", value: createdAt });
562
+ }
563
+ }
564
+ return /* @__PURE__ */ jsxs4("div", { className: "orion-admin-grid", style: { alignItems: "start" }, children: [
565
+ /* @__PURE__ */ jsxs4("div", { children: [
566
+ /* @__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." }) }),
567
+ url ? /* @__PURE__ */ jsx4(
568
+ "button",
569
+ {
570
+ className: "orion-admin-action-button",
571
+ onClick: copyUrl,
572
+ style: { marginTop: "0.6rem", width: "100%" },
573
+ type: "button",
574
+ children: copied ? "Copied!" : "Copy URL"
575
+ }
576
+ ) : null
577
+ ] }),
578
+ /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gap: "0.8rem" }, children: [
579
+ 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: [
580
+ /* @__PURE__ */ jsx4("span", { className: "orion-admin-meta-label", children: row.label }),
581
+ /* @__PURE__ */ jsx4("span", { className: "orion-admin-meta-value", children: row.value })
582
+ ] }, row.label)) }) : null,
583
+ /* @__PURE__ */ jsxs4("form", { action: updateAction, className: "orion-admin-form", children: [
584
+ /* @__PURE__ */ jsx4("input", { name: "id", type: "hidden", value: id }),
585
+ /* @__PURE__ */ jsxs4("label", { children: [
586
+ "Alt text",
587
+ /* @__PURE__ */ jsx4("input", { defaultValue: alt || "", name: "alt", required: true, type: "text" })
588
+ ] }),
589
+ /* @__PURE__ */ jsx4("button", { type: "submit", children: "Save" })
590
+ ] }),
591
+ confirmDelete ? /* @__PURE__ */ jsxs4("div", { className: "orion-admin-form", style: { borderColor: "#b42318" }, children: [
592
+ /* @__PURE__ */ jsx4("p", { style: { fontWeight: 700, margin: 0 }, children: "Are you sure you want to delete this asset?" }),
593
+ /* @__PURE__ */ jsx4("p", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem", margin: 0 }, children: "This action cannot be undone." }),
594
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "0.5rem" }, children: [
595
+ /* @__PURE__ */ jsxs4("form", { action: deleteAction, style: { flex: 1 }, children: [
596
+ /* @__PURE__ */ jsx4("input", { name: "id", type: "hidden", value: id }),
597
+ /* @__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" })
598
+ ] }),
599
+ /* @__PURE__ */ jsx4(
600
+ "button",
601
+ {
602
+ onClick: () => setConfirmDelete(false),
603
+ style: { background: "transparent", border: "1px solid var(--orion-admin-border)", borderRadius: 10, cursor: "pointer", flex: 1, fontWeight: 700, padding: "0.55rem 0.8rem" },
604
+ type: "button",
605
+ children: "Cancel"
606
+ }
607
+ )
608
+ ] })
609
+ ] }) : /* @__PURE__ */ jsx4(
610
+ "button",
611
+ {
612
+ className: "orion-admin-action-button",
613
+ onClick: () => setConfirmDelete(true),
614
+ style: { background: "#b42318" },
615
+ type: "button",
616
+ children: "Delete Asset"
617
+ }
618
+ )
619
+ ] })
620
+ ] });
621
+ }
622
+
623
+ // src/admin-app/components/MediaListItem.tsx
624
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
625
+ function formatFileSize2(bytes) {
626
+ if (bytes < 1024) return `${bytes} B`;
627
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
628
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
629
+ }
630
+ function MediaListItem({
631
+ id,
632
+ filename,
633
+ alt,
634
+ url,
635
+ filesize,
636
+ width,
637
+ height,
638
+ mimeType,
639
+ href
640
+ }) {
641
+ const label = filename || `Media ${id}`;
642
+ const altText = alt || "";
643
+ const metaParts = [];
644
+ if (typeof filesize === "number") metaParts.push(formatFileSize2(filesize));
645
+ if (typeof width === "number" && typeof height === "number") metaParts.push(`${width}\xD7${height}`);
646
+ if (typeof mimeType === "string") metaParts.push(mimeType);
647
+ return /* @__PURE__ */ jsxs5("a", { className: "orion-admin-list-item", href, children: [
648
+ /* @__PURE__ */ jsxs5("div", { style: { alignItems: "center", display: "flex", gap: "0.8rem" }, children: [
649
+ url ? /* @__PURE__ */ jsx5("img", { alt: altText || label, className: "orion-admin-media-preview", src: url }) : null,
650
+ /* @__PURE__ */ jsxs5("div", { children: [
651
+ /* @__PURE__ */ jsx5("strong", { children: label }),
652
+ /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", children: altText || "No alt text" }),
653
+ metaParts.length > 0 ? /* @__PURE__ */ jsx5("div", { className: "orion-admin-list-meta", style: { marginTop: "0.15rem" }, children: metaParts.join(" \xB7 ") }) : null
654
+ ] })
655
+ ] }),
656
+ /* @__PURE__ */ jsx5("span", { className: "orion-admin-list-meta", children: "Edit" })
657
+ ] });
658
+ }
659
+
660
+ // src/admin-app/components/MediaUploadForm.tsx
661
+ import { useState as useState5, useRef, useCallback } from "react";
662
+ import { useRouter } from "next/navigation";
663
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
664
+ var parseUploadError = async (response) => {
665
+ const fallback = "Upload failed. Please check the image and required fields, then try again.";
666
+ try {
667
+ const body = await response.json();
668
+ const nestedMessage = body.errors?.[0]?.data?.errors?.[0]?.message;
669
+ if (typeof nestedMessage === "string" && nestedMessage.trim().length > 0) {
670
+ return nestedMessage;
671
+ }
672
+ const topMessage = body.errors?.[0]?.message;
673
+ if (typeof topMessage === "string" && topMessage.trim().length > 0) {
674
+ return topMessage;
675
+ }
676
+ if (typeof body.message === "string" && body.message.trim().length > 0) {
677
+ return body.message;
678
+ }
679
+ } catch {
680
+ }
681
+ return fallback;
682
+ };
683
+ function MediaUploadForm() {
684
+ const router = useRouter();
685
+ const fileInputRef = useRef(null);
686
+ const [alt, setAlt] = useState5("");
687
+ const [file, setFile] = useState5(null);
688
+ const [preview, setPreview] = useState5(null);
689
+ const [dragging, setDragging] = useState5(false);
690
+ const [submitting, setSubmitting] = useState5(false);
691
+ const [error, setError] = useState5(null);
692
+ const handleFile = useCallback((selectedFile) => {
693
+ setFile(selectedFile);
694
+ if (preview) {
695
+ URL.revokeObjectURL(preview);
696
+ setPreview(null);
697
+ }
698
+ if (selectedFile && selectedFile.type.startsWith("image/")) {
699
+ setPreview(URL.createObjectURL(selectedFile));
700
+ }
701
+ }, [preview]);
702
+ const onDragOver = useCallback((e) => {
703
+ e.preventDefault();
704
+ setDragging(true);
705
+ }, []);
706
+ const onDragLeave = useCallback((e) => {
707
+ e.preventDefault();
708
+ setDragging(false);
709
+ }, []);
710
+ const onDrop = useCallback((e) => {
711
+ e.preventDefault();
712
+ setDragging(false);
713
+ const droppedFile = e.dataTransfer.files?.[0] || null;
714
+ if (droppedFile) {
715
+ handleFile(droppedFile);
716
+ }
717
+ }, [handleFile]);
718
+ const upload = async (event) => {
719
+ event.preventDefault();
720
+ if (!file) {
721
+ setError("Select an image to upload.");
722
+ return;
723
+ }
724
+ setSubmitting(true);
725
+ setError(null);
726
+ try {
727
+ const formData = new FormData();
728
+ const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
729
+ const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
730
+ formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
731
+ formData.set("alt", resolvedAlt);
732
+ formData.set("file", file);
733
+ const response = await fetch("/api/media", {
734
+ method: "POST",
735
+ credentials: "include",
736
+ body: formData
737
+ });
738
+ if (!response.ok) {
739
+ throw new Error(await parseUploadError(response));
740
+ }
741
+ setAlt("");
742
+ setFile(null);
743
+ if (preview) {
744
+ URL.revokeObjectURL(preview);
745
+ setPreview(null);
746
+ }
747
+ router.refresh();
748
+ } catch (err) {
749
+ setError(err instanceof Error ? err.message : "Upload failed");
750
+ } finally {
751
+ setSubmitting(false);
752
+ }
753
+ };
754
+ return /* @__PURE__ */ jsxs6("form", { className: "orion-admin-upload-form", onSubmit: upload, children: [
755
+ /* @__PURE__ */ jsxs6("label", { children: [
756
+ "Alt text",
757
+ /* @__PURE__ */ jsx6(
758
+ "input",
759
+ {
760
+ onChange: (event) => setAlt(event.target.value),
761
+ placeholder: "Describe the image",
762
+ type: "text",
763
+ value: alt
764
+ }
765
+ )
766
+ ] }),
767
+ /* @__PURE__ */ jsxs6(
768
+ "div",
769
+ {
770
+ className: `orion-admin-dropzone${dragging ? " is-dragging" : ""}${file ? " has-file" : ""}`,
771
+ onClick: () => fileInputRef.current?.click(),
772
+ onDragLeave,
773
+ onDragOver,
774
+ onDrop,
775
+ children: [
776
+ preview ? /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-preview", children: [
777
+ /* @__PURE__ */ jsx6("img", { alt: "Upload preview", src: preview }),
778
+ /* @__PURE__ */ jsx6("span", { children: file?.name })
779
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: "orion-admin-dropzone-label", children: [
780
+ /* @__PURE__ */ jsx6("strong", { children: "Drop an image here" }),
781
+ /* @__PURE__ */ jsx6("span", { children: "or click to browse" })
782
+ ] }),
783
+ /* @__PURE__ */ jsx6(
784
+ "input",
785
+ {
786
+ accept: "image/*",
787
+ onChange: (event) => handleFile(event.target.files?.[0] || null),
788
+ ref: fileInputRef,
789
+ style: { display: "none" },
790
+ type: "file"
791
+ }
792
+ )
793
+ ]
794
+ }
795
+ ),
796
+ error ? /* @__PURE__ */ jsx6("div", { className: "orion-admin-upload-error", children: error }) : null,
797
+ /* @__PURE__ */ jsx6("button", { disabled: submitting, type: "submit", children: submitting ? "Uploading..." : "Upload" })
798
+ ] });
799
+ }
800
+
801
+ // src/admin-app/components/PageEditorFrame.tsx
802
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState6 } from "react";
803
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
515
804
  function PageEditorFrame({ src }) {
516
- const iframeRef = useRef(null);
517
- const dirtyCheckTimerRef = useRef(null);
518
- const [saving, setSaving] = useState4(null);
519
- const [message, setMessage] = useState4("");
520
- const [error, setError] = useState4("");
521
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState4(false);
522
- const [awaitingDirtyCheck, setAwaitingDirtyCheck] = useState4(false);
523
- const [pendingNavigationURL, setPendingNavigationURL] = useState4(null);
524
- const [showUnsavedDialog, setShowUnsavedDialog] = useState4(false);
525
- const [canUndo, setCanUndo] = useState4(false);
526
- const [canRedo, setCanRedo] = useState4(false);
527
- const [sessionExpired, setSessionExpired] = useState4(false);
805
+ const iframeRef = useRef2(null);
806
+ const dirtyCheckTimerRef = useRef2(null);
807
+ const [saving, setSaving] = useState6(null);
808
+ const [message, setMessage] = useState6("");
809
+ const [error, setError] = useState6("");
810
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState6(false);
811
+ const [awaitingDirtyCheck, setAwaitingDirtyCheck] = useState6(false);
812
+ const [pendingNavigationURL, setPendingNavigationURL] = useState6(null);
813
+ const [showUnsavedDialog, setShowUnsavedDialog] = useState6(false);
814
+ const [canUndo, setCanUndo] = useState6(false);
815
+ const [canRedo, setCanRedo] = useState6(false);
816
+ const [sessionExpired, setSessionExpired] = useState6(false);
528
817
  const clearDirtyCheckTimer = () => {
529
818
  if (dirtyCheckTimerRef.current) {
530
819
  window.clearTimeout(dirtyCheckTimerRef.current);
@@ -695,8 +984,8 @@ function PageEditorFrame({ src }) {
695
984
  },
696
985
  []
697
986
  );
698
- return /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gap: "0.8rem" }, children: [
699
- /* @__PURE__ */ jsxs4(
987
+ return /* @__PURE__ */ jsxs7("div", { style: { display: "grid", gap: "0.8rem" }, children: [
988
+ /* @__PURE__ */ jsxs7(
700
989
  "div",
701
990
  {
702
991
  style: {
@@ -711,9 +1000,9 @@ function PageEditorFrame({ src }) {
711
1000
  padding: "0.7rem 0.8rem"
712
1001
  },
713
1002
  children: [
714
- /* @__PURE__ */ jsx4("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem" }, children: "Save changes to update the page layout and content." }),
715
- /* @__PURE__ */ jsxs4("div", { style: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
716
- /* @__PURE__ */ jsx4(
1003
+ /* @__PURE__ */ jsx7("div", { style: { color: "var(--orion-admin-muted)", fontSize: "0.9rem" }, children: "Save changes to update the page layout and content." }),
1004
+ /* @__PURE__ */ jsxs7("div", { style: { alignItems: "center", display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
1005
+ /* @__PURE__ */ jsx7(
717
1006
  "button",
718
1007
  {
719
1008
  className: "orion-admin-action-button",
@@ -724,7 +1013,7 @@ function PageEditorFrame({ src }) {
724
1013
  children: "Undo"
725
1014
  }
726
1015
  ),
727
- /* @__PURE__ */ jsx4(
1016
+ /* @__PURE__ */ jsx7(
728
1017
  "button",
729
1018
  {
730
1019
  className: "orion-admin-action-button",
@@ -735,7 +1024,7 @@ function PageEditorFrame({ src }) {
735
1024
  children: "Redo"
736
1025
  }
737
1026
  ),
738
- /* @__PURE__ */ jsx4(
1027
+ /* @__PURE__ */ jsx7(
739
1028
  "button",
740
1029
  {
741
1030
  className: "orion-admin-action-button",
@@ -745,7 +1034,7 @@ function PageEditorFrame({ src }) {
745
1034
  children: saving === "draft" ? "Saving..." : "Save Draft"
746
1035
  }
747
1036
  ),
748
- /* @__PURE__ */ jsx4(
1037
+ /* @__PURE__ */ jsx7(
749
1038
  "button",
750
1039
  {
751
1040
  className: "orion-admin-action-button",
@@ -760,9 +1049,9 @@ function PageEditorFrame({ src }) {
760
1049
  ]
761
1050
  }
762
1051
  ),
763
- message ? /* @__PURE__ */ jsx4("div", { style: { color: "#0f7d52", fontSize: "0.9rem", fontWeight: 700 }, children: message }) : null,
764
- error ? /* @__PURE__ */ jsx4("div", { className: "orion-admin-error", children: error }) : null,
765
- sessionExpired ? /* @__PURE__ */ jsx4(
1052
+ message ? /* @__PURE__ */ jsx7("div", { style: { color: "#0f7d52", fontSize: "0.9rem", fontWeight: 700 }, children: message }) : null,
1053
+ error ? /* @__PURE__ */ jsx7("div", { className: "orion-admin-error", children: error }) : null,
1054
+ sessionExpired ? /* @__PURE__ */ jsx7(
766
1055
  "div",
767
1056
  {
768
1057
  style: {
@@ -777,7 +1066,7 @@ function PageEditorFrame({ src }) {
777
1066
  children: "Session expired. Log in again in a new tab, then save your changes."
778
1067
  }
779
1068
  ) : null,
780
- /* @__PURE__ */ jsx4(
1069
+ /* @__PURE__ */ jsx7(
781
1070
  "iframe",
782
1071
  {
783
1072
  ref: iframeRef,
@@ -797,7 +1086,7 @@ function PageEditorFrame({ src }) {
797
1086
  }
798
1087
  }
799
1088
  ),
800
- pendingNavigationURL && showUnsavedDialog ? /* @__PURE__ */ jsx4(
1089
+ pendingNavigationURL && showUnsavedDialog ? /* @__PURE__ */ jsx7(
801
1090
  "div",
802
1091
  {
803
1092
  "data-orion-unsaved-dialog": "true",
@@ -811,7 +1100,7 @@ function PageEditorFrame({ src }) {
811
1100
  position: "fixed",
812
1101
  zIndex: 2e3
813
1102
  },
814
- children: /* @__PURE__ */ jsxs4(
1103
+ children: /* @__PURE__ */ jsxs7(
815
1104
  "div",
816
1105
  {
817
1106
  style: {
@@ -826,12 +1115,12 @@ function PageEditorFrame({ src }) {
826
1115
  width: "100%"
827
1116
  },
828
1117
  children: [
829
- /* @__PURE__ */ jsxs4("div", { children: [
830
- /* @__PURE__ */ jsx4("div", { style: { color: "var(--orion-admin-text)", fontSize: "1rem", fontWeight: 700 }, children: "Unsaved changes" }),
831
- /* @__PURE__ */ jsx4("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?" })
1118
+ /* @__PURE__ */ jsxs7("div", { children: [
1119
+ /* @__PURE__ */ jsx7("div", { style: { color: "var(--orion-admin-text)", fontSize: "1rem", fontWeight: 700 }, children: "Unsaved changes" }),
1120
+ /* @__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?" })
832
1121
  ] }),
833
- /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "0.6rem", justifyContent: "flex-end" }, children: [
834
- /* @__PURE__ */ jsx4(
1122
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "0.6rem", justifyContent: "flex-end" }, children: [
1123
+ /* @__PURE__ */ jsx7(
835
1124
  "button",
836
1125
  {
837
1126
  className: "orion-admin-action-button",
@@ -843,7 +1132,7 @@ function PageEditorFrame({ src }) {
843
1132
  children: "Stay"
844
1133
  }
845
1134
  ),
846
- /* @__PURE__ */ jsx4(
1135
+ /* @__PURE__ */ jsx7(
847
1136
  "button",
848
1137
  {
849
1138
  className: "orion-admin-action-button",
@@ -870,5 +1159,8 @@ export {
870
1159
  AdminShellClient,
871
1160
  HeaderNavEditorWithPreview,
872
1161
  HeaderNavItemsEditor,
1162
+ MediaDetailPanel,
1163
+ MediaListItem,
1164
+ MediaUploadForm,
873
1165
  PageEditorFrame
874
1166
  };
@@ -1,3 +1,3 @@
1
- export { A as AdminBreadcrumbItem, a as AdminBreadcrumbs, b as AdminNavIcon, c as AdminNavInput, d as AdminNavItem, e as AdminNavLinkItem, f as AdminPage, g as AdminPageLinkOption, h as AdminPageRecord, j as AdminRole, N as NestedNavItem, k as NestedNavItemInput, l as NestedNavTree, m as buildAdminPageLinkOptions, n as buildNestedNavTree, o as getAdminNavRows, p as navItemIsActive, q as normalizeAdminNavInputs, r as normalizeNestedNavItems, s as parseAdminHeaderNavFromForm, t as roleCanAccessNav } from '../index-TJmWr6aF.mjs';
1
+ export { A as AdminBreadcrumbItem, a as AdminBreadcrumbs, b as AdminNavIcon, c as AdminNavInput, d as AdminNavItem, e as AdminNavLinkItem, f as AdminPage, g as AdminPageLinkOption, h as AdminPageRecord, j as AdminRole, M as MediaDetailPanelProps, k as MediaListItemProps, N as NestedNavItem, l as NestedNavItemInput, m as NestedNavTree, n as buildAdminPageLinkOptions, o as buildNestedNavTree, p as getAdminNavRows, q as navItemIsActive, r as normalizeAdminNavInputs, s as normalizeNestedNavItems, t as parseAdminHeaderNavFromForm, u as roleCanAccessNav } from '../index-ZbOx4OCF.mjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { A as AdminBreadcrumbItem, a as AdminBreadcrumbs, b as AdminNavIcon, c as AdminNavInput, d as AdminNavItem, e as AdminNavLinkItem, f as AdminPage, g as AdminPageLinkOption, h as AdminPageRecord, j as AdminRole, N as NestedNavItem, k as NestedNavItemInput, l as NestedNavTree, m as buildAdminPageLinkOptions, n as buildNestedNavTree, o as getAdminNavRows, p as navItemIsActive, q as normalizeAdminNavInputs, r as normalizeNestedNavItems, s as parseAdminHeaderNavFromForm, t as roleCanAccessNav } from '../index-TJmWr6aF.js';
1
+ export { A as AdminBreadcrumbItem, a as AdminBreadcrumbs, b as AdminNavIcon, c as AdminNavInput, d as AdminNavItem, e as AdminNavLinkItem, f as AdminPage, g as AdminPageLinkOption, h as AdminPageRecord, j as AdminRole, M as MediaDetailPanelProps, k as MediaListItemProps, N as NestedNavItem, l as NestedNavItemInput, m as NestedNavTree, n as buildAdminPageLinkOptions, o as buildNestedNavTree, p as getAdminNavRows, q as navItemIsActive, r as normalizeAdminNavInputs, s as normalizeNestedNavItems, t as parseAdminHeaderNavFromForm, u as roleCanAccessNav } from '../index-ZbOx4OCF.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -253,3 +253,99 @@
253
253
  grid-template-columns: 1fr;
254
254
  }
255
255
  }
256
+
257
+ .orion-admin-media-preview {
258
+ border-radius: 10px;
259
+ display: block;
260
+ height: 68px;
261
+ object-fit: cover;
262
+ width: 68px;
263
+ }
264
+
265
+ .orion-admin-meta-table {
266
+ display: grid;
267
+ gap: 0;
268
+ padding: 0;
269
+ }
270
+
271
+ .orion-admin-meta-row {
272
+ align-items: center;
273
+ display: flex;
274
+ gap: 1rem;
275
+ justify-content: space-between;
276
+ padding: 0.6rem 1rem;
277
+ }
278
+
279
+ .orion-admin-meta-row:not(:last-child) {
280
+ border-bottom: 1px solid var(--orion-admin-card-border);
281
+ }
282
+
283
+ .orion-admin-meta-label {
284
+ color: var(--orion-admin-muted);
285
+ font-size: 0.88rem;
286
+ }
287
+
288
+ .orion-admin-meta-value {
289
+ font-size: 0.92rem;
290
+ font-weight: 700;
291
+ text-align: right;
292
+ }
293
+
294
+ .orion-admin-dropzone {
295
+ align-items: center;
296
+ border: 2px dashed var(--orion-admin-border);
297
+ border-radius: 12px;
298
+ cursor: pointer;
299
+ display: flex;
300
+ justify-content: center;
301
+ min-height: 120px;
302
+ padding: 1rem;
303
+ transition: border-color 0.15s ease, background 0.15s ease;
304
+ }
305
+
306
+ .orion-admin-dropzone:hover {
307
+ border-color: var(--orion-admin-muted);
308
+ }
309
+
310
+ .orion-admin-dropzone.is-dragging {
311
+ background: rgba(24, 95, 69, 0.06);
312
+ border-color: rgba(24, 95, 69, 0.5);
313
+ }
314
+
315
+ .orion-admin-dropzone.has-file {
316
+ border-style: solid;
317
+ }
318
+
319
+ .orion-admin-dropzone-label {
320
+ display: grid;
321
+ gap: 0.2rem;
322
+ text-align: center;
323
+ }
324
+
325
+ .orion-admin-dropzone-label strong {
326
+ font-size: 0.95rem;
327
+ }
328
+
329
+ .orion-admin-dropzone-label span {
330
+ color: var(--orion-admin-muted);
331
+ font-size: 0.85rem;
332
+ }
333
+
334
+ .orion-admin-dropzone-preview {
335
+ align-items: center;
336
+ display: flex;
337
+ gap: 0.8rem;
338
+ }
339
+
340
+ .orion-admin-dropzone-preview img {
341
+ border-radius: 8px;
342
+ height: 60px;
343
+ object-fit: cover;
344
+ width: 60px;
345
+ }
346
+
347
+ .orion-admin-dropzone-preview span {
348
+ color: var(--orion-admin-text);
349
+ font-size: 0.9rem;
350
+ font-weight: 600;
351
+ }
@@ -31,6 +31,32 @@ type AdminPageProps = {
31
31
  };
32
32
  declare function AdminPage({ title, description, breadcrumbs, actions, children }: AdminPageProps): react_jsx_runtime.JSX.Element;
33
33
 
34
+ type MediaDetailPanelProps = {
35
+ id: string;
36
+ filename?: string;
37
+ alt?: string;
38
+ url?: string;
39
+ filesize?: number;
40
+ width?: number;
41
+ height?: number;
42
+ mimeType?: string;
43
+ createdAt?: string;
44
+ updateAction: (formData: FormData) => void;
45
+ deleteAction: (formData: FormData) => void;
46
+ };
47
+
48
+ type MediaListItemProps = {
49
+ id: string;
50
+ filename?: string;
51
+ alt?: string;
52
+ url?: string;
53
+ filesize?: number;
54
+ width?: number;
55
+ height?: number;
56
+ mimeType?: string;
57
+ href: string;
58
+ };
59
+
34
60
  type NestedNavItemInput = {
35
61
  href?: string;
36
62
  label?: string;
@@ -82,6 +108,8 @@ declare const index_AdminPage: typeof AdminPage;
82
108
  type index_AdminPageLinkOption = AdminPageLinkOption;
83
109
  type index_AdminPageRecord = AdminPageRecord;
84
110
  type index_AdminRole = AdminRole;
111
+ type index_MediaDetailPanelProps = MediaDetailPanelProps;
112
+ type index_MediaListItemProps = MediaListItemProps;
85
113
  type index_NestedNavItem = NestedNavItem;
86
114
  type index_NestedNavItemInput = NestedNavItemInput;
87
115
  type index_NestedNavTree = NestedNavTree;
@@ -94,7 +122,7 @@ declare const index_normalizeNestedNavItems: typeof normalizeNestedNavItems;
94
122
  declare const index_parseAdminHeaderNavFromForm: typeof parseAdminHeaderNavFromForm;
95
123
  declare const index_roleCanAccessNav: typeof roleCanAccessNav;
96
124
  declare namespace index {
97
- export { type index_AdminBreadcrumbItem as AdminBreadcrumbItem, index_AdminBreadcrumbs as AdminBreadcrumbs, type index_AdminNavIcon as AdminNavIcon, type index_AdminNavInput as AdminNavInput, type index_AdminNavItem as AdminNavItem, type index_AdminNavLinkItem as AdminNavLinkItem, index_AdminPage as AdminPage, type index_AdminPageLinkOption as AdminPageLinkOption, type index_AdminPageRecord as AdminPageRecord, type index_AdminRole as AdminRole, type index_NestedNavItem as NestedNavItem, type index_NestedNavItemInput as NestedNavItemInput, type index_NestedNavTree as NestedNavTree, index_buildAdminPageLinkOptions as buildAdminPageLinkOptions, index_buildNestedNavTree as buildNestedNavTree, index_getAdminNavRows as getAdminNavRows, index_navItemIsActive as navItemIsActive, index_normalizeAdminNavInputs as normalizeAdminNavInputs, index_normalizeNestedNavItems as normalizeNestedNavItems, index_parseAdminHeaderNavFromForm as parseAdminHeaderNavFromForm, index_roleCanAccessNav as roleCanAccessNav };
125
+ export { type index_AdminBreadcrumbItem as AdminBreadcrumbItem, index_AdminBreadcrumbs as AdminBreadcrumbs, type index_AdminNavIcon as AdminNavIcon, type index_AdminNavInput as AdminNavInput, type index_AdminNavItem as AdminNavItem, type index_AdminNavLinkItem as AdminNavLinkItem, index_AdminPage as AdminPage, type index_AdminPageLinkOption as AdminPageLinkOption, type index_AdminPageRecord as AdminPageRecord, type index_AdminRole as AdminRole, type index_MediaDetailPanelProps as MediaDetailPanelProps, type index_MediaListItemProps as MediaListItemProps, type index_NestedNavItem as NestedNavItem, type index_NestedNavItemInput as NestedNavItemInput, type index_NestedNavTree as NestedNavTree, index_buildAdminPageLinkOptions as buildAdminPageLinkOptions, index_buildNestedNavTree as buildNestedNavTree, index_getAdminNavRows as getAdminNavRows, index_navItemIsActive as navItemIsActive, index_normalizeAdminNavInputs as normalizeAdminNavInputs, index_normalizeNestedNavItems as normalizeNestedNavItems, index_parseAdminHeaderNavFromForm as parseAdminHeaderNavFromForm, index_roleCanAccessNav as roleCanAccessNav };
98
126
  }
99
127
 
100
- export { type AdminBreadcrumbItem as A, type NestedNavItem as N, AdminBreadcrumbs as a, type AdminNavIcon as b, type AdminNavInput as c, type AdminNavItem as d, type AdminNavLinkItem as e, AdminPage as f, type AdminPageLinkOption as g, type AdminPageRecord as h, index as i, type AdminRole as j, type NestedNavItemInput as k, type NestedNavTree as l, buildAdminPageLinkOptions as m, buildNestedNavTree as n, getAdminNavRows as o, navItemIsActive as p, normalizeAdminNavInputs as q, normalizeNestedNavItems as r, parseAdminHeaderNavFromForm as s, roleCanAccessNav as t };
128
+ export { type AdminBreadcrumbItem as A, type MediaDetailPanelProps as M, type NestedNavItem as N, AdminBreadcrumbs as a, type AdminNavIcon as b, type AdminNavInput as c, type AdminNavItem as d, type AdminNavLinkItem as e, AdminPage as f, type AdminPageLinkOption as g, type AdminPageRecord as h, index as i, type AdminRole as j, type MediaListItemProps as k, type NestedNavItemInput as l, type NestedNavTree as m, buildAdminPageLinkOptions as n, buildNestedNavTree as o, getAdminNavRows as p, navItemIsActive as q, normalizeAdminNavInputs as r, normalizeNestedNavItems as s, parseAdminHeaderNavFromForm as t, roleCanAccessNav as u };
@@ -31,6 +31,32 @@ type AdminPageProps = {
31
31
  };
32
32
  declare function AdminPage({ title, description, breadcrumbs, actions, children }: AdminPageProps): react_jsx_runtime.JSX.Element;
33
33
 
34
+ type MediaDetailPanelProps = {
35
+ id: string;
36
+ filename?: string;
37
+ alt?: string;
38
+ url?: string;
39
+ filesize?: number;
40
+ width?: number;
41
+ height?: number;
42
+ mimeType?: string;
43
+ createdAt?: string;
44
+ updateAction: (formData: FormData) => void;
45
+ deleteAction: (formData: FormData) => void;
46
+ };
47
+
48
+ type MediaListItemProps = {
49
+ id: string;
50
+ filename?: string;
51
+ alt?: string;
52
+ url?: string;
53
+ filesize?: number;
54
+ width?: number;
55
+ height?: number;
56
+ mimeType?: string;
57
+ href: string;
58
+ };
59
+
34
60
  type NestedNavItemInput = {
35
61
  href?: string;
36
62
  label?: string;
@@ -82,6 +108,8 @@ declare const index_AdminPage: typeof AdminPage;
82
108
  type index_AdminPageLinkOption = AdminPageLinkOption;
83
109
  type index_AdminPageRecord = AdminPageRecord;
84
110
  type index_AdminRole = AdminRole;
111
+ type index_MediaDetailPanelProps = MediaDetailPanelProps;
112
+ type index_MediaListItemProps = MediaListItemProps;
85
113
  type index_NestedNavItem = NestedNavItem;
86
114
  type index_NestedNavItemInput = NestedNavItemInput;
87
115
  type index_NestedNavTree = NestedNavTree;
@@ -94,7 +122,7 @@ declare const index_normalizeNestedNavItems: typeof normalizeNestedNavItems;
94
122
  declare const index_parseAdminHeaderNavFromForm: typeof parseAdminHeaderNavFromForm;
95
123
  declare const index_roleCanAccessNav: typeof roleCanAccessNav;
96
124
  declare namespace index {
97
- export { type index_AdminBreadcrumbItem as AdminBreadcrumbItem, index_AdminBreadcrumbs as AdminBreadcrumbs, type index_AdminNavIcon as AdminNavIcon, type index_AdminNavInput as AdminNavInput, type index_AdminNavItem as AdminNavItem, type index_AdminNavLinkItem as AdminNavLinkItem, index_AdminPage as AdminPage, type index_AdminPageLinkOption as AdminPageLinkOption, type index_AdminPageRecord as AdminPageRecord, type index_AdminRole as AdminRole, type index_NestedNavItem as NestedNavItem, type index_NestedNavItemInput as NestedNavItemInput, type index_NestedNavTree as NestedNavTree, index_buildAdminPageLinkOptions as buildAdminPageLinkOptions, index_buildNestedNavTree as buildNestedNavTree, index_getAdminNavRows as getAdminNavRows, index_navItemIsActive as navItemIsActive, index_normalizeAdminNavInputs as normalizeAdminNavInputs, index_normalizeNestedNavItems as normalizeNestedNavItems, index_parseAdminHeaderNavFromForm as parseAdminHeaderNavFromForm, index_roleCanAccessNav as roleCanAccessNav };
125
+ export { type index_AdminBreadcrumbItem as AdminBreadcrumbItem, index_AdminBreadcrumbs as AdminBreadcrumbs, type index_AdminNavIcon as AdminNavIcon, type index_AdminNavInput as AdminNavInput, type index_AdminNavItem as AdminNavItem, type index_AdminNavLinkItem as AdminNavLinkItem, index_AdminPage as AdminPage, type index_AdminPageLinkOption as AdminPageLinkOption, type index_AdminPageRecord as AdminPageRecord, type index_AdminRole as AdminRole, type index_MediaDetailPanelProps as MediaDetailPanelProps, type index_MediaListItemProps as MediaListItemProps, type index_NestedNavItem as NestedNavItem, type index_NestedNavItemInput as NestedNavItemInput, type index_NestedNavTree as NestedNavTree, index_buildAdminPageLinkOptions as buildAdminPageLinkOptions, index_buildNestedNavTree as buildNestedNavTree, index_getAdminNavRows as getAdminNavRows, index_navItemIsActive as navItemIsActive, index_normalizeAdminNavInputs as normalizeAdminNavInputs, index_normalizeNestedNavItems as normalizeNestedNavItems, index_parseAdminHeaderNavFromForm as parseAdminHeaderNavFromForm, index_roleCanAccessNav as roleCanAccessNav };
98
126
  }
99
127
 
100
- export { type AdminBreadcrumbItem as A, type NestedNavItem as N, AdminBreadcrumbs as a, type AdminNavIcon as b, type AdminNavInput as c, type AdminNavItem as d, type AdminNavLinkItem as e, AdminPage as f, type AdminPageLinkOption as g, type AdminPageRecord as h, index as i, type AdminRole as j, type NestedNavItemInput as k, type NestedNavTree as l, buildAdminPageLinkOptions as m, buildNestedNavTree as n, getAdminNavRows as o, navItemIsActive as p, normalizeAdminNavInputs as q, normalizeNestedNavItems as r, parseAdminHeaderNavFromForm as s, roleCanAccessNav as t };
128
+ export { type AdminBreadcrumbItem as A, type MediaDetailPanelProps as M, type NestedNavItem as N, AdminBreadcrumbs as a, type AdminNavIcon as b, type AdminNavInput as c, type AdminNavItem as d, type AdminNavLinkItem as e, AdminPage as f, type AdminPageLinkOption as g, type AdminPageRecord as h, index as i, type AdminRole as j, type MediaListItemProps as k, type NestedNavItemInput as l, type NestedNavTree as m, buildAdminPageLinkOptions as n, buildNestedNavTree as o, getAdminNavRows as p, navItemIsActive as q, normalizeAdminNavInputs as r, normalizeNestedNavItems as s, parseAdminHeaderNavFromForm as t, roleCanAccessNav as u };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { i as admin } from './index-Dj21uD_B.mjs';
2
- export { i as adminApp } from './index-TJmWr6aF.mjs';
2
+ export { i as adminApp } from './index-ZbOx4OCF.mjs';
3
3
  export { i as blocks } from './index-CluwY0ZQ.mjs';
4
4
  export { i as nextjs } from './index-CpG3UHcS.mjs';
5
5
  export { i as studio } from './index-CmR6NInu.mjs';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { i as admin } from './index-Dj21uD_B.js';
2
- export { i as adminApp } from './index-TJmWr6aF.js';
2
+ export { i as adminApp } from './index-ZbOx4OCF.js';
3
3
  export { i as blocks } from './index-CluwY0ZQ.js';
4
4
  export { i as nextjs } from './index-CpG3UHcS.js';
5
5
  export { i as studio } from './index-CmR6NInu.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.5.0-beta.51",
3
+ "version": "0.5.0-beta.53",
4
4
  "description": "Unified Payload CMS toolkit for Orion Studios",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",