Overview
The CV Builder editor provides a comprehensive form-based interface for creating professional resumes. The editor features real-time preview, automatic saving, drag-and-drop section reordering, and intelligent validation.
Architecture
The editor is built using React Hook Form and renders dynamically based on section order:
// components/cv-builder.tsx:964-970
const methods = useForm < CVData >({
defaultValues: {
... initialCVData ,
template: initialTemplate ,
},
mode: "onChange" ,
});
Key Features
The editor supports 8 customizable sections:
Personal Details
Experience
Education
// components/forms/PersonalDetailsForm.tsx
interface PersonalInfo {
fullName : string ;
email : string ;
phone : string ;
address : string ;
jobTitle : string ;
summary : string ;
website ?: string ;
linkedin ?: string ;
github ?: string ;
profileImageUrl ?: string ;
}
Real-Time Preview
Changes in the editor are instantly reflected in the live preview panel:
// components/cv-builder.tsx:1040-1043
function LivePreviewWrapper ({ control , template }) {
const data = useNormalizedCVData ( control );
return < CVPreview data ={ data } template ={ template } />;
}
The preview automatically updates as you type, using React Hook Form’s control object to watch all form changes.
Auto-Save
The editor includes intelligent auto-save functionality that runs every 5 seconds:
// hooks/useCVDraft.ts:21
const AUTO_SAVE_INTERVAL = 5000 ; // 5 seconds
Auto-save behavior:
Local Draft
All changes are automatically saved to localStorage every 5 seconds to prevent data loss
Cloud Sync
When authenticated, changes sync to Firebase after local save completes
Change Detection
Only saves when data has actually changed (lib/backend/cvDraftStorage.ts:128)
Auto-save only triggers when the form contains meaningful content - empty forms are not saved to avoid cluttering storage.
Section Management
Reordering Sections
Users can drag-and-drop sections to customize the order they appear in their CV:
// components/cv-builder.tsx:797-800
onSectionOrderChange = {(order) => {
methods . setValue ( "sectionOrder" , order , { shouldDirty: true , shouldTouch: true });
}}
The section sidebar (components/layout/SectionSidebar.tsx) provides the drag-and-drop interface.
Hiding Sections
Users can toggle section visibility to exclude sections from their CV:
// components/cv-builder.tsx:210-217
const toggleSectionVisibility = useCallback (( sectionId : SectionId ) => {
if ( sectionId === "personal" ) return ;
const current = methods . getValues ( "hiddenSections" ) || [];
const updated = current . includes ( sectionId )
? current . filter (( id ) => id !== sectionId )
: [ ... current , sectionId ];
methods . setValue ( "hiddenSections" , updated , { shouldDirty: true });
}, [ methods ]);
The “personal” section cannot be hidden as it contains essential contact information.
The editor includes scroll spy functionality that highlights the active section as you scroll:
// components/cv-builder.tsx:197-202
const { pauseObserver } = useScrollSpy ({
sectionOrder ,
hiddenSections ,
onSectionChange: setActiveSection ,
containerId: "editor-scroll-container" ,
});
Clicking a section in the sidebar automatically scrolls to that section with a smooth animation:
// components/cv-builder.tsx:247
container . scrollTo ({ top: scrollTo , behavior: "smooth" });
The editor uses Zod schemas for runtime validation:
// lib/backend/cvValidation.ts
export function validateCVData ( data : unknown ) : CVData | null {
const result = cvDataSchema . safeParse ( data );
return result . success ? result . data : null ;
}
Validation runs when:
Loading saved data from localStorage or cloud
Before PDF export
Before saving versions
Responsive Design
Mobile Tab Toggle
On mobile devices, the editor and preview are shown in tabs:
// components/cv-builder.tsx:758-776
< div className = "md:hidden flex border-b border-slate-200" >
< Button
variant = { activeTab === "editor" ? "gradient" : "ghost" }
fullWidth
onClick = { () => setActiveTab ( "editor" ) }
leftIcon = { < PenTool className = "w-5 h-5" /> }
>
Editor
</ Button >
< Button
variant = { activeTab === "preview" ? "gradient" : "ghost" }
fullWidth
onClick = { () => setActiveTab ( "preview" ) }
leftIcon = { < Eye className = "w-5 h-5" /> }
>
Preview
</ Button >
</ div >
Desktop Split View
On desktop, editor and preview are shown side-by-side with responsive widths:
// components/cv-builder.tsx:782-787
< motion.div
className = "w-full md:w-1/2 lg:w-[45%] xl:w-[40%]"
>
{ /* Editor */ }
</ motion.div >
< div className = "w-full md:w-1/2 lg:w-[55%] xl:w-[60%]" >
{ /* Preview */ }
</ div >
Guest Mode
Users can start editing without authentication. A guest mode banner is displayed:
// components/cv-builder.tsx:838-859
{ ! user && (
< div className = "mt-3 p-3 rounded-lg bg-indigo-50 border border-indigo-200" >
< div className = "flex items-center gap-3" >
< CloudOff className = "w-4 h-4" />
< div >
< p className = "text-sm font-medium" > Guest Mode </ p >
< p className = "text-xs" > Sign in to save your CV to the cloud </ p >
</ div >
< Button onClick = { () => setShowAuthModal ( true ) } > Sign in </ Button >
</ div >
</ div >
)}
In guest mode:
All data is saved to localStorage only
No cloud sync or version control
Data persists across browser sessions
Can migrate to cloud by signing in
Clear All
Users can clear all form data and start fresh:
// components/cv-builder.tsx:400-405
const handleClearAll = () => {
setShowClearConfirm ( false );
methods . reset ( initialCVData );
clearDraft ();
toast ({ title: "All fields cleared. Starting fresh." , type: "info" });
};
Clearing all data also removes the localStorage draft. This action cannot be undone unless you have saved versions.
Save Status Indicator
The editor displays the current save status in the header:
// components/cv-builder.tsx:61-66
const saveButtonConfig = {
idle: { icon: Save , text: "Save" , className: "" },
saving: { icon: Loader2 , text: "Saving..." , className: "animate-spin" },
saved: { icon: Check , text: "Saved" , className: "" },
error: { icon: CloudOff , text: "Failed" , className: "" },
};
States:
idle : Ready to save
saving : Upload in progress
saved : Successfully synced to cloud
error : Save failed (data still in localStorage)
Debounced Auto-Save
Auto-save is debounced to avoid excessive writes:
// hooks/useCVDraft.ts:136-147
const saveToDraft = useCallback (() => {
const currentData = getValues ();
// Only save if data has changed from last save
if (
! lastSavedDataRef . current ||
hasDataChanged ( lastSavedDataRef . current , currentData )
) {
saveDraft ( currentData );
lastSavedDataRef . current = currentData ;
}
}, [ getValues ]);
Memoized Computed Values
Expensive computations are memoized:
// components/cv-builder.tsx:101
const normalizedData = useNormalizedCVData ( methods . control );
Reduced Motion Support
Respects user’s motion preferences:
// components/cv-builder.tsx:99
const reducedMotion = useReducedMotion () ?? false ;