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.
)} {slug && (
Your careers page is at {publicCareersURL(slug)}
)} {postings.isError ? ( {(postings.error as Error).message} ) : list.length === 0 ? ( ) : ( )} {setStatus.isError && (
{(setStatus.error as Error).message}
)} {mayPublish && (
)} setNewOpen(false)} />
) } const emptyPosting: PostingInput = { requisition_id: '', title: '', summary: '', responsibilities: '', qualifications: '', location_text: '', remote_allowed: false, employment_type: 'contract', clearance_text: '', pay_range_text: '', } function NewPostingModal({ open, onClose }: { open: boolean; onClose: () => void }) { const create = useCreatePosting() const reqs = useRequisitions({ status: 'open' }) const [v, setV] = useState(emptyPosting) const openReqs = reqs.data?.requisitions ?? [] return (
{ e.preventDefault() if (!v.requisition_id || !v.title.trim()) return create.mutate(v, { onSuccess: () => { setV(emptyPosting) onClose() }, }) }} > This becomes a public page. Everything written here is read by people outside your organization, so leave out anything about the client that is not yours to publish. setV({ ...v, title: e.target.value })} className={fieldCls} />