add: more things

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-04 07:27:30 +08:00
parent 536c644623
commit 054064b99e
6 changed files with 101 additions and 24 deletions
@@ -1,8 +1,25 @@
import { ImageIcon } from "lucide-react";
import { ZoomableImage } from "@/modules/admin/components/courses/LessonsPreview";
import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc";
import { MediaFallback } from "@/components/generic/MediaFallback";
// content shape: { asset_id?, url?, storage_provider?, alt? }
//
// url is redacted (null) server-side for S3 assets — see redactS3Url() in
// controllers/admin/assets.controller.js. Resolve through useAssetPreviewSrc
// (media.util.js) instead of reading content.url directly, so S3-backed
// images re-mint a fresh stream token/thumbnail at render time.
export function ImageBlock({ content }) {
if (!content.url) {
const { src, loading } = useAssetPreviewSrc(
{ asset_id: content?.asset_id, storage_provider: content?.storage_provider, file_url: content?.url, thumbnail_url: content?.url },
{ scope: "client" },
);
if (loading) {
return <MediaFallback className="aspect-video rounded-lg" />;
}
if (!src) {
return (
<div className="flex items-center justify-center aspect-video rounded-lg border border-dashed text-xs text-muted-foreground bg-muted/20 gap-1.5">
<ImageIcon className="h-4 w-4" />
@@ -11,5 +28,5 @@ export function ImageBlock({ content }) {
);
}
return <ZoomableImage url={content.url} alt={content.alt} />;
}
return <ZoomableImage url={src} alt={content?.alt} />;
}
@@ -165,6 +165,7 @@ export function VideoBlock({ content }) {
// ── Stream state ──────────────────────────────────────────────────────────
const [blobUrl, setBlobUrl] = useState(null);
const [freshThumb, setFreshThumb] = useState(null);
const [fetchLoading, setFetchLoading] = useState(false);
const [fetchError, setFetchError] = useState(false);
@@ -193,7 +194,11 @@ export function VideoBlock({ content }) {
const assetId = content?.asset_id;
const storageProvider = content?.storage_provider;
const poster = content?.thumbnail_url ?? undefined;
// content.thumbnail_url is a presigned S3 URL captured at CMS pick time —
// it expires. For S3 assets, use the fresh thumbnail_url minted alongside
// the stream token below instead; only fall back to the persisted value
// for chibisafe (stable public CDN URL).
const poster = (storageProvider === "s3" ? freshThumb : content?.thumbnail_url) ?? undefined;
// ── Resolve stream URL → set as video src directly ───────────────────────
//
@@ -206,6 +211,7 @@ export function VideoBlock({ content }) {
if (!assetId) return;
setBlobUrl(null);
setFreshThumb(null);
setFetchError(false);
setPlaying(false);
setProgress(0);
@@ -231,10 +237,13 @@ export function VideoBlock({ content }) {
// S3 — get token then stream directly; no blob download
const { data } = await api.post("/client/media/token", { asset_id: assetId });
if (cancelled) return;
const { token } = data?.data ?? {};
const { token, thumbnail_url } = data?.data ?? {};
if (!token) throw new Error("No token returned");
if (!cancelled) setBlobUrl(`${API_BASE}/client/media/stream/${token}`);
if (!cancelled) {
setBlobUrl(`${API_BASE}/client/media/stream/${token}`);
if (thumbnail_url) setFreshThumb(thumbnail_url);
}
} catch (err) {
if (cancelled) return;