import { useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { KeyRound, LogOut, ShieldCheck, UserPlus } from 'lucide-react' import { adminApi, ROLES, type UserSummary } from '../lib/admin' import { authApi } from '../lib/auth' import { useAuth } from '../lib/useAuth' import { Badge, Panel } from '../components/ui' const isAdmin = (role: string) => role === 'owner' || role === 'admin' export function Settings() { const { principal, logout } = useAuth() return (
{principal && isAdmin(principal.role) && }

Federated SSO (Login.gov, Entra Gov, PIV CAC) and public signup are a later sub phase. Invites are delivered manually at pilot, the link is shown after you invite a teammate.

) } function AccountPanel({ onLogout, role, orgID }: { onLogout: () => void; role: string; orgID: string }) { const [oldPw, setOldPw] = useState('') const [newPw, setNewPw] = useState('') const [msg, setMsg] = useState('') const change = useMutation({ mutationFn: () => authApi.changePassword(oldPw, newPw), onSuccess: () => { setMsg('Password changed.') setOldPw('') setNewPw('') }, onError: () => setMsg('Could not change the password. Check the current one.'), }) const logoutAll = useMutation({ mutationFn: () => authApi.logoutAll() }) return (
{role || 'unknown role'} org {orgID.slice(0, 8)}
{ e.preventDefault() setMsg('') if (newPw.length < 8) { setMsg('Use at least 8 characters.') return } change.mutate() }} >
Change password
{msg &&

{msg}

}
) } function TeamPanel({ meRole }: { meRole: string }) { const qc = useQueryClient() const users = useQuery({ queryKey: ['users'], queryFn: adminApi.listUsers }) const invalidate = () => qc.invalidateQueries({ queryKey: ['users'] }) const [email, setEmail] = useState('') const [name, setName] = useState('') const [role, setRole] = useState('member') const [inviteLink, setInviteLink] = useState('') const invite = useMutation({ mutationFn: () => adminApi.invite(email.trim(), name.trim(), role), onSuccess: (r) => { setInviteLink(`${window.location.origin}/accept?token=${r.token}`) setEmail('') setName('') invalidate() }, }) return (
{ e.preventDefault() if (email.trim() && name.trim()) { invite.mutate() } }} >
Role
{inviteLink && (
Send this invite link to the new teammate: {inviteLink}
)}
{(users.data ?? []).map((u) => ( ))}
User Role Status MFA
{users.isLoading &&

Loading team…

}
) } function UserRow({ u, meRole, onChanged }: { u: UserSummary; meRole: string; onChanged: () => void }) { const setRole = useMutation({ mutationFn: (role: string) => adminApi.changeRole(u.id, role), onSuccess: onChanged }) const setStatus = useMutation({ mutationFn: (status: 'active' | 'disabled') => adminApi.changeStatus(u.id, status), onSuccess: onChanged, }) const resetMfa = useMutation({ mutationFn: () => adminApi.resetMFA(u.id), onSuccess: onChanged }) const disabled = u.status === 'disabled' return (
{u.full_name}
{u.email}
{u.role === 'owner' ? ( owner ) : ( )} {u.status} {u.mfa_enrolled ? 'enrolled' : 'not enrolled'} {u.role !== 'owner' && (
)} ) } function LabeledInput({ label, value, onChange, type = 'text', }: { label: string value: string onChange: (v: string) => void type?: string }) { return ( ) }