/** * Team Management Page (S-7.2) * * Dashboard page for managing org members: * - List members with roles * - Invite by email with role selection * - Change roles (owner/admin only) * - Remove members * - Pending invitations list */ import { getErrorMessage } from '@agentkitai/agentlens-core'; import React, { useState, useCallback, useEffect } from 'react'; import { useOrg } from './OrgContext'; import { getOrgMembers, getOrgInvitations, inviteMember, cancelInvitation, changeMemberRole, removeMember, transferOwnership, type CloudOrgMember, type CloudInvitation, } from './api'; const ROLES = ['owner', 'admin', 'member', 'viewer'] as const; const INVITE_ROLES = ['admin', 'member', 'viewer'] as const; export function TeamManagement(): React.ReactElement { const { currentOrg } = useOrg(); const [members, setMembers] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Invite form const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('member'); const [inviting, setInviting] = useState(false); const [inviteError, setInviteError] = useState(null); const [inviteSuccess, setInviteSuccess] = useState(null); const orgId = currentOrg?.id; const fetchData = useCallback(async () => { if (!orgId) return; setLoading(true); try { const [m, i] = await Promise.all([ getOrgMembers(orgId), getOrgInvitations(orgId), ]); setMembers(m); setInvitations(i); setError(null); } catch (err: unknown) { setError(getErrorMessage(err) || 'Failed to load team data'); } finally { setLoading(false); } }, [orgId]); useEffect(() => { fetchData(); }, [fetchData]); const handleInvite = async (e: React.FormEvent) => { e.preventDefault(); if (!orgId || !inviteEmail.trim()) return; setInviting(true); setInviteError(null); setInviteSuccess(null); try { await inviteMember(orgId, inviteEmail.trim(), inviteRole); setInviteSuccess(`Invitation sent to ${inviteEmail}`); setInviteEmail(''); fetchData(); } catch (err: unknown) { setInviteError(getErrorMessage(err) || 'Failed to send invitation'); } finally { setInviting(false); } }; const handleCancelInvitation = async (invId: string) => { if (!orgId) return; try { await cancelInvitation(orgId, invId); fetchData(); } catch (err: unknown) { setError(getErrorMessage(err)); } }; const handleChangeRole = async (userId: string, newRole: string) => { if (!orgId) return; try { await changeMemberRole(orgId, userId, newRole); fetchData(); } catch (err: unknown) { setError(getErrorMessage(err)); } }; const handleRemove = async (userId: string, name: string) => { if (!orgId) return; if (!confirm(`Remove ${name} from the organization?`)) return; try { await removeMember(orgId, userId); fetchData(); } catch (err: unknown) { setError(getErrorMessage(err)); } }; const handleTransfer = async (userId: string, name: string) => { if (!orgId) return; if (!confirm(`Transfer ownership to ${name}? You will become an admin.`)) return; try { await transferOwnership(orgId, userId); fetchData(); } catch (err: unknown) { setError(getErrorMessage(err)); } }; if (!currentOrg) { return (
Select an organization to manage team members.
); } return (

Team Management

Manage members of {currentOrg.name}

{error && (
{error}
)} {/* ─── Invite Form ───────────────────────────────────────── */}

Invite Member

setInviteEmail(e.target.value)} placeholder="colleague@example.com" required className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
{inviteError &&

{inviteError}

} {inviteSuccess &&

{inviteSuccess}

}
{/* ─── Members List ──────────────────────────────────────── */}

Members ({members.length})

{loading ? (
Loading…
) : ( {members.map((m) => ( ))}
Member Role Joined Actions

{m.display_name || m.email.split('@')[0]}

{m.email}

{new Date(m.joined_at).toLocaleDateString()}
{m.role !== 'owner' && ( )}
)}
{/* ─── Pending Invitations ───────────────────────────────── */} {invitations.length > 0 && (

Pending Invitations ({invitations.length})

{invitations.map((inv) => ( ))}
Email Role Invited By Expires Actions
{inv.email} {inv.role.charAt(0).toUpperCase() + inv.role.slice(1)} {inv.invited_by_name || 'Unknown'} {new Date(inv.expires_at).toLocaleDateString()}
)}
); } export default TeamManagement;