diff --git a/.bolt/config.json b/.bolt/config.json deleted file mode 100644 index f8da040..0000000 --- a/.bolt/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "template": "vite-shadcn" -} diff --git a/.bolt/ignore b/.bolt/ignore deleted file mode 100644 index 8b4f41a..0000000 --- a/.bolt/ignore +++ /dev/null @@ -1,2 +0,0 @@ -src/components/ui/* -hooks/use-toast.ts diff --git a/.bolt/prompt b/.bolt/prompt deleted file mode 100644 index 48bbd8d..0000000 --- a/.bolt/prompt +++ /dev/null @@ -1,5 +0,0 @@ -For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. - -By default, this template supports JSX syntax with Tailwind CSS classes, the shadcn/ui library, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. - -Use icons from lucide-react for logos. diff --git a/README.md b/README.md index b53c12d..d1a1004 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ A modern, feature-rich resume builder that helps professionals create stunning resumes with ease. Built with React, TypeScript, and Tailwind CSS. +### Live Preview + +[TalenScript hosted on Netlify](https://talentscript.netlify.app/) + ## 🌟 Features ### ✨ **Professional Templates** @@ -19,6 +23,7 @@ A modern, feature-rich resume builder that helps professionals create stunning r - **11 Accent Colors**: Customize your resume's color scheme - **Profile Image Support**: Optional photo placement with privacy controls - **Real-time Preview**: See changes instantly as you edit +- **Multi-Page Preview**: Accurate page-by-page representation with print boundaries ### πŸ“ **Comprehensive Content Management** - **Personal Information**: Contact details, social links, and profile photo @@ -32,26 +37,30 @@ A modern, feature-rich resume builder that helps professionals create stunning r ### πŸ”§ **Advanced Functionality** - **Drag & Drop Reordering**: Organize experience and education entries -- **Local Data Persistence**: Automatic saving to browser storage -- **PDF Export**: High-quality PDF generation for download +- **Smart PDF Generation**: Multi-page PDF export with proper page breaks +- **PDF Sharing**: Direct PDF sharing via native share API +- **Print Area Visualization**: Clear boundaries showing printable content +- **User Authentication**: Secure login with cloud data synchronization - **Interactive Tutorial**: Guided onboarding for new users - **Responsive Design**: Works perfectly on desktop and mobile -- **Privacy-First**: No data sent to external servers +- **User Feedback System**: Integrated feedback collection via Netlify Forms ## πŸ” Authentication & Data Storage -- **πŸ”‘ Logged-in users**: Resume data is securely saved to your account in MongoDB Atlas. Access your resume from any device after logging in. +- **πŸ”‘ Logged-in users**: Resume data is securely saved to your account in MongoDB Atlas. Access your resume from any device after logging in. Data automatically loads upon login and resets upon logout. - **πŸ–₯️ Guests**: Resume data is saved locally in your browser. Do not use this on public/shared computers. ## πŸ›‘οΈ Security - Passwords are hashed and salted using bcrypt before being stored in the database. -- JWT is used for authentication. +- JWT is used for authentication with 7-day expiration. +- Client-side data validation and sanitization. ## πŸ‘οΈ Privacy Notice - If you are not logged in, your data is only stored locally and is not sent to any server. - If you are logged in, your data is securely stored in the database and is not shared with third parties. +- Feedback submissions are processed through Netlify Forms with optional contact information. ## πŸš€ Live Demo @@ -60,12 +69,12 @@ A modern, feature-rich resume builder that helps professionals create stunning r ## πŸ“Έ Screenshots ### Main Interface -![TalentScript Main Interface](docs/screenshots/main-interface.png) +![TalentScript Main Interface](docs/main-interface.png) ### Template Showcase | Minimal | Modern | Creative | |---------|--------|----------| -| ![Minimal Template](docs/screenshots/minimal-template.png) | ![Modern Template](docs/screenshots/modern-template.png) | ![Creative Template](docs/screenshots/creative-template.png) | +| ![Minimal Template](docs/minimal-template.png) | ![Modern Template](docs/modern-template.png) | ![Creative Template](docs/creative-template.png) | ## πŸ› οΈ Technology Stack @@ -82,9 +91,10 @@ A modern, feature-rich resume builder that helps professionals create stunning r - **Lucide React** - Consistent icon library - **Framer Motion** - Smooth animations (via Tailwind) -### PDF Generation -- **html2canvas 1.4.1** - HTML to canvas conversion -- **jsPDF 2.5.1** - Client-side PDF generation +### PDF Generation & Sharing +- **html2canvas 1.4.1** - HTML to canvas conversion with multi-page support +- **jsPDF 2.5.1** - Client-side PDF generation with automatic page splitting +- **Web Share API** - Native PDF sharing capabilities ### Development Tools - **ESLint** - Code linting and formatting @@ -175,17 +185,25 @@ npm run preview ### Getting Started 1. **Launch the Application**: Open TalentScript in your browser -2. **Complete the Tutorial**: Follow the interactive guide for new users -3. **Add Your Information**: Fill in your personal details, experience, and education -4. **Customize Your Design**: Choose a template, font, and color scheme -5. **Save Your Progress**: Use the save button to store your data locally -6. **Download Your Resume**: Export as PDF when ready +2. **Sign Up or Continue as Guest**: Create an account for cloud sync or use locally +3. **Complete the Tutorial**: Follow the interactive guide for new users +4. **Add Your Information**: Fill in your personal details, experience, and education +5. **Customize Your Design**: Choose a template, font, and color scheme +6. **Preview Your Resume**: View multi-page layout with print boundaries +7. **Save Your Progress**: Data saves automatically (locally or to cloud) +8. **Share or Download**: Export as PDF or share directly ### Data Management -- **Local Storage**: All data is saved locally in your browser -- **Privacy First**: No information is sent to external servers +- **Cloud Sync**: Logged-in users get cross-device synchronization +- **Local Storage**: Guest data saved in browser with privacy protection - **Auto-Save**: Changes are automatically preserved -- **Export Options**: Download as high-quality PDF +- **Smart Export**: Multi-page PDF generation with proper formatting +- **Direct Sharing**: Share PDF files via native device sharing + +### Print Preview Features +- **Page Boundaries**: Visual indicators show printable areas +- **Multi-Page Layout**: Content automatically flows across pages +- **Print-Safe Formatting**: Prevents content from being cut mid-line ### Customization Options @@ -242,18 +260,25 @@ To modify templates or add new features: ## πŸš€ Deployment ### Netlify (Recommended) -The application is optimized for Netlify deployment: +The application is optimized for Netlify deployment with integrated services: 1. **Connect your repository** to Netlify 2. **Build settings**: - Build command: `npm run build` - Publish directory: `dist` -3. **Deploy**: Automatic deployment on git push +3. **Environment variables**: Configure MongoDB and JWT secrets +4. **Enable Netlify Forms**: Automatic form detection for feedback system +5. **Deploy**: Automatic deployment on git push + +### Backend Deployment +- **MongoDB Atlas**: Cloud database hosting +- **Node.js Server**: Deploy to Heroku, Railway, or similar platforms +- **Environment Configuration**: Secure credential management ### Other Platforms -- **Vercel**: Works out of the box -- **GitHub Pages**: Requires build step configuration -- **AWS S3**: Static hosting with CloudFront +- **Vercel**: Works with additional configuration for forms +- **GitHub Pages**: Static hosting (limited backend functionality) +- **AWS S3**: Static hosting with CloudFront (requires separate backend) ## 🀝 Contributing @@ -293,16 +318,17 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## πŸ—ΊοΈ Roadmap ### Upcoming Features -- [ ] Multiple resume support -- [ ] Cloud storage integration -- [ ] Additional templates -- [ ] Cover letter builder -- [ ] LinkedIn import +- [ ] Additional professional templates +- [ ] Cover letter builder integration +- [ ] LinkedIn profile import - [ ] ATS optimization scoring -- [ ] Collaborative editing -- [ ] Mobile app +- [ ] Collaborative editing features +- [ ] Mobile app development +- [ ] Advanced analytics dashboard +- [ ] Custom branding options ### Version History +- **v1.3.0** - Multi-page preview, PDF sharing, user authentication, feedback system - **v1.2.0** - Interactive tutorial, drag-and-drop reordering, enhanced privacy controls - **v1.1.0** - Multiple templates, font customization, color themes - **v1.0.0** - Initial release with core functionality @@ -311,4 +337,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file **Built with ❀️ by the TalentScript Team** -[Live Demo](https://talentscript.netlify.app) | [Report Bug](https://github.com/yourusername/talentscript/issues) | [Request Feature](https://github.com/yourusername/talentscript/issues) \ No newline at end of file +[Live Demo](https://talentscript.netlify.app) | [Report Bug](https://github.com/yourusername/talentscript/issues) | [Request Feature](https://github.com/yourusername/talentscript/issues) diff --git a/TEMPLATE_AUDIT_REPORT.md b/TEMPLATE_AUDIT_REPORT.md new file mode 100644 index 0000000..d1e35ad --- /dev/null +++ b/TEMPLATE_AUDIT_REPORT.md @@ -0,0 +1,276 @@ +# Template Audit Report + +Generated: 2025-07-23T10:25:30.877Z + +Total templates found: 27 + +## AcademicTemplate.tsx +- **Export Name:** AcademicTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## BoldTemplate.tsx +- **Export Name:** BoldTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## CleanMinimalistTemplate.tsx +- **Export Name:** CleanMinimalistTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## CleanProfessionalTemplate.tsx +- **Export Name:** CleanProfessionalTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## CorporateTemplate.tsx +- **Export Name:** CorporateTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## CreativeColorBlocksTemplate.tsx +- **Export Name:** CreativeColorBlocksTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## CreativeTemplate.tsx +- **Export Name:** CreativeTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ElegantTemplate.tsx +- **Export Name:** ElegantTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ExecutiveTemplate.tsx +- **Export Name:** ExecutiveTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## MinimalistATS.tsx +- **Export Name:** MinimalistATS +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## MinimalistProfessionalTemplate.tsx +- **Export Name:** MinimalistProfessionalTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## MinimalistSidebarTemplate.tsx +- **Export Name:** MinimalistSidebarTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## MinimalistTemplate.tsx +- **Export Name:** MinimalistTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## MinimalTemplate.tsx +- **Export Name:** MinimalTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernDarkSidebarTemplate.tsx +- **Export Name:** ModernDarkSidebarTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernExecutiveTemplate.tsx +- **Export Name:** ModernExecutiveTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernProfessionalTemplate.tsx +- **Export Name:** ModernProfessionalTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernSidebarTemplate.tsx +- **Export Name:** ModernSidebarTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernTemplate.tsx +- **Export Name:** ModernTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernTimelineTemplate.tsx +- **Export Name:** ModernTimelineTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ModernTwoColumn.tsx +- **Export Name:** ModernTwoColumn +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ProfessionalBlueTemplate.tsx +- **Export Name:** ProfessionalBlueTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ProfessionalSidebarTemplate.tsx +- **Export Name:** ProfessionalSidebarTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ProfessionalSummaryTemplate.tsx +- **Export Name:** ProfessionalSummaryTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## ProfessionalTemplate.tsx +- **Export Name:** ProfessionalTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## TechTemplate.tsx +- **Export Name:** TechTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + +## TimelineTemplate.tsx +- **Export Name:** TimelineTemplate +- **Has TemplateProps:** βœ… +- **Has Correct Export:** βœ… +- **Has Default Export:** ❌ + +βœ… No issues found + +--- + diff --git a/backup/templates/AcademicTemplate.tsx b/backup/templates/AcademicTemplate.tsx new file mode 100644 index 0000000..0f959ea --- /dev/null +++ b/backup/templates/AcademicTemplate.tsx @@ -0,0 +1,383 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, BookOpen, GraduationCap, Award, Briefcase, FileText, Book } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function AcademicTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Researcher / Academic'} +

+ +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + + Personal Website + + )} + {personalInfo.linkedin && ( + + + LinkedIn + + )} + {personalInfo.github && ( + + + GitHub + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ + Research Profile +

+

+ {summary} +

+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} + {edu.description && ( +
+

+ {edu.description} +

+
+ )} +
+ ))} +
+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + {experience.some(exp => exp.company.toLowerCase().includes('research') || + exp.jobTitle.toLowerCase().includes('research')) + ? 'Research Experience' + : 'Professional Experience'} +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'publications': + // Publications would be part of the projects array with type 'publication' + const publications = projects.filter(proj => proj.type === 'publication'); + if (publications.length === 0) return null; + + return ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.name} +

+

+ {pub.description} +

+ {pub.technologies && pub.technologies.length > 0 && ( +
+ Keywords:{' '} + + {pub.technologies.join(', ')} + +
+ )} + {pub.url && ( + + View Publication + + )} +
+ ))} +
+
+ ); + + case 'projects': + // Filter out publications as they're handled separately + const nonPublicationProjects = projects.filter(proj => proj.type !== 'publication'); + if (nonPublicationProjects.length === 0) return null; + + return ( +
+

+ + Research Projects +

+
+ {nonPublicationProjects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+

+ Technologies/Methods: +

+
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+
+ )} +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications & Training +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Research Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/BoldTemplate.tsx b/backup/templates/BoldTemplate.tsx new file mode 100644 index 0000000..b4feeaf --- /dev/null +++ b/backup/templates/BoldTemplate.tsx @@ -0,0 +1,298 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function BoldTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections } = data; + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Professional Title'} +

+
+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + linkedin.com/in/... +
+ )} + {personalInfo.github && ( +
+ + github.com/... +
+ )} +
+
+ + {summary && ( +
+

+ + PROFILE +

+

+ {summary} +

+
+ )} +
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.achievements && edu.achievements.length > 0 && ( +
    + {edu.achievements.map((achievement, idx) => ( +
  • + β€’ + {achievement} +
  • + ))} +
+ )} +
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + SKILLS & EXPERTISE +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/CleanMinimalistTemplate.tsx b/backup/templates/CleanMinimalistTemplate.tsx new file mode 100644 index 0000000..d15310a --- /dev/null +++ b/backup/templates/CleanMinimalistTemplate.tsx @@ -0,0 +1,198 @@ +import { Mail, Phone, MapPin, Linkedin, Globe } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function CleanMinimalistTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

+ {personalInfo.fullName} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo.location && ( + + + {personalInfo.location} + + )} + {personalInfo.linkedin && ( + + + LinkedIn + + )} + {personalInfo.website && ( + + + {personalInfo.website.replace(/^https?:\/\//, '')} + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

+ SUMMARY +

+

+ {summary} +

+
+ )} + +
+ {/* Left Column */} +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ PROFESSIONAL EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && `, ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ EDUCATION +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.fieldOfStudy} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ SKILLS +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+
+
+ ); +} diff --git a/backup/templates/CleanProfessionalTemplate.tsx b/backup/templates/CleanProfessionalTemplate.tsx new file mode 100644 index 0000000..0fce631 --- /dev/null +++ b/backup/templates/CleanProfessionalTemplate.tsx @@ -0,0 +1,200 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function CleanProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo.location && ( + + + {personalInfo.location} + + )} + {personalInfo.website && ( + + + {personalInfo.website.replace(/^https?:\/\//, '')} + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ Professional Summary +

+

{summary}

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} β€’ {exp.location} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+

+ {edu.degree} +

+

+ {edu.school} β€’ {edu.location} +

+

+ {formatDate(edu.graduationDate)} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Skills +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} β€’ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map((section) => renderSection(section))} +
+ ); +} diff --git a/backup/templates/CorporateTemplate.tsx b/backup/templates/CorporateTemplate.tsx new file mode 100644 index 0000000..c54df7f --- /dev/null +++ b/backup/templates/CorporateTemplate.tsx @@ -0,0 +1,319 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, BookOpen, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function CorporateTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Professional Title'} +

+ +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + + {formatUrl(personalInfo.website)} + + )} + {personalInfo.linkedin && ( + + + {formatUrl(personalInfo.linkedin).split('/').pop()} + + )} + {personalInfo.github && ( + + + {formatUrl(personalInfo.github).split('/').pop()} + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ + Professional Summary +

+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ + {formatDate(edu.graduationDate)} + +
+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/CreativeColorBlocksTemplate.tsx b/backup/templates/CreativeColorBlocksTemplate.tsx new file mode 100644 index 0000000..c7ec765 --- /dev/null +++ b/backup/templates/CreativeColorBlocksTemplate.tsx @@ -0,0 +1,220 @@ +import { ResumeData } from '@/types/resume'; +import { Briefcase, GraduationCap, User, Code, Mail, Phone, MapPin, Globe } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function CreativeColorBlocksTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, projects } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + // Color variants for different sections + const colorVariants = { + primary: 'bg-blue-600 text-white', + secondary: 'bg-blue-100 text-blue-900', + accent: 'bg-yellow-400 text-gray-900', + light: 'bg-gray-50 text-gray-800', + dark: 'bg-gray-800 text-white', + }; + + return ( +
+ {/* Header with color block */} +
+
+
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName +
+ )} +
+

{personalInfo.fullName || 'Your Name'}

+

{personalInfo.title || 'Professional Title'}

+
+
+
+
+ +
+
+ {/* Left Column */} +
+ {/* Contact */} +
+

CONTACT

+
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.graduationDate && ( +

+ {formatDate(edu.graduationDate)} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+ )} +
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Summary */} + {summary && ( +
+

+ + ABOUT ME +

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

PROJECTS

+
+ {projects.map((project) => ( +
+

{project.name}

+ {project.url && ( + + {project.url.replace(/^https?:\/\//, '')} + + )} +

{project.description}

+
+ ))} +
+
+ )} +
+
+
+
+ ); +} diff --git a/backup/templates/CreativeTemplate.tsx b/backup/templates/CreativeTemplate.tsx new file mode 100644 index 0000000..37bcbec --- /dev/null +++ b/backup/templates/CreativeTemplate.tsx @@ -0,0 +1,361 @@ +import React from 'react'; +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function CreativeTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + // Remove protocol and www for cleaner display + return url.replace(/^https?:\/\/(www\.)?/, ''); + }; + + const leftSections = visibleSections.filter(section => + ['personalInfo', 'skills', 'certifications', 'references'].includes(section.type) + ); + + const rightSections = visibleSections.filter(section => + ['summary', 'experience', 'education', 'projects'].includes(section.type) + ); + + const renderLeftSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+ {customization.showProfileImage && personalInfo.profileImage && ( + Profile + )} +

+ {personalInfo.fullName || 'Your Name'} +

+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ Skills +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + case 'references': + if (references.length === 0) return null; + return ( +
+

+ References +

+
+ {references.map((ref) => ( +
+

+ {ref.name} +

+

+ {ref.title} +

+

+ {ref.company} +

+

+ {ref.relationship} +

+

+ {ref.email} β€’ {ref.phone} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + const renderRightSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'summary': + if (!summary) return null; + return ( +
+

+ Professional Summary +

+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ Work Experience +

+
+ {experience.map((exp, index) => ( +
+
+ {index < experience.length - 1 && ( +
+ )} + +
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && `, ${exp.location}`} +

+
+
+ {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} +
+
+
    + {exp.description.filter(desc => desc.trim()).map((desc, descIndex) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ Education +

+
+ {education.map((edu, index) => ( +
+
+ {index < education.length - 1 && ( +
+ )} + +
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ {formatDate(edu.graduationDate)} +
+
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ Projects +

+
+ {projects.map((project, index) => ( +
+
+ {index < projects.length - 1 && ( +
+ )} + +
+

+ {project.name} +

+
+ {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`} +
+
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, techIndex) => ( + + {tech} + + ))} +
+ )} + {project.url && ( +

+ {formatUrl(project.url)} +

+ )} +
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {/* Left Sidebar */} +
+ {leftSections.map(renderLeftSection)} +
+ + {/* Right Content */} +
+ {rightSections.map(renderRightSection)} +
+
+ ); +} \ No newline at end of file diff --git a/backup/templates/ElegantTemplate.tsx b/backup/templates/ElegantTemplate.tsx new file mode 100644 index 0000000..c2dcbc4 --- /dev/null +++ b/backup/templates/ElegantTemplate.tsx @@ -0,0 +1,281 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ElegantTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections } = data; + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+
+
+

