feat(ui): add cv application frontend and configuration

This commit is contained in:
Tuan-Dat Tran
2026-02-23 13:47:08 +01:00
parent cbf40908a6
commit 7f06ee7f53
43 changed files with 2849 additions and 0 deletions

74
src/lib/cv-data.js Normal file
View File

@@ -0,0 +1,74 @@
import defaultData from './cv.json';
export const DEFAULT_DATA = defaultData;
export function validateCV(data) {
const errors = [];
if (!data.personal) {
errors.push('personal is required');
} else {
if (!data.personal.name?.trim()) errors.push('personal.name is required');
if (!data.personal.title?.trim()) errors.push('personal.title is required');
if (!data.personal.email?.trim()) errors.push('personal.email is required');
}
if (!Array.isArray(data.experience)) {
errors.push('experience must be an array');
}
if (!data.skills || typeof data.skills !== 'object') {
errors.push('skills must be an object');
}
if (!Array.isArray(data.education)) {
errors.push('education must be an array');
}
if (!Array.isArray(data.projects)) {
errors.push('projects must be an array');
}
return {
valid: errors.length === 0,
errors
};
}
export function mergeCVData(base, updates) {
return {
...base,
...updates,
personal: { ...base.personal, ...updates.personal }
};
}
export function exportToJSON(data) {
return JSON.stringify(data, null, 2);
}
export function importFromJSON(jsonString) {
try {
const parsed = JSON.parse(jsonString);
const validation = validateCV(parsed);
if (!validation.valid) {
return { success: false, errors: validation.errors };
}
return { success: true, data: parsed };
} catch (e) {
return { success: false, errors: [`JSON parse error: ${e.message}`] };
}
}
export function downloadJSON(data, filename = 'cv.json') {
const json = exportToJSON(data);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}