'use client'; import { useState, useEffect, useCallback } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeft, Plus, Loader2, Crown, Shield, MessageSquare, Trash2, UserPlus, } 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 { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; interface Member { id: string; role: 'ADMIN' | 'COMMENTATOR'; userId: string; user: { id: string; name: string | null; email: string | null; image: string | null; }; } interface Owner { id: string; name: string | null; email: string | null; image: string | null; } export default function ProjectMembersPage() { const params = useParams(); const router = useRouter(); const projectId = params.projectId as string; const [members, setMembers] = useState([]); const [owner, setOwner] = useState(null); const [isLoading, setIsLoading] = useState(true); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState<'ADMIN' | 'COMMENTATOR'>('COMMENTATOR'); const [isInviting, setIsInviting] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const fetchMembers = useCallback(async () => { try { const res = await fetch(`/api/projects/${projectId}/members`); if (!res.ok) { if (res.status === 403) router.push('/dashboard'); return; } const data = await res.json(); setMembers(data.data.members); setOwner(data.data.owner); } catch { setError('Failed to load members'); } finally { setIsLoading(false); } }, [projectId, router]); useEffect(() => { fetchMembers(); }, [fetchMembers]); const handleInvite = async (e: React.FormEvent) => { e.preventDefault(); setIsInviting(true); setError(''); setSuccess(''); try { const res = await fetch(`/api/projects/${projectId}/members`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: inviteEmail, role: inviteRole }), }); const data = await res.json(); if (!res.ok) { setError(data.error || 'Failed to invite member'); return; } if (data.user) { setSuccess(`Invited ${data.user.name || data.user.email || inviteEmail} as ${inviteRole.toLowerCase()}`); } else { setSuccess(data.message || `Invitation sent to ${inviteEmail}`); } setInviteEmail(''); fetchMembers(); } catch { setError('Something went wrong'); } finally { setIsInviting(false); } }; const handleRoleChange = async (memberId: string, newRole: string) => { try { const res = await fetch(`/api/projects/${projectId}/members/${memberId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: newRole }), }); if (!res.ok) { const data = await res.json(); setError(data.error || 'Failed to update role'); return; } fetchMembers(); } catch { setError('Failed to update role'); } }; const handleRemove = async (memberId: string) => { if (!confirm('Are you sure you want to remove this member?')) return; try { const res = await fetch(`/api/projects/${projectId}/members/${memberId}`, { method: 'DELETE', }); if (!res.ok) { const data = await res.json(); setError(data.error || 'Failed to remove member'); return; } fetchMembers(); } catch { setError('Failed to remove member'); } }; if (isLoading) { return (
); } return (
Back to Project

Project Members

Manage who has access to this project. Admins can manage settings and delete content. Commentators can only view and leave comments.

{/* Invite Form */} Invite Member Invite someone by email. They must have an account to be added.
setInviteEmail(e.target.value)} required disabled={isInviting} />
{error && (
{error}
)} {success && (
{success}
)}
{/* Members List */} Current Members Admin — can manage project settings, members, and delete content.{' '} Commentator — can view and comment only. {/* Owner */} {owner && (
{owner.name?.charAt(0).toUpperCase() ?? 'U'}

{owner.name || 'Unnamed'}

{owner.email}

Owner
)} {/* Members */} {members.map((member) => (
{member.user.name?.charAt(0).toUpperCase() ?? 'U'}

{member.user.name || 'Unnamed'}

{member.user.email}

))} {members.length === 0 && (

No members yet. Invite someone above.

)}
); }