-
-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 3-Step Resume Builder Feature with PDF Download Capability #798
Add 3-Step Resume Builder Feature with PDF Download Capability #798
Conversation
WalkthroughThe changes encapsulate the development of a resume builder application, including a complete overhaul of the HTML structure, the introduction of a CSS file for styling, and the addition of JavaScript functionality. The application now allows users to dynamically add sections for personal information, education, job experience, and projects. Users can select templates, preview their resumes, and download them as PDFs. The overall design is modernized with improved layout, accessibility, and user interaction features. Changes
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Outside diff range and nitpick comments (10)
Resume.css (5)
1-15
: LGTM! Consider adding max-width for larger screens.The body and form container styles create a clean, modern look that aligns well with the PR objectives. The use of
box-shadow
adds depth, and the 90% width allows for responsiveness.Consider adding a
max-width
to.form-container
for better readability on larger screens:.form-container { max-width: 1200px; /* or any suitable value */ /* existing styles... */ }
29-53
: LGTM! Consider adding focus styles for improved accessibility.The form element styles create a consistent and responsive look. The vertical resize for textareas is a good UX decision.
For improved accessibility, consider adding focus styles to input and textarea elements:
input[type="text"]:focus, input[type="email"]:focus, input[type="tel"]:focus, input[type="url"]:focus, textarea:focus { outline: none; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.5); }
76-130
: LGTM! Consider adding keyboard focus styles for template cards.The template selection styles align well with the PR objectives and create an interactive, responsive layout using flexbox. The hover effects on template cards enhance the user experience.
For improved accessibility, consider adding keyboard focus styles to the template cards:
.template-card:focus-within { outline: 2px solid #007bff; outline-offset: 2px; }
132-174
: LGTM! Consider using consistent units for font sizes.The resume preview styles create a clear structure and visual hierarchy, aligning well with the PR objectives. The specific styles for headings and paragraphs enhance readability.
For consistency, consider using rem units for all font sizes in the resume preview:
#resume-display h1 { font-size: 2rem; } #resume-display h2 { font-size: 1.5rem; } #resume-display h3 { font-size: 1.2rem; }
176-194
: LGTM! Consider using variables for consistent colors.The final button styles create visually distinct buttons for different actions, aligning well with the PR objectives. The use of Bootstrap-like colors helps with user recognition of button purposes.
For better maintainability and consistency, consider using CSS variables for colors:
:root { --warning-color: #f0ad4e; --success-color: #5cb85c; } #prev-step-3 { background-color: var(--warning-color); } #download-resume { background-color: var(--success-color); }resume.html (2)
13-13
: Typo in Logo Text: "Resum Resume"The logo text currently reads "Resum Resume". If this is unintentional, consider correcting it to "Resume Resume" or your intended branding.
245-246
: Consider Using a Local Copy of External LibrariesIncluding
html2pdf.js
via a CDN is acceptable, but relying on external resources can lead to issues if the CDN is unavailable. Consider hosting a local copy of the library to ensure consistent availability.Alternatively, ensure proper fallback mechanisms are in place if the CDN cannot be reached.
Resume.js (3)
98-109
: Improper Handling of Validation FeedbackCurrently, if validation fails, an alert is shown. This interrupts the user experience and may not be user-friendly.
Consider displaying validation messages inline next to the relevant fields to provide better feedback.
123-126
: Duplicate Validation of Template SelectionThe check for
!selectedTemplate
before proceeding is redundant since the "Next" button is disabled until a template is selected.You can remove this validation to streamline the code.
251-255
: PDF Generation Margin Units InconsistencyIn the PDF options, the margin is specified as
0.5
, but the unit is not explicitly set, which may cause inconsistencies in the generated PDF.Specify the unit for the margin to ensure consistent formatting.
const options = { margin: [0.5, 0.5, 0.5, 0.5], // Top, right, bottom, left margins in inches // other options };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (3)
templates/basic.jpg
is excluded by!**/*.jpg
templates/classic.jpg
is excluded by!**/*.jpg
templates/modern.jpg
is excluded by!**/*.jpg
📒 Files selected for processing (7)
- Resume.css (1 hunks)
- Resume.js (1 hunks)
- resume.html (1 hunks)
- templates/basic.html (1 hunks)
- templates/classic.html (1 hunks)
- templates/modern.html (1 hunks)
- templates/templates.css (1 hunks)
✅ Files skipped from review due to trivial changes (4)
- templates/basic.html
- templates/classic.html
- templates/modern.html
- templates/templates.css
🧰 Additional context used
🔇 Additional comments (6)
Resume.css (3)
17-27
: LGTM! Good use of rem units and visual hierarchy.The heading styles create a clear visual hierarchy and use rem units, which is great for accessibility and responsiveness. The margin-bottom values help with proper spacing between elements.
55-74
: LGTM! Excellent button styles with accessibility considerations.The button styles create visually appealing and interactive elements. The use of transitions for hover and focus effects enhances the user experience. The focus styles improve accessibility by providing a clear visual indicator for keyboard navigation.
1-194
: Overall, excellent CSS implementation for the resume builder.The CSS file successfully implements styles that align with the PR objectives, creating a cohesive and responsive design for the 3-step resume builder. The use of modern CSS techniques like flexbox, transitions, and hover effects enhances the user experience. Minor suggestions were made throughout the review to further improve accessibility, consistency, and maintainability.
resume.html (2)
93-94
: Sanitize and Parse Comma-Separated InputsThe Skills and Languages fields accept comma-separated values. Ensure that the JavaScript code properly trims whitespace and handles potential input errors to prevent issues during resume generation.
Consider updating the parsing logic in
Resume.js
to handle extra spaces and missing commas gracefully.Also applies to: 155-156
185-188
: Ensure Navigation Buttons Function CorrectlyVerify that the "Prev Step" and "Next Step" buttons have the correct
onclick
handlers and that the functionsshowStep(1)
andproceedToNextStep(3)
are properly defined inResume.js
.Resume.js (1)
202-238
: Potential XSS Vulnerability in Resume GenerationData inserted into the resume HTML is taken directly from user input without sanitization, which could lead to Cross-Site Scripting (XSS) attacks.
[security]
Sanitize user input before including it in the HTML to prevent XSS vulnerabilities.
function sanitizeInput(input) { const div = document.createElement('div'); div.textContent = input; return div.innerHTML; } // Use sanitizeInput on all user inputs let educationHTML = educationEntries.map(edu => ` <div> <strong>${sanitizeInput(edu.institute)}</strong> (${sanitizeInput(edu.startYear)} - ${sanitizeInput(edu.endYear)})<br> <em>${sanitizeInput(edu.course)}</em><br> <span>Score: ${sanitizeInput(edu.score)}</span> </div> `).join(''); // Repeat for other sections
<style> | ||
@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap'); | ||
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@500&display=swap'); | ||
body{ | ||
padding: 0; | ||
} | ||
header{ | ||
position: relative; | ||
margin-bottom: 5rem; | ||
} | ||
footer{ | ||
margin-top: 5rem; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Move Inline Styles to External Stylesheet
Inline styles are included within the <body>
element. It's best practice to place styles in external CSS files or within the <head>
section to improve maintainability and performance.
Apply this diff to remove inline styles from the HTML file:
- <style>
- body{
- padding: 0;
- }
- header{
- position: relative;
- margin-bottom: 5rem;
- }
- footer{
- margin-top: 5rem;
- }
- </style>
Add these styles to Resume.css
:
body {
padding: 0;
}
header {
position: relative;
margin-bottom: 5rem;
}
footer {
margin-top: 5rem;
}
<label>Name:</label> | ||
<input type="text" id="name" placeholder="Enter your name"> | ||
|
||
.none | ||
{ | ||
display: none; | ||
} | ||
.resume | ||
{ | ||
margin-top: 40px; | ||
margin-bottom: 40px; | ||
margin-left: 50px; | ||
width: 800px; | ||
box-shadow: rgba(17, 17, 26, 0.1) 0px 4px 16px, rgba(17, 17, 26, 0.1) 0px 8px 24px, rgba(17, 17, 26, 0.1) 0px 16px 56px; | ||
} | ||
{ | ||
background-color: #fff; | ||
padding: 30px 50px; | ||
height: 1120px; | ||
<label>Profile:</label> | ||
<textarea id="profile" placeholder="Write about yourself"></textarea> | ||
|
||
} | ||
<label>Email:</label> | ||
<input type="email" id="email" placeholder="Enter your email"> | ||
|
||
<label>Contact Number:</label> | ||
<input type="tel" id="contact" placeholder="Enter your contact number"> | ||
|
||
<label>Location:</label> | ||
<input type="text" id="location" placeholder="Enter your location"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Associate Labels with Inputs for Accessibility
The <label>
elements are not associated with their corresponding form controls using the for
attribute. This affects accessibility, especially for users relying on screen readers. Ensure each <label>
has a for
attribute matching the id
of its associated input element.
Example fix:
-<label>Name:</label>
-<input type="text" id="name" placeholder="Enter your name">
+<label for="name">Name:</label>
+<input type="text" id="name" placeholder="Enter your name">
-<label>Profile:</label>
-<textarea id="profile" placeholder="Write about yourself"></textarea>
+<label for="profile">Profile:</label>
+<textarea id="profile" placeholder="Write about yourself"></textarea>
-<label>Email:</label>
-<input type="email" id="email" placeholder="Enter your email">
+<label for="email">Email:</label>
+<input type="email" id="email" placeholder="Enter your email">
-<label>Contact Number:</label>
-<input type="tel" id="contact" placeholder="Enter your contact number">
+<label for="contact">Contact Number:</label>
+<input type="tel" id="contact" placeholder="Enter your contact number">
-<label>Location:</label>
-<input type="text" id="location" placeholder="Enter your location">
+<label for="location">Location:</label>
+<input type="text" id="location" placeholder="Enter your location">
Apply similar changes to all other labels and inputs throughout the form.
Also applies to: 71-85, 102-116, 136-147, 155-156
<input type="text" placeholder="Start Year"> | ||
|
||
<label>End Year:</label> | ||
<input type="text" placeholder="End Year"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use Appropriate Input Types for Year Fields
The Start Year
and End Year
input fields are using type="text"
. Consider using type="number"
or type="date"
for better input validation and user experience.
Apply this diff:
-<input type="text" placeholder="Start Year">
+<input type="number" placeholder="Start Year">
-<input type="text" placeholder="End Year">
+<input type="number" placeholder="End Year">
Also applies to: 109-112
return Array.from(document.querySelectorAll('.education-entry')).map(entry => ({ | ||
institute: entry.querySelector('input[placeholder="Enter institute name"]').value, | ||
course: entry.querySelector('input[placeholder="Enter course name"]').value, | ||
startYear: entry.querySelector('input[placeholder="Start Year"]').value, | ||
endYear: entry.querySelector('input[placeholder="End Year"]').value, | ||
score: entry.querySelector('input[placeholder="Score/Grade"]').value, | ||
})); | ||
} | ||
|
||
function collectExperienceData() { | ||
return Array.from(document.querySelectorAll('.experience-entry')).map(entry => ({ | ||
jobPosition: entry.querySelector('input[placeholder="Enter job position"]').value, | ||
companyName: entry.querySelector('input[placeholder="Enter company name"]').value, | ||
startYear: entry.querySelector('input[placeholder="Start Year"]').value, | ||
endYear: entry.querySelector('input[placeholder="End Year"]').value, | ||
description: entry.querySelector('textarea[placeholder="Describe your job experience"]').value, | ||
})); | ||
} | ||
|
||
function collectProjectData() { | ||
return Array.from(document.querySelectorAll('.project-entry')).map(entry => ({ | ||
title: entry.querySelector('input[placeholder="Enter project title"]').value, | ||
description: entry.querySelector('textarea[placeholder="Describe the project"]').value, | ||
responsibilities: entry.querySelector('textarea[placeholder="Describe your role"]').value, | ||
link: entry.querySelector('input[placeholder="Enter project link"]').value, | ||
})); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lack of Validation for Dynamically Added Entries
The collected data from education, experience, and project entries does not undergo validation, which might result in incomplete or incorrect resume sections.
Implement validation for the dynamically added entries to ensure all necessary fields are filled.
}; | ||
|
||
// Generate and download the PDF | ||
html2pdf().from(resumeElement).set(options).save(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error Handling for PDF Generation
If an error occurs during PDF generation, the user is not notified, and the application may fail silently.
Implement error handling to catch and inform the user of any issues during PDF generation.
html2pdf().from(resumeElement).set(options).save()
+ .catch(function(error) {
+ alert('An error occurred while generating the PDF: ' + error.message);
+ });
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
html2pdf().from(resumeElement).set(options).save(); | |
html2pdf().from(resumeElement).set(options).save() | |
.catch(function(error) { | |
alert('An error occurred while generating the PDF: ' + error.message); | |
}); |
if (!name || !profile || !email || !contact || !location) { | ||
alert('Please fill out all required fields in Step 1.'); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Email and Contact Number Format Validation
The validation only checks if fields are filled but doesn't validate the format of the email and contact number, which could lead to incorrect data being submitted.
Enhance the validation to check for proper email and contact number formats using regular expressions.
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ const contactPattern = /^\d{10}$/;
if (!name || !profile || !email || !contact || !location) {
alert('Please fill out all required fields in Step 1.');
return;
+ } else if (!emailPattern.test(email)) {
+ alert('Please enter a valid email address.');
+ return;
+ } else if (!contactPattern.test(contact)) {
+ alert('Please enter a valid 10-digit contact number.');
+ return;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (!name || !profile || !email || !contact || !location) { | |
alert('Please fill out all required fields in Step 1.'); | |
return; | |
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
const contactPattern = /^\d{10}$/; | |
if (!name || !profile || !email || !contact || !location) { | |
alert('Please fill out all required fields in Step 1.'); | |
return; | |
} else if (!emailPattern.test(email)) { | |
alert('Please enter a valid email address.'); | |
return; | |
} else if (!contactPattern.test(contact)) { | |
alert('Please enter a valid 10-digit contact number.'); | |
return; | |
} |
function selectTemplate(templateName) { | ||
selectedTemplate = templateName; | ||
console.log("Selected Template: ", selectedTemplate); | ||
document.querySelectorAll('.template-card').forEach(card => { | ||
card.classList.remove('selected'); | ||
}); | ||
document.querySelector(`[onclick="selectTemplate('${templateName}')"]`).classList.add('selected'); | ||
document.getElementById('next-step-2').disabled = false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible DOM Selection Issue in Template Selection
Using document.querySelector(
[onclick="selectTemplate('${templateName}')"])
assumes that the onclick
attribute is used, which may not be reliable if event listeners are attached differently.
Consider adding a unique identifier or data attribute to template cards to select them more reliably.
// Add a data attribute to template cards in HTML:
// <div class="template-card" data-template-name="template1">...</div>
function selectTemplate(templateName) {
selectedTemplate = templateName;
console.log("Selected Template: ", selectedTemplate);
document.querySelectorAll('.template-card').forEach(card => {
card.classList.remove('selected');
+ if (card.dataset.templateName === templateName) {
+ card.classList.add('selected');
+ }
});
document.getElementById('next-step-2').disabled = false;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function selectTemplate(templateName) { | |
selectedTemplate = templateName; | |
console.log("Selected Template: ", selectedTemplate); | |
document.querySelectorAll('.template-card').forEach(card => { | |
card.classList.remove('selected'); | |
}); | |
document.querySelector(`[onclick="selectTemplate('${templateName}')"]`).classList.add('selected'); | |
document.getElementById('next-step-2').disabled = false; | |
} | |
function selectTemplate(templateName) { | |
selectedTemplate = templateName; | |
console.log("Selected Template: ", selectedTemplate); | |
document.querySelectorAll('.template-card').forEach(card => { | |
card.classList.remove('selected'); | |
if (card.dataset.templateName === templateName) { | |
card.classList.add('selected'); | |
} | |
}); | |
document.getElementById('next-step-2').disabled = false; | |
} |
document.getElementById('reset').addEventListener('click', function() { | ||
location.reload(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inefficient Use of location.reload()
Using location.reload()
resets the entire application state, which may not be necessary and can be jarring to the user.
Provide a function to reset only the necessary form fields and variables without reloading the page.
document.getElementById('reset').addEventListener('click', function() {
- location.reload();
+ // Reset form fields
+ document.querySelector('form').reset();
+ // Clear dynamically added sections
+ document.getElementById('education-fields').innerHTML = '';
+ document.getElementById('experience-fields').innerHTML = '';
+ document.getElementById('project-fields').innerHTML = '';
+ // Reset variables
+ selectedTemplate = '';
+ showStep(1);
});
Committable suggestion was skipped due to low confidence.
document.getElementById('add-education').addEventListener('click', function() { | ||
const educationFields = document.getElementById('education-fields'); | ||
const newEducation = document.createElement('div'); | ||
newEducation.classList.add('education-entry'); | ||
newEducation.innerHTML = ` | ||
<label>Institute:</label> | ||
<input type="text" placeholder="Enter institute name"> | ||
|
||
<label>Course:</label> | ||
<input type="text" placeholder="Enter course name"> | ||
|
||
<label>Start Year:</label> | ||
<input type="text" placeholder="Start Year"> | ||
|
||
<label>End Year:</label> | ||
<input type="text" placeholder="End Year"> | ||
|
||
<label>Score/Grade:</label> | ||
<input type="text" placeholder="Score/Grade"> | ||
`; | ||
educationFields.appendChild(newEducation); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory Leak Risk in Dynamic Education Sections
The event listener for the 'add-education' button creates new DOM elements but does not provide a way to remove them. This could lead to a buildup of unused DOM nodes if the user adds and removes entries frequently.
Consider adding functionality to remove education entries if needed. Here's a possible enhancement:
+ // Adding a remove button to each education entry
newEducation.innerHTML = `
<label>Institute:</label>
<input type="text" placeholder="Enter institute name">
<!-- other inputs -->
+ <button type="button" class="remove-education">Remove</button>
`;
// Append event listener to remove education entry
+ newEducation.querySelector('.remove-education').addEventListener('click', function() {
+ educationFields.removeChild(newEducation);
+ });
Committable suggestion was skipped due to low confidence.
document.getElementById('add-experience').addEventListener('click', function() { | ||
const experienceFields = document.getElementById('experience-fields'); | ||
const newExperience = document.createElement('div'); | ||
newExperience.classList.add('experience-entry'); | ||
newExperience.innerHTML = ` | ||
<label>Job Position:</label> | ||
<input type="text" placeholder="Enter job position"> | ||
|
||
<label>Company Name:</label> | ||
<input type="text" placeholder="Enter company name"> | ||
|
||
<label>Start Year:</label> | ||
<input type="text" placeholder="Start Year"> | ||
|
||
<label>End Year:</label> | ||
<input type="text" placeholder="End Year"> | ||
|
||
<label>Description:</label> | ||
<textarea placeholder="Describe your job experience"></textarea> | ||
`; | ||
experienceFields.appendChild(newExperience); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Repeated Code in Dynamic Section Addition Functions
The code for dynamically adding education, experience, and project sections is very similar. This repetition violates the DRY (Don't Repeat Yourself) principle.
Refactor the code to create a generic function for adding new entries, reducing redundancy.
+ function addSection(entryType, fieldsContainerId, entryClass, entryHTML) {
+ const fieldsContainer = document.getElementById(fieldsContainerId);
+ const newEntry = document.createElement('div');
+ newEntry.classList.add(entryClass);
+ newEntry.innerHTML = entryHTML;
+ fieldsContainer.appendChild(newEntry);
+ }
document.getElementById('add-education').addEventListener('click', function() {
const entryHTML = `
<!-- Education entry HTML -->
`;
+ addSection('education', 'education-fields', 'education-entry', entryHTML);
});
document.getElementById('add-experience').addEventListener('click', function() {
const entryHTML = `
<!-- Experience entry HTML -->
`;
+ addSection('experience', 'experience-fields', 'experience-entry', entryHTML);
});
document.getElementById('add-project').addEventListener('click', function() {
const entryHTML = `
<!-- Project entry HTML -->
`;
+ addSection('project', 'project-fields', 'project-entry', entryHTML);
});
Committable suggestion was skipped due to low confidence.
Issue Reference: #757 – Resume Builder Feature with 3-Step Process
Description:
This PR adds a Resume Builder feature, enabling users to create and download resumes using a simple three-step process. The feature is fully responsive and utilizes a dynamic transition-based UI, allowing for a streamlined user experience without reloading the page.
Feature Breakdown:
Step 1: User Information Input
Step 2: Template Selection
Step 3: Resume Preview and Download
Key Features:
Seamless transitions: Hides and shows different sections of the page as users move between steps.
Responsive design: Ensures optimal functionality across various devices and screen sizes.
PDF download styling matches the browser preview.
Screenshots:
Summary by CodeRabbit
Release Notes
New Features
Styling Enhancements
Documentation