From 2847cef81b4e5484e5e2b5cea7dec355ccbf3ac6 Mon Sep 17 00:00:00 2001 From: geulah Date: Sat, 11 Oct 2025 17:42:05 +0100 Subject: [PATCH] feat(templates): add html sanitization for summary sections Add supportsPhoto flag to template registry and conditionally render photo URL field --- cv-engine/src/editors/PersonalEditor.tsx | 47 ++++++++++++--------- cv-engine/src/templates/ClassicTemplate.tsx | 3 +- cv-engine/src/templates/MinimalTemplate.tsx | 3 +- cv-engine/src/templates/ModernTemplate.tsx | 3 +- cv-engine/src/templates/registry.ts | 9 ++-- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/cv-engine/src/editors/PersonalEditor.tsx b/cv-engine/src/editors/PersonalEditor.tsx index 91b9c52..e63100f 100644 --- a/cv-engine/src/editors/PersonalEditor.tsx +++ b/cv-engine/src/editors/PersonalEditor.tsx @@ -1,10 +1,13 @@ import React, { useState, useEffect } from 'react'; -import { usePersonalData, useCvStore } from '../store/cvStore'; +import { usePersonalData, useCvStore, useTemplateId } from '../store/cvStore'; +import { templatesMap } from '../templates/registry'; import { normalizeUrl } from '../schema/cvSchema'; const PersonalEditor: React.FC = () => { const personalData = usePersonalData(); const { updatePersonal } = useCvStore(); + const templateId = useTemplateId(); + const supportsPhoto = !!templatesMap[templateId]?.supportsPhoto; const [formData, setFormData] = useState(personalData); const [errors, setErrors] = useState>({}); @@ -223,26 +226,28 @@ const PersonalEditor: React.FC = () => { /> - {/* Photo URL */} -
- - - {errors.photoUrl && ( -

{errors.photoUrl}

- )} -
+ {/* Photo URL (only for templates that support photos) */} + {supportsPhoto && ( +
+ + + {errors.photoUrl && ( +

{errors.photoUrl}

+ )} +
+ )} {/* Address Fields */} diff --git a/cv-engine/src/templates/ClassicTemplate.tsx b/cv-engine/src/templates/ClassicTemplate.tsx index f234b22..6561350 100644 --- a/cv-engine/src/templates/ClassicTemplate.tsx +++ b/cv-engine/src/templates/ClassicTemplate.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { CV } from '../schema/cvSchema'; +import { sanitizeHtml } from '../schema/cvSchema'; interface ClassicTemplateProps { cv: CV; @@ -35,7 +36,7 @@ const ClassicTemplate: React.FC = ({ cv }) => { {summary && (

Professional Summary

-
+
)} diff --git a/cv-engine/src/templates/MinimalTemplate.tsx b/cv-engine/src/templates/MinimalTemplate.tsx index f8731e9..3d18522 100644 --- a/cv-engine/src/templates/MinimalTemplate.tsx +++ b/cv-engine/src/templates/MinimalTemplate.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { CV } from '../schema/cvSchema'; +import { sanitizeHtml } from '../schema/cvSchema'; interface MinimalTemplateProps { cv: CV; className?: string; } @@ -25,7 +26,7 @@ const MinimalTemplate: React.FC = ({ cv, className = '' }) {summary && (

Summary

-
+
)} diff --git a/cv-engine/src/templates/ModernTemplate.tsx b/cv-engine/src/templates/ModernTemplate.tsx index b0bba44..49a22a1 100644 --- a/cv-engine/src/templates/ModernTemplate.tsx +++ b/cv-engine/src/templates/ModernTemplate.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type { CV } from '../schema/cvSchema'; +import { sanitizeHtml } from '../schema/cvSchema'; interface ModernTemplateProps { cv: CV; className?: string; } @@ -37,7 +38,7 @@ const ModernTemplate: React.FC = ({ cv, className = '' }) = {summary && (

Professional Summary

-
+
)} diff --git a/cv-engine/src/templates/registry.ts b/cv-engine/src/templates/registry.ts index 1b7acab..bb6246d 100644 --- a/cv-engine/src/templates/registry.ts +++ b/cv-engine/src/templates/registry.ts @@ -17,13 +17,14 @@ export interface TemplateMeta { category: TemplateCategory; thumbnail: string; // path to asset component: React.FC<{ cv: CV }>; + supportsPhoto?: boolean; } export const templatesRegistry: TemplateMeta[] = [ - { id: 'ats', name: 'ATS-Friendly', category: 'ATS', thumbnail: atsThumb, component: ATSTemplate }, - { id: 'classic', name: 'Classic', category: 'Visual', thumbnail: classicThumb, component: ClassicTemplate }, - { id: 'modern', name: 'Modern', category: 'Visual', thumbnail: modernThumb, component: ModernTemplate }, - { id: 'minimal', name: 'Minimal', category: 'Visual', thumbnail: minimalThumb, component: MinimalTemplate }, + { id: 'ats', name: 'ATS-Friendly', category: 'ATS', thumbnail: atsThumb, component: ATSTemplate, supportsPhoto: false }, + { id: 'classic', name: 'Classic', category: 'Visual', thumbnail: classicThumb, component: ClassicTemplate, supportsPhoto: false }, + { id: 'modern', name: 'Modern', category: 'Visual', thumbnail: modernThumb, component: ModernTemplate, supportsPhoto: true }, + { id: 'minimal', name: 'Minimal', category: 'Visual', thumbnail: minimalThumb, component: MinimalTemplate, supportsPhoto: false }, ]; export const templatesMap = Object.fromEntries(templatesRegistry.map(t => [t.id, t])); \ No newline at end of file