'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeft, Loader2, Globe, Lock, UserPlus, Trash2, AlertTriangle, Settings, Save, Tag, Plus, X, } 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'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog'; type Visibility = 'PRIVATE' | 'INVITE' | 'PUBLIC'; const visibilityOptions: { value: Visibility; label: string; description: string; icon: React.ReactNode; }[] = [ { value: 'PRIVATE', label: 'Private', description: 'Only you can access this project', icon: , }, { value: 'INVITE', label: 'Invite Only', description: 'Share with specific people via email', icon: , }, { value: 'PUBLIC', label: 'Public', description: 'Anyone with the link can view', icon: , }, ]; interface ProjectSettingsPageProps { projectId: string; } interface CommentTag { id: string; name: string; color: string; position: number; } export default function ProjectSettingsPageClient({ projectId }: ProjectSettingsPageProps) { const router = useRouter(); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [deleteConfirmation, setDeleteConfirmation] = useState(''); const [formData, setFormData] = useState({ name: '', description: '', visibility: 'PRIVATE' as Visibility, allowDownloads: false, }); // Tag management state const [tags, setTags] = useState([]); const [newTagName, setNewTagName] = useState(''); const [newTagColor, setNewTagColor] = useState('#3B82F6'); const [isAddingTag, setIsAddingTag] = useState(false); const [editingTagId, setEditingTagId] = useState(null); const [editTagName, setEditTagName] = useState(''); const [editTagColor, setEditTagColor] = useState(''); useEffect(() => { // Fetch project data fetch(`/api/projects/${projectId}`) .then((res) => res.json()) .then((data) => { if (data.error) { setError(data.error); } else { const project = data.data; setFormData({ name: project.name || '', description: project.description || '', visibility: project.visibility || 'PRIVATE', allowDownloads: project.allowDownloads ?? false, }); } }) .catch(() => setError('Failed to load project')) .finally(() => setIsLoading(false)); // Fetch tags fetch(`/api/projects/${projectId}/tags`) .then((res) => res.json()) .then((data) => { if (Array.isArray(data.data)) { setTags(data.data); } }) .catch(() => { /* Silent fail - tags are optional */ }); }, [projectId]); const handleSave = async (e: React.FormEvent) => { e.preventDefault(); setIsSaving(true); setError(''); setSuccess(''); try { const response = await fetch(`/api/projects/${projectId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); const data = await response.json(); if (!response.ok) { setError(data.error || 'Failed to update project'); return; } setSuccess('Project settings saved successfully'); setTimeout(() => setSuccess(''), 3000); } catch { setError('Something went wrong. Please try again.'); } finally { setIsSaving(false); } }; const handleAddTag = async () => { if (!newTagName.trim()) return; setIsAddingTag(true); try { const res = await fetch(`/api/projects/${projectId}/tags`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newTagName.trim(), color: newTagColor }), }); if (res.ok) { const data = await res.json(); const newTag = data.data; setTags([...tags, newTag]); setNewTagName(''); setNewTagColor('#3B82F6'); } } catch { // Silent fail } finally { setIsAddingTag(false); } }; const handleUpdateTag = async (tagId: string) => { if (!editTagName.trim()) return; try { const res = await fetch(`/api/projects/${projectId}/tags/${tagId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: editTagName.trim(), color: editTagColor }), }); if (res.ok) { const data = await res.json(); const updated = data.data; setTags(tags.map((t) => (t.id === tagId ? updated : t))); setEditingTagId(null); } } catch { // Silent fail } }; const handleDeleteTag = async (tagId: string) => { try { const res = await fetch(`/api/projects/${projectId}/tags/${tagId}`, { method: 'DELETE', }); if (res.ok) { setTags(tags.filter((t) => t.id !== tagId)); } } catch { // Silent fail } }; const handleDelete = async () => { if (deleteConfirmation !== formData.name) { setError('Project name does not match'); return; } setIsDeleting(true); setError(''); try { const response = await fetch(`/api/projects/${projectId}`, { method: 'DELETE', }); if (!response.ok) { const data = await response.json(); setError(data.error || 'Failed to delete project'); return; } router.push('/dashboard'); } catch { setError('Something went wrong. Please try again.'); } finally { setIsDeleting(false); } }; if (isLoading) { return ( ); } return ( Back to Project {/* General Settings */} Project Settings Update your project details and access settings Project Name setFormData((prev) => ({ ...prev, name: e.target.value }))} required disabled={isSaving} className="h-11" /> Description setFormData((prev) => ({ ...prev, description: e.target.value })) } rows={3} disabled={isSaving} className="resize-none" /> Who can access? {visibilityOptions.map((option) => ( setFormData((prev) => ({ ...prev, visibility: option.value })) } disabled={isSaving} className={`w-full flex items-center gap-4 p-4 rounded-xl border-2 text-left transition-all ${ formData.visibility === option.value ? 'border-primary bg-primary/5 ring-1 ring-primary/20' : 'border-border hover:border-border/80 hover:bg-accent/50' }`} > {option.icon} {option.label} {option.description} {formData.visibility === option.value && ( )} ))} Project downloads Allow viewers to download project files. Project admins can always download. When enabled on a public project, anyone with the link can download files without signing in. setFormData((prev) => ({ ...prev, allowDownloads: !prev.allowDownloads })) } disabled={isSaving} className={`w-full flex items-center justify-between gap-4 p-4 rounded-xl border-2 text-left transition-all ${ formData.allowDownloads ? 'border-primary bg-primary/5 ring-1 ring-primary/20' : 'border-border hover:border-border/80 hover:bg-accent/50' }`} > Allow viewer downloads Public and invited viewers can download files when enabled. On public projects this includes unauthenticated visitors. {formData.allowDownloads && ( )} {error && ( {error} )} {success && ( {success} )} {isSaving && } Save Changes {/* Comment Tags */} Comment Tags Customize tags for categorizing comments on videos {/* Existing tags */} {tags.map((tag) => ( {editingTagId === tag.id ? ( <> setEditTagColor(e.target.value)} className="w-8 h-8 rounded cursor-pointer border-0" /> setEditTagName(e.target.value)} className="flex-1 h-8" onKeyDown={(e) => e.key === 'Enter' && handleUpdateTag(tag.id)} /> handleUpdateTag(tag.id)} > setEditingTagId(null)} > > ) : ( <> {tag.name} { setEditingTagId(tag.id); setEditTagName(tag.name); setEditTagColor(tag.color); }} > Edit handleDeleteTag(tag.id)} > > )} ))} {/* Add new tag */} setNewTagColor(e.target.value)} className="w-8 h-8 rounded cursor-pointer border-0" /> setNewTagName(e.target.value)} className="flex-1 h-8" onKeyDown={(e) => e.key === 'Enter' && handleAddTag()} /> {isAddingTag ? ( ) : ( )} {/* Danger Zone */} Danger Zone Irreversible actions that will permanently affect your project Delete this project This action cannot be undone Delete Delete "{formData.name}"? This will permanently delete this project and all of its videos, versions, and comments. This action cannot be undone. Type {formData.name} to confirm setDeleteConfirmation(e.target.value)} placeholder="Project name" className="h-11" /> setDeleteConfirmation('')}> Cancel {isDeleting && } Delete Project ); }
Allow viewers to download project files. Project admins can always download. When enabled on a public project, anyone with the link can download files without signing in.
This action cannot be undone
This will permanently delete this project and all of its videos, versions, and comments. This action cannot be undone.