+ {personalInfo.fullName || 'Your Name'} +

+
+

+ {personalInfo.title || 'Professional Title'} +

+
+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + linkedin.com/in/... +
+ )} +
+
+ + {summary && ( +
+

+ + Profile +

+

+ {summary} +

+
+ )} +
+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/ExecutiveTemplate.tsx b/backup/templates/ExecutiveTemplate.tsx new file mode 100644 index 0000000..5309599 --- /dev/null +++ b/backup/templates/ExecutiveTemplate.tsx @@ -0,0 +1,284 @@ +import React from 'react'; +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Award, Briefcase, GraduationCap, Code, BookOpen } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ExecutiveTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+ {personalInfo.jobTitle && ( +

+ {personalInfo.jobTitle} +

+ )} +
+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+
+
+

+ Executive Profile +

+
+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+
+ +

+ Professional Experience +

+
+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+
+ +

+ Education +

+
+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+
+ +

+ Core Competencies +

+
+
+ {skills.map((skill) => ( +
+ β€’ + + {skill.name}{skill.level ? ` (${skill.level})` : ''} + +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+
+ +

+ Certifications +

+
+
+ {certifications.map((cert) => ( +
+
+

+ {cert.name} +

+ + {cert.date && formatDate(cert.date)} + +
+ {cert.issuer && ( +

+ {cert.issuer} +

+ )} +
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+
+ +

+ Key Projects +

+
+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ {project.url && ( + + View Project + + )} +
+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +

+ {project.description} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/MinimalTemplate.tsx b/backup/templates/MinimalTemplate.tsx new file mode 100644 index 0000000..cb9c50e --- /dev/null +++ b/backup/templates/MinimalTemplate.tsx @@ -0,0 +1,319 @@ +import React from 'react'; +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function MinimalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + // Remove protocol and www for cleaner display + return url.replace(/^https?:\/\/(www\.)?/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+ {customization.showProfileImage && personalInfo.profileImage && ( + Profile + )} +

+ {personalInfo.fullName || 'Your Name'} +

+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} +
+ + {(personalInfo.website || personalInfo.linkedin || personalInfo.github) && ( +
+ {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+ )} +
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ Professional Summary +

+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && `, ${exp.location}`} +

+
+
+ {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} +
+
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ {formatDate(edu.graduationDate)} +
+
+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ Skills +

+
+ {skills.map((skill) => ( +
+ {skill.name} + + {skill.level} + +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+
+

+ {cert.name} +

+

+ {cert.issuer} +

+
+
+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+
+ {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`} +
+
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, index) => ( + + {tech} + + ))} +
+ )} + {project.url && ( +

+ {formatUrl(project.url)} +

+ )} +
+ ))} +
+
+ ); + + case 'references': + if (references.length === 0) return null; + return ( +
+

+ Professional References +

+
+ {references.map((ref) => ( +
+
+
+

+ {ref.name} +

+

+ {ref.title} at {ref.company} +

+

+ {ref.relationship} +

+
+
+
{ref.email}
+
{ref.phone}
+
+
+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(renderSection)} +
+ ); +} \ No newline at end of file diff --git a/backup/templates/MinimalistATS.tsx b/backup/templates/MinimalistATS.tsx new file mode 100644 index 0000000..8247c71 --- /dev/null +++ b/backup/templates/MinimalistATS.tsx @@ -0,0 +1,126 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function MinimalistATS({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo.fullName}

+ {personalInfo.title &&

{personalInfo.title}

} +
+ {personalInfo.email && {personalInfo.email}} + {personalInfo.phone && {personalInfo.phone}} + {personalInfo.location && {personalInfo.location}} + {personalInfo.linkedin && linkedin.com/in/username} +
+
+ + {/* Summary */} + {summary && ( +
+

SUMMARY

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

EXPERIENCE

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

EDUCATION

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/backup/templates/MinimalistProfessionalTemplate.tsx b/backup/templates/MinimalistProfessionalTemplate.tsx new file mode 100644 index 0000000..72fa78e --- /dev/null +++ b/backup/templates/MinimalistProfessionalTemplate.tsx @@ -0,0 +1,307 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function MinimalistProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+
+

+ {personalInfo.title || 'Professional Title'} +

+ +
+ {personalInfo.email && ( + + {personalInfo.email} + + )} + {personalInfo.phone && ( + {personalInfo.phone} + )} + {personalInfo.location && ( + {personalInfo.location} + )} + {personalInfo.website && ( + + {formatUrl(personalInfo.website)} + + )} + {personalInfo.linkedin && ( + + LinkedIn + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+
+

+ Profile +

+

+ {summary} +

+
+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+
+

+ Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+
+

+ Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+
+

+ Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+
+

+ Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} + {cert.date && ` β€’ ${formatDate(cert.date)}`} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/MinimalistSidebarTemplate.tsx b/backup/templates/MinimalistSidebarTemplate.tsx new file mode 100644 index 0000000..29b7ff7 --- /dev/null +++ b/backup/templates/MinimalistSidebarTemplate.tsx @@ -0,0 +1,166 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Briefcase, GraduationCap, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function MinimalistSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName +
+ )} + +
+
+

CONTACT

+
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {personalInfo.website.replace(/^https?:\/\//, '')} +
+ )} +
+
+ + {skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Professional Title'} +

+ + {summary && ( +
+

+ + PROFILE +

+

{summary}

+
+ )} + + {experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ )} + + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/backup/templates/MinimalistTemplate.tsx b/backup/templates/MinimalistTemplate.tsx new file mode 100644 index 0000000..5efe0cd --- /dev/null +++ b/backup/templates/MinimalistTemplate.tsx @@ -0,0 +1,261 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function MinimalistTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+

+ {personalInfo.fullName || 'Your Name'} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} + +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ About +

+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company}{exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school}{edu.location && ` β€’ ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} + {cert.date && ` β€’ ${formatDate(cert.date)}`} +

+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ {project.url && ( + + View Project + + )} +
+ {project.technologies && project.technologies.length > 0 && ( +

+ {project.technologies.join(' β€’ ')} +

+ )} +

+ {project.description} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/ModernDarkSidebarTemplate.tsx b/backup/templates/ModernDarkSidebarTemplate.tsx new file mode 100644 index 0000000..267bd3f --- /dev/null +++ b/backup/templates/ModernDarkSidebarTemplate.tsx @@ -0,0 +1,192 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Briefcase, GraduationCap, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ModernDarkSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName +
+ )} + +
+
+

CONTACT

+
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + )} +
+
+ + {skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} + + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+
{cert.name}
+
{cert.issuer}
+ {cert.date && ( +
+ {new Date(cert.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short' })} +
+ )} +
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Professional Title'} +

+
+ + {summary && ( +
+

+ + ABOUT ME +

+

{summary}

+
+ )} + + {experience.length > 0 && ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/backup/templates/ModernExecutiveTemplate.tsx b/backup/templates/ModernExecutiveTemplate.tsx new file mode 100644 index 0000000..ade357e --- /dev/null +++ b/backup/templates/ModernExecutiveTemplate.tsx @@ -0,0 +1,220 @@ +import { Mail, Phone, MapPin, Linkedin, Globe, Briefcase, GraduationCap, Award, Code } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ModernExecutiveTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Sidebar */} +
+ {/* Profile Image */} + {personalInfo.profileImage && ( +
+ {personalInfo.fullName} +
+ )} + + {/* Contact */} +
+

+ Contact +

+
    + {personalInfo.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo.linkedin && ( +
  • + + LinkedIn +
  • + )} + {personalInfo.website && ( +
  • + + {personalInfo.website.replace(/^https?:\/\//, '')} +
  • + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

+ Skills +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+ + {/* Main Content */} +
+ {/* Header */} +
+

+ {personalInfo.fullName} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ + {/* Summary */} + {summary && ( +
+

+ Professional Summary +

+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+

{exp.jobTitle}

+
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.fieldOfStudy} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/backup/templates/ModernProfessionalTemplate.tsx b/backup/templates/ModernProfessionalTemplate.tsx new file mode 100644 index 0000000..bf58969 --- /dev/null +++ b/backup/templates/ModernProfessionalTemplate.tsx @@ -0,0 +1,366 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User, BookOpen } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ModernProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Professional Title'} +

+ +
+ +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + + Website + + )} + {personalInfo.linkedin && ( + + + LinkedIn + + )} + {personalInfo.github && ( + + + GitHub + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ + Professional Profile +

+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+
+ )} +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/ModernSidebarTemplate.tsx b/backup/templates/ModernSidebarTemplate.tsx new file mode 100644 index 0000000..25759f3 --- /dev/null +++ b/backup/templates/ModernSidebarTemplate.tsx @@ -0,0 +1,266 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User, BookOpen } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ModernSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName} +
+ )} + +
+
+

CONTACT

+
+ {personalInfo.email && ( + + )} + {personalInfo.phone && ( + + )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + )} + {personalInfo.linkedin && ( + + )} + {personalInfo.github && ( + + )} +
+
+ + {skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} + + {certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+
+

+ {personalInfo.fullName} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ + {summary && ( +
+

+ + PROFILE +

+

{summary}

+
+ )} + + {experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {projects.length > 0 && ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ + {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`} + +
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, idx) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/backup/templates/ModernTemplate.tsx b/backup/templates/ModernTemplate.tsx new file mode 100644 index 0000000..b338344 --- /dev/null +++ b/backup/templates/ModernTemplate.tsx @@ -0,0 +1,339 @@ +import React from 'react'; +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ModernTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + // Remove protocol and www for cleaner display + return url.replace(/^https?:\/\/(www\.)?/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+ {customization.showProfileImage && personalInfo.profileImage && ( + Profile + )} +
+

+ {personalInfo.fullName || 'Your Name'} +

+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+
+
+

+ Professional Summary +

+
+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+
+
+

+ Work Experience +

+
+
+ {experience.map((exp) => ( +
+
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && `, ${exp.location}`} +

+
+
+ {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} +
+
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+
+
+

+ Education +

+
+
+ {education.map((edu) => ( +
+
+
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ {formatDate(edu.graduationDate)} +
+
+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+
+
+

+ Skills +

+
+
+ {skills.map((skill) => ( +
+ {skill.name} + + {skill.level} + +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+
+
+

+ Certifications +

+
+
+ {certifications.map((cert) => ( +
+
+

+ {cert.name} +

+

+ {cert.issuer} +

+
+
+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+
+
+

+ Projects +

+
+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+
+ {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`} +
+
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, index) => ( + + {tech} + + ))} +
+ )} + {project.url && ( +

+ {formatUrl(project.url)} +

+ )} +
+ ))} +
+
+ ); + + case 'references': + if (references.length === 0) return null; + return ( +
+
+
+

+ Professional References +

+
+
+ {references.map((ref) => ( +
+
+
+

+ {ref.name} +

+

+ {ref.title} at {ref.company} +

+

+ {ref.relationship} +

+
+
+
{ref.email}
+
{ref.phone}
+
+
+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(renderSection)} +
+ ); +} \ No newline at end of file diff --git a/backup/templates/ModernTimelineTemplate.tsx b/backup/templates/ModernTimelineTemplate.tsx new file mode 100644 index 0000000..6f3528d --- /dev/null +++ b/backup/templates/ModernTimelineTemplate.tsx @@ -0,0 +1,207 @@ +import { Mail, Phone, MapPin, Linkedin, Globe, Briefcase, GraduationCap, Award } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ModernTimelineTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

+ {personalInfo.fullName} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo.location && ( + + + {personalInfo.location} + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

About Me

+

{summary}

+
+ )} + + {/* Timeline Section */} +
+ {/* Timeline Line */} +
+ +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp, idx) => ( +
+
+
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.fieldOfStudy && ( +

+ {edu.fieldOfStudy} +

+ )} + {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ ))} +
+
+ )} +
+
+ + {/* Skills & Certifications */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ + Key Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/backup/templates/ModernTwoColumn.tsx b/backup/templates/ModernTwoColumn.tsx new file mode 100644 index 0000000..effc367 --- /dev/null +++ b/backup/templates/ModernTwoColumn.tsx @@ -0,0 +1,205 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ModernTwoColumn({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Left Column */} +
+ {/* Profile */} +
+
+ {personalInfo.profileImage ? ( + {personalInfo.fullName} + ) : ( +
+ + {personalInfo.fullName.split(' ').map(n => n[0]).join('')} + +
+ )} +
+

{personalInfo.fullName}

+ {personalInfo.title && ( +

{personalInfo.title}

+ )} +
+ + {/* Contact */} +
+

Contact

+
    + {personalInfo.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo.linkedin && ( +
  • + + LinkedIn +
  • + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

Skills

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Summary */} + {summary && ( +
+

+ About Me +

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+

{exp.jobTitle}

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ Education +

+
+ {education.map((edu) => ( +
+
+
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/backup/templates/ProfessionalBlueTemplate.tsx b/backup/templates/ProfessionalBlueTemplate.tsx new file mode 100644 index 0000000..a961f1c --- /dev/null +++ b/backup/templates/ProfessionalBlueTemplate.tsx @@ -0,0 +1,150 @@ +import { ResumeData } from '@/types/resume'; +import { Briefcase, GraduationCap, Code, User } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ProfessionalBlueTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, sections } = data; + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName +
+ )} +
+

{personalInfo.fullName || 'Your Name'}

+

{personalInfo.title || 'Professional Title'}

+
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ + PROFILE +

+

{summary}

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + +
+
+
+
+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/ProfessionalSidebarTemplate.tsx b/backup/templates/ProfessionalSidebarTemplate.tsx new file mode 100644 index 0000000..d91b338 --- /dev/null +++ b/backup/templates/ProfessionalSidebarTemplate.tsx @@ -0,0 +1,175 @@ +import { Mail, Phone, MapPin, Linkedin, Globe, Briefcase, GraduationCap, Award } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ProfessionalSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Sidebar */} +
+ {/* Profile */} +
+ {personalInfo.profileImage ? ( +
+ {personalInfo.fullName} +
+ ) : ( +
+ + {personalInfo.fullName.split(' ').map(n => n[0]).join('')} + +
+ )} +

{personalInfo.fullName}

+ {personalInfo.title &&

{personalInfo.title}

} +
+ + {/* Contact */} +
+

Contact

+
    + {personalInfo.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo.linkedin && ( +
  • + + LinkedIn +
  • + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

Skills

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} +
+ + {/* Main Content */} +
+ {/* Summary */} + {summary && ( +
+

+ Professional Summary +

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.fieldOfStudy} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

GPA: {edu.gpa}

+ )} +
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/backup/templates/ProfessionalSummaryTemplate.tsx b/backup/templates/ProfessionalSummaryTemplate.tsx new file mode 100644 index 0000000..58f1bc7 --- /dev/null +++ b/backup/templates/ProfessionalSummaryTemplate.tsx @@ -0,0 +1,189 @@ +import { ResumeData } from '@/types/resume'; +import { Briefcase, GraduationCap, User, Code, Mail, Phone, MapPin, Globe } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ProfessionalSummaryTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo.fullName || 'Your Name'}

+

{personalInfo.title || 'Professional Title'}

+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

+ + PROFESSIONAL SUMMARY +

+

{summary}

+
+ )} + +
+ {/* Left Column */} +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+
{cert.name}
+
{cert.issuer}
+ {cert.date && ( +
+ {new Date(cert.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short' })} +
+ )} +
+ ))} +
+
+ )} +
+
+
+ ); +} diff --git a/backup/templates/ProfessionalTemplate.tsx b/backup/templates/ProfessionalTemplate.tsx new file mode 100644 index 0000000..d87c90e --- /dev/null +++ b/backup/templates/ProfessionalTemplate.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+ {personalInfo.jobTitle && ( +

+ {personalInfo.jobTitle} +

+ )} +
+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+
+
+

+ Professional Summary +

+
+

+ {summary} +

+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+
+
+

+ Professional Experience +

+
+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
+ + {exp.company}{exp.location && ` β€’ ${exp.location}`} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ ); + + // Continue with other section types... + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/TechTemplate.tsx b/backup/templates/TechTemplate.tsx new file mode 100644 index 0000000..cd658f7 --- /dev/null +++ b/backup/templates/TechTemplate.tsx @@ -0,0 +1,321 @@ +import { ResumeData } from '@/types/resume'; +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Code, Cpu, BookOpen, Award, Briefcase, GraduationCap } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function TechTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, sections, customization } = data; + + const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + const renderSection = (section: typeof sections[0]) => { + switch (section.type) { + case 'personalInfo': + return ( +
+
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Software Engineer'} +

+
+
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.linkedin && ( + + + LinkedIn + + )} + {personalInfo.github && ( + + + GitHub + + )} + {personalInfo.website && ( + + + Website + + )} +
+
+
+ ); + + case 'summary': + if (!summary) return null; + return ( +
+

+ + Technical Profile +

+

+ {summary} +

+
+ ); + + case 'skills': + if (skills.length === 0) return null; + return ( +
+

+ + Technical Skills +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ ); + + case 'experience': + if (experience.length === 0) return null; + return ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ ); + + case 'projects': + if (projects.length === 0) return null; + return ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ ); + + case 'education': + if (education.length === 0) return null; + return ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ + {formatDate(edu.graduationDate)} + +
+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ ); + + case 'certifications': + if (certifications.length === 0) return null; + return ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ ); + + default: + return null; + } + }; + + return ( +
+ {visibleSections.map(section => renderSection(section))} +
+ ); +} diff --git a/backup/templates/TimelineTemplate.tsx b/backup/templates/TimelineTemplate.tsx new file mode 100644 index 0000000..30b9a1f --- /dev/null +++ b/backup/templates/TimelineTemplate.tsx @@ -0,0 +1,183 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function TimelineTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + const accentColor = data.customization.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo.fullName}

