Files

126 lines
4.0 KiB
TypeScript

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<void> {
if (busy || url.trim().length === 0) return;
setBusy(true);
setError("");
try {
onExtracted(await post<JobExtract>("/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 (
<div className="home">
<div className="home-card">
<h1>CV Tailor</h1>
<p className="sub">
{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."}
</p>
{mode === "url" ? (
<form
className="home-form"
onSubmit={(e) => {
e.preventDefault();
void submitUrl();
}}
>
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://boards.greenhouse.io/company/jobs/123456"
autoFocus
required
/>
<button type="submit" className="btn primary" disabled={busy}>
{busy ? "Extracting…" : "Tailor Résumé"}
</button>
</form>
) : (
<form
className="paste-form"
onSubmit={(e) => {
e.preventDefault();
submitPaste();
}}
>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Job opportunity title, e.g. Senior Platform Engineer at Aptos"
autoFocus
required
/>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Paste the full job description here…"
rows={12}
required
/>
<button type="submit" className="btn primary">
Tailor Résumé
</button>
</form>
)}
{error && <div className="error">{error}</div>}
{mode === "url" ? (
<>
<p className="hint">
Server-rendered boards (Greenhouse, Lever, Workable, Ashby job-post pages) extract
best.
</p>
<button type="button" className="link" onClick={() => switchMode("paste")}>
Or manually paste job description
</button>
</>
) : (
<button type="button" className="link" onClick={() => switchMode("url")}>
Or extract from a job posting URL
</button>
)}
</div>
</div>
);
}