"use client"; import NextImage from "next/image"; import { useState, useRef } from "react"; import { useRouter } from "next/navigation"; import { uploadProductImage } from "@/actions/products/upload-image"; import { createProduct } from "@/actions/products/create-product"; import { AdminInput, AdminTextInput, AdminTextarea, AdminSelect } from "./design-system"; export default function NewProductForm() { const router = useRouter(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const fileInputRef = useRef(null); const [pendingImageUrl, setPendingImageUrl] = useState(""); // Form state const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [price, setPrice] = useState(""); const [type, setType] = useState("Pickup"); const [brandId, setBrandId] = useState(""); const [isTaxable, setIsTaxable] = useState("true"); const [pickupType, setPickupType] = useState("scheduled_stop"); const [active, setActive] = useState("true"); async function handleFileSelect(file: File) { const validTypes = ["image/png", "image/jpeg", "image/webp"]; if (!validTypes.includes(file.type)) { setUploadError("Only PNG, JPEG, and WebP images are allowed."); return; } if (file.size > 5 * 1024 * 1024) { setUploadError("Image must be under 5MB."); return; } // Client-side resize to max 1200px width const resizedBuffer = await resizeImage(file, 1200); const resizedFile = new File([resizedBuffer], file.name, { type: "image/jpeg" }); setUploadError(null); setUploading(true); const result = await uploadProductImage("__NEW__", resizedFile); setUploading(false); if (result.success) { setPendingImageUrl(result.imageUrl); setImagePreview(result.imageUrl); } else { setUploadError(result.error ?? "Upload failed."); } } async function resizeImage(file: File, maxWidth: number): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { let { width, height } = img; if (width > maxWidth) { height = Math.round(height * (maxWidth / width)); width = maxWidth; } const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d")!; ctx.drawImage(img, 0, 0, width, height); canvas.toBlob((blob) => { if (!blob) { reject(new Error("Failed to resize image")); return; } blob.arrayBuffer().then((buf) => resolve(buf)); }, "image/jpeg", 0.85); }; img.onerror = reject; img.src = URL.createObjectURL(file); }); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(null); // Use pendingImageUrl if image was uploaded, otherwise null const imageUrl = pendingImageUrl || null; const result = await createProduct(brandId, { name, description, price: parseFloat(price), type, active: active === "true", image_url: imageUrl, is_taxable: isTaxable === "true", pickup_type: pickupType, }); if (!result.success) { setError(result.error ?? "Failed to create product"); setLoading(false); return; } router.push("/admin/products"); router.refresh(); } return (
{error && (
{error}
)} setName(e.target.value)} placeholder="e.g. Dozen Sweet Corn" autoComplete="off" /> setDescription(e.target.value)} rows={3} placeholder="e.g. Fresh-picked Olathe sweet corn." />
setPrice(e.target.value)} placeholder="12.00" /> setType(e.target.value)} options={[ { value: "Pickup", label: "Pickup" }, { value: "Shipping", label: "Shipping" }, { value: "Pickup & Shipping", label: "Pickup & Shipping" }, ]} />
setBrandId(e.target.value)} options={[ { value: "", label: "Select brand..." }, { value: "64294306-5f42-463d-a5e8-2ad6c81a96de", label: "Tuxedo Corn" }, { value: "b1cb7a96-d82b-40b1-80b1-d6dd26c56e28", label: "Indian River Direct" }, ]} /> setIsTaxable(e.target.value)} options={[ { value: "true", label: "Yes — taxable (default)" }, { value: "false", label: "No — non-taxable" }, ]} /> setPickupType(e.target.value)} options={[ { value: "scheduled_stop", label: "Scheduled Stop — requires stop selection at checkout" }, { value: "shed", label: "Shed Pickup — uses description as location" }, ]} /> setActive(e.target.value)} options={[ { value: "true", label: "Yes — show on storefront" }, { value: "false", label: "No — hide from storefront" }, ]} />

JPG, PNG, WebP · 1200px max width · max 5MB (ideally under 2MB)

e.preventDefault()} onDrop={(e) => { e.preventDefault(); const file = e.dataTransfer.files[0]; if (file) handleFileSelect(file); }} onClick={() => fileInputRef.current?.click()} className={` flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed p-6 cursor-pointer transition-colors ${uploadError ? "border-red-400" : "border-zinc-600 hover:border-slate-400 hover:bg-zinc-900"} ${uploading ? "opacity-50 pointer-events-none" : ""} `} > {uploading ? ( <>
Uploading... ) : imagePreview ? ( <> Click or drop to replace ) : ( <> Drag & drop or click to upload )} { const file = e.target.files?.[0]; if (file) handleFileSelect(file); }} />
{uploadError &&

{uploadError}

} {imagePreview && ( )}
Cancel
); }