Introduce modern, editorial, and minimal visual themes for live preview and ATS-compliant PDF exports
This commit is contained in:
parent
d9e108ba1c
commit
a8dddccbdb
2 changed files with 485 additions and 182 deletions
|
|
@ -166,19 +166,19 @@ export async function GET(req: Request) {
|
|||
doc.on("error", (err) => reject(err));
|
||||
});
|
||||
|
||||
// --- Header ---
|
||||
// --- Modern ATS Header ---
|
||||
doc
|
||||
.fontSize(22)
|
||||
.fillColor("#111827")
|
||||
.fontSize(24)
|
||||
.fillColor("#0f172a")
|
||||
.font("Helvetica-Bold")
|
||||
.text(name, { align: "center" });
|
||||
.text(name, 40, 40, { align: "left" });
|
||||
|
||||
if (headline) {
|
||||
doc
|
||||
.fontSize(12)
|
||||
.fontSize(11)
|
||||
.fillColor("#2563eb")
|
||||
.font("Helvetica-Bold")
|
||||
.text(headline, { align: "center" });
|
||||
.text(headline.toUpperCase(), { align: "left" });
|
||||
}
|
||||
|
||||
const contactParts = [];
|
||||
|
|
@ -188,14 +188,14 @@ export async function GET(req: Request) {
|
|||
|
||||
if (contactParts.length > 0) {
|
||||
doc
|
||||
.fontSize(9)
|
||||
.fillColor("#4b5563")
|
||||
.fontSize(8.5)
|
||||
.fillColor("#64748b")
|
||||
.font("Helvetica")
|
||||
.text(contactParts.join(" | "), { align: "center" });
|
||||
.text(contactParts.join(" | "), { align: "left" });
|
||||
}
|
||||
|
||||
doc.moveDown(0.8);
|
||||
doc.strokeColor("#e5e7eb").lineWidth(1).moveTo(40, doc.y).lineTo(572, doc.y).stroke();
|
||||
doc.moveDown(0.6);
|
||||
doc.strokeColor("#0f172a").lineWidth(1.5).moveTo(40, doc.y).lineTo(572, doc.y).stroke();
|
||||
doc.moveDown(0.8);
|
||||
|
||||
// --- Professional Summary ---
|
||||
|
|
|
|||
|
|
@ -3,140 +3,339 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Document, Page, Text, View, StyleSheet, PDFDownloadLink } from "@react-pdf/renderer";
|
||||
|
||||
const pdfStyles = StyleSheet.create({
|
||||
page: {
|
||||
padding: 35,
|
||||
fontFamily: "Helvetica",
|
||||
fontSize: 9.5,
|
||||
color: "#18181b",
|
||||
lineHeight: 1.45,
|
||||
},
|
||||
header: {
|
||||
marginBottom: 14,
|
||||
borderBottomWidth: 1.5,
|
||||
borderBottomColor: "#18181b",
|
||||
paddingBottom: 8,
|
||||
},
|
||||
name: {
|
||||
fontSize: 18,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#09090b",
|
||||
marginBottom: 2,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
title: {
|
||||
fontSize: 10.5,
|
||||
color: "#3f3f46",
|
||||
marginBottom: 4,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
},
|
||||
contact: {
|
||||
fontSize: 8.5,
|
||||
color: "#52525b",
|
||||
},
|
||||
section: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 9.5,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#09090b",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#e4e4e7",
|
||||
paddingBottom: 2,
|
||||
marginBottom: 6,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
jobHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 2,
|
||||
},
|
||||
jobTitle: {
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 9.5,
|
||||
},
|
||||
company: {
|
||||
color: "#52525b",
|
||||
fontFamily: "Helvetica",
|
||||
},
|
||||
bullet: {
|
||||
marginLeft: 8,
|
||||
marginBottom: 2,
|
||||
fontSize: 8.5,
|
||||
color: "#27272a",
|
||||
},
|
||||
});
|
||||
// Styles for React-PDF export by theme
|
||||
const themesPDF = {
|
||||
modern: StyleSheet.create({
|
||||
page: {
|
||||
padding: 38,
|
||||
fontFamily: "Helvetica",
|
||||
fontSize: 9.5,
|
||||
color: "#1e293b",
|
||||
lineHeight: 1.45,
|
||||
},
|
||||
header: {
|
||||
marginBottom: 14,
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor: "#0f172a",
|
||||
paddingBottom: 10,
|
||||
},
|
||||
name: {
|
||||
fontSize: 22,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#0f172a",
|
||||
marginBottom: 3,
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
title: {
|
||||
fontSize: 11,
|
||||
color: "#2563eb",
|
||||
marginBottom: 6,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.8,
|
||||
},
|
||||
contact: {
|
||||
fontSize: 8.5,
|
||||
color: "#64748b",
|
||||
},
|
||||
section: {
|
||||
marginBottom: 13,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 10,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#0f172a",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#cbd5e1",
|
||||
paddingBottom: 3,
|
||||
marginBottom: 7,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.8,
|
||||
},
|
||||
jobHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
marginBottom: 2,
|
||||
},
|
||||
jobTitle: {
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 10,
|
||||
color: "#0f172a",
|
||||
},
|
||||
company: {
|
||||
color: "#2563eb",
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 9.5,
|
||||
},
|
||||
bullet: {
|
||||
marginLeft: 10,
|
||||
marginBottom: 2.5,
|
||||
fontSize: 9,
|
||||
color: "#334155",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
skillsRow: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: 4,
|
||||
},
|
||||
skillBadge: {
|
||||
backgroundColor: "#f1f5f9",
|
||||
borderWidth: 0.5,
|
||||
borderColor: "#cbd5e1",
|
||||
borderRadius: 3,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 6,
|
||||
fontSize: 8,
|
||||
color: "#334155",
|
||||
fontFamily: "Helvetica-Bold",
|
||||
marginBottom: 3,
|
||||
marginRight: 4,
|
||||
},
|
||||
}),
|
||||
|
||||
const ResumePDFDocument = ({ data }: { data: any }) => (
|
||||
<Document>
|
||||
<Page size="A4" style={pdfStyles.page}>
|
||||
<View style={pdfStyles.header}>
|
||||
<Text style={pdfStyles.name}>{data.profile?.name || "Applicant Name"}</Text>
|
||||
{data.profile?.title && <Text style={pdfStyles.title}>{data.profile.title}</Text>}
|
||||
<Text style={pdfStyles.contact}>
|
||||
{[data.profile?.email, data.profile?.phone, data.profile?.location].filter(Boolean).join(" | ")}
|
||||
</Text>
|
||||
</View>
|
||||
editorial: StyleSheet.create({
|
||||
page: {
|
||||
padding: 40,
|
||||
fontFamily: "Times-Roman",
|
||||
fontSize: 10,
|
||||
color: "#1c1917",
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
header: {
|
||||
marginBottom: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#1c1917",
|
||||
paddingBottom: 10,
|
||||
textAlign: "center",
|
||||
},
|
||||
name: {
|
||||
fontSize: 24,
|
||||
fontFamily: "Times-Bold",
|
||||
color: "#1c1917",
|
||||
marginBottom: 4,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
title: {
|
||||
fontSize: 11,
|
||||
color: "#78350f",
|
||||
marginBottom: 5,
|
||||
fontFamily: "Times-Italic",
|
||||
},
|
||||
contact: {
|
||||
fontSize: 9,
|
||||
color: "#57534e",
|
||||
},
|
||||
section: {
|
||||
marginBottom: 14,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 10.5,
|
||||
fontFamily: "Times-Bold",
|
||||
color: "#1c1917",
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: "#a8a29e",
|
||||
paddingBottom: 2,
|
||||
marginBottom: 7,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
jobHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 2,
|
||||
},
|
||||
jobTitle: {
|
||||
fontFamily: "Times-Bold",
|
||||
fontSize: 10.5,
|
||||
color: "#1c1917",
|
||||
},
|
||||
company: {
|
||||
color: "#44403c",
|
||||
fontFamily: "Times-Italic",
|
||||
fontSize: 10,
|
||||
},
|
||||
bullet: {
|
||||
marginLeft: 10,
|
||||
marginBottom: 2.5,
|
||||
fontSize: 9.5,
|
||||
color: "#292524",
|
||||
},
|
||||
skillsRow: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
skillBadge: {
|
||||
fontSize: 9,
|
||||
color: "#292524",
|
||||
fontFamily: "Times-Roman",
|
||||
marginRight: 6,
|
||||
},
|
||||
}),
|
||||
|
||||
{data.profile?.summary && (
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.sectionTitle}>Professional Summary</Text>
|
||||
<Text style={{ fontSize: 9, color: "#27272a" }}>{data.profile.summary}</Text>
|
||||
minimal: StyleSheet.create({
|
||||
page: {
|
||||
padding: 36,
|
||||
fontFamily: "Helvetica",
|
||||
fontSize: 9,
|
||||
color: "#18181b",
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
header: {
|
||||
marginBottom: 14,
|
||||
paddingBottom: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#e4e4e7",
|
||||
},
|
||||
name: {
|
||||
fontSize: 20,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#09090b",
|
||||
marginBottom: 2,
|
||||
},
|
||||
title: {
|
||||
fontSize: 10,
|
||||
color: "#52525b",
|
||||
marginBottom: 4,
|
||||
fontFamily: "Helvetica",
|
||||
},
|
||||
contact: {
|
||||
fontSize: 8.5,
|
||||
color: "#71717a",
|
||||
},
|
||||
section: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 9,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
color: "#09090b",
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: "#e4e4e7",
|
||||
paddingBottom: 2,
|
||||
marginBottom: 6,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
jobHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 2,
|
||||
},
|
||||
jobTitle: {
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 9.5,
|
||||
},
|
||||
company: {
|
||||
color: "#52525b",
|
||||
fontFamily: "Helvetica",
|
||||
},
|
||||
bullet: {
|
||||
marginLeft: 8,
|
||||
marginBottom: 2,
|
||||
fontSize: 8.5,
|
||||
color: "#27272a",
|
||||
},
|
||||
skillsRow: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
skillBadge: {
|
||||
fontSize: 8.5,
|
||||
color: "#27272a",
|
||||
marginRight: 8,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const ResumePDFDocument = ({ data, theme = "modern" }: { data: any; theme?: "modern" | "editorial" | "minimal" }) => {
|
||||
const styles = themesPDF[theme] || themesPDF.modern;
|
||||
|
||||
return (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.name}>{data.profile?.name || "Applicant Name"}</Text>
|
||||
{data.profile?.title && <Text style={styles.title}>{data.profile.title}</Text>}
|
||||
<Text style={styles.contact}>
|
||||
{[data.profile?.email, data.profile?.phone, data.profile?.location].filter(Boolean).join(" | ")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{Array.isArray(data.work) && data.work.length > 0 && (
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.sectionTitle}>Professional Experience</Text>
|
||||
{data.work.map((item: any, i: number) => (
|
||||
<View key={i} style={{ marginBottom: 8 }}>
|
||||
<View style={pdfStyles.jobHeader}>
|
||||
<Text style={pdfStyles.jobTitle}>
|
||||
{item.position || "Position"}{" "}
|
||||
{item.company ? <Text style={pdfStyles.company}>— {item.company}</Text> : null}
|
||||
{/* Summary */}
|
||||
{data.profile?.summary && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Professional Summary</Text>
|
||||
<Text style={{ fontSize: theme === "editorial" ? 9.5 : 9, color: theme === "modern" ? "#334155" : "#27272a" }}>
|
||||
{data.profile.summary}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Experience */}
|
||||
{Array.isArray(data.work) && data.work.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Professional Experience</Text>
|
||||
{data.work.map((item: any, i: number) => (
|
||||
<View key={i} style={{ marginBottom: 9 }}>
|
||||
<View style={styles.jobHeader}>
|
||||
<Text style={styles.jobTitle}>{item.position || "Position"}</Text>
|
||||
{item.company && <Text style={styles.company}>{item.company}</Text>}
|
||||
</View>
|
||||
{item.summary && (
|
||||
<Text style={{ marginBottom: 3, fontSize: 8.5, color: "#64748b" }}>
|
||||
{item.summary}
|
||||
</Text>
|
||||
)}
|
||||
{Array.isArray(item.highlights) &&
|
||||
item.highlights.map((h: string, hIdx: number) => (
|
||||
<Text key={hIdx} style={styles.bullet}>• {h}</Text>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Skills */}
|
||||
{Array.isArray(data.skills) && data.skills.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Skills & Technical Proficiencies</Text>
|
||||
<View style={styles.skillsRow}>
|
||||
{theme === "modern" ? (
|
||||
data.skills.map((s: string, idx: number) => (
|
||||
<Text key={idx} style={styles.skillBadge}>{s}</Text>
|
||||
))
|
||||
) : (
|
||||
<Text style={{ fontSize: 9 }}>{data.skills.join(" • ")}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Education */}
|
||||
{Array.isArray(data.education) && data.education.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Education & Certifications</Text>
|
||||
{data.education.map((edu: any, i: number) => (
|
||||
<View key={i} style={{ marginBottom: 4 }}>
|
||||
<Text style={styles.jobTitle}>
|
||||
{edu.degree || "Degree / Program"}
|
||||
{edu.institution ? ` — ${edu.institution}` : ""}
|
||||
</Text>
|
||||
</View>
|
||||
{item.summary && <Text style={{ marginBottom: 3, fontSize: 8.5 }}>{item.summary}</Text>}
|
||||
{Array.isArray(item.highlights) &&
|
||||
item.highlights.map((h: string, hIdx: number) => (
|
||||
<Text key={hIdx} style={pdfStyles.bullet}>• {h}</Text>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{Array.isArray(data.skills) && data.skills.length > 0 && (
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.sectionTitle}>Skills & Competencies</Text>
|
||||
<Text style={{ fontSize: 9 }}>{data.skills.join(" • ")}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{Array.isArray(data.education) && data.education.length > 0 && (
|
||||
<View style={pdfStyles.section}>
|
||||
<Text style={pdfStyles.sectionTitle}>Education & Certifications</Text>
|
||||
{data.education.map((edu: any, i: number) => (
|
||||
<View key={i} style={{ marginBottom: 4 }}>
|
||||
<Text style={pdfStyles.jobTitle}>
|
||||
{edu.degree && edu.institution
|
||||
? `${edu.degree} — ${edu.institution}`
|
||||
: edu.degree || edu.institution || "High School / Technical Education"}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
||||
export function ResumePreview({ data }: { data: any }) {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [theme, setTheme] = useState<"classic" | "modern" | "compact">("classic");
|
||||
const [theme, setTheme] = useState<"modern" | "editorial" | "minimal">("modern");
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
|
|
@ -148,78 +347,147 @@ export function ResumePreview({ data }: { data: any }) {
|
|||
const education = data.education || [];
|
||||
|
||||
return (
|
||||
<div className="bg-white border border-zinc-200 rounded-md p-5 sticky top-16 shadow-sm">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-zinc-200 mb-4 flex-wrap gap-2">
|
||||
<div className="bg-white border border-stone-300 rounded-sm p-5 sticky top-16 shadow-[2px_2px_0px_#1c1917]">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-stone-200 mb-4 flex-wrap gap-2">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-xs font-mono font-bold text-zinc-900 uppercase tracking-wider">
|
||||
Live ATS Preview
|
||||
<span className="text-xs font-mono font-bold text-stone-900 uppercase tracking-wider">
|
||||
ATS Visual Template
|
||||
</span>
|
||||
|
||||
<select
|
||||
value={theme}
|
||||
onChange={(e) => setTheme(e.target.value as any)}
|
||||
className="text-[11px] font-mono border border-zinc-200 rounded px-2 py-0.5 bg-zinc-50 text-zinc-800"
|
||||
className="text-xs font-mono border border-stone-300 rounded-none px-2.5 py-1 bg-stone-50 text-stone-900 font-semibold focus:outline-none focus:ring-1 focus:ring-stone-900"
|
||||
>
|
||||
<option value="classic">Theme: Classic ATS</option>
|
||||
<option value="modern">Theme: Modern Tech</option>
|
||||
<option value="compact">Theme: Compact Executive</option>
|
||||
<option value="modern">Modern Tech (Navy & Slate Accent)</option>
|
||||
<option value="editorial">Executive Editorial (Warm Serif & Rust)</option>
|
||||
<option value="minimal">Clean Monochrome (Standard ATS)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isClient && (
|
||||
<PDFDownloadLink
|
||||
document={<ResumePDFDocument data={data} />}
|
||||
fileName={`${(profile.name || "Resume").replace(/\s+/g, "_")}_ATS.pdf`}
|
||||
className="text-xs font-mono font-semibold bg-zinc-900 hover:bg-zinc-800 text-white px-3 py-1 rounded transition-colors inline-block"
|
||||
document={<ResumePDFDocument data={data} theme={theme} />}
|
||||
fileName={`${(profile.name || "Resume").replace(/\s+/g, "_")}_${theme.toUpperCase()}_ATS.pdf`}
|
||||
className="text-xs font-mono font-semibold bg-stone-900 hover:bg-stone-800 text-stone-100 px-3.5 py-1.5 border border-stone-950 shadow-[1px_1px_0px_#000] transition-colors inline-block"
|
||||
>
|
||||
Download ATS PDF
|
||||
Export Styled PDF
|
||||
</PDFDownloadLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Styled Responsive Visual Document View */}
|
||||
<div
|
||||
className={`bg-white border p-6 rounded text-xs space-y-4 max-h-[680px] overflow-y-auto ${
|
||||
className={`bg-white border p-7 text-xs space-y-5 max-h-[680px] overflow-y-auto transition-all ${
|
||||
theme === "modern"
|
||||
? "border-zinc-300 font-sans shadow-inner"
|
||||
: theme === "compact"
|
||||
? "border-zinc-200 font-mono text-[11px]"
|
||||
? "border-slate-300 font-sans shadow-md"
|
||||
: theme === "editorial"
|
||||
? "border-amber-200/80 font-serif shadow-md bg-[#fffdfa]"
|
||||
: "border-zinc-300 font-sans"
|
||||
}`}
|
||||
>
|
||||
<div className="border-b pb-3 border-zinc-300">
|
||||
<h2 className="text-lg font-bold text-zinc-900 uppercase tracking-tight">{profile.name || "Your Full Name"}</h2>
|
||||
<p className="text-zinc-700 font-bold text-xs mt-0.5">{profile.title || "Target Job Title"}</p>
|
||||
<div className="text-zinc-500 mt-1 flex flex-wrap gap-x-3 text-[11px] font-mono">
|
||||
{/* Header Block */}
|
||||
<div
|
||||
className={`pb-4 ${
|
||||
theme === "modern"
|
||||
? "border-b-2 border-slate-900"
|
||||
: theme === "editorial"
|
||||
? "border-b border-stone-800 text-center"
|
||||
: "border-b border-zinc-300"
|
||||
}`}
|
||||
>
|
||||
<h2
|
||||
className={`text-2xl font-bold tracking-tight ${
|
||||
theme === "modern"
|
||||
? "text-slate-950"
|
||||
: theme === "editorial"
|
||||
? "font-serif text-3xl text-stone-950"
|
||||
: "text-zinc-950 uppercase"
|
||||
}`}
|
||||
>
|
||||
{profile.name || "Your Full Name"}
|
||||
</h2>
|
||||
|
||||
<p
|
||||
className={`text-xs font-semibold mt-1 uppercase tracking-wider ${
|
||||
theme === "modern"
|
||||
? "text-blue-700 font-mono"
|
||||
: theme === "editorial"
|
||||
? "text-amber-800 italic normal-case text-sm"
|
||||
: "text-zinc-600 font-mono"
|
||||
}`}
|
||||
>
|
||||
{profile.title || "Target Professional Title"}
|
||||
</p>
|
||||
|
||||
<div
|
||||
className={`mt-2 flex flex-wrap gap-x-3 gap-y-1 text-[11px] font-mono ${
|
||||
theme === "editorial" ? "justify-center text-stone-600" : "text-slate-600"
|
||||
}`}
|
||||
>
|
||||
{profile.email && <span>{profile.email}</span>}
|
||||
{profile.phone && <span>{profile.phone}</span>}
|
||||
{profile.location && <span>{profile.location}</span>}
|
||||
{profile.phone && <span>• {profile.phone}</span>}
|
||||
{profile.location && <span>• {profile.location}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary Block */}
|
||||
{profile.summary && (
|
||||
<div>
|
||||
<h3 className="font-bold text-zinc-900 border-b border-zinc-200 pb-0.5 mb-1 uppercase text-[10px] tracking-wider font-mono">
|
||||
Summary
|
||||
<div className="space-y-1.5">
|
||||
<h3
|
||||
className={`font-mono text-[10px] font-bold uppercase tracking-wider pb-0.5 border-b ${
|
||||
theme === "modern"
|
||||
? "text-slate-900 border-slate-200"
|
||||
: theme === "editorial"
|
||||
? "text-stone-900 border-stone-300"
|
||||
: "text-zinc-800 border-zinc-200"
|
||||
}`}
|
||||
>
|
||||
Professional Summary
|
||||
</h3>
|
||||
<p className="leading-relaxed text-zinc-700">{profile.summary}</p>
|
||||
<p className="leading-relaxed text-stone-700 text-xs">{profile.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Experience Block */}
|
||||
{work.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-bold text-zinc-900 border-b border-zinc-200 pb-0.5 mb-2 uppercase text-[10px] tracking-wider font-mono">
|
||||
<div className="space-y-2">
|
||||
<h3
|
||||
className={`font-mono text-[10px] font-bold uppercase tracking-wider pb-0.5 border-b ${
|
||||
theme === "modern"
|
||||
? "text-slate-900 border-slate-200"
|
||||
: theme === "editorial"
|
||||
? "text-stone-900 border-stone-300"
|
||||
: "text-zinc-800 border-zinc-200"
|
||||
}`}
|
||||
>
|
||||
Professional Experience
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
|
||||
<div className="space-y-4">
|
||||
{work.map((w: any, idx: number) => (
|
||||
<div key={idx}>
|
||||
<p className="font-bold text-zinc-900">
|
||||
{w.position || "Position"}{" "}
|
||||
{w.company ? <span className="text-zinc-600 font-medium">— {w.company}</span> : null}
|
||||
</p>
|
||||
{w.summary && <p className="text-zinc-700 mt-0.5 text-xs">{w.summary}</p>}
|
||||
<div key={idx} className="space-y-1">
|
||||
<div className="flex justify-between items-baseline flex-wrap gap-1">
|
||||
<span className="font-bold text-slate-950 text-sm">{w.position || "Position"}</span>
|
||||
{w.company && (
|
||||
<span
|
||||
className={`text-xs font-semibold ${
|
||||
theme === "modern"
|
||||
? "text-blue-700 font-mono"
|
||||
: theme === "editorial"
|
||||
? "text-amber-900 italic font-serif"
|
||||
: "text-zinc-700 font-mono"
|
||||
}`}
|
||||
>
|
||||
{w.company}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{w.summary && <p className="text-slate-600 italic text-xs mb-1">{w.summary}</p>}
|
||||
|
||||
{Array.isArray(w.highlights) && w.highlights.length > 0 && (
|
||||
<ul className="list-disc list-inside text-zinc-700 mt-1 space-y-0.5">
|
||||
<ul className="list-disc list-outside ml-4 text-slate-700 space-y-1 text-xs leading-relaxed">
|
||||
{w.highlights.map((h: string, hIdx: number) => (
|
||||
<li key={hIdx}>{h}</li>
|
||||
))}
|
||||
|
|
@ -231,26 +499,61 @@ export function ResumePreview({ data }: { data: any }) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Skills Block */}
|
||||
{skills.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-bold text-zinc-900 border-b border-zinc-200 pb-0.5 mb-1.5 uppercase text-[10px] tracking-wider font-mono">
|
||||
Skills & Competencies
|
||||
<div className="space-y-2">
|
||||
<h3
|
||||
className={`font-mono text-[10px] font-bold uppercase tracking-wider pb-0.5 border-b ${
|
||||
theme === "modern"
|
||||
? "text-slate-900 border-slate-200"
|
||||
: theme === "editorial"
|
||||
? "text-stone-900 border-stone-300"
|
||||
: "text-zinc-800 border-zinc-200"
|
||||
}`}
|
||||
>
|
||||
Skills & Technical Proficiencies
|
||||
</h3>
|
||||
<p className="text-zinc-800 font-mono text-[11px] leading-relaxed">{skills.join(" • ")}</p>
|
||||
|
||||
{theme === "modern" ? (
|
||||
<div className="flex flex-wrap gap-1.5 pt-0.5">
|
||||
{skills.map((s: string, idx: number) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="bg-slate-100 border border-slate-200 text-slate-800 text-[11px] font-mono px-2 py-0.5 rounded-sm font-medium"
|
||||
>
|
||||
{s}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-slate-800 text-xs font-mono leading-relaxed">{skills.join(" • ")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Education Block */}
|
||||
{education.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-bold text-zinc-900 border-b border-zinc-200 pb-0.5 mb-1.5 uppercase text-[10px] tracking-wider font-mono">
|
||||
<div className="space-y-1.5">
|
||||
<h3
|
||||
className={`font-mono text-[10px] font-bold uppercase tracking-wider pb-0.5 border-b ${
|
||||
theme === "modern"
|
||||
? "text-slate-900 border-slate-200"
|
||||
: theme === "editorial"
|
||||
? "text-stone-900 border-stone-300"
|
||||
: "text-zinc-800 border-zinc-200"
|
||||
}`}
|
||||
>
|
||||
Education & Certifications
|
||||
</h3>
|
||||
|
||||
<div className="space-y-1">
|
||||
{education.map((edu: any, idx: number) => (
|
||||
<p key={idx} className="text-zinc-800">
|
||||
<span className="font-bold">{edu.degree || edu.institution || "High School Diploma"}</span>
|
||||
{edu.institution && edu.degree ? <span className="text-zinc-600"> — {edu.institution}</span> : null}
|
||||
</p>
|
||||
<div key={idx} className="text-xs text-slate-900">
|
||||
<span className="font-bold">{edu.degree || "Degree / Program"}</span>
|
||||
{edu.institution && (
|
||||
<span className="text-slate-600 font-medium"> — {edu.institution}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue