Files
starr-philproperties/src/components/generic/Blocks/Admin/TextImageBlock.jsx
T
kennethobsequio 054064b99e add: more things
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-07-04 07:27:30 +08:00

121 lines
4.2 KiB
React

// components/generic/CMS/Blocks/TextImageBlock.jsx
import { useState } from "react";
import { ImageIcon } from "lucide-react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
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 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 (
<div className="space-y-4">
{/* ── Image position ── */}
<div className="space-y-1.5 max-w-[200px]">
<Label>Image Position</Label>
<Select
value={content.image_position ?? "right"}
onValueChange={(v) => onUpdate({ ...content, image_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>
{/* ── Image side ── */}
<div className={[
"space-y-1.5",
content.image_position === "left" ? "md:order-first" : "",
].join(" ")}>
<Label>Image</Label>
{loading ? (
<MediaFallback className="w-full aspect-video rounded-lg" />
) : src ? (
<div
className="relative rounded-lg overflow-hidden cursor-pointer group"
onClick={() => setPickerOpen(true)}
>
<img
src={src}
alt={content.alt ?? ""}
className="w-full aspect-video object-cover"
/>
<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 Image</p>
</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"
>
<ImageIcon className="h-8 w-8 text-muted-foreground/50" />
<p className="text-sm text-muted-foreground">Click to select an image</p>
</button>
)}
{content.asset_id && (
<div className="space-y-1.5">
<Label>Alt Text</Label>
<Input
placeholder="Describe the image..."
value={content.alt ?? ""}
onChange={(e) => onUpdate({ ...content, alt: e.target.value })}
/>
</div>
)}
</div>
</div>
{!readOnly && (
<AssetPickerSheet
open={pickerOpen}
onOpenChange={setPickerOpen}
fileType="image"
onSelect={(asset) => onUpdate({
...content,
asset_id: asset.asset_id,
storage_provider: asset.storage_provider ?? null,
url: asset.file_url,
})}
/>
)}
</div>
);
}