+ {personalInfo.title && ( +

{personalInfo.title}

+ )} +
+ {personalInfo.email && {personalInfo.email}} + {personalInfo.phone && {personalInfo.phone}} + {personalInfo.location && {personalInfo.location}} +
+
+ + {/* Summary */} + {summary && ( +
+

Professional Summary

+

{summary}

+
+ )} + + {/* Timeline Section */} +
+ {/* Timeline Line */} +
+ +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp, idx) => ( +
+
+
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ ))} +
+
+ )} +
+
+ + {/* Skills & Certifications */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ + Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/backup/templates/types.ts b/backup/templates/types.ts new file mode 100644 index 0000000..6f36d51 --- /dev/null +++ b/backup/templates/types.ts @@ -0,0 +1,58 @@ +import { ResumeData } from '@/types/resume'; + +export interface TemplateProps { + data: ResumeData; +} + +export interface TemplateSectionProps { + title: string; + className?: string; + children: React.ReactNode; +} + +export interface ExperienceItemProps { + jobTitle: string; + company: string; + location?: string; + startDate: string; + endDate: string; + isCurrentJob: boolean; + description: string[]; + className?: string; +} + +export interface EducationItemProps { + degree: string; + school: string; + location?: string; + graduationDate: string; + fieldOfStudy?: string; + gpa?: string; + className?: string; +} + +export interface SkillItemProps { + name: string; + level: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert'; + className?: string; +} + +export interface CertificationItemProps { + name: string; + issuer: string; + date: string; + expirationDate?: string; + className?: string; +} + +export interface TemplateStyles { + fontFamily?: string; + primaryColor?: string; + secondaryColor?: string; + textColor?: string; + backgroundColor?: string; + spacing?: { + section?: string; + item?: string; + }; +} diff --git a/docs/TEMPLATE_INTEGRATION_PLAN.md b/docs/TEMPLATE_INTEGRATION_PLAN.md new file mode 100644 index 0000000..708591c --- /dev/null +++ b/docs/TEMPLATE_INTEGRATION_PLAN.md @@ -0,0 +1,88 @@ +# Resume Template Integration Plan + +## Phase 1: Template Integration + +### 1.1 Audit Existing Templates +- [ ] Create a complete inventory of all 27 template files +- [ ] Verify each template implements the correct `TemplateProps` interface +- [ ] Ensure consistent export naming conventions + +### 1.2 Create Template Registry +- [ ] Create a new file `templateRegistry.ts` to manage all templates +- [ ] Import and register all 27 templates with metadata (name, category, preview image) +- [ ] Implement a template selection component + +### 1.3 Update Resume Preview +- [ ] Modify the preview component to use the selected template +- [ ] Implement template switching functionality +- [ ] Add loading states for template changes + +## Phase 2: UI/UX Improvements + +### 2.1 Style Section Redesign +- [ ] Update the Style tab to display all 27 templates +- [ ] Implement a responsive grid layout for template selection +- [ ] Add search and filter functionality for templates +- [ ] Include template preview thumbnails + +### 2.2 Template Previews +- [ ] Generate or create preview images for each template +- [ ] Implement hover effects and selection states +- [ ] Add template details on hover + +## Phase 3: Testing & Optimization + +### 3.1 ATS Compatibility Testing +- [ ] Test each template with ATS simulation tools +- [ ] Ensure proper semantic HTML structure +- [ ] Verify text extraction works correctly + +### 3.2 Print Testing +- [ ] Test print output for each template +- [ ] Ensure proper page breaks and margins +- [ ] Verify color contrast for printed versions + +### 3.3 Performance Optimization +- [ ] Implement code splitting for template loading +- [ ] Optimize template components for performance +- [ ] Add lazy loading for template previews + +## Phase 4: Documentation + +### 4.1 Update README.md +- [ ] Add a section about the 27 available templates +- [ ] Include template previews in the documentation +- [ ] Document template features and best use cases + +### 4.2 Developer Documentation +- [ ] Document how to add new templates +- [ ] Create template development guidelines +- [ ] Document template props and requirements + +## Phase 5: Final Review + +### 5.1 Code Review +- [ ] Review all template implementations +- [ ] Ensure consistent styling and behavior +- [ ] Verify all templates work with all features + +### 5.2 User Testing +- [ ] Test template selection and switching +- [ ] Verify all templates render correctly +- [ ] Test on different screen sizes + +## Implementation Notes: +1. **Template Organization**: + - Group templates by category (Minimalist, Professional, Creative, etc.) + - Use consistent naming conventions + - Maintain a clean file structure + +2. **Performance Considerations**: + - Lazy load template components + - Optimize preview generation + - Cache template previews + +3. **Accessibility**: + - Ensure all templates meet WCAG guidelines + - Test with screen readers + - Verify keyboard navigation diff --git a/docs/creative-template.png b/docs/creative-template.png new file mode 100644 index 0000000..09879e5 Binary files /dev/null and b/docs/creative-template.png differ diff --git a/docs/main-interface.png b/docs/main-interface.png new file mode 100644 index 0000000..b126ece Binary files /dev/null and b/docs/main-interface.png differ diff --git a/docs/minimal-template.png b/docs/minimal-template.png new file mode 100644 index 0000000..f8e04d7 Binary files /dev/null and b/docs/minimal-template.png differ diff --git a/docs/modern-template.png b/docs/modern-template.png new file mode 100644 index 0000000..a502b7c Binary files /dev/null and b/docs/modern-template.png differ diff --git a/package-lock.json b/package-lock.json index 82d502f..0ca01d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "embla-carousel-react": "^8.3.0", "express": "^5.1.0", "html2canvas": "^1.4.1", + "huggingface": "^1.4.0", "input-otp": "^1.2.4", "jsonwebtoken": "^9.0.2", "jspdf": "^2.5.1", @@ -68,7 +69,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", - "@types/node": "^22.7.3", + "@types/node": "^22.16.5", "@types/react": "^18.3.9", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", @@ -79,7 +80,8 @@ "globals": "^15.9.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", - "typescript": "^5.5.3", + "ts-node": "^10.9.2", + "typescript": "^5.8.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } @@ -410,6 +412,30 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -2650,6 +2676,34 @@ "win32" ] }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2758,12 +2812,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "dev": true, + "version": "22.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", + "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", + "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/prop-types": { @@ -3079,7 +3134,7 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -3096,6 +3151,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3418,9 +3486,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true, "funding": [ { @@ -3435,7 +3503,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/canvg": { "version": "3.0.11", @@ -3998,6 +4067,13 @@ "node": ">= 0.10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4146,6 +4222,15 @@ "node": ">=12" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", @@ -4202,6 +4287,16 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -4772,6 +4867,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -4868,6 +4986,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5126,6 +5256,19 @@ "node": ">= 0.8" } }, + "node_modules/huggingface": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/huggingface/-/huggingface-1.4.0.tgz", + "integrity": "sha512-1R79GhEZRkAzpC6KZMJT++V3EhEjODHyOTeVNOjP3zD7p4HZ1rDfuw2ucjIjLWWljGyvxWWASEIvXF2hHqCJzw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "isomorphic-unfetch": "4.0.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5283,6 +5426,16 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isomorphic-unfetch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz", + "integrity": "sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^3.2.0", + "unfetch": "^5.0.0" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -5580,6 +5733,13 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5841,6 +6001,44 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -7215,6 +7413,57 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -7247,10 +7496,11 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7283,10 +7533,20 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unfetch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz", + "integrity": "sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==", + "license": "MIT", + "workspaces": [ + "./packages/isomorphic-unfetch" + ] }, "node_modules/unpipe": { "version": "1.0.0", @@ -7391,6 +7651,13 @@ "base64-arraybuffer": "^1.0.2" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -7492,6 +7759,15 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -7671,6 +7947,16 @@ "node": ">= 14" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f5f8cd0..ffe2b3c 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "embla-carousel-react": "^8.3.0", "express": "^5.1.0", "html2canvas": "^1.4.1", + "huggingface": "^1.4.0", "input-otp": "^1.2.4", "jsonwebtoken": "^9.0.2", "jspdf": "^2.5.1", @@ -70,7 +71,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", - "@types/node": "^22.7.3", + "@types/node": "^22.16.5", "@types/react": "^18.3.9", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", @@ -81,7 +82,8 @@ "globals": "^15.9.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", - "typescript": "^5.5.3", + "ts-node": "^10.9.2", + "typescript": "^5.8.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..8334c6d Binary files /dev/null and b/public/logo.png differ diff --git a/scripts/audit-templates.js b/scripts/audit-templates.js new file mode 100644 index 0000000..2283a70 --- /dev/null +++ b/scripts/audit-templates.js @@ -0,0 +1,54 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const templatesDir = path.join(__dirname, '..', 'src', 'components', 'resume-builder', 'templates'); +const outputFile = path.join(__dirname, '..', 'TEMPLATE_AUDIT_REPORT.md'); + +// Get all template files +const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.tsx')); + +// Initialize report +let report = '# Template Audit Report\n\n'; +report += `Generated: ${new Date().toISOString()}\n\n`; +report += `Total templates found: ${templateFiles.length}\n\n`; + +// Check each template file +templateFiles.forEach((file, index) => { + const filePath = path.join(templatesDir, file); + const content = fs.readFileSync(filePath, 'utf8'); + + // Basic checks + const hasTemplateProps = content.includes('interface TemplateProps') || content.includes('type TemplateProps'); + const hasExport = content.includes(`export function ${file.replace('.tsx', '')}`); + const hasDefaultExport = content.includes('export default'); + + // Add to report + report += `## ${file}\n`; + report += `- **Export Name:** ${file.replace('.tsx', '')}\n`; + report += `- **Has TemplateProps:** ${hasTemplateProps ? 'βœ…' : '❌'}\n`; + report += `- **Has Correct Export:** ${hasExport ? 'βœ…' : '❌'}\n`; + report += `- **Has Default Export:** ${hasDefaultExport ? 'βœ…' : '❌'}\n`; + + // Check for common issues + const issues = []; + if (!hasTemplateProps) issues.push('Missing TemplateProps interface/type'); + if (!hasExport) issues.push(`Missing export for ${file.replace('.tsx', '')}`); + + if (issues.length > 0) { + report += '\n**Issues:**\n'; + issues.forEach(issue => { + report += `- ❌ ${issue}\n`; + }); + } else { + report += '\nβœ… No issues found\n'; + } + + report += '\n---\n\n'; +}); + +// Save the report +fs.writeFileSync(outputFile, report); +console.log(`Audit complete. Report saved to: ${outputFile}`); diff --git a/scripts/audit-templates.ts b/scripts/audit-templates.ts new file mode 100644 index 0000000..dce6a32 --- /dev/null +++ b/scripts/audit-templates.ts @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; + +const templatesDir = path.join(process.cwd(), 'src/components/resume-builder/templates'); +const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.tsx')); + +interface TemplateAuditResult { + filename: string; + hasTemplateProps: boolean; + exportName: string; + hasDefaultExport: boolean; + issues: string[]; +} + +const auditResults: TemplateAuditResult[] = []; + +// Check each template file +templateFiles.forEach(filename => { + const filePath = path.join(templatesDir, filename); + const content = fs.readFileSync(filePath, 'utf8'); + + const result: TemplateAuditResult = { + filename, + hasTemplateProps: content.includes('interface TemplateProps') || content.includes('type TemplateProps'), + exportName: '', + hasDefaultExport: content.includes('export default'), + issues: [] + }; + + // Extract export name + const exportMatch = content.match(/export\s+(?:function|const|class)\s+(\w+)/); + if (exportMatch) { + result.exportName = exportMatch[1]; + } else { + result.issues.push('No named export found'); + } + + // Check for common issues + if (!result.hasTemplateProps) { + result.issues.push('Missing TemplateProps interface/type'); + } + + // Check if export name matches filename + const expectedExportName = filename.replace('.tsx', ''); + if (result.exportName && result.exportName !== expectedExportName) { + result.issues.push(`Export name (${result.exportName}) doesn't match filename (${expectedExportName})`); + } + + auditResults.push(result); +}); + +// Generate report +console.log('## Template Audit Report'); +console.log(`Generated: ${new Date().toISOString()}`); +console.log(`Templates Audited: ${auditResults.length}\n`); + +// Summary +const templatesWithIssues = auditResults.filter(t => t.issues.length > 0); +console.log('### Summary'); +console.log(`- Total Templates: ${auditResults.length}`); +console.log(`- Templates with issues: ${templatesWithIssues.length}`); +console.log(`- Templates without TemplateProps: ${auditResults.filter(t => !t.hasTemplateProps).length}`); +console.log(`- Templates with default export: ${auditResults.filter(t => t.hasDefaultExport).length}\n`); + +// Detailed report +if (templatesWithIssues.length > 0) { + console.log('### Templates with Issues'); + templatesWithIssues.forEach(template => { + console.log(`\n#### ${template.filename}`); + if (template.exportName) { + console.log(`- Export: ${template.exportName}`); + } + template.issues.forEach(issue => { + console.log(`- ❌ ${issue}`); + }); + }); +} + +// All templates list +console.log('\n### All Templates'); +console.log('| Filename | Export Name | Issues |'); +console.log('|----------|-------------|--------|'); +auditResults.forEach(template => { + const status = template.issues.length === 0 ? 'βœ…' : '❌'; + console.log(`| ${template.filename} | ${template.exportName || 'N/A'} | ${template.issues.length} issue(s) |`); +}); + +// Save report to file +const reportPath = path.join(process.cwd(), 'TEMPLATE_AUDIT_REPORT.md'); +const reportStream = fs.createWriteStream(reportPath); + +reportStream.write('# Template Audit Report\n'); +reportStream.write(`Generated: ${new Date().toISOString()}\n\n`); + +// Summary +auditResults.forEach(template => { + reportStream.write(`## ${template.filename}\n`); + reportStream.write(`- Export: ${template.exportName || 'N/A'}\n`); + reportStream.write(`- Has TemplateProps: ${template.hasTemplateProps ? 'βœ…' : '❌'}\n`); + reportStream.write(`- Has Default Export: ${template.hasDefaultExport ? 'βœ…' : '❌'}\n`); + + if (template.issues.length > 0) { + reportStream.write('### Issues\n'); + template.issues.forEach(issue => { + reportStream.write(`- ❌ ${issue}\n`); + }); + } else { + reportStream.write('βœ… No issues found\n'); + } + reportStream.write('\n'); +}); + +reportStream.end(); + +console.log(`\nAudit complete. Detailed report saved to: ${reportPath}`); diff --git a/scripts/rename-templates.js b/scripts/rename-templates.js new file mode 100644 index 0000000..beaf0bb --- /dev/null +++ b/scripts/rename-templates.js @@ -0,0 +1,158 @@ +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); + +const readdir = promisify(fs.readdir); +const rename = promisify(fs.rename); +const stat = promisify(fs.stat); + +// Template mapping: old name -> new name +const templateMapping = { + // Minimal Style (MIN-XX-Name) + 'MinimalTemplate.tsx': 'MIN-01-Minimal.tsx', + 'MinimalistTemplate.tsx': 'MIN-02-Minimalist.tsx', + 'MinimalistATS.tsx': 'MIN-03-ATS.tsx', + 'MinimalistSidebarTemplate.tsx': 'MIN-04-Sidebar.tsx', + 'CleanMinimalistTemplate.tsx': 'MIN-05-Clean.tsx', + + // Professional Style (PRO-XX-Name) + 'ProfessionalTemplate.tsx': 'PRO-01-Professional.tsx', + 'ProfessionalSidebarTemplate.tsx': 'PRO-02-Sidebar.tsx', + 'ProfessionalBlueTemplate.tsx': 'PRO-03-Blue.tsx', + 'ProfessionalSummaryTemplate.tsx': 'PRO-04-Summary.tsx', + 'MinimalistProfessionalTemplate.tsx': 'PRO-05-Minimalist.tsx', + 'ModernProfessionalTemplate.tsx': 'PRO-06-Modern.tsx', + 'CleanProfessionalTemplate.tsx': 'PRO-07-Clean.tsx', + + // Creative Style (CRE-XX-Name) + 'CreativeTemplate.tsx': 'CRE-01-Creative.tsx', + 'CreativeColorBlocksTemplate.tsx': 'CRE-02-ColorBlocks.tsx', + 'ModernTemplate.tsx': 'CRE-03-Modern.tsx', + 'ModernSidebarTemplate.tsx': 'CRE-04-ModernSidebar.tsx', + 'ModernDarkSidebarTemplate.tsx': 'CRE-05-DarkSidebar.tsx', + + // Executive Style (EXE-XX-Name) + 'ExecutiveTemplate.tsx': 'EXE-01-Executive.tsx', + 'ModernExecutiveTemplate.tsx': 'EXE-02-Modern.tsx', + 'CorporateTemplate.tsx': 'EXE-03-Corporate.tsx', + + // Other Style (OTH-XX-Name) + 'AcademicTemplate.tsx': 'OTH-01-Academic.tsx', + 'BoldTemplate.tsx': 'OTH-02-Bold.tsx', + 'ElegantTemplate.tsx': 'OTH-03-Elegant.tsx', + 'TechTemplate.tsx': 'OTH-04-Tech.tsx', + 'TimelineTemplate.tsx': 'OTH-05-Timeline.tsx', + 'ModernTimelineTemplate.tsx': 'OTH-06-ModernTimeline.tsx', + 'ModernTwoColumn.tsx': 'OTH-07-TwoColumn.tsx' +}; + +// ID mapping for template registry +const idMapping = { + // Minimal Style + 'minimal': 'min-01-minimal', + 'minimalist': 'min-02-minimalist', + 'minimalist-ats': 'min-03-ats', + 'minimalist-sidebar': 'min-04-sidebar', + 'clean-minimalist': 'min-05-clean', + + // Professional Style + 'professional': 'pro-01-professional', + 'professional-sidebar': 'pro-02-sidebar', + 'professional-blue': 'pro-03-blue', + 'professional-summary': 'pro-04-summary', + 'minimalist-professional': 'pro-05-minimalist', + 'modern-professional': 'pro-06-modern', + 'clean-professional': 'pro-07-clean', + + // Creative Style + 'creative': 'cre-01-creative', + 'creative-color-blocks': 'cre-02-color-blocks', + 'modern': 'cre-03-modern', + 'modern-sidebar': 'cre-04-modern-sidebar', + 'modern-dark-sidebar': 'cre-05-dark-sidebar', + + // Executive Style + 'executive': 'exe-01-executive', + 'modern-executive': 'exe-02-modern', + 'corporate': 'exe-03-corporate', + + // Other Style + 'academic': 'oth-01-academic', + 'bold': 'oth-02-bold', + 'elegant': 'oth-03-elegant', + 'tech': 'oth-04-tech', + 'timeline': 'oth-05-timeline', + 'modern-timeline': 'oth-06-modern-timeline', + 'modern-two-column': 'oth-07-two-column' +}; + +async function updateTemplateRegistry() { + const registryPath = path.join(__dirname, '../src/components/resume-builder/templateRegistry.ts'); + let registryContent = await fs.promises.readFile(registryPath, 'utf8'); + + // Update imports + for (const [oldName, newName] of Object.entries(templateMapping)) { + const oldImportName = oldName.replace('.tsx', ''); + const newImportName = newName.replace('.tsx', ''); + + // Update imports + const importRegex = new RegExp(`import\('\..\/templates\/${oldName.replace('.', '\.')}'\)`, 'g'); + registryContent = registryContent.replace(importRegex, `import('..\/templates\/${newName}')`); + + // Update component names in the registry + const componentRegex = new RegExp(`component: templates\.${oldImportName}`, 'g'); + registryContent = registryContent.replace(componentRegex, `component: templates.${newImportName}`); + } + + // Update template IDs in the registry + for (const [oldId, newId] of Object.entries(idMapping)) { + const idRegex = new RegExp(`id: '${oldId}'`, 'g'); + registryContent = registryContent.replace(idRegex, `id: '${newId}'`); + } + + // Write the updated registry back to disk + await fs.promises.writeFile(registryPath, registryContent, 'utf8'); + console.log('Template registry updated successfully'); +} + +async function renameTemplateFiles() { + const templatesDir = path.join(__dirname, '../src/components/resume-builder/templates'); + const files = await readdir(templatesDir); + + for (const file of files) { + if (templateMapping[file]) { + const oldPath = path.join(templatesDir, file); + const newPath = path.join(templatesDir, templateMapping[file]); + + try { + await rename(oldPath, newPath); + console.log(`Renamed: ${file} -> ${templateMapping[file]}`); + } catch (err) { + console.error(`Error renaming ${file}:`, err); + } + } + } +} + +async function main() { + try { + console.log('Starting template renaming process...'); + + // First rename all the files + console.log('\nRenaming template files...'); + await renameTemplateFiles(); + + // Then update the template registry + console.log('\nUpdating template registry...'); + await updateTemplateRegistry(); + + console.log('\nTemplate renaming completed successfully!'); + console.log('Please review the changes and run tests to ensure everything works as expected.'); + + } catch (error) { + console.error('Error during template renaming:', error); + process.exit(1); + } +} + +main(); diff --git a/server/index.js b/server/index.js index f991b40..5a671f1 100644 --- a/server/index.js +++ b/server/index.js @@ -8,7 +8,7 @@ dotenv.config(); const app = express(); app.use(cors()); -app.use(express.json({ limit: "5mb" })); +app.use(express.json({ limit: "10mb" })); app.use("/api/auth", authRoutes); app.use("/api/resume", resumeRoutes); diff --git a/server/routes/auth.js b/server/routes/auth.js index 1696510..52ca9d8 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -29,7 +29,13 @@ router.post('/login', async (req, res) => { const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' }); const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' }); - res.json({ token }); + res.json({ + token, + email: user.email, + name: user.name, + avatar: user.avatar, + resumeData: user.resumeData || null, + }); } catch (err) { res.status(500).json({ message: 'Server error' }); } diff --git a/src/App.tsx b/src/App.tsx index 0738b78..6c4a3a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from 'react'; +import { Suspense } from 'react'; import { ResumeBuilder } from '@/components/resume-builder/ResumeBuilder'; import './App.css'; import TestApi from '../src/components/TestApi.tsx'; @@ -14,9 +14,7 @@ function App() {
}> - { - - }} /> +
diff --git a/src/components/CookieBanner.tsx b/src/components/CookieBanner.tsx index 1f076af..99ccf3a 100644 --- a/src/components/CookieBanner.tsx +++ b/src/components/CookieBanner.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; -function CookieBanner({ onShowTerms }: { onShowTerms: () => void }) { +function CookieBanner() { const [visible, setVisible] = useState(false); const [glow, setGlow] = useState(true); diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 557146f..c6dc51f 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; @@ -27,10 +27,16 @@ export default function Footer() { const [openDoc, setOpenDoc] = useState(null); const [docContent, setDocContent] = useState(''); const [feedbackOpen, setFeedbackOpen] = useState(false); - const [feedback, setFeedback] = useState(''); + const [formData, setFormData] = useState({ name: '', email: '', message: '' }); const [feedbackStatus, setFeedbackStatus] = useState(''); - const handleOpenDoc = async (doc) => { + const encode = (data: any) => { + return Object.keys(data) + .map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key])) + .join("&"); + }; + + const handleOpenDoc = async (doc: any) => { setDocContent('Loading...'); setOpenDoc(doc.key); try { @@ -42,12 +48,38 @@ export default function Footer() { } }; - const handleFeedbackSubmit = (e) => { + const handleFeedbackSubmit = async (e: any) => { e.preventDefault(); - setFeedbackStatus('Thank you for your feedback!'); - setFeedback(''); - setTimeout(() => setFeedbackStatus(''), 3000); - // Netlify integration to be added later + setFeedbackStatus('Sending...'); + + // Check if running on Netlify (production) + if (window.location.hostname.includes('netlify.app') || window.location.hostname.includes('netlify.com')) { + try { + await fetch('/', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: encode({ 'form-name': 'feedback', ...formData }) + }); + + setFeedbackStatus('Thank you for your feedback!'); + setFormData({ name: '', email: '', message: '' }); + setTimeout(() => { + setFeedbackStatus(''); + setFeedbackOpen(false); + }, 2000); + } catch (error) { + setFeedbackStatus('Failed to send feedback. Please try again.'); + setTimeout(() => setFeedbackStatus(''), 3000); + } + } else { + // Local development fallback + setFeedbackStatus('Thank you for your feedback! (Local mode - not actually sent)'); + setFormData({ name: '', email: '', message: '' }); + setTimeout(() => { + setFeedbackStatus(''); + setFeedbackOpen(false); + }, 2000); + } }; return ( @@ -61,8 +93,8 @@ export default function Footer() { key={doc.key} className="hover:underline focus:outline-none px-1 py-0.5 rounded transition-colors duration-100" style={{ - fontSize: "11px", - lineHeight: "1.2", + fontSize: 11, + lineHeight: 1.2, color: "#1a1a1a", background: "#f1f1f1", border: "1px solid #1a1a1a", @@ -77,8 +109,8 @@ export default function Footer() { +
+ ) : ( +
+ {filteredTemplates.map((template: TemplateItem) => { + const isSelected = selectedTemplateId === template.id; + return ( + + + +
onSelectTemplate(template.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + onSelectTemplate(template.id); + } + }} + className={cn( + 'group relative cursor-pointer overflow-hidden rounded-lg border-2 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2', + isSelected ? 'border-primary ring-2 ring-primary/20' : 'border-transparent hover:border-primary/50' + )} + > +
+ }> + + +
+ {isSelected && ( +
+ +
+ )} +
+
+ +
+
+ }> + + +
+
+

{template.name}

+

{template.description}

+
+
+ + {template.category} + + {template.atsFriendly && ( + + + ATS Friendly + + )} + {template.tags?.map((tag: string) => ( + + {tag} + + ))} +
+
+
+
+
+ ); + })} +
+ )} + + +
+
+ ); +} diff --git a/src/components/resume-builder/form-sections/CertificationsForm.tsx b/src/components/resume-builder/form-sections/CertificationsForm.tsx index 2189280..c3bfd35 100644 --- a/src/components/resume-builder/form-sections/CertificationsForm.tsx +++ b/src/components/resume-builder/form-sections/CertificationsForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; diff --git a/src/components/resume-builder/form-sections/CustomizationForm.tsx b/src/components/resume-builder/form-sections/CustomizationForm.tsx index ec8b86b..b2f73db 100644 --- a/src/components/resume-builder/form-sections/CustomizationForm.tsx +++ b/src/components/resume-builder/form-sections/CustomizationForm.tsx @@ -1,10 +1,11 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { useResume } from '@/contexts/ResumeContext'; import { GOOGLE_FONTS, loadGoogleFont, preloadAllFonts } from '@/utils/fontLoader'; +import { TemplateSelector } from '../TemplateSelector'; const ACCENT_COLORS = [ { name: 'Blue', value: '#3B82F6' }, @@ -20,12 +21,6 @@ const ACCENT_COLORS = [ { name: 'Gray', value: '#6B7280' } ]; -const TEMPLATES = [ - { name: 'Minimal', value: 'minimal', description: 'Clean and simple design, perfect for ATS systems' }, - { name: 'Modern', value: 'modern', description: 'Contemporary layout with accent colors and clean typography' }, - { name: 'Creative', value: 'creative', description: 'Stand out with a unique design that includes photo placement' } -]; - export function CustomizationForm() { const { state, dispatch } = useResume(); const { template, customization } = state.resumeData; @@ -42,8 +37,9 @@ export function CustomizationForm() { } }, [customization.fontFamily]); - const updateTemplate = (newTemplate: typeof template) => { - dispatch({ type: 'UPDATE_TEMPLATE', payload: newTemplate }); + const updateTemplate = (templateId: string) => { + // Type assertion to satisfy TypeScript - we'll ensure templateId is valid in the TemplateSelector + dispatch({ type: 'UPDATE_TEMPLATE', payload: templateId as any }); }; const updateCustomization = (updates: Partial) => { @@ -68,33 +64,12 @@ export function CustomizationForm() { {/* Template Selection */}
- -
- {TEMPLATES.map((templateOption) => ( -
updateTemplate(templateOption.value as typeof template)} - > -
-
-
-

{templateOption.name}

-

{templateOption.description}

-
-
-
- ))} + +
+
diff --git a/src/components/resume-builder/form-sections/PersonalInfoForm.tsx b/src/components/resume-builder/form-sections/PersonalInfoForm.tsx index 967e3eb..77718e7 100644 --- a/src/components/resume-builder/form-sections/PersonalInfoForm.tsx +++ b/src/components/resume-builder/form-sections/PersonalInfoForm.tsx @@ -1,10 +1,8 @@ -import React from 'react'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; -import { Mail, Upload, X } from 'lucide-react'; +import { Upload, X } from 'lucide-react'; import { useResume } from '@/contexts/ResumeContext'; -import { SquareUser, FolderPen, Phone, Map, Globe, Linkedin, Github } from "lucide-react"; export function PersonalInfoForm() { const { state, dispatch } = useResume(); diff --git a/src/components/resume-builder/form-sections/ProjectsForm.tsx b/src/components/resume-builder/form-sections/ProjectsForm.tsx index 6180637..45bba8e 100644 --- a/src/components/resume-builder/form-sections/ProjectsForm.tsx +++ b/src/components/resume-builder/form-sections/ProjectsForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; diff --git a/src/components/resume-builder/form-sections/ReferencesForm.tsx b/src/components/resume-builder/form-sections/ReferencesForm.tsx index b4c2510..cf8e4e1 100644 --- a/src/components/resume-builder/form-sections/ReferencesForm.tsx +++ b/src/components/resume-builder/form-sections/ReferencesForm.tsx @@ -1,9 +1,8 @@ -import React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Plus, Trash2, Users } from 'lucide-react'; +import { Plus, Trash2 } from 'lucide-react'; import { useResume } from '@/contexts/ResumeContext'; import { Reference } from '@/types/resume'; diff --git a/src/components/resume-builder/form-sections/SummaryForm.tsx b/src/components/resume-builder/form-sections/SummaryForm.tsx index 5d7276d..d1b8fe7 100644 --- a/src/components/resume-builder/form-sections/SummaryForm.tsx +++ b/src/components/resume-builder/form-sections/SummaryForm.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { useResume } from '@/contexts/ResumeContext'; diff --git a/src/components/resume-builder/logo.png b/src/components/resume-builder/logo.png new file mode 100644 index 0000000..8334c6d Binary files /dev/null and b/src/components/resume-builder/logo.png differ diff --git a/src/components/resume-builder/templateRegistry.ts b/src/components/resume-builder/templateRegistry.ts new file mode 100644 index 0000000..da73816 --- /dev/null +++ b/src/components/resume-builder/templateRegistry.ts @@ -0,0 +1,434 @@ +import { lazy, LazyExoticComponent } from 'react'; +import { TemplateMeta, TemplateCategory } from './types'; +import { TemplateProps } from './templates/types'; + +// Define the type for our template components +type TemplateComponent = (props: TemplateProps) => JSX.Element; +type LazyTemplate = LazyExoticComponent; + +// Helper function to create lazy-loaded template components with proper typing +const createLazyTemplate = ( + importFn: () => Promise, + exportName: keyof T & string +): LazyTemplate => { + return lazy(async () => { + const module = await importFn(); + return { default: module[exportName] }; + }); +}; + +// Lazy load all templates with proper typing +const templates = { + AcademicTemplate: createLazyTemplate( + () => import('./templates/AcademicTemplate'), + 'AcademicTemplate' + ), + BoldTemplate: createLazyTemplate( + () => import('./templates/BoldTemplate'), + 'BoldTemplate' + ), + CleanMinimalistTemplate: createLazyTemplate( + () => import('./templates/CleanMinimalistTemplate'), + 'CleanMinimalistTemplate' + ), + CleanProfessionalTemplate: createLazyTemplate( + () => import('./templates/CleanProfessionalTemplate'), + 'CleanProfessionalTemplate' + ), + CorporateTemplate: createLazyTemplate( + () => import('./templates/CorporateTemplate'), + 'CorporateTemplate' + ), + CreativeColorBlocksTemplate: createLazyTemplate( + () => import('./templates/CreativeColorBlocksTemplate'), + 'CreativeColorBlocksTemplate' + ), + CreativeTemplate: createLazyTemplate( + () => import('./templates/CreativeTemplate'), + 'CreativeTemplate' + ), + ElegantTemplate: createLazyTemplate( + () => import('./templates/ElegantTemplate'), + 'ElegantTemplate' + ), + ExecutiveTemplate: createLazyTemplate( + () => import('./templates/ExecutiveTemplate'), + 'ExecutiveTemplate' + ), + MinimalTemplate: createLazyTemplate( + () => import('./templates/MinimalTemplate'), + 'MinimalTemplate' + ), + MinimalistATS: createLazyTemplate( + () => import('./templates/MinimalistATS'), + 'MinimalistATS' + ), + MinimalistProfessionalTemplate: createLazyTemplate( + () => import('./templates/MinimalistProfessionalTemplate'), + 'MinimalistProfessionalTemplate' + ), + MinimalistSidebarTemplate: createLazyTemplate( + () => import('./templates/MinimalistSidebarTemplate'), + 'MinimalistSidebarTemplate' + ), + MinimalistTemplate: createLazyTemplate( + () => import('./templates/MinimalistTemplate'), + 'MinimalistTemplate' + ), + ModernDarkSidebarTemplate: createLazyTemplate( + () => import('./templates/ModernDarkSidebarTemplate'), + 'ModernDarkSidebarTemplate' + ), + ModernExecutiveTemplate: createLazyTemplate( + () => import('./templates/ModernExecutiveTemplate'), + 'ModernExecutiveTemplate' + ), + ModernProfessionalTemplate: createLazyTemplate( + () => import('./templates/ModernProfessionalTemplate'), + 'ModernProfessionalTemplate' + ), + ModernSidebarTemplate: createLazyTemplate( + () => import('./templates/ModernSidebarTemplate'), + 'ModernSidebarTemplate' + ), + ModernTemplate: createLazyTemplate( + () => import('./templates/ModernTemplate'), + 'ModernTemplate' + ), + ModernTimelineTemplate: createLazyTemplate( + () => import('./templates/ModernTimelineTemplate'), + 'ModernTimelineTemplate' + ), + ModernTwoColumn: createLazyTemplate( + () => import('./templates/ModernTwoColumn'), + 'ModernTwoColumn' + ), + ProfessionalBlueTemplate: createLazyTemplate( + () => import('./templates/ProfessionalBlueTemplate'), + 'ProfessionalBlueTemplate' + ), + ProfessionalSidebarTemplate: createLazyTemplate( + () => import('./templates/ProfessionalSidebarTemplate'), + 'ProfessionalSidebarTemplate' + ), + ProfessionalSummaryTemplate: createLazyTemplate( + () => import('./templates/ProfessionalSummaryTemplate'), + 'ProfessionalSummaryTemplate' + ), + ProfessionalTemplate: createLazyTemplate( + () => import('./templates/ProfessionalTemplate'), + 'ProfessionalTemplate' + ), + TechTemplate: createLazyTemplate( + () => import('./templates/TechTemplate'), + 'TechTemplate' + ), + TimelineTemplate: createLazyTemplate( + () => import('./templates/TimelineTemplate'), + 'TimelineTemplate' + ), +}; + +// Template metadata with descriptions and categories +export const templateRegistry: TemplateMeta[] = [ + { + id: 'clean-professional', + name: 'Clean Professional', + description: 'A clean and professional resume template with a balanced layout.', + category: TemplateCategory.PROFESSIONAL, + component: templates.CleanProfessionalTemplate, + tags: ['professional', 'clean', 'ats-friendly'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'modern-executive', + name: 'Modern Executive', + description: 'A modern executive resume with a sidebar for key information.', + category: TemplateCategory.MODERN, + component: templates.ModernExecutiveTemplate, + tags: ['modern', 'executive', 'sidebar'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'minimalist-ats', + name: 'Minimalist ATS', + description: 'Optimized for Applicant Tracking Systems with clean typography and structure.', + category: TemplateCategory.MINIMALIST, + component: templates.MinimalistATS, + tags: ['minimalist', 'ats-optimized', 'clean'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'creative-color-blocks', + name: 'Creative Color Blocks', + description: 'A creative template with color blocks for a modern, eye-catching design.', + category: TemplateCategory.CREATIVE, + component: templates.CreativeColorBlocksTemplate, + tags: ['creative', 'colorful', 'modern'], + atsFriendly: false, + lastUpdated: '2025-07-23', + }, + // Add more templates with their metadata + { + id: 'academic', + name: 'Academic', + description: 'Designed for academic and research positions with emphasis on publications.', + category: TemplateCategory.SPECIALIZED, + component: templates.AcademicTemplate, + tags: ['academic', 'research', 'publications'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'bold', + name: 'Bold', + description: 'A bold and confident design that makes a strong impression.', + category: TemplateCategory.CREATIVE, + component: templates.BoldTemplate, + tags: ['bold', 'modern', 'creative'], + atsFriendly: false, + lastUpdated: '2025-07-23', + }, + { + id: 'clean-minimalist', + name: 'Clean Minimalist', + description: 'A minimalist design focused on content with plenty of white space.', + category: TemplateCategory.MINIMALIST, + component: templates.CleanMinimalistTemplate, + tags: ['minimalist', 'clean', 'simple'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'corporate', + name: 'Corporate', + description: 'A traditional corporate resume with a professional layout.', + category: TemplateCategory.PROFESSIONAL, + component: templates.CorporateTemplate, + tags: ['corporate', 'professional', 'traditional'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'minimalist', + name: 'Minimalist', + description: 'A clean and minimalist resume template with a focus on readability.', + category: TemplateCategory.MINIMALIST, + component: templates.MinimalistTemplate, + tags: ['minimalist', 'clean', 'simple'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'modern-timeline', + name: 'Modern Timeline', + description: 'A modern design with a timeline layout for work experience.', + category: TemplateCategory.MODERN, + component: templates.ModernTimelineTemplate, + tags: ['modern', 'timeline', 'creative'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'professional-sidebar', + name: 'Professional Sidebar', + description: 'Professional layout with a sidebar for key information.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ProfessionalSidebarTemplate, + tags: ['professional', 'sidebar', 'modern'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'creative', + name: 'Creative', + description: 'A creative resume with a sidebar and timeline.', + category: TemplateCategory.CREATIVE, + component: templates.CreativeTemplate, + tags: ['creative', 'sidebar', 'timeline'], + atsFriendly: false, + lastUpdated: '2025-07-23', + }, + { + id: 'elegant', + name: 'Elegant', + description: 'An elegant and sophisticated resume template.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ElegantTemplate, + tags: ['elegant', 'professional', 'sophisticated'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'executive', + name: 'Executive', + description: 'A resume template for executives and managers.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ExecutiveTemplate, + tags: ['executive', 'professional', 'manager'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'minimal', + name: 'Minimal', + description: 'A minimal resume template with a clean and simple layout.', + category: TemplateCategory.MINIMALIST, + component: templates.MinimalTemplate, + tags: ['minimal', 'simple', 'clean'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'minimalist-professional', + name: 'Minimalist Professional', + description: 'A minimalist and professional resume template.', + category: TemplateCategory.MINIMALIST, + component: templates.MinimalistProfessionalTemplate, + tags: ['minimalist', 'professional', 'clean'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'minimalist-sidebar', + name: 'Minimalist Sidebar', + description: 'A minimalist resume with a sidebar for key information.', + category: TemplateCategory.MINIMALIST, + component: templates.MinimalistSidebarTemplate, + tags: ['minimalist', 'sidebar', 'clean'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'modern-dark-sidebar', + name: 'Modern Dark Sidebar', + description: 'A modern resume with a dark sidebar for a stylish look.', + category: TemplateCategory.MODERN, + component: templates.ModernDarkSidebarTemplate, + tags: ['modern', 'dark', 'sidebar'], + atsFriendly: false, + lastUpdated: '2025-07-23', + }, + { + id: 'modern', + name: 'Modern', + description: 'A modern and stylish resume template.', + category: TemplateCategory.MODERN, + component: templates.ModernTemplate, + tags: ['modern', 'stylish', 'creative'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'modern-two-column', + name: 'Modern Two Column', + description: 'A modern resume with a two-column layout.', + category: TemplateCategory.MODERN, + component: templates.ModernTwoColumn, + tags: ['modern', 'two-column', 'professional'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'professional-blue', + name: 'Professional Blue', + description: 'A professional resume with blue accents.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ProfessionalBlueTemplate, + tags: ['professional', 'blue', 'corporate'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'professional-summary', + name: 'Professional Summary', + description: 'A professional resume with a prominent summary section.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ProfessionalSummaryTemplate, + tags: ['professional', 'summary', 'corporate'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'professional', + name: 'Professional', + description: 'A classic and professional resume template.', + category: TemplateCategory.PROFESSIONAL, + component: templates.ProfessionalTemplate, + tags: ['professional', 'classic', 'corporate'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'tech', + name: 'Tech', + description: 'A resume template for tech professionals.', + category: TemplateCategory.SPECIALIZED, + component: templates.TechTemplate, + tags: ['tech', 'software', 'developer'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, + { + id: 'timeline', + name: 'Timeline', + description: 'A resume template with a timeline layout for experience.', + category: TemplateCategory.CREATIVE, + component: templates.TimelineTemplate, + tags: ['timeline', 'creative', 'modern'], + atsFriendly: true, + lastUpdated: '2025-07-23', + }, +]; + +// Helper functions +export const getTemplateById = (id: string) => { + return templateRegistry.find(template => template.id === id); +}; + +export const getTemplatesByCategory = (category: TemplateCategory) => { + return templateRegistry.filter(template => template.category === category); +}; + +export const getTemplatesByTag = (tag: string) => { + return templateRegistry.filter(template => + template.tags?.includes(tag.toLowerCase()) + ); +}; + +export const getATSFriendlyTemplates = () => { + return templateRegistry.filter(template => template.atsFriendly); +}; + +// Get the lazy-loaded component for a template by ID +export const getTemplateComponent = (templateId: string) => { + // Normalize the template ID to match the registry format (kebab-case to PascalCase) + const normalizedId = templateId + .toLowerCase() + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join('') + 'Template'; + + // Try to find the exact match first + if (normalizedId in templates) { + return templates[normalizedId as keyof typeof templates]; + } + + // Fallback to case-insensitive search + const componentKey = Object.keys(templates).find( + key => key.toLowerCase() === normalizedId.toLowerCase() + ); + + if (componentKey && componentKey in templates) { + return templates[componentKey as keyof typeof templates]; + } + + // If still not found, log a warning and return MinimalistTemplate as fallback + console.warn(`Template component for ID "${templateId}" not found, falling back to MinimalistTemplate`); + return templates.MinimalistTemplate || templates.CleanProfessionalTemplate; +}; + +export default templateRegistry; diff --git a/src/components/resume-builder/templates/AcademicTemplate.tsx b/src/components/resume-builder/templates/AcademicTemplate.tsx new file mode 100644 index 0000000..6e0f483 --- /dev/null +++ b/src/components/resume-builder/templates/AcademicTemplate.tsx @@ -0,0 +1,343 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Book, GraduationCap, Award, Briefcase, FileText } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function AcademicTemplate({ data }: TemplateProps) { + // Destructure with default values to prevent undefined errors + const { + personalInfo, + summary, + experience, + education, + skills, + certifications, + projects, + publications, + customization + } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Researcher / Academic'} +

+ +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + + Personal Website + + )} + {personalInfo?.linkedin && ( + + + LinkedIn + + )} + {personalInfo?.github && ( + + + GitHub + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ + Research Profile +

+

+ {summary} +

+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+ +
+
+

+ {edu.degree} +

+

+ {edu.school} +

+
+
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + {experience.some(exp => exp.company.toLowerCase().includes('research') || + exp.jobTitle.toLowerCase().includes('research')) + ? 'Research Experience' + : 'Professional Experience'} +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.authors.join(', ')} ({formatDate(pub.publicationDate)}) +

+

+ {pub.journal} +

+ {pub.doi && ( +

+ DOI: {pub.doi} +

+ )} + {pub.url && ( + + View Publication + + )} +
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Research Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+

+ Technologies: +

+

+ {project.technologies.join(', ')} +

+
+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications & Training +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Research Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/BoldTemplate.tsx b/src/components/resume-builder/templates/BoldTemplate.tsx new file mode 100644 index 0000000..f01cf67 --- /dev/null +++ b/src/components/resume-builder/templates/BoldTemplate.tsx @@ -0,0 +1,277 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function BoldTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info & Summary */} +
+
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Professional Title'} +

