import {useState} from 'react'; import useSWR from 'swr'; import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Input, Select, SelectContent, SelectItem, SelectItemWithDescription, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@plunk/ui'; import {MembershipSchemas} from '@plunk/shared'; import {MoreVertical, Trash2, UserPlus} from 'lucide-react'; import {AnimatePresence, motion} from 'framer-motion'; import {useForm} from 'react-hook-form'; import {zodResolver} from '@hookform/resolvers/zod'; import type {z} from 'zod'; import {network} from '../lib/network'; interface Member { userId: string; email: string; role: 'OWNER' | 'ADMIN' | 'MEMBER'; } interface TeamSettingsProps { projectId: string; currentUserRole: 'OWNER' | 'ADMIN' | 'MEMBER'; currentUserId: string; } type AddMemberForm = z.infer; export function TeamSettings({projectId, currentUserRole, currentUserId}: TeamSettingsProps) { const [showAddDialog, setShowAddDialog] = useState(false); const [memberToRemove, setMemberToRemove] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const {data, mutate, isLoading} = useSWR<{success: boolean; data: Member[]}>( projectId ? `/projects/${projectId}/members` : null, {revalidateOnFocus: false}, ); const members = data?.data || []; const canManageMembers = currentUserRole === 'OWNER' || currentUserRole === 'ADMIN'; const form = useForm({ resolver: zodResolver(MembershipSchemas.addMember), defaultValues: { email: '', role: 'MEMBER', }, }); const handleAddMember = async (values: AddMemberForm) => { setIsSubmitting(true); setError(null); setSuccess(null); try { await network.fetch('POST', `/projects/${projectId}/members`, values); setSuccess('Member added successfully'); await mutate(); form.reset(); setShowAddDialog(false); // Clear success message after 3 seconds setTimeout(() => setSuccess(null), 3000); } catch (err: unknown) { if (err instanceof Error) { setError(err.message); } else { setError('Failed to add member'); } } finally { setIsSubmitting(false); } }; const handleRemoveMember = async () => { if (!memberToRemove) return; setIsSubmitting(true); setError(null); setSuccess(null); try { await network.fetch('DELETE', `/projects/${projectId}/members/${memberToRemove.userId}`); setSuccess('Member removed successfully'); await mutate(); setMemberToRemove(null); // Clear success message after 3 seconds setTimeout(() => setSuccess(null), 3000); } catch (err: unknown) { if (err instanceof Error) { setError(err.message); } else { setError('Failed to remove member'); } } finally { setIsSubmitting(false); } }; const handleUpdateRole = async (userId: string, newRole: 'ADMIN' | 'MEMBER') => { setError(null); setSuccess(null); try { await network.fetch( 'PATCH', `/projects/${projectId}/members/${userId}`, {role: newRole}, ); setSuccess('Member role updated successfully'); await mutate(); // Clear success message after 3 seconds setTimeout(() => setSuccess(null), 3000); } catch (err: unknown) { if (err instanceof Error) { setError(err.message); } else { setError('Failed to update member role'); } } }; const getRoleBadgeVariant = (role: Member['role']) => { switch (role) { case 'OWNER': return 'default'; case 'ADMIN': return 'secondary'; case 'MEMBER': return 'outline'; default: return 'outline'; } }; return (
{success && ( {success} )} {error && ( {error} )}
Team Members Manage who has access to this project
{canManageMembers && ( )}
{isLoading ? (
) : members.length === 0 ? (
No members found
) : ( Email Role {members.map(member => { const isCurrentUser = member.userId === currentUserId; const isOwner = member.role === 'OWNER'; const canModify = canManageMembers && !isOwner && !isCurrentUser; return (
{member.email} {isCurrentUser && ( You )}
{canModify ? ( ) : ( {member.role.toLowerCase()} )} {canModify && ( setMemberToRemove(member)} > Remove )}
); })}
)} {/* Add Member Dialog */} { if (!open) { setError(null); } setShowAddDialog(open); }} > Add Team Member Add a user to this project by their email address. They must have an existing account.
{error && (
{error}
)} ( Email Address )} /> ( Role )} />
{/* Remove Member Dialog */} setMemberToRemove(null)}> Remove Team Member Are you sure you want to remove {memberToRemove?.email} from this project? They will immediately lose access.
); }