/* dashboard.jsx — document manager / home screen */
function timeAgo(ts) {
if (!ts) return "";
const d = Date.now() - new Date(ts).getTime();
if (d < 60000) return "Just now";
if (d < 3600000) return Math.floor(d / 60000) + "m ago";
if (d < 86400000) return Math.floor(d / 3600000) + "h ago";
if (d < 604800000) return Math.floor(d / 86400000) + "d ago";
return new Date(ts).toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
function loadAllDocs() {
try { return JSON.parse(localStorage.getItem("bdr_docs") || "[]"); } catch { return []; }
}
function saveAllDocs(docs) { localStorage.setItem("bdr_docs", JSON.stringify(docs)); }
function loadDocData(id) { try { return JSON.parse(localStorage.getItem("bdr_doc_" + id)); } catch { return null; } }
function saveDocData(id, payload) { localStorage.setItem("bdr_doc_" + id, JSON.stringify(payload)); }
window.timeAgo = timeAgo;
window.loadAllDocs = loadAllDocs;
window.saveAllDocs = saveAllDocs;
window.loadDocData = loadDocData;
window.saveDocData = saveDocData;
function DashDocCard({ doc, onOpen, onDelete, onDuplicate }) {
const docPayload = React.useMemo(() => loadDocData(doc.id) || {}, [doc.id]);
const data = docPayload.data || window.SAMPLE_RESUME;
const design = docPayload.design || { font: "auto", accent: "#1C9BE6" };
const accent = design.accent || "#1C9BE6";
const [menuOpen, setMenuOpen] = React.useState(false);
return (
{doc.name || "Untitled Résumé"}
{(window.TEMPLATES && window.TEMPLATES[doc.template] ? window.TEMPLATES[doc.template].name : "Modern")} · {timeAgo(doc.updatedAt)}
);
}
function Dashboard({ onOpen, onNew }) {
const [docs, setDocs] = React.useState(loadAllDocs);
const [confirmDel, setConfirmDel] = React.useState(null);
const fileRef = React.useRef(null);
const handleDelete = function(id) {
const next = docs.filter(function(d) { return d.id !== id; });
saveAllDocs(next);
try { localStorage.removeItem("bdr_doc_" + id); } catch(e) {}
setDocs(next);
setConfirmDel(null);
};
const handleDuplicate = function(id) {
const src = docs.find(function(d) { return d.id === id; });
if (!src) return;
const newId = Math.random().toString(36).slice(2, 9);
const srcData = loadDocData(id);
const newDoc = Object.assign({}, src, { id: newId, name: (src.name || "Résumé") + " (copy)", updatedAt: new Date().toISOString() });
const next = [newDoc].concat(docs);
saveAllDocs(next);
if (srcData) saveDocData(newId, Object.assign({}, srcData, { docName: newDoc.name }));
setDocs(next);
};
const handleImport = function(e) {
const f = e.target.files && e.target.files[0];
if (!f) return;
const reader = new FileReader();
reader.onload = function(ev) {
try {
const payload = JSON.parse(ev.target.result);
const id = Math.random().toString(36).slice(2, 9);
const newDoc = {
id: id,
name: payload.docName || f.name.replace(/\.json$/, "") || "Imported Résumé",
template: payload.template || "modern",
updatedAt: new Date().toISOString(),
};
const next = [newDoc].concat(docs);
saveAllDocs(next);
saveDocData(id, payload);
setDocs(next);
onOpen(id);
} catch(err) {
alert("Could not read the file. Make sure it is an exported BDRecruit résumé (.json).");
}
};
reader.readAsText(f);
e.target.value = "";
};
const delDoc = docs.find(function(d) { return d.id === confirmDel; });
const tplKeys = window.TEMPLATES ? Object.keys(window.TEMPLATES) : [];
return (
My Résumés
{docs.length} document{docs.length !== 1 ? "s" : ""} saved locally
{docs.length === 0 ? (
No résumés yet
Create your first résumé and land that next role.
) : (
{docs.map(function(doc) {
return (
);
})}
)}
Start from a template
All templates are ATS-verified — switch any time without losing your content.
{tplKeys.map(function(key) {
const tpl = window.TEMPLATES[key];
return (
);
})}
Cover Letter
Write a tailored cover letter that references your résumé data automatically.
Job Match
Paste any job description and instantly see which keywords your résumé is missing.
ATS Score
Real-time scoring across 7 weighted categories so recruiters and bots both love your résumé.
Smart Export
Download a pixel-perfect PDF or a clean ATS-safe plain-text PDF — your choice.
{confirmDel && (
Delete "{delDoc ? delDoc.name : "this résumé"}"?
This can't be undone. All data for this résumé will be permanently removed.
)}
);
}
window.Dashboard = Dashboard;