import { useEffect, useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { KeyRound, LogOut, ShieldCheck, UserCog } from 'lucide-react'
import { Link } from 'react-router-dom'
import { authApi } from '../lib/auth'
import { useAuth } from '../lib/useAuth'
import { CAP, can } from '../lib/can'
import { TimeZonePanel } from './TimeZonePanel'
import {
InviteDefaultsPanel,
MailPanel,
RecordScopePanel,
SettingsLoadFailure,
WhoSeesWhatPanel,
WrapRatePanel,
AedtNoticePanel,
} from './OrgSettingsPanels'
import { Badge, Panel } from '../components/ui'
export function Settings() {
const { principal, logout } = useAuth()
return (
{/* SELF SERVICE PROFILE, amendment 6 decision 4, Track U item 2. It sits FIRST and above the
password form because it is the thing a person most often comes here to do, and because the
defect this closes was that there was no path at all: a name was written once at invite
acceptance and never again, by anyone, so correcting a typo escalated to the top of the ladder
and failed there too. Every role sees this panel; editing your own name is not an act of
authority, so there is no capability check around it. */}
{/* Account management lives on ONE screen. Settings used to carry its own Team panel: a second
user list on a different query key, whose invite sent no tool set and so created accounts
entitled to nothing, and whose role changes did not invalidate the other screen. Two surfaces
for the same thing disagreed about what access a person had, which is the worst possible thing
for an access control screen to be ambiguous about. */}
{can(principal, CAP.userInvite) && (
Create accounts, set roles, and choose which tools each person can see.
Open accounts and access
)}
{/* Owner only: the server refuses these for anyone else, so offering them would be buttons that fail.
The order is the order an owner sets a company up in. Mail comes first because it is the one that
unblocks everything else: until a message can leave, an invitation is a link somebody has to copy
into their own mail client by hand. Then who sees what, then which records they see at all, then
the defaults nobody thinks about until one of them is wrong.
The TIME ZONE stays where it was, in its own panel, and is deliberately not repeated here. It lives
on the organization and drives the task queue, the daily report and every period rollup. A second
control would let somebody set a working day that the rest of the product ignores. */}
{can(principal, CAP.orgSettings) && (
<>
>
)}
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.
)
}
const fieldCls =
'w-full rounded-md border border-line/70 bg-navy-950/60 px-3 py-1.5 text-sm outline-none focus:border-blue/60'
// ProfilePanel is the self service identity edit, available to every role on their OWN account.
//
// The server takes the subject from the session, never from this form, so there is no user id in the
// request and no way to aim it at another account. On success the session is refreshed rather than the
// local state patched, so the new name comes back from the server and renders everywhere the identity
// renders (the shell header, the accounts list) instead of only in the box that was typed into.
function ProfilePanel() {
const { principal, refresh } = useAuth()
const [fullName, setFullName] = useState('')
const [title, setTitle] = useState('')
const [phone, setPhone] = useState('')
const [msg, setMsg] = useState('')
// Seeded from the session once it resolves. Without this the fields render empty on first paint and an
// unedited save would read as blanking a title the person already had.
useEffect(() => {
setFullName(principal?.full_name ?? '')
setTitle(principal?.title ?? '')
setPhone(principal?.phone ?? '')
}, [principal?.full_name, principal?.title, principal?.phone])
const save = useMutation({
mutationFn: () => authApi.updateOwnProfile({ full_name: fullName, title, phone }),
onSuccess: async () => {
setMsg('Saved. Your name now shows everywhere you appear.')
await refresh()
},
onError: (e: Error) => setMsg(e.message || 'Could not save your profile.'),
})
const dirty =
fullName !== (principal?.full_name ?? '') ||
title !== (principal?.title ?? '') ||
phone !== (principal?.phone ?? '')
return (
)
}
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 (