mirror of
https://github.com/prdlk/cv.git
synced 2026-08-02 17:31:41 +00:00
init(app): Setup client package for web api specific logic
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user