import { useState } from 'react'
import { Link } from 'react-router-dom'
import {
CalendarClock,
Check,
Copy,
ExternalLink,
Globe,
Inbox,
Plus,
UserPlus,
X,
} from 'lucide-react'
import { Badge, Modal, Panel } from '../components/ui'
import { Empty, Labeled, Note, Select, btnGhost, btnPrimary, fieldCls } from '../components/form'
import { CAP, can } from '../lib/can'
import { useAuth } from '../lib/useAuth'
import { useOrgBranding } from '../lib/orgQueries'
import {
APPLICATION_STATUSES,
useConvertApplication,
useCreateOffer,
useCreatePosting,
useInboundApplications,
useInterviewOffers,
usePostings,
useReviewApplication,
useSetPostingStatus,
useWithdrawOffer,
type InboundApplication,
type PostingInput,
} from '../lib/careers'
import { useRequisitions } from '../tools/recruitmatch/requisitionQueries'
// CAREERS: what the outside world sees, and what it sends back.
//
// Three things on one screen because they are one flow: a posting goes public, applications arrive from it,
// and the ones worth pursuing get an interview. Splitting them into three screens would mean a recruiter
// checking three places to find out whether the job they published this morning worked.
//
// THE PUBLIC LINK IS THE PRODUCT HERE. A posting nobody can reach is a draft, so the address is shown, is
// copyable in one click, and is opened in a new tab from the same screen. A recruiter should be able to
// publish and then look at what a stranger sees, without asking anybody how.
export function Careers() {
const { principal } = useAuth()
const mayPublish = can(principal, CAP.requirementEdit)
const mayReview = can(principal, CAP.candidateWrite)
return (
)
}
// publicCareersURL builds the address a stranger uses. It is derived from the current origin rather than
// configured, because the application is served from the same host as the API, and a configured base would
// be one more thing that can be wrong in staging and right in production.
function publicCareersURL(slug: string, token?: string): string {
const base = `${window.location.origin}/careers/${encodeURIComponent(slug)}`
return token ? `${base}/${encodeURIComponent(token)}` : base
}
function CopyLink({ url, label }: { url: string; label: string }) {
const [copied, setCopied] = useState(false)
return (
)
}
function PostingsPanel({ mayPublish }: { mayPublish: boolean }) {
const branding = useOrgBranding()
const postings = usePostings()
const setStatus = useSetPostingStatus()
const [newOpen, setNewOpen] = useState(false)
const slug = branding.data?.slug ?? ''
const list = postings.data?.postings ?? []
const published = list.filter((p) => p.status === 'published')
return (
{!slug && (
This organization has no public handle set, so a posting has no address to publish to. An
operator sets it when the account is provisioned.
{/* The link is only offered for a LIVE posting. Handing somebody a link to a draft would be
handing them a 404, and the server is right to answer one. */}
{p.status === 'published' && slug && (
)}
{mayPublish && p.status === 'draft' && (
)}
{mayPublish && p.status === 'published' && (
)}
{/* An application that BECAME a candidate links to the record rather than offering convert again.
Converting twice would create the duplicate this product has a whole screen for removing. */}
{app.candidate_id ? (
Open the record
) : (
mayReview && (
<>
>
)
)}
{app.candidate_id && mayReview && (
)}
{open && (
{app.cover_note && (
{app.cover_note}
)}
{app.phone && {app.phone}}
{app.clearance_self_reported && clearance stated as {app.clearance_self_reported}}
came from {app.source}
{/* Consent is shown because it is the legal basis for holding this person's details at all, and
an application without one is a fact somebody needs to see rather than a missing field. */}
{app.consented_at ? (
consented {new Date(app.consented_at).toLocaleDateString()}
) : (
no consent recorded
)}
{app.status_reason && Recorded reason: {app.status_reason}}
{mayReview && !app.candidate_id && (
Offer somebody a few times and send them the link. They pick one, and the interview is created when
they do, so nobody spends a morning trading messages about Thursday.