'use client'; import { ReactNode, useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { ArrowLeft, Clock3, Crown, Loader2, MailX, MessageSquare, Plus, Shield, 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; } interface PendingInvitation { id: string; email: string; role: 'ADMIN' | 'COMMENTATOR'; createdAt: string; expiresAt: string; invitedBy: { id: string; name: string | null; email: string | null; }; } interface MembersManagementPageProps { apiBasePath: string; backHref: string; backLabel: string; title: string; subtitle: string; membersDescription: ReactNode; } export function MembersManagementPage({ apiBasePath, backHref, backLabel, title, subtitle, membersDescription, }: MembersManagementPageProps) { const router = useRouter(); const [members, setMembers] = useState([]); const [owner, setOwner] = useState(null); const [pendingInvitations, setPendingInvitations] = useState([]); const [isLoading, setIsLoading] = useState(true); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState<'ADMIN' | 'COMMENTATOR'>('COMMENTATOR'); const [isInviting, setIsInviting] = useState(false); const [cancelingInvitationId, setCancelingInvitationId] = useState(null); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const fetchMembers = useCallback(async () => { try { const res = await fetch(`${apiBasePath}/members`, { cache: 'no-store', }); if (!res.ok) { if (res.status === 401) { router.push('/login'); return; } if (res.status === 403) { router.push('/dashboard'); return; } return; } const data = await res.json(); setMembers(data.data.members); setOwner(data.data.owner); setPendingInvitations(data.data.pendingInvitations || []); } catch { setError('Failed to load members'); } finally { setIsLoading(false); } }, [apiBasePath, router]); useEffect(() => { fetchMembers(); }, [fetchMembers]); useEffect(() => { const interval = window.setInterval(() => { void fetchMembers(); }, 5000); return () => window.clearInterval(interval); }, [fetchMembers]); const handleInvite = async (e: React.FormEvent) => { e.preventDefault(); setIsInviting(true); setError(''); setSuccess(''); try { const res = await fetch(`${apiBasePath}/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(`${apiBasePath}/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(`${apiBasePath}/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'); } }; const handleCancelInvitation = async (invitationId: string) => { setCancelingInvitationId(invitationId); setError(''); setSuccess(''); try { const res = await fetch(`${apiBasePath}/members/invitations/${invitationId}`, { method: 'DELETE', }); if (!res.ok) { const data = await res.json(); setError(data.error || 'Failed to cancel invitation'); return; } setSuccess('Invitation canceled'); fetchMembers(); } catch { setError('Failed to cancel invitation'); } finally { setCancelingInvitationId(null); } }; if (isLoading) { return (
); } return (
{backLabel}

{title}

{subtitle}

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}
)}
Current Members {membersDescription} {owner && (
{owner.name?.charAt(0).toUpperCase() ?? 'U'}

{owner.name || 'Unnamed'}

{owner.email}

Owner
)} {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.

)}
Pending Invitations Invitations that were sent but not accepted yet. {pendingInvitations.map((invitation) => (

{invitation.email}

{invitation.role === 'ADMIN' ? 'Admin' : 'Commentator'} ยท Sent by {invitation.invitedBy.name || invitation.invitedBy.email || 'Unknown'}

Expires {new Date(invitation.expiresAt).toLocaleString()}

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

No pending invitations.

)}
); }