import { useState } from 'react'
import { ArrowLeft, Briefcase, Mail, Phone, Plus, Star, UserCheck, UserMinus, UserPlus } from 'lucide-react'
import { Link, useParams } from 'react-router-dom'
import { ToolHeader } from '../../components/ToolHeader'
import { Modal, Panel } from '../../components/ui'
import { useAuth } from '../../lib/useAuth'
import { recruitBrand } from './brand'
import {
useAddContact,
useClaimClient,
useClient,
useReleaseClient,
useUpdateClient,
useUpdateContact,
} from './clientQueries'
const inputCls =
'w-full rounded-lg border border-line/70 bg-navy-900 px-3 py-2 text-sm text-ink outline-none focus:border-recruit'
export function ClientDetail() {
const { clientId } = useParams()
const { principal } = useAuth()
const q = useClient(clientId ?? null)
const update = useUpdateClient()
const claim = useClaimClient()
const release = useReleaseClient()
const addContact = useAddContact()
const updateContact = useUpdateContact()
const [editing, setEditing] = useState(false)
const [industry, setIndustry] = useState('')
const [notes, setNotes] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [cName, setCName] = useState('')
const [cTitle, setCTitle] = useState('')
const [cEmail, setCEmail] = useState('')
const [cHM, setCHM] = useState(false)
if (q.isLoading) return Loading client…
if (q.isError || !q.data) {
return (
Could not load this client. {(q.error as Error)?.message}
)
}
const c = q.data
const mine = !!c.owner_user_id && c.owner_user_id === principal?.user_id
const unowned = !c.owner_user_id
// The lock, stated plainly. Anyone may read; only the holder writes, and an unowned account is
// claimable. An admin can still act, and the server is the authority on that, so this only shapes
// what is offered rather than what is enforced.
const canEdit = mine || unowned
const saveEdits = () => {
update.mutate(
{ id: c.id, patch: { expected_updated_at: c.updated_at, industry, notes } },
{ onSuccess: () => setEditing(false) },
)
}
const submitContact = () => {
addContact.mutate(
{
clientId: c.id,
patch: { full_name: cName, title: cTitle, email: cEmail, is_hiring_manager: cHM },
},
{
onSuccess: () => {
setCName('')
setCTitle('')
setCEmail('')
setCHM(false)
setAddOpen(false)
},
},
)
}
return (
All clients
{c.name}
{c.industry || 'No industry set'} · {c.status}
{c.uei ? ` · UEI ${c.uei}` : ''}
{c.owner_user_id ? (
<>
{mine ? 'You hold this account' : `Held by ${c.owner_name}`}
{mine && (
release.mutate(c.id)}
disabled={release.isPending}
className="flex items-center gap-1.5 rounded-lg border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:text-ink disabled:opacity-50"
>
Release
)}
>
) : (
claim.mutate(c.id)}
disabled={claim.isPending}
className="flex items-center gap-1.5 rounded-lg border border-recruit/50 px-3 py-1.5 text-xs font-semibold text-recruit hover:bg-recruit/10 disabled:opacity-50"
>
Claim this account
)}
{/* Someone else holds it. Say who, and say what to do about it, rather than only disabling the
buttons and leaving the reason to be guessed. */}
{!canEdit && (
{c.owner_name} is working this account. Talk to them before making changes, or ask an admin to
reassign it.
)}
{canEdit && !editing && (
{
setIndustry(c.industry ?? '')
setNotes(c.notes ?? '')
setEditing(true)
}}
className="mt-3 rounded-lg border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:text-ink"
>
Edit details
)}
{editing && (
Industry
setIndustry(e.target.value)} className={inputCls} />
Notes
{update.isError && (
// A 409 here means the row moved after it was read, which is a reload rather than a retry.
{(update.error as Error).message}
)}
Save
setEditing(false)}
className="rounded-lg border border-line/70 px-3 py-2 text-sm font-semibold text-muted hover:text-ink"
>
Cancel
)}
{c.notes && !editing && {c.notes}
}
{/* The client screen is where a recruiter decides what to work next, so the way to its open
roles belongs here rather than only in the global list. */}
View job orders for {c.name}
{canEdit && (
setAddOpen(true)}
className="flex items-center gap-2 rounded-lg border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:text-ink"
>
Add contact
)}
{c.contacts.length === 0 ? (
No contacts yet. Add the hiring manager so job orders have somewhere to go.
) : (
{c.contacts.map((k) => (
{k.is_primary && }
{k.full_name}
{k.is_hiring_manager && (
Hiring manager
)}
{k.title || 'No title'}
{k.email ? ` · ${k.email}` : ''}
{k.phone ? ` · ${k.phone}` : ''}
{k.email && (
)}
{k.phone && (
)}
{canEdit && !k.is_primary && (
updateContact.mutate({
id: k.id,
clientId: c.id,
patch: { expected_updated_at: k.updated_at, is_primary: true },
})
}
className="rounded-lg border border-line/70 px-2 py-1 text-[10px] font-semibold text-muted hover:text-ink"
>
Make primary
)}
))}
)}
setAddOpen(false)} title="Add contact">
)
}