+
+ +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + linkedin.com/in/... +
+ )} + {personalInfo?.github && ( +
+ + github.com/... +
+ )} +
+
+ + {summary && ( +
+

+ + PROFILE +

+

+ {summary} +

+
+ )} +
+ + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.achievements && edu.achievements.length > 0 && ( +
    + {edu.achievements.map((achievement, idx) => ( +
  • + β€’ + {achievement} +
  • + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + SKILLS & EXPERTISE +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/CleanMinimalistTemplate.tsx b/src/components/resume-builder/templates/CleanMinimalistTemplate.tsx new file mode 100644 index 0000000..9e4277f --- /dev/null +++ b/src/components/resume-builder/templates/CleanMinimalistTemplate.tsx @@ -0,0 +1,238 @@ +import { Mail, Phone, MapPin, Linkedin, Globe } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function CleanMinimalistTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + const accentColor = customization?.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

+ {personalInfo?.fullName} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo?.location && ( + + + {personalInfo.location} + + )} + {personalInfo?.linkedin && ( + + + LinkedIn + + )} + {personalInfo?.website && ( + + + {personalInfo.website.replace(/^https?:\/\//, '')} + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

+ SUMMARY +

+

+ {summary} +

+
+ )} + +
+ {/* Left Column */} +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ PROFESSIONAL EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && `, ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ EDUCATION +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.fieldOfStudy} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ SKILLS +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ PROJECTS +

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + {tech} + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ PUBLICATIONS +

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+
+
+ ); +} diff --git a/src/components/resume-builder/templates/CleanProfessionalTemplate.tsx b/src/components/resume-builder/templates/CleanProfessionalTemplate.tsx new file mode 100644 index 0000000..6a184f1 --- /dev/null +++ b/src/components/resume-builder/templates/CleanProfessionalTemplate.tsx @@ -0,0 +1,229 @@ +import { Mail, Phone, MapPin, Globe, Briefcase, GraduationCap, Code, Award, Star, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function CleanProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo?.location && ( + + + {personalInfo.location} + + )} + {personalInfo?.website && ( + + + {personalInfo.website.replace(/^https?:\/\//, '')} + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ Professional Summary +

+

{summary}

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} β€’ {exp.location} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+

+ {edu.degree} +

+

+ {edu.school} β€’ {edu.location} +

+

+ {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Skills +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} β€’ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + {tech} + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/CorporateTemplate.tsx b/src/components/resume-builder/templates/CorporateTemplate.tsx new file mode 100644 index 0000000..722523c --- /dev/null +++ b/src/components/resume-builder/templates/CorporateTemplate.tsx @@ -0,0 +1,319 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function CorporateTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Professional Title'} +

+ +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + + {formatUrl(personalInfo.website)} + + )} + {personalInfo?.linkedin && ( + + + {formatUrl(personalInfo.linkedin).split('/').pop()} + + )} + {personalInfo?.github && ( + + + {formatUrl(personalInfo.github).split('/').pop()} + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ + Professional Summary +

+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ + {edu.graduationDate ? formatDate(edu.graduationDate): ''} + +
+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/CreativeColorBlocksTemplate.tsx b/src/components/resume-builder/templates/CreativeColorBlocksTemplate.tsx new file mode 100644 index 0000000..548ba83 --- /dev/null +++ b/src/components/resume-builder/templates/CreativeColorBlocksTemplate.tsx @@ -0,0 +1,256 @@ +import { Briefcase, GraduationCap, User, Code, Mail, Phone, MapPin, Globe, Award } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function CreativeColorBlocksTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + // Color variants for different sections + const colorVariants = { + primary: 'bg-blue-600 text-white', + secondary: 'bg-blue-100 text-blue-900', + accent: 'bg-yellow-400 text-gray-900', + light: 'bg-gray-50 text-gray-800', + dark: 'bg-gray-800 text-white', + }; + + return ( +
+ {/* Header with color block */} +
+
+
+ {personalInfo?.profileImage && ( +
+ {personalInfo.fullName +
+ )} +
+

{personalInfo?.fullName || 'Your Name'}

+

{personalInfo?.title || 'Professional Title'}

+
+
+
+
+ +
+
+ {/* Left Column */} +
+ {/* Contact */} +
+

CONTACT

+
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.graduationDate && ( +

+ {formatDate(edu.graduationDate)} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Summary */} + {summary && ( +
+

+ + ABOUT ME +

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

PROJECTS

+
+ {projects.map((project) => ( +
+

{project.name}

+ {project.url && ( + + {project.url.replace(/^https?:\/\//, '')} + + )} +

{project.description}

+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

PUBLICATIONS

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+
+
+
+ ); +} diff --git a/src/components/resume-builder/templates/CreativeTemplate.tsx b/src/components/resume-builder/templates/CreativeTemplate.tsx index 37bcbec..34d6a32 100644 --- a/src/components/resume-builder/templates/CreativeTemplate.tsx +++ b/src/components/resume-builder/templates/CreativeTemplate.tsx @@ -1,15 +1,8 @@ -import React from 'react'; -import { ResumeData } from '@/types/resume'; import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; - -interface TemplateProps { - data: ResumeData; -} +import { TemplateProps } from './types'; export function CreativeTemplate({ data }: TemplateProps) { - const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; - - const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, references, customization } = data || {}; const formatDate = (dateStr: string) => { if (!dateStr) return ''; @@ -23,85 +16,76 @@ export function CreativeTemplate({ data }: TemplateProps) { return url.replace(/^https?:\/\/(www\.)?/, ''); }; - const leftSections = visibleSections.filter(section => - ['personalInfo', 'skills', 'certifications', 'references'].includes(section.type) - ); - - const rightSections = visibleSections.filter(section => - ['summary', 'experience', 'education', 'projects'].includes(section.type) - ); + return ( +
+ {/* Left Sidebar */} +
+ {/* Personal Info */} +
+ {customization?.showProfileImage && personalInfo?.profileImage && ( + Profile + )} +

+ {personalInfo?.fullName || 'Your Name'} +

- const renderLeftSection = (section: typeof sections[0]) => { - switch (section.type) { - case 'personalInfo': - return ( -
- {customization.showProfileImage && personalInfo.profileImage && ( - Profile +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo?.github && ( +
+ + {formatUrl(personalInfo.github)} +
)} -

- {personalInfo.fullName || 'Your Name'} -

- -
- {personalInfo.email && ( -
- - {personalInfo.email} -
- )} - {personalInfo.phone && ( -
- - {personalInfo.phone} -
- )} - {personalInfo.location && ( -
- - {personalInfo.location} -
- )} - {personalInfo.website && ( -
- - {formatUrl(personalInfo.website)} -
- )} - {personalInfo.linkedin && ( -
- - {formatUrl(personalInfo.linkedin)} -
- )} - {personalInfo.github && ( -
- - {formatUrl(personalInfo.github)} -
- )} -
- ); +
- case 'skills': - if (skills.length === 0) return null; - return ( -
+ {/* Skills */} + {skills && skills.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily }}> Skills

{skills.map((skill) => (
- {skill.name} - {skill.level} + {skill.name} + {skill.level}
- ); + )} - case 'certifications': - if (certifications.length === 0) return null; - return ( -
+ {/* Certifications */} + {certifications && certifications.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily }}> Certifications

{certifications.map((cert) => (
-

+

{cert.name}

-

+

{cert.issuer}

-

+

{formatDate(cert.date)} {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`}

@@ -144,176 +127,167 @@ export function CreativeTemplate({ data }: TemplateProps) { ))}
- ); + )} - case 'references': - if (references.length === 0) return null; - return ( -
+ {/* References */} + {references && references.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily }}> References

