"use client"; import { useState, useRef } from "react"; import Image from "next/image"; import { useTranslations } from "next-intl"; import { Upload, X, Star } from "lucide-react"; import { Button } from "@/components/ui/button"; import { getPublicUrl } from "@/lib/storage"; import { uploadToPresignedPost } from "@/lib/upload-client"; export type PhotoEntry = { key: string; isCover: boolean; preview: string; sizeMb: number; }; export function PhotoUploader({ recipeId, photos, onChange, }: { recipeId: string; photos: PhotoEntry[]; onChange: (photos: PhotoEntry[]) => void; }) { const t = useTranslations("recipeForm"); const [uploading, setUploading] = useState(false); const inputRef = useRef(null); async function handleFiles(files: FileList) { setUploading(true); try { // Accumulate locally instead of calling onChange(...photos, entry) per file — // `photos` is a stale closure over the prop from when handleFiles was called, // so multiple onChange calls in this loop would each overwrite the previous // one's addition instead of stacking (only the last uploaded file would stick). let next = photos; for (const file of Array.from(files)) { const res = await fetch("/api/v1/upload/presign", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ recipeId, contentType: file.type, fileSize: file.size }), }); if (!res.ok) continue; const { url, fields, key, sizeMb } = await res.json() as { url: string; fields: Record; key: string; sizeMb: number }; const uploaded = await uploadToPresignedPost(url, fields, file); if (!uploaded) continue; const isFirst = next.length === 0; next = [...next, { key, isCover: isFirst, preview: URL.createObjectURL(file), sizeMb }]; onChange(next); } } finally { setUploading(false); } } function setCover(key: string) { onChange(photos.map((p) => ({ ...p, isCover: p.key === key }))); } function remove(key: string) { const filtered = photos.filter((p) => p.key !== key); if (filtered.length > 0 && !filtered.some((p) => p.isCover)) { filtered[0]!.isCover = true; } onChange(filtered); } return (
{photos.map((photo) => (
Recipe photo
{photo.isCover && ( cover )}
))}
e.target.files && handleFiles(e.target.files)} />
); }