mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
git-subtree-dir: apps/web git-subtree-mainline:eaa6d2276agit-subtree-split:459af9d46a
124 lines
4.7 KiB
React
124 lines
4.7 KiB
React
// components/generic/CMS/Blocks/TextVideoBlock.jsx
|
|
|
|
import { useState } from "react";
|
|
import { VideoIcon } from "lucide-react";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} 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);
|
|
|
|
// 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 (
|
|
<div className="space-y-4">
|
|
|
|
{/* ── Video position ── */}
|
|
<div className="space-y-1.5 max-w-[200px]">
|
|
<Label>Video Position</Label>
|
|
<Select
|
|
value={content.video_position ?? "right"}
|
|
onValueChange={(v) => onUpdate({ ...content, video_position: v })}
|
|
>
|
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">Left</SelectItem>
|
|
<SelectItem value="right">Right</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
{/* ── Text side — WYSIWYG ── */}
|
|
<div className="space-y-1.5">
|
|
<Label>Content</Label>
|
|
<RichTextEditor
|
|
blockId={`${blockId}_text`}
|
|
value={content.body ?? ""}
|
|
onChange={(html) => onUpdate({ ...content, body: html })}
|
|
/>
|
|
</div>
|
|
|
|
{/* ── Video side ── */}
|
|
<div className={[
|
|
"space-y-1.5",
|
|
content.video_position === "left" ? "md:order-first" : "",
|
|
].join(" ")}>
|
|
<Label>Video</Label>
|
|
|
|
{loading ? (
|
|
<MediaFallback className="w-full aspect-video rounded-lg" />
|
|
) : content.asset_id ? (
|
|
<div
|
|
className="relative rounded-lg overflow-hidden cursor-pointer group"
|
|
onClick={() => setPickerOpen(true)}
|
|
>
|
|
{thumb ? (
|
|
<img
|
|
src={thumb}
|
|
alt="Video thumbnail"
|
|
className="w-full aspect-video object-cover"
|
|
/>
|
|
) : (
|
|
<div className="w-full aspect-video bg-muted flex items-center justify-center">
|
|
<VideoIcon className="h-10 w-10 text-muted-foreground/50" />
|
|
</div>
|
|
)}
|
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
|
<p className="text-white text-sm font-medium">Change Video</p>
|
|
</div>
|
|
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
<div className="h-12 w-12 rounded-full bg-black/50 flex items-center justify-center">
|
|
<VideoIcon className="h-5 w-5 text-white" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => setPickerOpen(true)}
|
|
className="w-full aspect-video rounded-lg border-2 border-dashed border-muted-foreground/30 hover:border-primary/50 hover:bg-muted/20 transition-colors flex flex-col items-center justify-center gap-2"
|
|
>
|
|
<VideoIcon className="h-8 w-8 text-muted-foreground/50" />
|
|
<p className="text-sm text-muted-foreground">Click to select a video</p>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{!readOnly && (
|
|
<AssetPickerSheet
|
|
open={pickerOpen}
|
|
onOpenChange={setPickerOpen}
|
|
fileType="video"
|
|
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,
|
|
duration_seconds: Number(asset.duration) || 0,
|
|
})}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
} |