"use client"; /** * UpdateBackgroundModal — swap or remove the image behind a post card. * * Two actions: * • Upload Image — pick a file → signed-URL upload → PUT to BFF * → returns the refreshed post. * • Remove Background — PUT empty image set → post falls back to the * palette-aware quote-card gradient. * * Renders as a centred modal on desktop and a bottom drawer on mobile, * mirroring the pattern used by AuthModal / ConfirmModal so the UX stays * consistent across the app. */ import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { ImageIcon, Loader2, Trash2, Upload, X } from "lucide-react"; import { toast } from "sonner"; import { Drawer, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; import { posts, SocialError, type Post } from "@/lib/social"; type Busy = "upload" | "remove" | null; export function UpdateBackgroundModal({ open, onClose, post, isMobile, onUpdated, }: { open: boolean; onClose: () => void; post: Post; isMobile: boolean; /** Fires with the refreshed post after a successful upload or removal. */ onUpdated?: (updated: Post) => void; }) { const fileInput = useRef(null); const [busy, setBusy] = useState(null); const hasImage = (post.images?.length ?? 0) > 0; // Body-scroll lock + Esc-to-close — desktop modal only. The drawer // handles both internally. useEffect(() => { if (!open || isMobile) return; const prevOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; function onKey(e: KeyboardEvent) { if (e.key === "Escape" && busy === null) onClose(); } document.addEventListener("keydown", onKey); return () => { document.body.style.overflow = prevOverflow; document.removeEventListener("keydown", onKey); }; }, [open, isMobile, busy, onClose]); async function onUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (fileInput.current) fileInput.current.value = ""; if (!file) return; setBusy("upload"); try { const url = await posts.uploadImage(file); const updated = await posts.replaceImages(post.id, [url]); toast.success("Background updated"); onUpdated?.(updated); onClose(); } catch (err) { toast.error( err instanceof SocialError ? err.message : "Could not update background.", ); } finally { setBusy(null); } } async function onRemove() { setBusy("remove"); try { const updated = await posts.replaceImages(post.id, []); toast.success("Background removed"); onUpdated?.(updated); onClose(); } catch (err) { toast.error( err instanceof SocialError ? err.message : "Could not remove background.", ); } finally { setBusy(null); } } if (!open) return null; const body = (
{hasImage ? ( ) : null}
); if (isMobile) { return ( !o && busy === null && onClose()}> Update Background
{body}
); } // Desktop: centred modal with portal + backdrop, same pattern as // ConfirmModal/AuthModal so behaviour stays consistent. if (typeof document === "undefined") return null; return createPortal( <>
busy === null && onClose()} aria-hidden />

Update Background

Pick an image to set as the card's background, or remove the current one.

{body}
, document.body, ); }