"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; import { Copy, Check, ExternalLink, Eye, EyeOff, Lock, Film } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; const schema = z.object({ projectId: z.string().min(1, "Select a project"), label: z.string().min(1, "Label is required"), email: z.string().email("Invalid email"), expiresInDays: z.number().int().positive().default(30), password: z.string().optional(), }); type FormValues = z.infer; interface Project { id: string; name: string; code: string; } interface ShareReviewDialogProps { children: React.ReactNode; clientId: string; clientEmail: string; projects: Project[]; } export function ShareReviewDialog({ children, clientEmail, projects, }: ShareReviewDialogProps) { const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [portalUrl, setPortalUrl] = useState(null); const [isPasswordProtected, setIsPasswordProtected] = useState(false); const [copied, setCopied] = useState(false); const [showPassword, setShowPassword] = useState(false); const [episodes, setEpisodes] = useState([]); const [selectedEpisodes, setSelectedEpisodes] = useState([]); const [loadingEpisodes, setLoadingEpisodes] = useState(false); const { toast } = useToast(); const router = useRouter(); const { register, handleSubmit, watch, setValue, reset, formState: { errors }, } = useForm({ resolver: zodResolver(schema), defaultValues: { projectId: projects[0]?.id ?? "", label: "Review Round 1", email: clientEmail, expiresInDays: 30, password: "", }, }); const selectedProjectId = watch("projectId"); // Load episodes when project changes useEffect(() => { if (!selectedProjectId) return; setSelectedEpisodes([]); setLoadingEpisodes(true); fetch(`/api/projects/${selectedProjectId}/episodes`) .then((r) => r.json()) .then((data) => setEpisodes(data.episodes ?? [])) .catch(() => setEpisodes([])) .finally(() => setLoadingEpisodes(false)); }, [selectedProjectId]); const toggleEpisode = (ep: string) => { setSelectedEpisodes((prev) => prev.includes(ep) ? prev.filter((e) => e !== ep) : [...prev, ep] ); }; const handleCopy = async () => { if (!portalUrl) return; await navigator.clipboard.writeText(portalUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleReset = () => { setPortalUrl(null); setIsPasswordProtected(false); setSelectedEpisodes([]); reset({ projectId: projects[0]?.id ?? "", label: "Review Round 1", email: clientEmail, expiresInDays: 30, password: "", }); }; const onSubmit = async (values: FormValues) => { setLoading(true); try { const res = await fetch("/api/review-sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...values, password: values.password?.trim() || undefined, allowedEpisodes: selectedEpisodes.length > 0 ? selectedEpisodes : [], }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error ?? "Failed to create review link"); } const data = await res.json(); setPortalUrl(data.portalUrl); setIsPasswordProtected(!!(values.password?.trim())); router.refresh(); } catch (e) { toast({ title: "Failed to create review link", description: e instanceof Error ? e.message : undefined, variant: "destructive", }); } finally { setLoading(false); } }; return ( { if (!o) { setPortalUrl(null); } setOpen(o); }} > {children} Share Review Link {portalUrl ? (

Your review link is ready. Copy it and share it with your client.

{isPasswordProtected && (
This link is password protected. Share the password separately.
)} {selectedEpisodes.length > 0 && (
Scoped to {selectedEpisodes.length === 1 ? "episode" : "episodes"}: {selectedEpisodes.join(", ")}
)}
{portalUrl}
) : (
{errors.projectId &&

{errors.projectId.message}

}
{/* Episode filter — only shown when the project has episodes */} {!loadingEpisodes && episodes.length > 0 && (
{episodes.map((ep) => { const active = selectedEpisodes.includes(ep); return ( ); })}
{selectedEpisodes.length > 0 && (

Showing shots from: {selectedEpisodes.join(", ")}

)}
)}
{errors.label &&

{errors.label.message}

}
{errors.email &&

{errors.email.message}

}
)}
); }