{references.map((ref) => (
-

+

{ref.name}

-

+

{ref.title}

-

+

{ref.company}

-

+

{ref.relationship}

-

+

{ref.email} β€’ {ref.phone}

))}
- ); - - default: - return null; - } - }; + )} +
- const renderRightSection = (section: typeof sections[0]) => { - switch (section.type) { - case 'summary': - if (!summary) return null; - return ( -
+ {/* Right Content */} +
+ {/* Summary */} + {summary && ( +

+ style={{ fontFamily: customization?.fontFamily, color: customization?.accentColor, borderColor: customization?.accentColor }}> Professional Summary

-

+

{summary}

- ); + )} - case 'experience': - if (experience.length === 0) return null; - return ( -
+ {/* Experience */} + {experience && experience.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, color: customization?.accentColor, borderColor: customization?.accentColor }}> Work Experience

{experience.map((exp, index) => (
+ style={{ backgroundColor: customization?.accentColor }}>
{index < experience.length - 1 && (
+ style={{ backgroundColor: customization?.accentColor, opacity: 0.3 }}>
)}
-

+

{exp.jobTitle}

-

+

{exp.company}{exp.location && `, ${exp.location}`}

+ style={{ fontFamily: customization?.fontFamily }}> {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)}
    {exp.description.filter(desc => desc.trim()).map((desc, descIndex) => ( -
  • {desc}
  • +
  • {desc}
  • ))}
))}
- ); + )} - case 'education': - if (education.length === 0) return null; - return ( -
+ {/* Education */} + {education && education.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, color: customization?.accentColor, borderColor: customization?.accentColor }}> Education

{education.map((edu, index) => (
+ style={{ backgroundColor: customization?.accentColor }}>
{index < education.length - 1 && (
+ style={{ backgroundColor: customization?.accentColor, opacity: 0.3 }}>
)}
-

+

{edu.degree}

-

+

{edu.school}{edu.location && `, ${edu.location}`}

{edu.gpa && ( -

+

GPA: {edu.gpa}

)}
- {formatDate(edu.graduationDate)} + style={{ fontFamily: customization?.fontFamily }}> + {edu.graduationDate ? formatDate(edu.graduationDate) : ''}
))}
- ); + )} - case 'projects': - if (projects.length === 0) return null; - return ( -
+ {/* Projects */} + {projects && projects.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, color: customization?.accentColor, borderColor: customization?.accentColor }}> Projects

{projects.map((project, index) => (
+ style={{ backgroundColor: customization?.accentColor }}>
{index < projects.length - 1 && (
+ style={{ backgroundColor: customization?.accentColor, opacity: 0.3 }}>
)}
-

+

{project.name}

+ style={{ fontFamily: customization?.fontFamily }}> {formatDate(project.startDate)} {project.endDate && ` - ${formatDate(project.endDate)}`}
-

+

{project.description}

{project.technologies.length > 0 && ( @@ -322,7 +296,7 @@ export function CreativeTemplate({ data }: TemplateProps) { {tech} @@ -330,7 +304,7 @@ export function CreativeTemplate({ data }: TemplateProps) {
)} {project.url && ( -

+

{formatUrl(project.url)}

)} @@ -338,23 +312,38 @@ export function CreativeTemplate({ data }: TemplateProps) { ))}
- ); + )} - default: - return null; - } - }; - - return ( -
- {/* Left Sidebar */} -
- {leftSections.map(renderLeftSection)} -
- - {/* Right Content */} -
- {rightSections.map(renderRightSection)} + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ Publications +

+
+ {publications.map((pub, index) => ( +
+
+ {index < publications.length - 1 && ( +
+ )} +

+ {pub.title} +

+

+ {pub.journal} +

+

+ {formatDate(pub.publicationDate)} +

+
+ ))} +
+
+ )}
); diff --git a/src/components/resume-builder/templates/ElegantTemplate.tsx b/src/components/resume-builder/templates/ElegantTemplate.tsx new file mode 100644 index 0000000..651c3c4 --- /dev/null +++ b/src/components/resume-builder/templates/ElegantTemplate.tsx @@ -0,0 +1,284 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Briefcase, GraduationCap, Code, Award, User, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + + +export function ElegantTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info & Summary */} +
+
+
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+
+

+ {personalInfo?.title || 'Professional Title'} +

