'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import { ArrowLeft, Check, Copy, Link2, Loader2, RefreshCcw, ShieldOff, Lock, ShieldCheck } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; interface VideoSharePageProps { projectId: string; videoId: string; } interface ShareLinkData { id: string; token: string; allowGuests: boolean; allowDownloads: boolean; hasPassword: boolean; } interface ShareResponse { data: { link: ShareLinkData | null; shareUrl: string | null; }; error?: string; } export default function VideoSharePageClient({ projectId, videoId }: VideoSharePageProps) { const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [copied, setCopied] = useState(false); const [error, setError] = useState(''); const [shareUrl, setShareUrl] = useState(null); const [hasPassword, setHasPassword] = useState(false); const [password, setPassword] = useState(''); const [allowDownloads, setAllowDownloads] = useState(false); useEffect(() => { if (!projectId || !videoId) return; async function loadShareLink() { setLoading(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}/videos/${videoId}/share`, { cache: 'no-store' }); const payload = (await response.json()) as ShareResponse; if (!response.ok || payload.error) { setError(payload.error || 'Failed to load share link'); setShareUrl(null); return; } setShareUrl(payload.data.shareUrl); setHasPassword(!!payload.data.link?.hasPassword); setAllowDownloads(!!payload.data.link?.allowDownloads); } catch { setError('Failed to load share link'); setShareUrl(null); setHasPassword(false); setAllowDownloads(false); } finally { setLoading(false); } } loadShareLink(); }, [projectId, videoId]); const copyLink = async () => { if (!shareUrl) return; await navigator.clipboard.writeText(shareUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const createShareLink = async () => { if (!projectId || !videoId) return; setSubmitting(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}/videos/${videoId}/share`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ allowGuests: true, allowDownloads }), }); const payload = (await response.json()) as ShareResponse; if (!response.ok || payload.error) { setError(payload.error || 'Failed to create share link'); return; } setShareUrl(payload.data.shareUrl); setHasPassword(!!payload.data.link?.hasPassword); setAllowDownloads(!!payload.data.link?.allowDownloads); setPassword(''); } catch { setError('Failed to create share link'); } finally { setSubmitting(false); } }; const revokeShareLink = async () => { if (!projectId || !videoId) return; setSubmitting(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}/videos/${videoId}/share`, { method: 'DELETE', }); if (!response.ok) { const payload = (await response.json().catch(() => null)) as { error?: string } | null; setError(payload?.error || 'Failed to revoke share link'); return; } setShareUrl(null); setHasPassword(false); setAllowDownloads(false); setPassword(''); } catch { setError('Failed to revoke share link'); } finally { setSubmitting(false); } }; const updateSecuritySettings = async (clearPassword = false) => { if (!projectId || !videoId || !shareUrl) return; setSubmitting(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}/videos/${videoId}/share`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...(clearPassword ? { clearPassword: true } : {}), ...(!clearPassword ? { password } : {}), }), }); const payload = (await response.json().catch(() => null)) as ShareResponse | { error?: string } | null; if (!response.ok || ('error' in (payload || {}) && payload?.error)) { setError((payload as { error?: string } | null)?.error || 'Failed to update link security'); return; } const data = (payload as ShareResponse).data; setShareUrl(data.shareUrl); setHasPassword(!!data.link?.hasPassword); setAllowDownloads(!!data.link?.allowDownloads); setPassword(''); } catch { setError('Failed to update link security'); } finally { setSubmitting(false); } }; const updateDownloadSetting = async (nextAllowDownloads: boolean) => { if (!projectId || !videoId || !shareUrl) return; setSubmitting(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}/videos/${videoId}/share`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ allowDownloads: nextAllowDownloads }), }); const payload = (await response.json().catch(() => null)) as ShareResponse | { error?: string } | null; if (!response.ok || ('error' in (payload || {}) && payload?.error)) { setError((payload as { error?: string } | null)?.error || 'Failed to update download setting'); return; } const data = (payload as ShareResponse).data; setShareUrl(data.shareUrl); setAllowDownloads(!!data.link?.allowDownloads); setHasPassword(!!data.link?.hasPassword); } catch { setError('Failed to update download setting'); } finally { setSubmitting(false); } }; return (
Back to Video Share Video For Review Create a private link so reviewers can watch and comment on this single video. {loading ? (
Loading link settings...
) : shareUrl ? (

Video download

Allow viewers with this link to download

{hasPassword ? : } Link password
setPassword(e.target.value)} disabled={submitting} /> {hasPassword && ( )}
) : ( )}

This link allows guests to leave comments without an account. You can optionally protect it with a password.

{error && (

{error}

)}
); }