mirror of
https://github.com/prdlk/cv.git
synced 2026-08-02 17:31:41 +00:00
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { useState } from "react";
|
|||
|
|
import type { GeneratedResume } from "../App";
|
||
|
|
import { ApiRequestError, compilePdf } from "../api";
|
||
|
|
|
||
|
|
interface PdfScreenProps {
|
||
|
|
result: GeneratedResume;
|
||
|
|
onBack: () => void;
|
||
|
|
onRecompiled: (result: GeneratedResume) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function PdfScreen({ result, onBack, onRecompiled }: PdfScreenProps) {
|
||
|
|
const [latex, setLatex] = useState(result.latex);
|
||
|
|
const [busy, setBusy] = useState(false);
|
||
|
|
const [error, setError] = useState("");
|
||
|
|
|
||
|
|
const dirty = latex !== result.latex;
|
||
|
|
|
||
|
|
async function recompile(): Promise<void> {
|
||
|
|
if (busy) return;
|
||
|
|
setBusy(true);
|
||
|
|
setError("");
|
||
|
|
try {
|
||
|
|
const blob = await compilePdf(latex);
|
||
|
|
URL.revokeObjectURL(result.pdfUrl);
|
||
|
|
onRecompiled({ ...result, latex, pdfUrl: URL.createObjectURL(blob) });
|
||
|
|
} catch (err) {
|
||
|
|
if (err instanceof ApiRequestError && err.log) {
|
||
|
|
setError(`${err.message}\n\n${err.log}`);
|
||
|
|
} else {
|
||
|
|
setError(err instanceof Error ? err.message : String(err));
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setBusy(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="pdf-screen">
|
||
|
|
<header className="topbar">
|
||
|
|
<button type="button" className="btn ghost" onClick={onBack}>
|
||
|
|
← Back to workspace
|
||
|
|
</button>
|
||
|
|
<div className="topbar-title">
|
||
|
|
<strong>Tailored résumé</strong>
|
||
|
|
<span className="muted">
|
||
|
|
plan source: {result.source}
|
||
|
|
{result.note ? ` (${result.note})` : ""}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div className="topbar-actions">
|
||
|
|
<button type="button" className="btn" onClick={() => void recompile()} disabled={busy || !dirty}>
|
||
|
|
{busy ? "Compiling…" : "Recompile"}
|
||
|
|
</button>
|
||
|
|
<a className="btn primary" href={result.pdfUrl} download="prad-nukala-resume.pdf">
|
||
|
|
Download PDF
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
{error && <pre className="error banner">{error}</pre>}
|
||
|
|
|
||
|
|
<div className="pdf-grid">
|
||
|
|
<section className="panel">
|
||
|
|
<h2>LaTeX source {dirty && <span className="muted">(edited — recompile to refresh preview)</span>}</h2>
|
||
|
|
<textarea
|
||
|
|
className="latex-editor"
|
||
|
|
value={latex}
|
||
|
|
onChange={(e) => setLatex(e.target.value)}
|
||
|
|
spellCheck={false}
|
||
|
|
/>
|
||
|
|
</section>
|
||
|
|
<section className="panel">
|
||
|
|
<h2>Preview</h2>
|
||
|
|
<iframe className="preview-frame" title="PDF preview" src={result.pdfUrl} />
|
||
|
|
</section>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|