"use client"; import { useCallback, useEffect, useState, type ChangeEvent, type KeyboardEvent, type ReactElement, type SyntheticEvent, } from "react"; import Link from "next/link"; import { Plus, Trash2, Users } from "lucide-react"; import { WorkspaceMemberRole } from "@mosaic/shared"; import { SettingsAccessDenied } from "@/components/settings/SettingsAccessDenied"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; 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, } from "@/components/ui/alert-dialog"; import { createTeam, deleteTeam, fetchTeams, updateTeam, type CreateTeamDto, type TeamRecord, type UpdateTeamDto, } from "@/lib/api/teams"; import { fetchUserWorkspaces } from "@/lib/api/workspaces"; const INITIAL_CREATE_FORM = { name: "", description: "", }; const INITIAL_DETAIL_FORM = { name: "", description: "", }; interface DetailInitialState { name: string; description: string; } function toMemberLabel(count: number): string { return `${String(count)} member${count === 1 ? "" : "s"}`; } export default function TeamsSettingsPage(): ReactElement { const [teams, setTeams] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(null); const [isAdmin, setIsAdmin] = useState(null); const [isCreateOpen, setIsCreateOpen] = useState(false); const [createForm, setCreateForm] = useState(INITIAL_CREATE_FORM); const [createError, setCreateError] = useState(null); const [isCreating, setIsCreating] = useState(false); const [detailTarget, setDetailTarget] = useState(null); const [detailForm, setDetailForm] = useState(INITIAL_DETAIL_FORM); const [detailInitial, setDetailInitial] = useState(null); const [detailError, setDetailError] = useState(null); const [isSavingDetails, setIsSavingDetails] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const loadTeams = useCallback(async (showLoadingState: boolean): Promise => { try { if (showLoadingState) { setIsLoading(true); } else { setIsRefreshing(true); } const data = await fetchTeams(); setTeams(data); setError(null); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Failed to load teams"); } finally { setIsLoading(false); setIsRefreshing(false); } }, []); useEffect(() => { fetchUserWorkspaces() .then((workspaces) => { const adminRoles: WorkspaceMemberRole[] = [ WorkspaceMemberRole.OWNER, WorkspaceMemberRole.ADMIN, ]; setIsAdmin(workspaces.some((workspace) => adminRoles.includes(workspace.role))); }) .catch(() => { setIsAdmin(true); // fail open }); }, []); useEffect(() => { if (isAdmin !== true) { return; } void loadTeams(true); }, [isAdmin, loadTeams]); function resetCreateForm(): void { setCreateForm(INITIAL_CREATE_FORM); setCreateError(null); } function openTeamDetails(team: TeamRecord): void { const nextDetailForm = { name: team.name, description: team.description ?? "", }; setDetailTarget(team); setDetailForm(nextDetailForm); setDetailInitial({ name: nextDetailForm.name, description: nextDetailForm.description, }); setDetailError(null); } function resetTeamDetails(): void { setDetailTarget(null); setDetailForm(INITIAL_DETAIL_FORM); setDetailInitial(null); setDetailError(null); } function handleTeamRowKeyDown(event: KeyboardEvent, team: TeamRecord): void { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openTeamDetails(team); } } async function handleCreateSubmit(event: SyntheticEvent): Promise { event.preventDefault(); setCreateError(null); const name = createForm.name.trim(); if (!name) { setCreateError("Name is required."); return; } const description = createForm.description.trim(); const dto: CreateTeamDto = { name }; if (description) { dto.description = description; } try { setIsCreating(true); await createTeam(dto); setIsCreateOpen(false); resetCreateForm(); await loadTeams(false); } catch (err: unknown) { setCreateError(err instanceof Error ? err.message : "Failed to create team"); } finally { setIsCreating(false); } } async function handleDetailSubmit(event: SyntheticEvent): Promise { event.preventDefault(); if (detailTarget === null || detailInitial === null) { return; } const name = detailForm.name.trim(); if (!name) { setDetailError("Name is required."); return; } const nextDescription = detailForm.description.trim(); const normalizedNextDescription = nextDescription.length > 0 ? nextDescription : null; const normalizedInitialDescription = detailInitial.description.trim().length > 0 ? detailInitial.description.trim() : null; const dto: UpdateTeamDto = {}; if (name !== detailInitial.name) { dto.name = name; } if (normalizedNextDescription !== normalizedInitialDescription) { dto.description = normalizedNextDescription; } if (Object.keys(dto).length === 0) { resetTeamDetails(); return; } try { setIsSavingDetails(true); setDetailError(null); await updateTeam(detailTarget.id, dto); resetTeamDetails(); await loadTeams(false); } catch (err: unknown) { setDetailError(err instanceof Error ? err.message : "Failed to update team"); } finally { setIsSavingDetails(false); } } async function confirmDelete(): Promise { if (!deleteTarget) { return; } try { setIsDeleting(true); await deleteTeam(deleteTarget.id); setDeleteTarget(null); await loadTeams(false); setError(null); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Failed to delete team"); } finally { setIsDeleting(false); } } if (isAdmin === null) { return ( Checking permissions... ); } if (!isAdmin) { return ; } return (

Teams

{teams.length} total

Create and manage workspace teams

{ if (!open && !isCreating) { resetCreateForm(); } setIsCreateOpen(open); }} > Create Team Create a team in the active workspace to organize members and permissions.
{ void handleCreateSubmit(event); }} className="space-y-4" >
) => { setCreateForm((prev) => ({ ...prev, name: event.target.value })); }} placeholder="Platform Team" maxLength={100} required />