+
+ +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + linkedin.com/in/... +
+ )} +
+
+ + {summary && ( +
+

+ + Profile +

+

+ {summary} +

+
+ )} +
+
+ + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/ExecutiveTemplate.tsx b/src/components/resume-builder/templates/ExecutiveTemplate.tsx new file mode 100644 index 0000000..631d80c --- /dev/null +++ b/src/components/resume-builder/templates/ExecutiveTemplate.tsx @@ -0,0 +1,285 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Award, Briefcase, GraduationCap, Code, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ExecutiveTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo?.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+ + {/* Summary */} + {summary && ( +
+
+
+

+ Executive Profile +

+
+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+
+ +

+ Professional Experience +

+
+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+
+ +

+ Education +

+
+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+
+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+
+ +

+ Core Competencies +

+
+
+ {skills.map((skill) => ( +
+ β€’ + + {skill.name}{skill.level ? ` (${skill.level})` : ''} + +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+
+ +

+ Certifications +

+
+
+ {certifications.map((cert) => ( +
+
+

+ {cert.name} +

+ + {cert.date && formatDate(cert.date)} + +
+ {cert.issuer && ( +

+ {cert.issuer} +

+ )} +
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+
+ +

+ Key Projects +

+
+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ {project.url && ( + + View Project + + )} +
+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +

+ {project.description} +

+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+
+ +

+ Publications +

+
+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/MinimalTemplate.tsx b/src/components/resume-builder/templates/MinimalTemplate.tsx index 093bd22..dfbfe97 100644 --- a/src/components/resume-builder/templates/MinimalTemplate.tsx +++ b/src/components/resume-builder/templates/MinimalTemplate.tsx @@ -1,16 +1,21 @@ -import React from 'react'; -import { ResumeData } from '@/types/resume'; -import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; - -interface TemplateProps { - data: ResumeData; -} +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Award } from 'lucide-react'; +import { TemplateProps } from './types'; export function MinimalTemplate({ data }: TemplateProps) { - const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + // Destructure with default values to prevent undefined errors + const { + personalInfo, + summary, + experience, + education, + skills, + certifications, + projects, + publications, + references, + customization + } = data || {}; - const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); - const formatDate = (dateStr: string) => { if (!dateStr) return ''; const date = new Date(dateStr + '-01'); @@ -23,90 +28,87 @@ export function MinimalTemplate({ data }: TemplateProps) { return url.replace(/^https?:\/\/(www\.)?/, ''); }; - const renderSection = (section: typeof sections[0]) => { - switch (section.type) { - case 'personalInfo': - return ( -
-
- {customization.showProfileImage && personalInfo.profileImage && ( - Profile + return ( +
+
+ {/* Personal Info */} +
+
+ {customization?.showProfileImage && personalInfo?.profileImage && ( + Profile + )} +

+ {personalInfo?.fullName || 'Your Name'} +

+ +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
)} -

- {personalInfo.fullName || 'Your Name'} -

- -
- {personalInfo.email && ( +
+ + {(personalInfo?.website || personalInfo?.linkedin || personalInfo?.github) && ( +
+ {personalInfo?.website && (
- - {personalInfo.email} + + {formatUrl(personalInfo.website)}
)} - {personalInfo.phone && ( + {personalInfo?.linkedin && (
- - {personalInfo.phone} + + {formatUrl(personalInfo.linkedin)}
)} - {personalInfo.location && ( + {personalInfo?.github && (
- - {personalInfo.location} + + {formatUrl(personalInfo.github)}
)}
- - {(personalInfo.website || personalInfo.linkedin || personalInfo.github) && ( -
- {personalInfo.website && ( -
- - {formatUrl(personalInfo.website)} -
- )} - {personalInfo.linkedin && ( -
- - {formatUrl(personalInfo.linkedin)} -
- )} - {personalInfo.github && ( -
- - {formatUrl(personalInfo.github)} -
- )} -
- )} -
+ )}
- ); +
- case 'summary': - if (!summary) return null; - return ( -
+ {/* Summary */} + {summary && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Professional Summary

-

+

{summary}

- ); + )} - case 'experience': - if (experience.length === 0) return null; - return ( -
+ {/* Experience */} + {experience && experience.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Work Experience

@@ -114,34 +116,33 @@ export function MinimalTemplate({ data }: TemplateProps) {
-

+

{exp.jobTitle}

-

+

{exp.company}{exp.location && `, ${exp.location}`}

-
+
{formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)}
    {exp.description.filter(desc => desc.trim()).map((desc, index) => ( -
  • {desc}
  • +
  • {desc}
  • ))}
))}
- ); + )} - case 'education': - if (education.length === 0) return null; - return ( -
+ {/* Education */} + {education && education.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Education

@@ -149,69 +150,67 @@ export function MinimalTemplate({ data }: TemplateProps) {
-

+

{edu.degree}

-

+

{edu.school}{edu.location && `, ${edu.location}`}

{edu.gpa && ( -

+

GPA: {edu.gpa}

)}
-
- {formatDate(edu.graduationDate)} +
+ {edu.graduationDate ? formatDate(edu.graduationDate) : ''}
))}
- ); + )} - case 'skills': - if (skills.length === 0) return null; - return ( -
+ {/* Skills */} + {skills && skills.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Skills

{skills.map((skill) => (
- {skill.name} - + {skill.name} + {skill.level}
))}
- ); + )} - case 'certifications': - if (certifications.length === 0) return null; - return ( -
-

+ {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ Certifications

-
+
{certifications.map((cert) => (
-

+

{cert.name}

-

+

{cert.issuer}

-
+
{formatDate(cert.date)} {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`}
@@ -219,29 +218,28 @@ export function MinimalTemplate({ data }: TemplateProps) { ))}
- ); + )} - case 'projects': - if (projects.length === 0) return null; - return ( -
+ {/* Projects */} + {projects && projects.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Projects

{projects.map((project) => (
-

+

{project.name}

-
+
{formatDate(project.startDate)} {project.endDate && ` - ${formatDate(project.endDate)}`}
-

+

{project.description}

{project.technologies.length > 0 && ( @@ -251,9 +249,9 @@ export function MinimalTemplate({ data }: TemplateProps) { key={index} className="px-2 py-1 text-xs rounded" style={{ - backgroundColor: customization.accentColor + '20', - color: customization.accentColor, - fontFamily: customization.fontFamily + backgroundColor: customization?.accentColor + '20', + color: customization?.accentColor, + fontFamily: customization?.fontFamily }} > {tech} @@ -262,7 +260,7 @@ export function MinimalTemplate({ data }: TemplateProps) {
)} {project.url && ( -

+

{formatUrl(project.url)}

)} @@ -270,14 +268,13 @@ export function MinimalTemplate({ data }: TemplateProps) { ))}
- ); + )} - case 'references': - if (references.length === 0) return null; - return ( -
+ {/* References */} + {references && references.length > 0 && ( +

+ style={{ fontFamily: customization?.fontFamily, borderColor: customization?.accentColor }}> Professional References

@@ -285,17 +282,17 @@ export function MinimalTemplate({ data }: TemplateProps) {
-

+

{ref.name}

-

+

{ref.title} at {ref.company}

-

+

{ref.relationship}

-
+
{ref.email}
{ref.phone}
@@ -304,16 +301,31 @@ export function MinimalTemplate({ data }: TemplateProps) { ))}
- ); - - default: - return null; - } - }; + )} - return ( -
- {visibleSections.map(renderSection)} + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
); } \ No newline at end of file diff --git a/src/components/resume-builder/templates/MinimalistATS.tsx b/src/components/resume-builder/templates/MinimalistATS.tsx new file mode 100644 index 0000000..b4fc4ff --- /dev/null +++ b/src/components/resume-builder/templates/MinimalistATS.tsx @@ -0,0 +1,158 @@ +import { TemplateProps } from './types'; + +export function MinimalistATS({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo?.fullName}

+ {personalInfo?.title &&

{personalInfo.title}

} +
+ {personalInfo?.email && {personalInfo.email}} + {personalInfo?.phone && {personalInfo.phone}} + {personalInfo?.location && {personalInfo.location}} + {personalInfo?.linkedin && linkedin.com/in/username} +
+
+ + {/* Summary */} + {summary && ( +
+

SUMMARY

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

EXPERIENCE

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

EDUCATION

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

PROJECTS

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

PUBLICATIONS

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} β€’ {formatDate(pub.publicationDate)} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/MinimalistProfessionalTemplate.tsx b/src/components/resume-builder/templates/MinimalistProfessionalTemplate.tsx new file mode 100644 index 0000000..4c1c5cf --- /dev/null +++ b/src/components/resume-builder/templates/MinimalistProfessionalTemplate.tsx @@ -0,0 +1,310 @@ +import { TemplateProps } from './types'; + +export function MinimalistProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+
+

+ {personalInfo?.title || 'Professional Title'} +

+ +
+ {personalInfo?.email && ( + + {personalInfo.email} + + )} + {personalInfo?.phone && ( + {personalInfo.phone} + )} + {personalInfo?.location && ( + {personalInfo.location} + )} + {personalInfo?.website && ( + + {formatUrl(personalInfo.website)} + + )} + {personalInfo?.linkedin && ( + + LinkedIn + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+
+

+ Profile +

+

+ {summary} +

+
+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+
+

+ Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+
+

+ Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+
+

+ Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+
+

+ Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} + {cert.date && ` β€’ ${formatDate(cert.date)}`} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+
+

+ Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/MinimalistSidebarTemplate.tsx b/src/components/resume-builder/templates/MinimalistSidebarTemplate.tsx new file mode 100644 index 0000000..2fa2f38 --- /dev/null +++ b/src/components/resume-builder/templates/MinimalistSidebarTemplate.tsx @@ -0,0 +1,222 @@ +import { Mail, Phone, MapPin, Globe, Briefcase, GraduationCap, User, Award, BookOpen, Star } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function MinimalistSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo?.profileImage && ( +
+ {personalInfo.fullName +
+ )} + +
+
+

CONTACT

+
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {personalInfo.website.replace(/^https?:\/\//, '')} +
+ )} +
+
+ + {skills && skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Professional Title'} +

+ + {summary && ( +
+

+ + PROFILE +

+

{summary}

+
+ )} + + {experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ )} + + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {certifications && certifications.length > 0 && ( +
+

+ + CERTIFICATIONS +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} + + {projects && projects.length > 0 && ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +
+ ))} +
+
+ )} + + {publications && publications.length > 0 && ( +
+

+ + PUBLICATIONS +

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} β€’ {formatDate(pub.publicationDate)} +

+
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/src/components/resume-builder/templates/MinimalistTemplate.tsx b/src/components/resume-builder/templates/MinimalistTemplate.tsx new file mode 100644 index 0000000..3ebfd1e --- /dev/null +++ b/src/components/resume-builder/templates/MinimalistTemplate.tsx @@ -0,0 +1,261 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function MinimalistTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info */} +
+

+ {personalInfo?.fullName || 'Your Name'} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} + +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo?.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ About +

+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ Experience +

+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company}{exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ Education +

+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+

+ {edu.school}{edu.location && ` β€’ ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} + {cert.date && ` β€’ ${formatDate(cert.date)}`} +

+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ {project.url && ( + + View Project + + )} +
+ {project.technologies && project.technologies.length > 0 && ( +

+ {project.technologies.join(' β€’ ')} +

+ )} +

+ {project.description} +

+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/ModernDarkSidebarTemplate.tsx b/src/components/resume-builder/templates/ModernDarkSidebarTemplate.tsx new file mode 100644 index 0000000..4aa7e46 --- /dev/null +++ b/src/components/resume-builder/templates/ModernDarkSidebarTemplate.tsx @@ -0,0 +1,229 @@ +import { Mail, Phone, MapPin, Globe, Briefcase, GraduationCap, User, BookOpen, Star } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernDarkSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo?.profileImage && ( +
+ {personalInfo.fullName +
+ )} + +
+
+

CONTACT

+
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + )} +
+
+ + {skills && skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} + + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+
{cert.name}
+
{cert.issuer}
+ {cert.date && ( +
+ {new Date(cert.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short' })} +
+ )} +
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Professional Title'} +

+
+ + {summary && ( +
+

+ + ABOUT ME +

+

{summary}

+
+ )} + + {experience.length > 0 && ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {projects && projects.length > 0 && ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +
+ ))} +
+
+ )} + + {publications && publications.length > 0 && ( +
+

+ + PUBLICATIONS +

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} β€’ {formatDate(pub.publicationDate)} +

+
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/src/components/resume-builder/templates/ModernExecutiveTemplate.tsx b/src/components/resume-builder/templates/ModernExecutiveTemplate.tsx new file mode 100644 index 0000000..01abbb1 --- /dev/null +++ b/src/components/resume-builder/templates/ModernExecutiveTemplate.tsx @@ -0,0 +1,262 @@ +import { Mail, Phone, MapPin, Linkedin, Globe, Briefcase, GraduationCap, Star, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernExecutiveTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + const accentColor = customization?.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Sidebar */} +
+ {/* Profile Image */} + {personalInfo?.profileImage && ( +
+ {personalInfo.fullName} +
+ )} + + {/* Contact */} +
+

+ Contact +

+
    + {personalInfo?.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo?.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo?.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo?.linkedin && ( +
  • + + LinkedIn +
  • + )} + {personalInfo?.website && ( +
  • + + {personalInfo.website.replace(/^https?:\/\//, '')} +
  • + )} +
+
+ + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ Skills +

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications.length > 0 && ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+ + {/* Main Content */} +
+ {/* Header */} +
+

+ {personalInfo?.fullName} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ + {/* Summary */} + {summary && ( +
+

+ Professional Summary +

+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+

{exp.jobTitle}

+
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.fieldOfStudy} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +

+ Technologies: {project.technologies.join(', ')} +

+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/src/components/resume-builder/templates/ModernProfessionalTemplate.tsx b/src/components/resume-builder/templates/ModernProfessionalTemplate.tsx new file mode 100644 index 0000000..0fe9154 --- /dev/null +++ b/src/components/resume-builder/templates/ModernProfessionalTemplate.tsx @@ -0,0 +1,367 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code, Award, User, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo?.fullName || 'Your Name'} +

+

+ {personalInfo?.title || 'Professional Title'} +

+ +
+ +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + + Website + + )} + {personalInfo?.linkedin && ( + + + LinkedIn + + )} + {personalInfo?.github && ( + + + GitHub + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ + Professional Profile +

+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} + {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Skills & Expertise +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/ModernSidebarTemplate.tsx b/src/components/resume-builder/templates/ModernSidebarTemplate.tsx new file mode 100644 index 0000000..0ca1ec2 --- /dev/null +++ b/src/components/resume-builder/templates/ModernSidebarTemplate.tsx @@ -0,0 +1,283 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, User, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const renderSidebar = () => ( +
+ {personalInfo?.profileImage && ( +
+ {personalInfo.fullName} +
+ )} + +
+
+

CONTACT

+
+ {personalInfo?.email && ( + + )} + {personalInfo?.phone && ( + + )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( + + )} + {personalInfo?.linkedin && ( + + )} + {personalInfo?.github && ( + + )} +
+
+ + {skills && skills.length > 0 && ( +
+

SKILLS

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + + {skill.level === 'Expert' ? 'β˜…β˜…β˜…β˜…β˜…' : + skill.level === 'Advanced' ? 'β˜…β˜…β˜…β˜…β˜†' : + skill.level === 'Intermediate' ? 'β˜…β˜…β˜…β˜†β˜†' : 'β˜…β˜…β˜†β˜†β˜†'} + +
+
+ ))} +
+
+ )} + + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

{cert.issuer}

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` β€’ Expires: ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+
+ ); + + const renderMainContent = () => ( +
+
+

+ {personalInfo?.fullName} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ + {summary && ( +
+

+ + PROFILE +

+

{summary}

+
+ )} + + {experience && experience.length > 0 && ( +
+

+ + EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {projects && projects.length > 0 && ( +
+

+ + PROJECTS +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

+ + {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`} + +
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, idx) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} + + {publications && publications.length > 0 && ( +
+

+ + PUBLICATIONS +

+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); + + return ( +
+ {renderSidebar()} + {renderMainContent()} +
+ ); +} diff --git a/src/components/resume-builder/templates/ModernTemplate.tsx b/src/components/resume-builder/templates/ModernTemplate.tsx index b338344..3faf50a 100644 --- a/src/components/resume-builder/templates/ModernTemplate.tsx +++ b/src/components/resume-builder/templates/ModernTemplate.tsx @@ -1,16 +1,9 @@ -import React from 'react'; -import { ResumeData } from '@/types/resume'; import { Mail, Phone, MapPin, Globe, Linkedin, Github } from 'lucide-react'; - -interface TemplateProps { - data: ResumeData; -} +import { TemplateProps } from './types'; export function ModernTemplate({ data }: TemplateProps) { - const { personalInfo, summary, experience, education, skills, certifications, projects, references, sections, customization } = data; + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, references, customization } = data || {}; - const visibleSections = sections.filter(section => section.isVisible).sort((a, b) => a.order - b.order); - const formatDate = (dateStr: string) => { if (!dateStr) return ''; const date = new Date(dateStr + '-01'); @@ -23,317 +16,324 @@ export function ModernTemplate({ data }: TemplateProps) { return url.replace(/^https?:\/\/(www\.)?/, ''); }; - const renderSection = (section: typeof sections[0]) => { - switch (section.type) { - case 'personalInfo': - return ( -
-
- {customization.showProfileImage && personalInfo.profileImage && ( - Profile + return ( +
+ {/* Personal Info */} +
+
+ {customization?.showProfileImage && personalInfo?.profileImage && ( + Profile + )} +
+

+ {personalInfo?.fullName || 'Your Name'} +

+ +
+ {personalInfo?.email && ( +
+ + {personalInfo.email} +
)} -
-

- {personalInfo.fullName || 'Your Name'} -

- -
- {personalInfo.email && ( -
- - {personalInfo.email} -
- )} - {personalInfo.phone && ( -
- - {personalInfo.phone} -
- )} - {personalInfo.location && ( -
- - {personalInfo.location} -
- )} - {personalInfo.website && ( -
- - {formatUrl(personalInfo.website)} -
- )} - {personalInfo.linkedin && ( -
- - {formatUrl(personalInfo.linkedin)} -
- )} - {personalInfo.github && ( -
- - {formatUrl(personalInfo.github)} -
- )} + {personalInfo?.phone && ( +
+ + {personalInfo.phone}
-
+ )} + {personalInfo?.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo?.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo?.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo?.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )}
- ); +
+
- case 'summary': - if (!summary) return null; - return ( -
-
-
-

- Professional Summary -

-
-

- {summary} -

+ {/* Summary */} + {summary && ( +
+
+
+

+ Professional Summary +

- ); +

+ {summary} +

+
+ )} - case 'experience': - if (experience.length === 0) return null; - return ( -
-
-
-

- Work Experience -

-
-
- {experience.map((exp) => ( -
-
-
-
-

- {exp.jobTitle} -

-

- {exp.company}{exp.location && `, ${exp.location}`} -

-
-
- {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} -
+ {/* Experience */} + {experience && experience.length > 0 && ( +
+
+
+

+ Work Experience +

+
+
+ {experience.map((exp) => ( +
+
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company}{exp.location && `, ${exp.location}`} +

+
+
+ {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)}
-
    - {exp.description.filter(desc => desc.trim()).map((desc, index) => ( -
  • {desc}
  • - ))} -
- ))} -
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))}
- ); +
+ )} - case 'education': - if (education.length === 0) return null; - return ( -
-
-
-

- Education -

-
-
- {education.map((edu) => ( -
-
-
-
-

- {edu.degree} -

-

- {edu.school}{edu.location && `, ${edu.location}`} + {/* Education */} + {education && education.length > 0 && ( +

+
+
+

+ Education +

+
+
+ {education.map((edu) => ( +
+
+
+
+

+ {edu.degree} +

+

+ {edu.school}{edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa}

- {edu.gpa && ( -

- GPA: {edu.gpa} -

- )} -
-
- {formatDate(edu.graduationDate)} -
+ )} +
+
+ {edu.graduationDate ? formatDate(edu.graduationDate) : ''}
- ))} -
+
+ ))}
- ); +
+ )} - case 'skills': - if (skills.length === 0) return null; - return ( -
-
-
-

- Skills -

-
-
- {skills.map((skill) => ( -
- {skill.name} - - {skill.level} - + {/* Skills */} + {skills && skills.length > 0 && ( +
+
+
+

+ Skills +

+
+
+ {skills.map((skill) => ( +
+ {skill.name} + + {skill.level} + +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+
+
+

+ Certifications +

+
+
+ {certifications.map((cert) => ( +
+
+

+ {cert.name} +

+

+ {cert.issuer} +

- ))} -
+
+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +
+
+ ))}
- ); +
+ )} - case 'certifications': - if (certifications.length === 0) return null; - return ( -
-
-
-

- Certifications -

-
-
- {certifications.map((cert) => ( -
-
-

- {cert.name} -

-

- {cert.issuer} -

-
+ {/* Projects */} + {projects && projects.length > 0 && ( +
+
+
+

+ Projects +

+
+
+ {projects.map((project) => ( +
+
+

+ {project.name} +

- {formatDate(cert.date)} - {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} + style={{ backgroundColor: customization?.accentColor, fontFamily: customization?.fontFamily }}> + {formatDate(project.startDate)} + {project.endDate && ` - ${formatDate(project.endDate)}`}
- ))} -
+

+ {project.description} +

+ {project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, index) => ( + + {tech} + + ))} +
+ )} + {project.url && ( +

+ {formatUrl(project.url)} +

+ )} +
+ ))}
- ); +
+ )} - case 'projects': - if (projects.length === 0) return null; - return ( -
-
-
-

- Projects -

-
-
- {projects.map((project) => ( -
-
-

- {project.name} + {/* References */} + {references && references.length > 0 && ( +
+
+
+

+ Professional References +

+
+
+ {references.map((ref) => ( +
+
+
+

+ {ref.name}

-
- {formatDate(project.startDate)} - {project.endDate && ` - ${formatDate(project.endDate)}`} -
-
-

- {project.description} -

- {project.technologies.length > 0 && ( -
- {project.technologies.map((tech, index) => ( - - {tech} - - ))} -
- )} - {project.url && ( -

- {formatUrl(project.url)} +

+ {ref.title} at {ref.company}

- )} +

+ {ref.relationship} +

+
+
+
{ref.email}
+
{ref.phone}
+
- ))} -
+
+ ))}

- ); +
+ )} - case 'references': - if (references.length === 0) return null; - return ( -
-
-
-

- Professional References -

-
-
- {references.map((ref) => ( -
-
-
-

- {ref.name} -

-

- {ref.title} at {ref.company} -

-

- {ref.relationship} -

-
-
-
{ref.email}
-
{ref.phone}
-
-
+ {/* Publications */} + {publications && publications.length > 0 && ( +
+
+
+

+ Publications +

+
+
+ {publications.map((pub) => ( +
+

+ {pub.title} +

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+ ))}
- ))}
-
- ); - - default: - return null; - } - }; - - return ( -
- {visibleSections.map(renderSection)} + )}
); } \ No newline at end of file diff --git a/src/components/resume-builder/templates/ModernTimelineTemplate.tsx b/src/components/resume-builder/templates/ModernTimelineTemplate.tsx new file mode 100644 index 0000000..b95a117 --- /dev/null +++ b/src/components/resume-builder/templates/ModernTimelineTemplate.tsx @@ -0,0 +1,269 @@ +import { Mail, Phone, MapPin, Briefcase, GraduationCap, Award, Star, BookOpen } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernTimelineTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + const accentColor = customization?.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

+ {personalInfo?.fullName} +

+ {personalInfo?.title && ( +

+ {personalInfo.title} +

+ )} +
+ {personalInfo?.email && ( + + + {personalInfo.email} + + )} + {personalInfo?.phone && ( + + + {personalInfo.phone} + + )} + {personalInfo?.location && ( + + + {personalInfo.location} + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

About Me

+

{summary}

+
+ )} + + {/* Timeline Section */} +
+ {/* Timeline Line */} +
+ +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.fieldOfStudy && ( +

+ {edu.fieldOfStudy} +

+ )} + {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ ))} +
+
+ )} +
+
+ + {/* Skills & Certifications */} +
+ {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Key Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+ + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + {tech} + ))} +
+ )} +
+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+
+
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/ModernTwoColumn.tsx b/src/components/resume-builder/templates/ModernTwoColumn.tsx new file mode 100644 index 0000000..d316fe9 --- /dev/null +++ b/src/components/resume-builder/templates/ModernTwoColumn.tsx @@ -0,0 +1,248 @@ +import { Mail, Phone, MapPin, Linkedin } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ModernTwoColumn({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + const accentColor = customization?.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Left Column */} +
+ {/* Profile */} +
+
+ {personalInfo?.profileImage ? ( + {personalInfo.fullName} + ) : ( +
+ + {personalInfo?.fullName.split(' ').map(n => n[0]).join('')} + +
+ )} +
+

{personalInfo?.fullName}

+ {personalInfo?.title && ( +

{personalInfo.title}

+ )} +
+ + {/* Contact */} +
+

Contact

+
    + {personalInfo?.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo?.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo?.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo?.linkedin && ( +
  • + + LinkedIn +
  • + )} +
+
+ + {/* Skills */} + {skills && skills.length > 0 && ( +
+

Skills

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Summary */} + {summary && ( +
+

+ About Me +

+

{summary}

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+

{exp.jobTitle}

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ Education +

+
+ {education.map((edu) => ( +
+
+
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ + {formatDate(edu.graduationDate)} + +
+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ Projects +

+
+ {projects.map((project) => ( +
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + {tech} + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ Publications +

+
+ {publications.map((pub) => ( +
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+ + View Publication + +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/src/components/resume-builder/templates/ProfessionalBlueTemplate.tsx b/src/components/resume-builder/templates/ProfessionalBlueTemplate.tsx new file mode 100644 index 0000000..475d5d3 --- /dev/null +++ b/src/components/resume-builder/templates/ProfessionalBlueTemplate.tsx @@ -0,0 +1,130 @@ +import { Briefcase, GraduationCap, Code, User } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ProfessionalBlueTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Personal Info */} +
+
+ {personalInfo.profileImage && ( +
+ {personalInfo.fullName +
+ )} +
+

{personalInfo.fullName || 'Your Name'}

+

{personalInfo.title || 'Professional Title'}

+
+
+
+ + {/* Summary */} + {summary && ( +
+

+ + PROFILE +

+

{summary}

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + +
+
+
+
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/ProfessionalSidebarTemplate.tsx b/src/components/resume-builder/templates/ProfessionalSidebarTemplate.tsx new file mode 100644 index 0000000..1b579fb --- /dev/null +++ b/src/components/resume-builder/templates/ProfessionalSidebarTemplate.tsx @@ -0,0 +1,175 @@ +import { Mail, Phone, MapPin, Linkedin, Briefcase, GraduationCap } from 'lucide-react'; +import { ResumeData } from '@/types/resume'; + +type TemplateProps = { + data: ResumeData; +}; + +export function ProfessionalSidebarTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills } = data; + const accentColor = data.customization.accentColor || '#2563eb'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Sidebar */} +
+ {/* Profile */} +
+ {personalInfo.profileImage ? ( +
+ {personalInfo.fullName} +
+ ) : ( +
+ + {personalInfo.fullName.split(' ').map(n => n[0]).join('')} + +
+ )} +

{personalInfo.fullName}

+ {personalInfo.title &&

{personalInfo.title}

} +
+ + {/* Contact */} +
+

Contact

+
    + {personalInfo.email && ( +
  • + + {personalInfo.email} +
  • + )} + {personalInfo.phone && ( +
  • + + {personalInfo.phone} +
  • + )} + {personalInfo.location && ( +
  • + + {personalInfo.location} +
  • + )} + {personalInfo.linkedin && ( +
  • + + LinkedIn +
  • + )} +
+
+ + {/* Skills */} + {skills.length > 0 && ( +
+

Skills

+
+ {skills.map((skill) => ( +
+
+ {skill.name} + {skill.level} +
+
+
+
+
+ ))} +
+
+ )} +
+ + {/* Main Content */} +
+ {/* Summary */} + {summary && ( +
+

+ Professional Summary +

+

{summary}

+
+ )} + + {/* Experience */} + {experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+

{edu.degree}

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+
+ {edu.relevantCourses ? edu.relevantCourses.join(', ') : ''} + {formatDate(edu.graduationDate)} +
+ {edu.gpa && ( +

GPA: {edu.gpa}

+ )} +
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/src/components/resume-builder/templates/ProfessionalSummaryTemplate.tsx b/src/components/resume-builder/templates/ProfessionalSummaryTemplate.tsx new file mode 100644 index 0000000..58f1bc7 --- /dev/null +++ b/src/components/resume-builder/templates/ProfessionalSummaryTemplate.tsx @@ -0,0 +1,189 @@ +import { ResumeData } from '@/types/resume'; +import { Briefcase, GraduationCap, User, Code, Mail, Phone, MapPin, Globe } from 'lucide-react'; + +interface TemplateProps { + data: ResumeData; +} + +export function ProfessionalSummaryTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications } = data; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo.fullName || 'Your Name'}

+

{personalInfo.title || 'Professional Title'}

+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( + + )} +
+
+ + {/* Summary */} + {summary && ( +
+

+ + PROFESSIONAL SUMMARY +

+

{summary}

+
+ )} + +
+ {/* Left Column */} +
+ {/* Experience */} + {experience.length > 0 && ( +
+

+ + WORK EXPERIENCE +

+
+ {experience.map((exp) => ( +
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education.length > 0 && ( +
+

+ + EDUCATION +

+
+ {education.map((edu) => ( +
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} + {edu.gpa && ` β€’ GPA: ${edu.gpa}`} +

+
+ ))} +
+
+ )} +
+ + {/* Right Column */} +
+ {/* Skills */} + {skills.length > 0 && ( +
+

+ + SKILLS +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

CERTIFICATIONS

+
+ {certifications.map((cert) => ( +
+
{cert.name}
+
{cert.issuer}
+ {cert.date && ( +
+ {new Date(cert.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short' })} +
+ )} +
+ ))} +
+
+ )} +
+
+
+ ); +} diff --git a/src/components/resume-builder/templates/ProfessionalTemplate.tsx b/src/components/resume-builder/templates/ProfessionalTemplate.tsx new file mode 100644 index 0000000..4d0c6fe --- /dev/null +++ b/src/components/resume-builder/templates/ProfessionalTemplate.tsx @@ -0,0 +1,174 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Briefcase, GraduationCap, Code } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function ProfessionalTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + const formatUrl = (url: string) => { + if (!url) return ''; + return url.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''); + }; + + return ( +
+ {/* Personal Info */} +
+
+

+ {personalInfo.fullName || 'Your Name'} +

+ {personalInfo.title && ( +

+ {personalInfo.title} +

+ )} +
+ +
+ {personalInfo.email && ( +
+ + {personalInfo.email} +
+ )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.website && ( +
+ + {formatUrl(personalInfo.website)} +
+ )} + {personalInfo.linkedin && ( +
+ + {formatUrl(personalInfo.linkedin)} +
+ )} + {personalInfo.github && ( +
+ + {formatUrl(personalInfo.github)} +
+ )} +
+
+ + {/* Summary */} + {summary && ( +
+
+
+

+ Professional Summary +

+
+

+ {summary} +

+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+
+ +

+ Professional Experience +

+
+
+ {experience.map((exp) => ( +
+
+

+ {exp.jobTitle} +

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
+ + {exp.company}{exp.location && ` β€’ ${exp.location}`} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • {desc}
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+
+ +

+ Education +

+
+
+ {education.map((edu) => ( +
+
+

+ {edu.degree} +

+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+
+ + {edu.school}{edu.location && ` β€’ ${edu.location}`} + +
+
+ ))} +
+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+
+ +

+ Skills +

+
+
+ {skills.map(skill => ( + {skill.name} + ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/TechTemplate.tsx b/src/components/resume-builder/templates/TechTemplate.tsx new file mode 100644 index 0000000..f0ced95 --- /dev/null +++ b/src/components/resume-builder/templates/TechTemplate.tsx @@ -0,0 +1,293 @@ +import { Mail, Phone, MapPin, Globe, Linkedin, Github, Code, Cpu, Award, Briefcase, GraduationCap } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function TechTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, customization } = data || {}; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + }; + + return ( +
+ {/* Personal Info */} +
+
+
+

+ {personalInfo.fullName || 'Your Name'} +

+

+ {personalInfo.title || 'Software Engineer'} +

+
+
+ {personalInfo.email && ( + + + {personalInfo.email} + + )} + {personalInfo.phone && ( +
+ + {personalInfo.phone} +
+ )} + {personalInfo.location && ( +
+ + {personalInfo.location} +
+ )} + {personalInfo.linkedin && ( + + + LinkedIn + + )} + {personalInfo.github && ( + + + GitHub + + )} + {personalInfo.website && ( + + + Website + + )} +
+
+
+ + {/* Summary */} + {summary && ( +
+

+ + Technical Profile +

+

+ {summary} +

+
+ )} + + {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Technical Skills +

+
+ {skills.map((skill) => ( +
+
+ + {skill.name} + + + {skill.level} + +
+
+
+
+
+ ))} +
+
+ )} + + {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Professional Experience +

+
+ {experience.map((exp) => ( +
+
+
+

+ {exp.jobTitle} +

+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+
    + {exp.description.filter(desc => desc.trim()).map((desc, index) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+

+ {project.name} + {project.url && ( + + (View Project) + + )} +

+ {project.startDate && ( + + {formatDate(project.startDate)} + {project.endDate ? ` - ${formatDate(project.endDate)}` : ' - Present'} + + )} +
+

+ {project.description} +

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + + {tech} + + ))} +
+ )} +
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+

+ {edu.degree} +

+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+ + {edu.graduationDate ? formatDate(edu.graduationDate) : ''} + +
+ {edu.relevantCourses && edu.relevantCourses.length > 0 && ( +
+

+ Relevant Coursework: +

+
+ {edu.relevantCourses.map((course, i) => ( + + {course} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

+ {cert.name} +

+

+ {cert.issuer} +

+

+ {formatDate(cert.date)} + {cert.expirationDate && ` - ${formatDate(cert.expirationDate)}`} +

+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/resume-builder/templates/TimelineTemplate.tsx b/src/components/resume-builder/templates/TimelineTemplate.tsx new file mode 100644 index 0000000..cdf26da --- /dev/null +++ b/src/components/resume-builder/templates/TimelineTemplate.tsx @@ -0,0 +1,248 @@ +import { Briefcase, GraduationCap, Code, Award, BookOpen, Star } from 'lucide-react'; +import { TemplateProps } from './types'; + +export function TimelineTemplate({ data }: TemplateProps) { + const { personalInfo, summary, experience, education, skills, certifications, projects, publications, customization } = data || {}; + const accentColor = customization?.accentColor || '#3b82f6'; + + const formatDate = (dateStr: string) => { + if (!dateStr) return ''; + const date = new Date(dateStr + '-01'); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' }); + }; + + return ( +
+ {/* Header */} +
+

{personalInfo?.fullName}

+ {personalInfo?.title && ( +

{personalInfo.title}

+ )} +
+ {personalInfo?.email && {personalInfo.email}} + {personalInfo?.phone && {personalInfo.phone}} + {personalInfo?.location && {personalInfo.location}} +
+
+ + {/* Summary */} + {summary && ( +
+

Professional Summary

+

{summary}

+
+ )} + + {/* Timeline Section */} +
+ {/* Timeline Line */} +
+ +
+ {/* Experience */} + {experience && experience.length > 0 && ( +
+

+ + Work Experience +

+
+ {experience.map((exp) => ( +
+
+
+
+

{exp.jobTitle}

+ + {formatDate(exp.startDate)} - {exp.isCurrentJob ? 'Present' : formatDate(exp.endDate)} + +
+

+ {exp.company} + {exp.location && ` β€’ ${exp.location}`} +

+
    + {exp.description.map((desc, idx) => ( +
  • + β€’ + {desc} +
  • + ))} +
+
+
+ ))} +
+
+ )} + + {/* Education */} + {education && education.length > 0 && ( +
+

+ + Education +

+
+ {education.map((edu) => ( +
+
+
+
+

{edu.degree}

+ + {formatDate(edu.graduationDate)} + +
+

+ {edu.school} + {edu.location && `, ${edu.location}`} +

+ {edu.gpa && ( +

+ GPA: {edu.gpa} +

+ )} +
+
+ ))} +
+
+ )} + + {/* Projects */} + {projects && projects.length > 0 && ( +
+

+ + Projects +

+
+ {projects.map((project) => ( +
+
+
+

{project.name}

+

{project.description}

+ {project.technologies && project.technologies.length > 0 && ( +
+ {project.technologies.map((tech, i) => ( + {tech} + ))} +
+ )} +
+
+ ))} +
+
+ )} + + {/* Publications */} + {publications && publications.length > 0 && ( +
+

+ + Publications +

+
+ {publications.map((pub) => ( +
+
+
+

{pub.title}

+

+ {pub.journal} + {pub.publicationDate && ` β€’ ${formatDate(pub.publicationDate)}`} +

+ + View Publication + +
+
+ ))} +
+
+ )} +
+
+ + {/* Skills & Certifications */} +
+ {/* Skills */} + {skills && skills.length > 0 && ( +
+

+ + Skills +

+
+ {skills.map((skill) => ( + + {skill.name} + + ))} +
+
+ )} + + {/* Certifications */} + {certifications && certifications.length > 0 && ( +
+

+ + Certifications +

+
+ {certifications.map((cert) => ( +
+

{cert.name}

+

+ {cert.issuer} β€’ {formatDate(cert.date)} +

+
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/src/components/resume-builder/templates/types.ts b/src/components/resume-builder/templates/types.ts new file mode 100644 index 0000000..6f36d51 --- /dev/null +++ b/src/components/resume-builder/templates/types.ts @@ -0,0 +1,58 @@ +import { ResumeData } from '@/types/resume'; + +export interface TemplateProps { + data: ResumeData; +} + +export interface TemplateSectionProps { + title: string; + className?: string; + children: React.ReactNode; +} + +export interface ExperienceItemProps { + jobTitle: string; + company: string; + location?: string; + startDate: string; + endDate: string; + isCurrentJob: boolean; + description: string[]; + className?: string; +} + +export interface EducationItemProps { + degree: string; + school: string; + location?: string; + graduationDate: string; + fieldOfStudy?: string; + gpa?: string; + className?: string; +} + +export interface SkillItemProps { + name: string; + level: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert'; + className?: string; +} + +export interface CertificationItemProps { + name: string; + issuer: string; + date: string; + expirationDate?: string; + className?: string; +} + +export interface TemplateStyles { + fontFamily?: string; + primaryColor?: string; + secondaryColor?: string; + textColor?: string; + backgroundColor?: string; + spacing?: { + section?: string; + item?: string; + }; +} diff --git a/src/components/resume-builder/types.ts b/src/components/resume-builder/types.ts new file mode 100644 index 0000000..1506996 --- /dev/null +++ b/src/components/resume-builder/types.ts @@ -0,0 +1,21 @@ +import { TemplateProps as TemplateComponentProps } from './templates/types'; + +export interface TemplateMeta { + id: string; + name: string; + description: string; + category: TemplateCategory; + previewImage?: string; + component: React.LazyExoticComponent<(props: TemplateComponentProps) => JSX.Element>; + tags?: string[]; + atsFriendly: boolean; + lastUpdated: string; +} + +export enum TemplateCategory { + PROFESSIONAL = 'Professional', + MODERN = 'Modern', + MINIMALIST = 'Minimalist', + CREATIVE = 'Creative', + SPECIALIZED = 'Specialized', +} diff --git a/src/components/tutorial/TutorialModal.tsx b/src/components/tutorial/TutorialModal.tsx index c06403b..b08885c 100644 --- a/src/components/tutorial/TutorialModal.tsx +++ b/src/components/tutorial/TutorialModal.tsx @@ -1,7 +1,6 @@ -import React, { useState, useEffect } from 'react'; -import { Dialog, DialogContent } from '@/components/ui/dialog'; +import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; -import { X, ChevronLeft, ChevronRight, User, FileText, Briefcase, Palette, Save, Download } from 'lucide-react'; +import { X, ChevronLeft, ChevronRight, User, Palette, Save, Download } from 'lucide-react'; interface TutorialStep { id: number; diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx index 1b7bc45..1c59433 100644 --- a/src/components/ui/calendar.tsx +++ b/src/components/ui/calendar.tsx @@ -58,8 +58,8 @@ function Calendar({ ...classNames, }} components={{ - IconLeft: ({ ...props }) => , - IconRight: ({ ...props }) => , + IconLeft: () => , + IconRight: () => , }} {...props} /> diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx index 13c1bd3..63d0845 100644 --- a/src/components/ui/chart.tsx +++ b/src/components/ui/chart.tsx @@ -1,10 +1,5 @@ import * as React from 'react'; import * as RechartsPrimitive from 'recharts'; -import { - NameType, - Payload, - ValueType, -} from 'recharts/types/component/DefaultTooltipContent'; import { cn } from '@/lib/utils'; diff --git a/src/contexts/ResumeContext.tsx b/src/contexts/ResumeContext.tsx index 62e7bac..d037a5f 100644 --- a/src/contexts/ResumeContext.tsx +++ b/src/contexts/ResumeContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useReducer, useEffect, ReactNode } from 'react'; -import { ResumeData, ResumeSection, WorkExperience, Education, Skill, Certification, Project, Reference } from '@/types/resume'; +import { ResumeData, WorkExperience, Education, Skill, Certification, Project, Publication, Reference } from '@/types/resume'; interface ResumeState { resumeData: ResumeData; @@ -28,10 +28,12 @@ type ResumeAction = | { type: 'ADD_PROJECT'; payload: Project } | { type: 'UPDATE_PROJECT'; payload: { id: string; data: Partial } } | { type: 'DELETE_PROJECT'; payload: string } + | { type: 'ADD_PUBLICATION'; payload: Publication } + | { type: 'UPDATE_PUBLICATION'; payload: { id: string; data: Partial } } + | { type: 'DELETE_PUBLICATION'; payload: string } | { type: 'ADD_REFERENCE'; payload: Reference } | { type: 'UPDATE_REFERENCE'; payload: { id: string; data: Partial } } | { type: 'DELETE_REFERENCE'; payload: string } - | { type: 'REORDER_SECTIONS'; payload: ResumeSection[] } | { type: 'UPDATE_TEMPLATE'; payload: ResumeData['template'] } | { type: 'UPDATE_CUSTOMIZATION'; payload: Partial } | { type: 'SET_LOADING'; payload: boolean } @@ -201,6 +203,17 @@ const initialResumeData: ResumeData = { endDate: "2022-01", }, ], + publications: [ + { + id: "1", + title: "The Impact of Machine Learning on Software Development Lifecycles", + authors: ["Sarah Johnson", "John Doe"], + journal: "Journal of Computer Science", + publicationDate: "2022-10", + doi: "10.1234/jcs.2022.5678", + url: "https://example.com/publication1" + } + ], references: [ { id: "1", @@ -230,53 +243,7 @@ const initialResumeData: ResumeData = { relationship: "Academic Reference", }, ], - sections: [ - { - id: "1", - type: "personalInfo", - title: "Personal Information", - isVisible: true, - order: 1, - }, - { - id: "2", - type: "summary", - title: "Professional Summary", - isVisible: true, - order: 2, - }, - { - id: "3", - type: "experience", - title: "Work Experience", - isVisible: true, - order: 3, - }, - { - id: "4", - type: "education", - title: "Education", - isVisible: true, - order: 4, - }, - { id: "5", type: "skills", title: "Skills", isVisible: true, order: 5 }, - { - id: "6", - type: "certifications", - title: "Certifications", - isVisible: true, - order: 6, - }, - { id: "7", type: "projects", title: "Projects", isVisible: true, order: 7 }, - { - id: "8", - type: "references", - title: "References", - isVisible: true, - order: 8, - }, - ], - template: "minimal", + template: "minimalist", customization: { fontFamily: "Inter", accentColor: "#3B82F6", @@ -503,6 +470,38 @@ function resumeReducer(state: ResumeState, action: ResumeAction): ResumeState { hasUnsavedChanges: true }; + case 'ADD_PUBLICATION': + return { + ...state, + resumeData: { + ...state.resumeData, + publications: [...state.resumeData.publications, action.payload] + }, + hasUnsavedChanges: true + }; + + case 'UPDATE_PUBLICATION': + return { + ...state, + resumeData: { + ...state.resumeData, + publications: state.resumeData.publications.map(pub => + pub.id === action.payload.id ? { ...pub, ...action.payload.data } : pub + ) + }, + hasUnsavedChanges: true + }; + + case 'DELETE_PUBLICATION': + return { + ...state, + resumeData: { + ...state.resumeData, + publications: state.resumeData.publications.filter(pub => pub.id !== action.payload) + }, + hasUnsavedChanges: true + }; + case 'ADD_REFERENCE': return { ...state, @@ -535,19 +534,19 @@ function resumeReducer(state: ResumeState, action: ResumeAction): ResumeState { hasUnsavedChanges: true }; - case 'REORDER_SECTIONS': + case 'UPDATE_TEMPLATE': { + // Ensure the template ID exists in the registry, otherwise fall back to 'clean-minimalist' + const templateId = typeof action.payload === 'string' ? action.payload : 'clean-minimalist'; + console.log('Updating template to:', templateId); return { ...state, - resumeData: { ...state.resumeData, sections: action.payload }, - hasUnsavedChanges: true - }; - - case 'UPDATE_TEMPLATE': - return { - ...state, - resumeData: { ...state.resumeData, template: action.payload }, + resumeData: { + ...state.resumeData, + template: templateId + }, hasUnsavedChanges: true }; + } case 'UPDATE_CUSTOMIZATION': return { diff --git a/src/contexts/ResumeData.tsx b/src/contexts/ResumeData.tsx index fc0bf1f..733dea5 100644 --- a/src/contexts/ResumeData.tsx +++ b/src/contexts/ResumeData.tsx @@ -1,6 +1,6 @@ import { ResumeData } from '@/types/resume' -const initialResumeData: ResumeData = { +const initialData: ResumeData = { personalInfo: { fullName: "Sarah Johnson", email: "sarah.johnson@email.com", @@ -163,6 +163,7 @@ const initialResumeData: ResumeData = { endDate: "2022-01", }, ], + publications: [], references: [ { id: "1", @@ -192,53 +193,7 @@ const initialResumeData: ResumeData = { relationship: "Academic Reference", }, ], - sections: [ - { - id: "1", - type: "personalInfo", - title: "Personal Information", - isVisible: true, - order: 1, - }, - { - id: "2", - type: "summary", - title: "Professional Summary", - isVisible: true, - order: 2, - }, - { - id: "3", - type: "experience", - title: "Work Experience", - isVisible: true, - order: 3, - }, - { - id: "4", - type: "education", - title: "Education", - isVisible: true, - order: 4, - }, - { id: "5", type: "skills", title: "Skills", isVisible: true, order: 5 }, - { - id: "6", - type: "certifications", - title: "Certifications", - isVisible: true, - order: 6, - }, - { id: "7", type: "projects", title: "Projects", isVisible: true, order: 7 }, - { - id: "8", - type: "references", - title: "References", - isVisible: true, - order: 8, - }, - ], - template: "minimal", + template: "clean-minimalist", customization: { fontFamily: "Inter", accentColor: "#3B82F6", @@ -246,4 +201,4 @@ const initialResumeData: ResumeData = { }, }; -export default initialResumeData; \ No newline at end of file +export default initialData; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 9e89c2d..322e97e 100644 --- a/src/index.css +++ b/src/index.css @@ -25,17 +25,17 @@ *::-webkit-scrollbar { height: 5px; width: 5px; - background-color:#5f5f5f5b; + background-color:#788ca96c; scale: 1; } *::-webkit-scrollbar-thumb { - background-color:#6f6f6fc8; - border-radius: 10px; + background-color:#67979552; + border-radius: 10px; } - +s *::-webkit-scrollbar-tracks { - background-color:#545454; + background-color:#88abb967; } a { @@ -172,6 +172,44 @@ button:focus-visible { color: #fff; } +.resume-preview-header { + width: 100%; + display: block; + position: fixed; + z-index: 99; + padding-bottom: 25px; + margin-bottom: 25px; +} + +/* Resume print styles */ +.resume-preview { + page-break-inside: avoid; + break-inside: + avoid; +} + +#resume-preview h1, +#resume-preview h2, +#resume-preview h3, +#resume-preview h4, +#resume-preview h5, +#resume-preview h6 { + page-break-after: avoid; + break-after: avoid; +} + +#resume-preview p, +#resume-preview li, +#resume-preview div { + page-break-inside: avoid; + break-inside: avoid; +} + +#resume-preview .break-inside-avoid { + page-break-inside: avoid; + break-inside: avoid; +} + @media print { .resume-preview { margin: 20px; @@ -179,4 +217,8 @@ button:focus-visible { background: white !important; page-break-inside: avoid; } + + #resume-preview { + box-shadow: none !important; + } } \ No newline at end of file diff --git a/src/types/resume.ts b/src/types/resume.ts index 3690c46..02f8138 100644 --- a/src/types/resume.ts +++ b/src/types/resume.ts @@ -1,5 +1,6 @@ export interface PersonalInfo { fullName: string; + title?: string; email: string; phone: string; location: string; @@ -21,13 +22,15 @@ export interface WorkExperience { } export interface Education { - id: string; + id:string; degree: string; school: string; location: string; graduationDate: string; gpa?: string; + fieldOfStudy?: string; relevantCourses?: string[]; + achievements?: string[]; } export interface Skill { @@ -54,6 +57,16 @@ export interface Project { endDate?: string; } +export interface Publication { + id: string; + title: string; + authors: string[]; + journal: string; + publicationDate: string; + doi?: string; + url?: string; +} + export interface Reference { id: string; name: string; @@ -72,15 +85,8 @@ export type ResumeSectionType = | 'skills' | 'certifications' | 'projects' - | 'references'; - -export interface ResumeSection { - id: string; - type: ResumeSectionType; - title: string; - isVisible: boolean; - order: number; -} + | 'references' + | 'publications'; export interface ResumeData { id?: string; @@ -91,9 +97,9 @@ export interface ResumeData { skills: Skill[]; certifications: Certification[]; projects: Project[]; + publications: Publication[]; references: Reference[]; - sections: ResumeSection[]; - template: 'minimal' | 'modern' | 'creative'; + template: string; // Template IDs are now validated at runtime customization: { fontFamily: string; accentColor: string; diff --git a/src/utils/pdfGenerator.ts b/src/utils/pdfGenerator.ts index 7eb7b23..ad65b75 100644 --- a/src/utils/pdfGenerator.ts +++ b/src/utils/pdfGenerator.ts @@ -16,15 +16,25 @@ export const generatePDF = async ( filename = 'resume.pdf', quality = 2, format = 'a4 || letter', - margin = 14, } = options; try { - // Find the resume element - const element = document.getElementById(elementId); - if (!element) { + // Find all page elements + const allPages = []; + const firstPage = document.getElementById(elementId); + if (!firstPage) { throw new Error('Resume element not found'); } + allPages.push(firstPage); + + // Find additional pages + let pageIndex = 2; + while (true) { + const nextPage = document.getElementById(`resume-preview-page-${pageIndex}`); + if (!nextPage) break; + allPages.push(nextPage); + pageIndex++; + } // Show loading state const loadingToast = document.createElement('div'); @@ -32,112 +42,50 @@ export const generatePDF = async ( loadingToast.className = 'fixed top-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-lg shadow-lg z-50'; document.body.appendChild(loadingToast); - // Configure canvas options for better quality - const canvas = await html2canvas(element, { - scale: quality * 2, // Higher scale for better quality - useCORS: true, - allowTaint: true, - backgroundColor: '#ffffff', - removeContainer: true, - imageTimeout: 0, - height: element.scrollHeight-50, - width: element.scrollWidth, - scrollX: 0, - scrollY: 50 - }); - // PDF dimensions in mm - const pageWidth = format === 'a4' ? 200 : 206; // A4: 210mm, Letter: 216mm (8.5") - const pageHeight = format === 'a4' ? 279 : 259; // A4: 297mm, Letter: 279mm (11") - - // Available space after margins - const availableWidth = pageWidth - (margin * 2); - const availableHeight = pageHeight - (margin * 2); - - // Convert canvas dimensions to mm (96 DPI to mm conversion) - const canvasWidthMM = (canvas.width * 25.4) / (96 * quality * 2); - const canvasHeightMM = (canvas.height * 25.4) / (96 * quality * 2); - - // Calculate scale to fit width - const scale = availableWidth / canvasWidthMM; - const scaledWidth = availableWidth; - const scaledHeight = canvasHeightMM * scale; + const pageWidth = format === 'a4' ? 216 : 210; + const pageHeight = format === 'a4' ? 279 : 297; // Create PDF - const pdf = new jsPDF(options); - - // Convert canvas to image data - const imgData = canvas.toDataURL('image/png', 2.0); + const pdf = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: format === 'a4' ? 'a4' : [pageWidth, pageHeight] + }); - // If content fits on one page - if (scaledHeight <= availableHeight) { + // Process each page element + for (let i = 0; i < allPages.length; i++) { + const pageElement = allPages[i]; + + // Create canvas for this page + const pageCanvas = await html2canvas(pageElement, { + scale: quality, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff', + removeContainer: true, + imageTimeout: 0, + width: pageElement.scrollWidth, + height: pageElement.scrollHeight + }); + + // Convert to image + const pageImgData = pageCanvas.toDataURL('image/png', 1.0); + + // Add new page if not the first + if (i > 0) { + pdf.addPage(); + } + + // Add image to PDF (full page) pdf.addImage( - imgData, - 'PNG', - margin, - margin, - scaledWidth, - scaledHeight, - undefined, - 'FAST' + pageImgData, + 'PNG', + 0, + 0, + pageWidth, + pageHeight ); - } else { - // Multi-page handling - let remainingHeight = scaledHeight; - let currentY = 0; - let pageNumber = 0; - - while (remainingHeight > 0) { - // Calculate the height for this page - const pageContentHeight = Math.min(remainingHeight, availableHeight); - - // Calculate the source rectangle for this page - const sourceY = (currentY / scaledHeight) * canvas.height; - const sourceHeight = (pageContentHeight / scaledHeight) * canvas.height; - - // Create a canvas for this page section - const pageCanvas = document.createElement('canvas'); - const pageCtx = pageCanvas.getContext('2d'); - - pageCanvas.width = canvas.width; - pageCanvas.height = sourceHeight; - - if (pageCtx) { - // Fill with white background - pageCtx.fillStyle = '#ffffff'; - pageCtx.fillRect(0, 0, pageCanvas.width, pageCanvas.height); - - // Draw the section of the original canvas - pageCtx.drawImage( - canvas, - 0, sourceY, canvas.width, sourceHeight, - 0, 0, pageCanvas.width, pageCanvas.height - ); - - // Add new page if not the first page - if (pageNumber > 0) { - pdf.addPage(); - } - - // Add the page image to PDF - const pageImgData = pageCanvas.toDataURL('image/png', 2.0); - pdf.addImage( - pageImgData, - 'PNG', - margin, - margin, - scaledWidth, - pageContentHeight, - undefined, - 'FAST' - ); - } - - // Update for next page - currentY += pageContentHeight; - remainingHeight -= pageContentHeight; - pageNumber++; - } } // Save the PDF @@ -183,6 +131,73 @@ export const generatePDF = async ( } }; +export const generatePDFBlob = async ( + elementId: string, + options: PDFOptions = {} +): Promise => { + const { + quality = 2, + format = 'letter' + } = options; + + // Find all page elements + const allPages = []; + const firstPage = document.getElementById(elementId); + if (!firstPage) { + throw new Error('Resume element not found'); + } + allPages.push(firstPage); + + let pageIndex = 2; + while (true) { + const nextPage = document.getElementById(`resume-preview-page-${pageIndex}`); + if (!nextPage) break; + allPages.push(nextPage); + pageIndex++; + } + + const pageWidth = format === 'letter' ? 216 : 210; + const pageHeight = format === 'letter' ? 279 : 297; + + const pdf = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: format === 'a4' ? 'a4' : [pageWidth, pageHeight] + }); + + for (let i = 0; i < allPages.length; i++) { + const pageElement = allPages[i]; + + const pageCanvas = await html2canvas(pageElement, { + scale: quality, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff', + removeContainer: true, + imageTimeout: 0, + width: pageElement.scrollWidth, + height: pageElement.scrollHeight + }); + + const pageImgData = pageCanvas.toDataURL('image/png', 1.0); + + if (i > 0) { + pdf.addPage(); + } + + pdf.addImage( + pageImgData, + 'PNG', + 0, + 0, + pageWidth, + pageHeight + ); + } + + return pdf.output('blob'); +}; + export const generateResumeFilename = (fullName: string): string => { const cleanName = fullName .replace(/[^a-zA-Z0-9\s]/g, ' ') // Remove special characters diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo new file mode 100644 index 0000000..0bb8c6b --- /dev/null +++ b/tsconfig.app.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/userApi.ts","./src/components/CookieBanner.tsx","./src/components/Footer.tsx","./src/components/TestApi.tsx","./src/components/resume-builder/Header.tsx","./src/components/resume-builder/ResumeBuilder.tsx","./src/components/resume-builder/ResumeForm.tsx","./src/components/resume-builder/ResumePreview.tsx","./src/components/resume-builder/TemplatePreview.tsx","./src/components/resume-builder/TemplateSelector.tsx","./src/components/resume-builder/templateRegistry.ts","./src/components/resume-builder/types.ts","./src/components/resume-builder/form-sections/CertificationsForm.tsx","./src/components/resume-builder/form-sections/CustomizationForm.tsx","./src/components/resume-builder/form-sections/EducationForm.tsx","./src/components/resume-builder/form-sections/ExperienceForm.tsx","./src/components/resume-builder/form-sections/PersonalInfoForm.tsx","./src/components/resume-builder/form-sections/ProjectsForm.tsx","./src/components/resume-builder/form-sections/ReferencesForm.tsx","./src/components/resume-builder/form-sections/SkillsForm.tsx","./src/components/resume-builder/form-sections/SummaryForm.tsx","./src/components/resume-builder/templates/AcademicTemplate.tsx","./src/components/resume-builder/templates/BoldTemplate.tsx","./src/components/resume-builder/templates/CleanMinimalistTemplate.tsx","./src/components/resume-builder/templates/CleanProfessionalTemplate.tsx","./src/components/resume-builder/templates/CorporateTemplate.tsx","./src/components/resume-builder/templates/CreativeColorBlocksTemplate.tsx","./src/components/resume-builder/templates/CreativeTemplate.tsx","./src/components/resume-builder/templates/ElegantTemplate.tsx","./src/components/resume-builder/templates/ExecutiveTemplate.tsx","./src/components/resume-builder/templates/MinimalTemplate.tsx","./src/components/resume-builder/templates/MinimalistATS.tsx","./src/components/resume-builder/templates/MinimalistProfessionalTemplate.tsx","./src/components/resume-builder/templates/MinimalistSidebarTemplate.tsx","./src/components/resume-builder/templates/MinimalistTemplate.tsx","./src/components/resume-builder/templates/ModernDarkSidebarTemplate.tsx","./src/components/resume-builder/templates/ModernExecutiveTemplate.tsx","./src/components/resume-builder/templates/ModernProfessionalTemplate.tsx","./src/components/resume-builder/templates/ModernSidebarTemplate.tsx","./src/components/resume-builder/templates/ModernTemplate.tsx","./src/components/resume-builder/templates/ModernTimelineTemplate.tsx","./src/components/resume-builder/templates/ModernTwoColumn.tsx","./src/components/resume-builder/templates/ProfessionalBlueTemplate.tsx","./src/components/resume-builder/templates/ProfessionalSidebarTemplate.tsx","./src/components/resume-builder/templates/ProfessionalSummaryTemplate.tsx","./src/components/resume-builder/templates/ProfessionalTemplate.tsx","./src/components/resume-builder/templates/TechTemplate.tsx","./src/components/resume-builder/templates/TimelineTemplate.tsx","./src/components/resume-builder/templates/types.ts","./src/components/tutorial/TutorialModal.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/aspect-ratio.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/carousel.tsx","./src/components/ui/chart.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input-otp.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/menubar.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/contexts/ResumeContext.tsx","./src/contexts/ResumeData.tsx","./src/hooks/use-toast.ts","./src/lib/utils.ts","./src/types/resume.ts","./src/utils/fontLoader.ts","./src/utils/pdfGenerator.ts"],"version":"5.8.3"} \ No newline at end of file diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000..3015526 --- /dev/null +++ b/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vite.config.ts"],"version":"5.8.3"} \ No newline at end of file