- Set up React + TypeScript + Vite project with TailwindCSS - Implement Zustand store for CV data management - Create personal details editor with validation - Add ATS-friendly template for CV preview - Build stepper navigation component - Include schema validation with Zod - Configure ESLint and Prettier for code quality
425 lines
14 KiB
Markdown
425 lines
14 KiB
Markdown
# CV Editor React Component — README
|
|
|
|
A reusable, plug-and-play React component that provides a CV/resume builder with:
|
|
|
|
* a form-based editor (stepper UI),
|
|
* a rich-text **Tiptap** summary editor,
|
|
* live preview (inline) and iframe-isolated printable preview (A4),
|
|
* structured JSON CV model (single source of truth),
|
|
* easy integration points for saving, exporting (server-side Puppeteer example), theming and templates.
|
|
|
|
This README explains how to install, integrate, customize, test and deploy the component in an existing React app or run it standalone.
|
|
|
|
---
|
|
|
|
# Table of contents
|
|
|
|
1. What this component does
|
|
2. Key features & design decisions
|
|
3. Quick start (install + run)
|
|
4. Component API (props & events)
|
|
5. File / folder structure (suggested)
|
|
6. How to plug into an existing React app (examples)
|
|
7. Templates: how they work & how to add more
|
|
8. Exporting PDFs (server-side Puppeteer example)
|
|
9. Security & sanitization (DOMPurify)
|
|
10. State management recommendations (Zustand + TanStack)
|
|
11. Styling and theming (Tailwind / CSS isolation)
|
|
12. Accessibility checklist
|
|
13. Testing & CI suggestions
|
|
14. Troubleshooting / common errors
|
|
15. Next steps & extension ideas
|
|
|
|
---
|
|
|
|
# 1. What this component does
|
|
|
|
This component provides a full CV editor UI you can drop into your React app. It stores the CV as a structured JSON model and renders templates from that model. The summary field is a rich editor (Tiptap) that stores content as HTML/JSON; previews are rendered inline and inside an iframe (print-ready HTML). For production PDF export you can reuse the same printable HTML on the server and render it with Puppeteer.
|
|
|
|
---
|
|
|
|
# 2. Key features & design decisions
|
|
|
|
* **Single source of truth**: the CV is a JSON object (easy to save, modify, export).
|
|
* **Templates as renderers**: templates are modular React components / HTML + CSS that accept the CV JSON and return DOM or printable HTML.
|
|
* **Rich summary**: Tiptap (ProseMirror) used for structured, extensible rich text with predictable HTML output.
|
|
* **Preview parity**: inline preview uses the same React template rendering to reduce export surprises; iframe preview isolates CSS for print fidelity.
|
|
* **Server-side parity**: printable HTML builder can be used on server in Puppeteer to generate pixel-perfect PDFs.
|
|
|
|
---
|
|
|
|
# 3. Quick start (install + run)
|
|
|
|
### Minimal dependencies
|
|
|
|
Add these packages to your project:
|
|
|
|
```bash
|
|
# Core
|
|
npm install react react-dom
|
|
|
|
# UI + styling (suggested)
|
|
npm install tailwindcss
|
|
|
|
# Rich text
|
|
npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-placeholder
|
|
|
|
# Optional & recommended
|
|
npm install dompurify
|
|
npm install axios
|
|
npm install zustand @tanstack/react-query
|
|
```
|
|
|
|
### Example package.json dev/start scripts
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"start": "vite", // or react-scripts start / next dev
|
|
"build": "vite build",
|
|
"test": "vitest"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Run
|
|
|
|
Add the component file (e.g. `CVEditorWireframe.jsx`) to your app and import it into a page. Start your app as usual (`npm start` / `npm run dev`).
|
|
|
|
---
|
|
|
|
# 4. Component API (props & events)
|
|
|
|
Use this component as `<CVEditor />`. Example usage:
|
|
|
|
```jsx
|
|
import CVEditor from './components/CVEditorWireframe'
|
|
|
|
function App() {
|
|
const initialCV = { /* optional initial JSON model */ }
|
|
|
|
async function handleSave(cvJson) {
|
|
// e.g. POST /api/cv
|
|
await axios.post('/api/cv', cvJson)
|
|
}
|
|
|
|
return (
|
|
<CVEditor
|
|
initialCV={initialCV}
|
|
templates={myTemplateList}
|
|
onSave={handleSave}
|
|
exportEndpoint="/api/export/pdf" // optional: server endpoint for export jobs
|
|
allowDownload={true}
|
|
theme="light"
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Props
|
|
|
|
* `initialCV` — `{}` CV JSON model to pre-populate the editor (optional).
|
|
* `templates` — `[{ id, name, renderer }]` list of templates. `renderer` can be a React component that accepts `{cv}` or a function that returns printable HTML.
|
|
* `onSave(cv)` — async callback called when user saves (or autosave triggers).
|
|
* `onExportRequest(cv)` — callback to call when user requests PDF export. If provided, component can call it (you can send to server for Puppeteer).
|
|
* `exportEndpoint` — optional server endpoint URL to POST CV JSON for export.
|
|
* `allowDownload` — `boolean`, whether to show Export/Download controls.
|
|
* `theme` — `'light' | 'dark'` (optional, used to add top-level class).
|
|
* `className` — additional CSS class for outer container.
|
|
* `onChange(cv)` — optional callback fired on any CV change (useful for autosave).
|
|
|
|
---
|
|
|
|
# 5. File / folder structure (suggested)
|
|
|
|
```
|
|
src/
|
|
components/
|
|
CVEditorWireframe.jsx // main component (editor + preview)
|
|
templates/
|
|
TemplateClassic.jsx
|
|
TemplateATS.jsx
|
|
editors/
|
|
RichSummaryEditor.jsx // Tiptap wrapper
|
|
utils/
|
|
printableHtml.js // buildPrintableHtml(cv)
|
|
escapeHtml.js
|
|
hooks/
|
|
useCvAutosave.js
|
|
pages/
|
|
EditorPage.jsx // where you mount CVEditor
|
|
server/
|
|
export/
|
|
exportPdf.js // Express + Puppeteer server code (optional)
|
|
```
|
|
|
|
---
|
|
|
|
# 6. How to plug into an existing React app
|
|
|
|
### A. As a child component (recommended)
|
|
|
|
1. Copy `CVEditorWireframe.jsx` and its dependencies into your project.
|
|
2. Import and render it inside an existing route or component.
|
|
3. Provide `initialCV`, `onSave` and `templates` props as needed.
|
|
|
|
```jsx
|
|
// routes/ProfileEditor.jsx
|
|
import CVEditor from '../components/CVEditorWireframe'
|
|
import TemplateClassic from '../components/templates/TemplateClassic'
|
|
|
|
const templates = [
|
|
{ id: 'classic', name: 'Classic', renderer: TemplateClassic },
|
|
// add ATS or other templates
|
|
]
|
|
|
|
export default function ProfileEditor() {
|
|
const initialCV = {...}
|
|
async function handleSave(cv) { await api.saveCv(cv) }
|
|
|
|
return <CVEditor initialCV={initialCV} templates={templates} onSave={handleSave} exportEndpoint="/api/export/pdf" />
|
|
}
|
|
```
|
|
|
|
### B. As an isolated micro-frontend
|
|
|
|
If you prefer it to be self-contained: bundle the component into its own package or iframe it from a separate deployment. But prefer simple import for easier code sharing and debugging.
|
|
|
|
---
|
|
|
|
# 7. Templates: how they work & how to add more
|
|
|
|
**Two options for templates:**
|
|
|
|
1. **React component template** — a React component that accepts `{ cv }` and renders markup. Use for inline preview and server rendering (with `renderToString`).
|
|
2. **HTML template function** — a function that receives `cv` and returns printable HTML string. Use for server-side export or when you want designers to provide standalone HTML/CSS files.
|
|
|
|
**Example React template**
|
|
|
|
```jsx
|
|
export default function TemplateClassic({ cv }) {
|
|
return (
|
|
<div className="template-root">
|
|
<h1>{cv.personal.firstName} {cv.personal.lastName}</h1>
|
|
<div dangerouslySetInnerHTML={{ __html: cv.summary }} />
|
|
{cv.work.map(w => (
|
|
<div key={w.id}>
|
|
<h3>{w.title}</h3>
|
|
<div>{w.company} • {w.period}</div>
|
|
<ul>{w.bullets.map((b,i) => <li key={i}>{b}</li>)}</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Guidelines**
|
|
|
|
* Provide both `visual` templates (fancier CSS) and `ATS` templates (semantic, simple markup).
|
|
* Keep template CSS isolated (CSS modules, scoped styles or render in iframe).
|
|
* Use the same rendering logic on the server for PDFs to ensure parity.
|
|
|
|
---
|
|
|
|
# 8. Exporting PDFs (server-side Puppeteer example)
|
|
|
|
### Why server-side?
|
|
|
|
Puppeteer produces consistent, pixel-perfect PDFs (server or worker) and you can offload CPU work off the client.
|
|
|
|
### Minimal Express endpoint (example)
|
|
|
|
```js
|
|
// server/export/exportPdf.js
|
|
import express from 'express';
|
|
import puppeteer from 'puppeteer';
|
|
|
|
const router = express.Router();
|
|
|
|
router.post('/export/pdf', async (req, res) => {
|
|
try {
|
|
const cv = req.body;
|
|
// reuse printable-html builder (same logic as client)
|
|
const html = buildPrintableHtml(cv);
|
|
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
|
const page = await browser.newPage();
|
|
await page.setContent(html, { waitUntil: 'networkidle0' });
|
|
const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
|
|
await browser.close();
|
|
|
|
res.set({
|
|
'Content-Type': 'application/pdf',
|
|
'Content-Disposition': `attachment; filename="${cv.personal.firstName}_${cv.personal.lastName}_CV.pdf"`
|
|
});
|
|
res.send(pdfBuffer);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Failed to create PDF' });
|
|
}
|
|
});
|
|
|
|
export default router;
|
|
```
|
|
|
|
**Notes**
|
|
|
|
* Use a worker or job queue (BullMQ) for high volume.
|
|
* Reuse the same HTML builder (`buildPrintableHtml`) on the server (move the builder to a shared package or duplicate carefully).
|
|
* If using serverless (Vercel / Netlify), be aware Puppeteer needs special setup (use `chrome-aws-lambda`).
|
|
|
|
---
|
|
|
|
# 9. Security & sanitization (DOMPurify)
|
|
|
|
Because summary HTML comes from users, always sanitize before rendering into `srcDoc` or sending to server.
|
|
|
|
```bash
|
|
npm install dompurify
|
|
```
|
|
|
|
```js
|
|
import DOMPurify from 'dompurify'
|
|
|
|
const safeHtml = DOMPurify.sanitize(cv.summary)
|
|
<div dangerouslySetInnerHTML={{ __html: safeHtml }} />
|
|
```
|
|
|
|
Sanitize on both client and server if the server stores the HTML or returns it to other users.
|
|
|
|
---
|
|
|
|
# 10. State management recommendations
|
|
|
|
* **Zustand** — simple local/global client state (editor draft, UI toggles).
|
|
* **TanStack Query** — server-state (fetching/saving CVs, export job status).
|
|
* Use both: keep in-memory editing state in Zustand, and use React Query for network operations (cache, optimistic updates, retries).
|
|
|
|
Example:
|
|
|
|
```js
|
|
// useCvAutosave.js
|
|
import { useMutation } from '@tanstack/react-query';
|
|
export function useAutosave() {
|
|
return useMutation(cv => api.saveCv(cv), { /* ... */ })
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# 11. Styling & theming
|
|
|
|
* Use **Tailwind CSS** for fast iteration: the wireframe uses tailwind utility classes.
|
|
* For templates, prefer scoped CSS (CSS modules) or provide a `template.css` imported only by the template to avoid leakage.
|
|
* For iframe preview, `srcDoc` includes template CSS directly—this isolates styles perfectly.
|
|
|
|
---
|
|
|
|
# 12. Accessibility checklist
|
|
|
|
* All form fields have accessible labels (use `label` with `htmlFor`).
|
|
* Provide keyboard navigation for stepper and drag/reorder (if implemented).
|
|
* Ensure color contrast meets WCAG AA for text.
|
|
* Use semantic elements (`<header>`, `<main>`, `<section>`, `<ul>`) in templates.
|
|
* Add `aria-live` regions for autosave status and export progress.
|
|
|
|
---
|
|
|
|
# 13. Testing & CI suggestions
|
|
|
|
* **Unit tests**: Vitest/Jest for utils (`escapeHtml`, `buildPrintableHtml`).
|
|
* **Visual regression**: Percy / Chromatic for templates and export output.
|
|
* **E2E**: Cypress or Playwright for full editor flows (create CV → preview → export).
|
|
* **CI**: run tests + lint; optionally run Puppeteer to generate sample PDFs in a job (be mindful of resource usage).
|
|
|
|
Example unit test for `escapeHtml`:
|
|
|
|
```js
|
|
import { escapeHtml } from '../utils/escapeHtml'
|
|
test('escapes special chars', () => {
|
|
expect(escapeHtml(`Tom & Jerry <script>`)).toContain('&')
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
# 14. Troubleshooting / common errors
|
|
|
|
* **Unterminated string constant**: often caused by unescaped newline characters inside template literals or improperly closed quotes. Ensure:
|
|
|
|
* Use backticks `` ` `` for multi-line template strings.
|
|
* Escape `\n` inside single/double quoted strings (e.g. `join('\\n')`).
|
|
* Ensure `escapeHtml` function returns properly and ends with `}`.
|
|
|
|
* **Tiptap SSR**: Tiptap relies on DOM; rendering server-side requires guarding the editor initialisation (only run in browser). Use `if (typeof window !== 'undefined')` or dynamic import.
|
|
|
|
* **Puppeteer memory/CPU**: scale PDF generation using a worker pool (e.g. `puppeteer-cluster`) or separate service.
|
|
|
|
* **Iframe `srcDoc` not rendering**: ensure the printable HTML is a full HTML document (doctype, head, meta charset). Some browsers block certain inline scripts—best to avoid inline scripts in `srcDoc`.
|
|
|
|
---
|
|
|
|
# 15. Next steps & extension ideas
|
|
|
|
* Add **template gallery** with thumbnails and filters (ATS vs Visual).
|
|
* Add **resume parser** (import old CVs) — use Rchilli/Sovren or build simple NER workflow.
|
|
* Add **AI suggestions** (OpenAI) for summary and bullet generation — integrate as optional helper.
|
|
* Add **collaboration** (CRDT or ProseMirror-based real-time editing).
|
|
* Add **version history** and downloads per version.
|
|
* Add **internationalisation** for multi-language templates.
|
|
|
|
---
|
|
|
|
# Example: Minimal `buildPrintableHtml` and `escapeHtml` utils
|
|
|
|
```js
|
|
// utils/escapeHtml.js
|
|
export function escapeHtml(str) {
|
|
if (!str) return ''
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
```
|
|
|
|
```js
|
|
// utils/printableHtml.js
|
|
import { escapeHtml } from './escapeHtml'
|
|
export function buildPrintableHtml(cv) {
|
|
const css = `@page{size:A4;margin:20mm}body{font-family:Inter,Arial...}`;
|
|
const summary = cv.summary || '';
|
|
const workHtml = (cv.work || []).map(w => `
|
|
<article>
|
|
<div style="display:flex;justify-content:space-between">
|
|
<div><strong>${escapeHtml(w.title)}</strong><div>${escapeHtml(w.company)}</div></div>
|
|
<div>${escapeHtml(w.period)}</div>
|
|
</div>
|
|
<ul>${(w.bullets || []).map(b => `<li>${escapeHtml(b)}</li>`).join('')}</ul>
|
|
</article>
|
|
`).join('');
|
|
return `<!doctype html><html><head><meta charset="utf-8"><style>${css}</style></head><body>
|
|
<header><h1>${escapeHtml(cv.personal.firstName)} ${escapeHtml(cv.personal.lastName)}</h1></header>
|
|
<section>${summary}</section>
|
|
<section>${workHtml}</section>
|
|
</body></html>`;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# Final notes
|
|
|
|
* This component is intentionally modular: swap the templates, storage, and export backend independently.
|
|
* For production, focus on sanitization (DOMPurify), server-side rendering parity (use same templates or same printable-html builder), and performance (worker pool for PDF generation).
|
|
|
|
If you'd like, I can:
|
|
|
|
* generate a ready-to-drop `CVEditorWireframe.jsx` file (complete component) and `TemplateClassic.jsx` for you to copy, or
|
|
* provide the Express + Puppeteer server file and a docker-compose example to run the PDF service, or
|
|
* scaffold a small repo with the full stack (client + server export).
|
|
|
|
Which would you like me to produce next?
|