From 054064b99edf3110a6d404998902c1fc73ee8cb7 Mon Sep 17 00:00:00 2001 From: Kenneth Obsequio Date: Sat, 4 Jul 2026 07:27:30 +0800 Subject: [PATCH] add: more things Signed-off-by: Kenneth Obsequio --- .../generic/Blocks/Admin/ImageBlock.jsx | 19 +++++++++-- .../generic/Blocks/Admin/TextImageBlock.jsx | 18 ++++++++-- .../generic/Blocks/Admin/TextVideoBlock.jsx | 16 +++++++-- .../generic/Blocks/Admin/VideoBlock.jsx | 34 +++++++++++++------ .../generic/Blocks/Client/ImageBlock.jsx | 23 +++++++++++-- .../generic/Blocks/Client/VideoBlock.jsx | 15 ++++++-- 6 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/components/generic/Blocks/Admin/ImageBlock.jsx b/src/components/generic/Blocks/Admin/ImageBlock.jsx index 6895a6d..a507bec 100644 --- a/src/components/generic/Blocks/Admin/ImageBlock.jsx +++ b/src/components/generic/Blocks/Admin/ImageBlock.jsx @@ -3,6 +3,8 @@ import { ImageIcon } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { AssetPickerSheet } from "../../AssetPickerSheet"; +import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc"; +import { MediaFallback } from "@/components/generic/MediaFallback"; function MediaPlaceholder({ onClick }) { @@ -23,17 +25,27 @@ function MediaPlaceholder({ onClick }) { export function ImageBlock({ content, onUpdate, readOnly = false }) { const [pickerOpen, setPickerOpen] = useState(false); + // content.url is redacted (null) server-side for S3 assets — see + // redactS3Url() in controllers/admin/assets.controller.js. Re-resolve + // through media.util.js instead of trusting the persisted url/thumbnail. + const { src, loading } = useAssetPreviewSrc( + { asset_id: content?.asset_id, storage_provider: content?.storage_provider, file_url: content?.url, thumbnail_url: content?.url }, + { scope: "admin" }, + ); + return (
- {content.url ? ( + {loading ? ( + + ) : src ? (
setPickerOpen(true)} > {content.alt @@ -45,7 +57,7 @@ export function ImageBlock({ content, onUpdate, readOnly = false }) { setPickerOpen(true)} /> )} - {content.url && ( + {content.asset_id && (
onUpdate({ ...content, asset_id: asset.asset_id, + storage_provider: asset.storage_provider ?? null, url: asset.file_url, })} /> diff --git a/src/components/generic/Blocks/Admin/TextImageBlock.jsx b/src/components/generic/Blocks/Admin/TextImageBlock.jsx index 5082193..cd51534 100644 --- a/src/components/generic/Blocks/Admin/TextImageBlock.jsx +++ b/src/components/generic/Blocks/Admin/TextImageBlock.jsx @@ -13,10 +13,19 @@ import { } from "@/components/ui/select"; import { RichTextEditor } from "./TextBlock"; // reuse the shared editor import { AssetPickerSheet } from "../../AssetPickerSheet"; +import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc"; +import { MediaFallback } from "@/components/generic/MediaFallback"; export function TextImageBlock({ content, onUpdate, blockId, readOnly = false }) { const [pickerOpen, setPickerOpen] = useState(false); + // content.url is redacted (null) server-side for S3 assets — re-resolve + // through media.util.js instead of trusting the persisted url/thumbnail. + const { src, loading } = useAssetPreviewSrc( + { asset_id: content?.asset_id, storage_provider: content?.storage_provider, file_url: content?.url, thumbnail_url: content?.url }, + { scope: "admin" }, + ); + return (
@@ -54,13 +63,15 @@ export function TextImageBlock({ content, onUpdate, blockId, readOnly = false }) ].join(" ")}> - {content.url ? ( + {loading ? ( + + ) : src ? (
setPickerOpen(true)} > {content.alt @@ -79,7 +90,7 @@ export function TextImageBlock({ content, onUpdate, blockId, readOnly = false }) )} - {content.url && ( + {content.asset_id && (
onUpdate({ ...content, asset_id: asset.asset_id, + storage_provider: asset.storage_provider ?? null, url: asset.file_url, })} /> diff --git a/src/components/generic/Blocks/Admin/TextVideoBlock.jsx b/src/components/generic/Blocks/Admin/TextVideoBlock.jsx index f83257a..59db657 100644 --- a/src/components/generic/Blocks/Admin/TextVideoBlock.jsx +++ b/src/components/generic/Blocks/Admin/TextVideoBlock.jsx @@ -12,11 +12,20 @@ import { } from "@/components/ui/select"; import { RichTextEditor } from "./TextBlock"; // reuse the shared editor import { AssetPickerSheet } from "../../AssetPickerSheet"; +import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc"; +import { MediaFallback } from "@/components/generic/MediaFallback"; export function TextVideoBlock({ content, onUpdate, blockId, readOnly = false }) { const [pickerOpen, setPickerOpen] = useState(false); - const thumb = content.thumbnail_url ?? null; + // content.thumbnail_url is a presigned S3 URL captured at pick time for + // S3 assets — it expires. Re-resolve through media.util.js instead of + // trusting the persisted value, mirroring VideoBlock.jsx. + const { thumbnailUrl, loading } = useAssetPreviewSrc( + { asset_id: content?.asset_id, storage_provider: content?.storage_provider, file_url: content?.url, thumbnail_url: content?.thumbnail_url }, + { scope: "admin" }, + ); + const thumb = thumbnailUrl ?? content.thumbnail_url ?? null; return (
@@ -55,7 +64,9 @@ export function TextVideoBlock({ content, onUpdate, blockId, readOnly = false }) ].join(" ")}> - {content.url ? ( + {loading ? ( + + ) : content.asset_id ? (
setPickerOpen(true)} @@ -101,6 +112,7 @@ export function TextVideoBlock({ content, onUpdate, blockId, readOnly = false }) onSelect={(asset) => onUpdate({ ...content, asset_id: asset.asset_id, + storage_provider: asset.storage_provider ?? null, url: asset.file_url, thumbnail_url: asset.thumbnail_url ?? null, })} diff --git a/src/components/generic/Blocks/Admin/VideoBlock.jsx b/src/components/generic/Blocks/Admin/VideoBlock.jsx index f0e2d89..cae1d1d 100644 --- a/src/components/generic/Blocks/Admin/VideoBlock.jsx +++ b/src/components/generic/Blocks/Admin/VideoBlock.jsx @@ -10,6 +10,8 @@ import { } from "lucide-react"; import { Label } from "@/components/ui/label"; import { AssetPickerSheet } from "../../AssetPickerSheet"; +import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc"; +import { MediaFallback } from "@/components/generic/MediaFallback"; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -25,6 +27,15 @@ const fmtTime = (s) => { export function VideoBlock({ content, onUpdate, readOnly = false }) { const [pickerOpen, setPickerOpen] = useState(false); + // content.url is redacted (null) server-side for S3 assets — see + // redactS3Url() in controllers/admin/assets.controller.js. Re-resolve + // through media.util.js instead of trusting the persisted url/thumbnail. + const { src, thumbnailUrl, loading } = useAssetPreviewSrc( + { asset_id: content?.asset_id, storage_provider: content?.storage_provider, file_url: content?.url, thumbnail_url: content?.thumbnail_url }, + { scope: "admin" }, + ); + const poster = thumbnailUrl ?? content?.thumbnail_url ?? undefined; + const vidRef = useRef(null); const [playing, setPlaying] = useState(false); const [progress, setProgress] = useState(0); @@ -41,7 +52,7 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { setCurrentTime(0); setTotalDuration(0); setOverlayVisible(true); - }, [content.url]); + }, [src]); // ── Video event listeners ───────────────────────────────────────────────── @@ -65,7 +76,7 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { v.removeEventListener("loadedmetadata", onLoaded); v.removeEventListener("ended", onEnded); }; - }, [content.url]); + }, [src]); // ── Controls ────────────────────────────────────────────────────────────── @@ -126,11 +137,12 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { // const handleSelect = (asset) => { onUpdate({ - asset_id: asset.asset_id, - url: asset.file_url, - thumbnail_url: asset.thumbnail_url ?? null, - title: asset.display_name, - tag: asset.extension?.toUpperCase() ?? "", + asset_id: asset.asset_id, + storage_provider: asset.storage_provider ?? null, + url: asset.file_url, + thumbnail_url: asset.thumbnail_url ?? null, + title: asset.display_name, + tag: asset.extension?.toUpperCase() ?? "", }); setPlaying(false); setProgress(0); @@ -145,7 +157,9 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) {
{!readOnly && } - {content.url ? ( + {loading ? ( + + ) : src ? (
{/* ── Video area ── */} @@ -157,8 +171,8 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) { >