'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, }); // 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', }); } }) .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 && ( )} ))} {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 ); }
This action cannot be undone
This will permanently delete this project and all of its videos, versions, and comments. This action cannot be undone.