import { useState } from "react"; import type { JobExtract } from "../../shared/types"; import { ApiRequestError, post } from "../api"; const MIN_TEXT_CHARS = 120; export function Home({ onExtracted }: { onExtracted: (jd: JobExtract) => void }) { const [mode, setMode] = useState<"url" | "paste">("url"); const [url, setUrl] = useState(""); const [title, setTitle] = useState(""); const [text, setText] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); async function submitUrl(): Promise { if (busy || url.trim().length === 0) return; setBusy(true); setError(""); try { onExtracted(await post("/api/extract", { url })); } catch (err) { setError(err instanceof ApiRequestError ? err.message : String(err)); setBusy(false); } } function submitPaste(): void { const trimmedTitle = title.trim(); const trimmedText = text.trim(); if (trimmedTitle.length === 0) { setError("Give the job opportunity a title."); return; } if (trimmedText.length < MIN_TEXT_CHARS) { setError(`Paste the full job description (at least ${MIN_TEXT_CHARS} characters).`); return; } onExtracted({ url: "", title: trimmedTitle, text: trimmedText, source: "manual" }); } function switchMode(next: "url" | "paste"): void { setMode(next); setError(""); } return (

CV Tailor

{mode === "url" ? "Paste a link to a job posting. The résumé corpus is matched against it, you refine the selection with an AI agent, and a one-page LaTeX résumé is compiled to PDF." : "Paste the job description below. The résumé corpus is matched against it, you refine the selection with an AI agent, and a one-page LaTeX résumé is compiled to PDF."}

{mode === "url" ? (
{ e.preventDefault(); void submitUrl(); }} > setUrl(e.target.value)} placeholder="https://boards.greenhouse.io/company/jobs/123456" autoFocus required />
) : (
{ e.preventDefault(); submitPaste(); }} > setTitle(e.target.value)} placeholder="Job opportunity title, e.g. Senior Platform Engineer at Aptos" autoFocus required />