"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { ExternalLink, Copy, Check, Trash2, Clock, Film, RotateCcw, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useToast } from "@/components/ui/use-toast"; import { cn, formatRelativeDate } from "@/lib/utils"; interface ReviewSession { id: string; token: string; label: string | null; email: string | null; expiresAt: Date | string | null; accessCount: number; isActive: boolean; allowedEpisodes: string[]; project: { name: string }; } interface ReviewSessionListProps { sessions: ReviewSession[]; } const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? ""; export function ReviewSessionList({ sessions }: ReviewSessionListProps) { const [copiedId, setCopiedId] = useState(null); const [deletingId, setDeletingId] = useState(null); const [reactivatingId, setReactivatingId] = useState(null); const { toast } = useToast(); const router = useRouter(); // Same token, new expiry — the URL the client already has starts working again. const handleReactivate = async (id: string, days: number) => { setReactivatingId(id); try { const res = await fetch(`/api/review-sessions?id=${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ expiresInDays: days }), }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error); } toast({ title: "Review link reactivated", description: `Valid for ${days} more days — same URL.` }); router.refresh(); } catch (e) { toast({ title: "Failed to reactivate link", description: e instanceof Error && e.message ? e.message : undefined, variant: "destructive", }); } finally { setReactivatingId(null); } }; const handleCopy = async (token: string, id: string) => { const url = `${APP_URL}/client/${token}`; await navigator.clipboard.writeText(url); setCopiedId(id); setTimeout(() => setCopiedId(null), 2000); }; const handleDeactivate = async (id: string) => { setDeletingId(id); try { const res = await fetch(`/api/review-sessions?id=${id}`, { method: "DELETE" }); if (!res.ok) throw new Error(); toast({ title: "Review link deactivated" }); router.refresh(); } catch { toast({ title: "Failed to deactivate link", variant: "destructive" }); } finally { setDeletingId(null); } }; if (sessions.length === 0) return null; return (

Active Review Links ({sessions.length})

{sessions.map((session) => { const portalUrl = `${APP_URL}/client/${session.token}`; const expired = session.expiresAt && new Date(session.expiresAt) < new Date(); return (

{session.label || "Untitled Review"}

{expired && ( Expired )}
{session.project.name} {session.email && ( <> · {session.email} )} {session.expiresAt && ( <> · {expired ? "Expired" : `Expires ${formatRelativeDate(session.expiresAt)}`} )}
{portalUrl}
{session.allowedEpisodes && session.allowedEpisodes.length > 0 && (
{session.allowedEpisodes.map((ep) => ( {ep} ))}
)}
{session.accessCount} view{session.accessCount !== 1 ? "s" : ""} {expired && ( Valid for {[7, 14, 30, 60, 90].map((d) => ( handleReactivate(session.id, d)}> {d} days ))} )}
); })}
); }