Job search tracker: CRUD, AI parse/tailor/cover via OmniRoute, legacy import
This commit is contained in:
59
src/app/resumes/[id]/resume-editor.tsx
Normal file
59
src/app/resumes/[id]/resume-editor.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { marked } from "marked";
|
||||
|
||||
export default function ResumeEditor({ resume }: { resume: { id: string; name: string; markdown: string } }) {
|
||||
const [md, setMd] = useState(resume.markdown);
|
||||
const [saved, setSaved] = useState(true);
|
||||
const [preview, setPreview] = useState(true);
|
||||
|
||||
async function save() {
|
||||
await fetch(`/api/resumes/${resume.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ markdown: md }),
|
||||
});
|
||||
setSaved(true);
|
||||
}
|
||||
|
||||
function printResume() {
|
||||
const w = window.open("", "_blank");
|
||||
if (!w) return;
|
||||
w.document.write(`<!DOCTYPE html><html><head><title>${resume.name}</title><style>
|
||||
body{font-family:Georgia,serif;max-width:800px;margin:2rem auto;padding:0 1rem;line-height:1.5;color:#222}
|
||||
h1{font-size:1.6rem;margin-bottom:0} h2{font-size:1.1rem;border-bottom:1px solid #ccc;padding-bottom:2px;margin-top:1.2rem}
|
||||
h3{font-size:1rem;margin-bottom:0} ul{margin-top:.3rem} li{margin:.15rem 0} @media print{body{margin:0}}
|
||||
</style></head><body>${marked.parse(md)}</body></html>`);
|
||||
w.document.close();
|
||||
setTimeout(() => w.print(), 300);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<Link href="/resumes" className="text-sm text-zinc-500 hover:text-blue-600">← Resumes</Link>
|
||||
<h1 className="text-xl font-bold">{resume.name}</h1>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setPreview(!preview)} className="rounded border border-zinc-300 dark:border-zinc-700 px-3 py-1.5 text-sm">
|
||||
{preview ? "Hide" : "Show"} preview
|
||||
</button>
|
||||
<button onClick={printResume} className="rounded border border-zinc-300 dark:border-zinc-700 px-3 py-1.5 text-sm">🖨 Print / PDF</button>
|
||||
<button onClick={save} disabled={saved} className="rounded bg-blue-600 text-white px-4 py-1.5 text-sm disabled:opacity-50 hover:bg-blue-700">
|
||||
{saved ? "Saved" : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`grid gap-4 ${preview ? "md:grid-cols-2" : ""}`}>
|
||||
<textarea value={md} onChange={(e) => { setMd(e.target.value); setSaved(false); }} rows={30}
|
||||
className="w-full rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-3 font-mono text-sm" />
|
||||
{preview && (
|
||||
<div className="rounded border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-4 prose prose-sm dark:prose-invert max-w-none overflow-auto"
|
||||
dangerouslySetInnerHTML={{ __html: marked.parse(md) as string }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user