"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { createShot } from "@/actions/shots"; import { useToast } from "@/components/ui/use-toast"; const shotSchema = z.object({ scene: z.string().min(1, "Scene is required").max(50).regex(/^[A-Z0-9_]+$/i, "Alphanumeric and underscore only"), episode: z.string().max(50).optional(), description: z.string().optional(), priority: z.enum(["LOW", "NORMAL", "HIGH", "URGENT"]), fps: z.coerce.number().min(1).max(120), }); type ShotFormValues = z.infer; interface NewShotDialogProps { projectId: string; projectType?: "STANDARD" | "EPISODIC"; open: boolean; onClose: () => void; onSuccess?: () => void; } export function NewShotDialog({ projectId, projectType = "STANDARD", open, onClose, onSuccess }: NewShotDialogProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [thumbnailFile, setThumbnailFile] = useState(null); const [thumbnailPreview, setThumbnailPreview] = useState(null); const { toast } = useToast(); const router = useRouter(); const isEpisodic = projectType === "EPISODIC"; const { register, handleSubmit, setValue, reset, formState: { errors }, } = useForm({ resolver: zodResolver(shotSchema), defaultValues: { priority: "NORMAL", fps: 24 }, }); const handleThumbnailChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setThumbnailFile(file); const reader = new FileReader(); reader.onload = (event) => { setThumbnailPreview(event.target?.result as string); }; reader.readAsDataURL(file); } }; const onSubmit = async (data: ShotFormValues) => { setIsSubmitting(true); try { let thumbnailUrl: string | undefined; // Upload thumbnail if provided if (thumbnailFile) { const formData = new FormData(); formData.append("file", thumbnailFile); formData.append("type", "image"); const uploadRes = await fetch("/api/upload", { method: "POST", body: formData, }); if (!uploadRes.ok) { throw new Error("Failed to upload thumbnail"); } const uploadData = await uploadRes.json(); thumbnailUrl = uploadData.url; } await createShot({ projectId, ...data, thumbnailUrl }); toast({ title: "Shot created" }); reset(); setThumbnailFile(null); setThumbnailPreview(null); router.refresh(); onSuccess?.(); onClose(); } catch (err) { toast({ title: "Failed to create shot", description: (err as Error).message, variant: "destructive", }); } finally { setIsSubmitting(false); } }; return ( !o && onClose()}> New Shot
{isEpisodic && (
{errors.episode && (

{errors.episode.message}

)}
)}
{errors.scene && (

{errors.scene.message}

)}

Shot code will be auto-generated (e.g.{" "} {isEpisodic ? "SHOW_EP01_SC010_0010" : "SHOW_SC010_0010"})