├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── components │ ├── styles │ │ ├── StyledSectionContainer.js │ │ ├── StyledButtonContainer.js │ │ ├── EditContainer.js │ │ ├── PreviewContainer.js │ │ ├── StyledPersonalInfoContainer.js │ │ ├── StyledPreviewSectionHeader.js │ │ ├── StyledSectionHeading.js │ │ ├── StyledForm.js │ │ ├── StyledHeader.js │ │ ├── DownloadPDFButton.js │ │ ├── StyledSectionButton.js │ │ ├── StyledMainButton.js │ │ ├── StyledSecondaryButton.js │ │ ├── Global.js │ │ └── StyledPreviewSectionContainer.js │ ├── previewMode │ │ ├── PreviewMode.js │ │ ├── Skills.js │ │ ├── PersonalInfo.js │ │ ├── Experience.js │ │ ├── Education.js │ │ └── Projects.js │ └── editMode │ │ ├── EditMode.js │ │ ├── Skills.js │ │ ├── PersonalInfo.js │ │ ├── Projects.js │ │ ├── Experience.js │ │ └── Education.js ├── index.js └── App.js ├── .gitignore ├── README.md └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BradySavarie/cv-builder/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BradySavarie/cv-builder/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BradySavarie/cv-builder/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/styles/StyledSectionContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledSectionContainer = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | `; 7 | -------------------------------------------------------------------------------- /src/components/styles/StyledButtonContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledButtonContainer = styled.div` 4 | display: flex; 5 | justify-content: space-between; 6 | `; 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById('root')); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/styles/EditContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const EditContainer = styled.div` 4 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); 5 | border-radius: 20px; 6 | min-height: 90vh; 7 | width: 70%; 8 | margin: 0 auto; 9 | margin-bottom: 20px; 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/styles/PreviewContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const PreviewContainer = styled.div` 4 | outline: 1px solid #ccc; 5 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); 6 | height: 90vh; 7 | aspect-ratio: 8.5/11; 8 | margin: 0 auto; 9 | margin-bottom: 20px; 10 | padding: 10px 20px; 11 | overflow-y: hidden; 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/styles/StyledPersonalInfoContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledPersonalInfoContainer = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | gap: 8px; 7 | padding: 10px; 8 | 9 | div { 10 | display: flex; 11 | justify-content: space-between; 12 | } 13 | 14 | p { 15 | font-size: 12px; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | printLink.txt -------------------------------------------------------------------------------- /src/components/styles/StyledPreviewSectionHeader.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledPreviewSectionHeader = styled.h3` 4 | padding: 10px; 5 | position: relative; 6 | line-height: 0.25; 7 | 8 | &::after { 9 | content: ''; 10 | position: absolute; 11 | bottom: 0; 12 | left: 10px; 13 | width: calc(100% - 20px); 14 | height: 1px; 15 | background-color: #333; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /src/components/styles/StyledSectionHeading.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledSectionHeading = styled.div` 4 | background-color: #f2f2f2; 5 | padding: 15px; 6 | text-align: center; 7 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); 8 | color: #333; 9 | font-size: 24px; 10 | text-transform: uppercase; 11 | letter-spacing: 2px; 12 | font-weight: bold; 13 | margin-bottom: 15px; 14 | margin-top: 5px; 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/styles/StyledForm.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledForm = styled.form` 4 | display: flex; 5 | flex-direction: column; 6 | gap: 5px; 7 | padding: 15px 10px; 8 | 9 | input, 10 | textarea { 11 | width: 100%; 12 | padding: 5px 10px; 13 | border: 2px solid #ccc; 14 | border-radius: 4px; 15 | transition: border-color 0.3s ease-in-out; 16 | } 17 | 18 | input:focus, 19 | textarea:focus { 20 | border-color: #555555; 21 | outline: none; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/components/styles/StyledHeader.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledHeader = styled.header` 4 | background-color: #f2f2f2; 5 | padding: 20px; 6 | text-align: center; 7 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); 8 | color: #333; 9 | font-size: 32px; 10 | text-transform: uppercase; 11 | letter-spacing: 2px; 12 | font-weight: bold; 13 | 14 | &::before { 15 | content: ''; 16 | display: block; 17 | height: 3px; 18 | background-color: #333; 19 | margin-bottom: 10px; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/styles/DownloadPDFButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const DownloadPDFButton = styled.button` 4 | background-color: #333; 5 | width: max-content; 6 | color: #fff; 7 | font-weight: bold; 8 | border: none; 9 | border-radius: 20px; 10 | padding: 10px 20px; 11 | cursor: pointer; 12 | outline: none; 13 | transition: background-color 0.3s; 14 | transform: scale(1.3); 15 | margin-bottom: 20px; 16 | 17 | &:hover { 18 | background-color: #fff; 19 | color: black; 20 | outline: 2px solid #ccc; 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/components/styles/StyledSectionButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledSectionButton = styled.button` 4 | background-color: #333; 5 | max-width: max-content; 6 | color: #fff; 7 | font-weight: bold; 8 | border: none; 9 | border-radius: 20px; 10 | padding: 10px 15px; 11 | cursor: pointer; 12 | outline: none; 13 | transition: background-color 0.3s; 14 | margin: 0 10px; 15 | margin-bottom: 15px; 16 | align-self: center; 17 | 18 | &:hover { 19 | background-color: #fff; 20 | color: black; 21 | outline: 2px solid #ccc; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/components/styles/StyledMainButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledMainButton = styled.button` 4 | background-color: #333; 5 | width: 150px; 6 | color: #fff; 7 | font-weight: bold; 8 | border: none; 9 | border-radius: 20px; 10 | padding: 10px 20px; 11 | cursor: pointer; 12 | outline: none; 13 | transition: background-color 0.3s; 14 | margin: 20px; 15 | transform: scale(1.3); 16 | 17 | &:hover { 18 | background-color: #fff; 19 | color: black; 20 | outline: 2px solid #ccc; 21 | } 22 | 23 | &:active { 24 | outline: 1px solid black; 25 | transform: scale(1.25); 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /src/components/styles/StyledSecondaryButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { StyledMainButton } from './StyledMainButton'; 3 | 4 | export const StyledSecondaryButton = styled(StyledMainButton)` 5 | background-color: #fff; 6 | width: 150px; 7 | color: black; 8 | font-weight: bold; 9 | border: none; 10 | border-radius: 20px; 11 | padding: 10px 20px; 12 | cursor: pointer; 13 | outline: 2px solid #ccc; 14 | transition: background-color 0.3s; 15 | margin: 20px; 16 | transform: scale(1); 17 | 18 | &:hover { 19 | background-color: #333; 20 | color: #fff; 21 | outline: none; 22 | } 23 | 24 | &:active { 25 | transform: scale(1.05); 26 | } 27 | `; 28 | -------------------------------------------------------------------------------- /src/components/previewMode/PreviewMode.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PersonalInfo from './PersonalInfo'; 3 | import Experience from './Experience'; 4 | import Education from './Education'; 5 | import Projects from './Projects'; 6 | import Skills from './Skills'; 7 | 8 | const PreviewMode = (props) => { 9 | const { inputs } = props; 10 | const { personalInfo, experience, education, projects, skills } = inputs; 11 | 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default PreviewMode; 24 | -------------------------------------------------------------------------------- /src/components/previewMode/Skills.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledPreviewSectionHeader } from '../styles/StyledPreviewSectionHeader'; 3 | import { StyledPreviewSectionContainer } from '../styles/StyledPreviewSectionContainer'; 4 | 5 | const Skills = (props) => { 6 | const { inputs } = props; 7 | 8 | return ( 9 | <> 10 | {inputs.length !== 0 && ( 11 | Skills 12 | )} 13 | 14 | 15 | {inputs.map((input) => ( 16 | 19 | ))} 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Skills; 26 | -------------------------------------------------------------------------------- /src/components/previewMode/PersonalInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledPersonalInfoContainer } from '../styles/StyledPersonalInfoContainer'; 3 | 4 | const PersonalInfo = (props) => { 5 | const { inputs } = props; 6 | 7 | return ( 8 | <> 9 | {inputs.map((input) => ( 10 |
11 | 12 |

13 | {input.firstName} {input.lastName} 14 |

15 |
16 |

{input.emailAddress}

17 |

{input.phoneNumber}

18 |

{input.location}

19 |
20 |
21 |
22 | ))} 23 | 24 | ); 25 | }; 26 | 27 | export default PersonalInfo; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

💼 CV-Builder

2 | 3 |

An ATS-Compatible Resume App

4 | 5 | Edit Mode 6 | 7 | This application enables users to create a professional resume that seamlessly integrates with applicant tracking systems (ATS), eliminating the need for manual data correction and retyping. Users can easily switch between edit and preview modes to visualize the automated formatting of their resume as they write. By clicking the Create Sample button, users can view a completed resume example, and the reset option clears all fields. Once completed, the resume can be downloaded as a PDF. 8 | 9 | Live Link: [bradysavarie.github.io/cv-builder/](https://bradysavarie.github.io/cv-builder/) 10 | 11 |
12 | 13 | This application is a part of The Odin Projects Full-Stack Javascript curriculum and was developed with the purpose of learning React and Styled Components. The main challenges I faced were learning how to create class components, works with props and state, and use the styled-components approach to writing css. 14 | 15 |

Built With:

16 | 17 | 22 | -------------------------------------------------------------------------------- /src/components/previewMode/Experience.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledPreviewSectionContainer } from '../styles/StyledPreviewSectionContainer'; 3 | import { StyledPreviewSectionHeader } from '../styles/StyledPreviewSectionHeader'; 4 | 5 | const Experience = (props) => { 6 | const { inputs } = props; 7 | 8 | return ( 9 | <> 10 | {inputs.length !== 0 && ( 11 | 12 | Experience 13 | 14 | )} 15 | 16 | {inputs.map((input) => ( 17 |
18 |
19 |

20 | {input.title}, {input.company} 21 |

22 |

23 | {input.startDate} - {input.endDate} 24 |

25 |
26 |

{input.description}

27 |
28 | ))} 29 |
30 | 31 | ); 32 | }; 33 | 34 | export default Experience; 35 | -------------------------------------------------------------------------------- /src/components/previewMode/Education.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledPreviewSectionHeader } from '../styles/StyledPreviewSectionHeader'; 3 | import { StyledPreviewSectionContainer } from '../styles/StyledPreviewSectionContainer'; 4 | 5 | const Education = (props) => { 6 | const { inputs } = props; 7 | 8 | return ( 9 | <> 10 | {inputs.length !== 0 && ( 11 | 12 | Education 13 | 14 | )} 15 | 16 | 17 | {inputs.map((input) => ( 18 |
19 |
20 |

21 | {input.school}, {input.qualification} 22 |

23 |

24 | {input.enrollmentDate} - {input.graduationDate} 25 |

26 |
27 |

{input.fieldOfStudy}

28 |
29 | ))} 30 |
31 | 32 | ); 33 | }; 34 | 35 | export default Education; 36 | -------------------------------------------------------------------------------- /src/components/styles/Global.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | const GlobalStyles = createGlobalStyle` 4 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;600;700&display=swap'); 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | background: white; 12 | color: hsl(192, 100%, 9%); 13 | font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; 14 | font-size: 14px; 15 | margin: 0; 16 | } 17 | 18 | h1, h2, h3, p, a, li { 19 | color: black; 20 | } 21 | 22 | h2 { 23 | margin: 10px 0px; 24 | } 25 | 26 | h3 { 27 | margin: 6px 0px; 28 | } 29 | 30 | h1 { 31 | color: black; 32 | text-transform: uppercase; 33 | text-align: center; 34 | font-size: 24px; 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | h2 { 40 | border-bottom: 1px solid #000000; 41 | text-transform: uppercase; 42 | font-size: 16px; 43 | padding: 0; 44 | } 45 | 46 | h3 { 47 | display: flex; 48 | font-size: 15px; 49 | padding: 0; 50 | justify-content: space-between; 51 | } 52 | 53 | p { 54 | margin: 0; 55 | padding: 0; 56 | } 57 | 58 | a { 59 | color: black; 60 | } 61 | 62 | ul { 63 | margin: 4px 0; 64 | padding-left: 24px; 65 | padding-right: 24px; 66 | } 67 | 68 | textarea { 69 | font-family: inherit; 70 | } 71 | `; 72 | 73 | export default GlobalStyles; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cv-builder", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "html2canvas": "^1.4.1", 10 | "jspdf": "^2.5.1", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "5.0.1", 14 | "styled-components": "^6.0.0-rc.3", 15 | "uniquid": "^1.1.4", 16 | "web-vitals": "^2.1.4" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "predeploy": "npm run build", 21 | "deploy": "gh-pages -d build", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "gh-pages": "^5.0.0" 46 | }, 47 | "homepage": "http://BradySavarie.github.io/cv-builder" 48 | } 49 | -------------------------------------------------------------------------------- /src/components/previewMode/Projects.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledPreviewSectionHeader } from '../styles/StyledPreviewSectionHeader'; 3 | import { StyledPreviewSectionContainer } from '../styles/StyledPreviewSectionContainer'; 4 | 5 | const Projects = (props) => { 6 | const { inputs } = props; 7 | 8 | return ( 9 | <> 10 | {inputs.length !== 0 && ( 11 | 12 | Projects 13 | 14 | )} 15 | 16 | 17 | {inputs.map((input) => ( 18 |
19 |
20 |

{input.title}

21 |

{input.description}

22 |
23 |
24 |

25 | Live Link: {input.liveLink} 26 |

27 |

28 | Repository Link:{' '} 29 | {input.repoLink} 30 |

31 |
32 |
33 | ))} 34 |
35 | 36 | ); 37 | }; 38 | 39 | export default Projects; 40 | -------------------------------------------------------------------------------- /src/components/styles/StyledPreviewSectionContainer.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledPreviewSectionContainer = styled.div` 4 | padding: 2px 10px; 5 | 6 | .experienceContainer { 7 | display: flex; 8 | flex-direction: column; 9 | gap: 3px; 10 | font-size: 9px; 11 | font-style: italic; 12 | margin-bottom: 12px; 13 | } 14 | 15 | .experienceDataContainer { 16 | display: flex; 17 | font-size: 10px; 18 | font-weight: bold; 19 | justify-content: space-between; 20 | margin-bottom: 2px; 21 | } 22 | 23 | .educationContainer { 24 | display: flex; 25 | flex-direction: column; 26 | gap: 3px; 27 | font-size: 9px; 28 | font-style: italic; 29 | margin-bottom: 12px; 30 | } 31 | 32 | .educationDataContainer { 33 | display: flex; 34 | font-size: 10px; 35 | font-weight: bold; 36 | justify-content: space-between; 37 | margin-bottom: 2px; 38 | } 39 | 40 | .projectContainer { 41 | display: flex; 42 | flex-direction: column; 43 | gap: 3px; 44 | margin-bottom: 12px; 45 | } 46 | 47 | .projectDataContainer { 48 | display: flex; 49 | flex-direction: column; 50 | gap: 3px; 51 | justify-content: space-between; 52 | font-weight: bold; 53 | margin-bottom: 3px; 54 | 55 | p.title { 56 | font-size: 10px; 57 | } 58 | 59 | p.description { 60 | font-size: 9px; 61 | font-weight: normal; 62 | } 63 | } 64 | 65 | .projectLinksContainer { 66 | font-size: 10px; 67 | } 68 | 69 | .skillsContainer { 70 | font-size: 12px; 71 | } 72 | `; 73 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/editMode/EditMode.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PersonalInfo from './PersonalInfo'; 3 | import Experience from './Experience'; 4 | import Education from './Education'; 5 | import Projects from './Projects'; 6 | import Skills from './Skills'; 7 | 8 | const EditMode = (props) => { 9 | const { inputs, handleInputChange, handleAddSection, handleDeleteSection } = 10 | props; 11 | const { personalInfo, experience, education, projects, skills } = inputs; 12 | 13 | return ( 14 | <> 15 | 21 | 27 | 33 | 39 | 45 | 46 | ); 47 | }; 48 | 49 | export default EditMode; 50 | -------------------------------------------------------------------------------- /src/components/editMode/Skills.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledSectionContainer } from '../styles/StyledSectionContainer'; 3 | import { StyledSectionHeading } from '../styles/StyledSectionHeading'; 4 | import { StyledForm } from '../styles/StyledForm'; 5 | import { StyledSectionButton } from '../styles/StyledSectionButton'; 6 | 7 | const Skills = (props) => { 8 | const { inputs, handleInputChange, handleAddSection, handleDeleteSection } = 9 | props; 10 | 11 | return ( 12 | <> 13 | 14 | Skills 15 | {inputs.map((input) => ( 16 |
17 | 18 | 26 | 27 | 32 | Delete Skill 33 | 34 |
35 | ))} 36 | 40 | Add Skill 41 | 42 |
43 | 44 | ); 45 | }; 46 | 47 | export default Skills; 48 | -------------------------------------------------------------------------------- /src/components/editMode/PersonalInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledForm } from '../styles/StyledForm'; 3 | import { StyledSectionHeading } from '../styles/StyledSectionHeading'; 4 | 5 | const PersonalInfo = (props) => { 6 | const { inputs, handleInputChange } = props; 7 | 8 | return ( 9 |
10 | {inputs.map((input) => ( 11 |
12 | Personal Info 13 | 14 | 22 | 23 | 31 | 32 | 40 | 41 | 49 | 50 | 58 | 59 |
60 | ))} 61 |
62 | ); 63 | }; 64 | 65 | export default PersonalInfo; 66 | -------------------------------------------------------------------------------- /src/components/editMode/Projects.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledSectionContainer } from '../styles/StyledSectionContainer'; 3 | import { StyledSectionButton } from '../styles/StyledSectionButton'; 4 | import { StyledForm } from '../styles/StyledForm'; 5 | import { StyledSectionHeading } from '../styles/StyledSectionHeading'; 6 | 7 | const Projects = (props) => { 8 | const { inputs, handleInputChange, handleAddSection, handleDeleteSection } = 9 | props; 10 | 11 | return ( 12 | <> 13 | 14 | Projects 15 | {inputs.map((input) => ( 16 |
17 | 18 | 26 | 27 | 34 | 35 | 43 | 44 | 52 | 53 | 58 | Delete Project 59 | 60 |
61 | ))} 62 | 66 | Add Project 67 | 68 |
69 | 70 | ); 71 | }; 72 | 73 | export default Projects; 74 | -------------------------------------------------------------------------------- /src/components/editMode/Experience.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledForm } from '../styles/StyledForm'; 3 | import { StyledSectionHeading } from '../styles/StyledSectionHeading'; 4 | import { StyledSectionButton } from '../styles/StyledSectionButton'; 5 | import { StyledSectionContainer } from '../styles/StyledSectionContainer'; 6 | 7 | const Experience = (props) => { 8 | const { inputs, handleInputChange, handleAddSection, handleDeleteSection } = 9 | props; 10 | 11 | return ( 12 |
13 | Experience 14 | 15 | {inputs.map((input) => ( 16 |
17 | 18 | 26 | 27 | 35 | 36 | 44 | 45 | 53 | 54 | 62 | 63 | 68 | Delete Experience 69 | 70 |
71 | ))} 72 | 76 | Add Experience 77 | 78 |
79 |
80 | ); 81 | }; 82 | 83 | export default Experience; 84 | -------------------------------------------------------------------------------- /src/components/editMode/Education.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledSectionContainer } from '../styles/StyledSectionContainer'; 3 | import { StyledSectionHeading } from '../styles/StyledSectionHeading'; 4 | import { StyledForm } from '../styles/StyledForm'; 5 | import { StyledSectionButton } from '../styles/StyledSectionButton'; 6 | 7 | const Education = (props) => { 8 | const { inputs, handleInputChange, handleAddSection, handleDeleteSection } = 9 | props; 10 | 11 | return ( 12 | <> 13 | Education 14 | 15 | {inputs.map((input) => ( 16 |
17 | 18 | 26 | 27 | 35 | 36 | 44 | 45 | 53 | 54 | 62 | 63 | 68 | Delete Education 69 | 70 |
71 | ))} 72 | 76 | Add Education 77 | 78 |
79 | 80 | ); 81 | }; 82 | 83 | export default Education; 84 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import EditMode from './components/editMode/EditMode'; 3 | import PreviewMode from './components/previewMode/PreviewMode'; 4 | import uniquid from 'uniquid'; 5 | import html2canvas from 'html2canvas'; 6 | import { jsPDF } from 'jspdf'; 7 | import GlobalStyles from './components/styles/Global'; 8 | import { StyledButtonContainer } from './components/styles/StyledButtonContainer'; 9 | import { StyledMainButton } from './components/styles/StyledMainButton'; 10 | import { StyledSecondaryButton } from './components/styles/StyledSecondaryButton'; 11 | import { PreviewContainer } from './components/styles/PreviewContainer'; 12 | import { EditContainer } from './components/styles/EditContainer'; 13 | import { StyledHeader } from './components/styles/StyledHeader'; 14 | import { DownloadPDFButton } from './components/styles/DownloadPDFButton'; 15 | 16 | const App = () => { 17 | const initialState = { 18 | mode: 'edit', 19 | inputs: { 20 | personalInfo: [ 21 | { 22 | key: uniquid(), 23 | firstName: '', 24 | lastName: '', 25 | location: '', 26 | phoneNumber: '', 27 | emailAddress: '', 28 | }, 29 | ], 30 | experience: [], 31 | education: [], 32 | projects: [], 33 | skills: [], 34 | }, 35 | }; 36 | 37 | const sampleData = { 38 | mode: 'edit', 39 | inputs: { 40 | personalInfo: [ 41 | { 42 | key: uniquid(), 43 | firstName: 'John', 44 | lastName: 'Doe', 45 | location: 'Toronto, ON', 46 | phoneNumber: '(555) 555 5555', 47 | emailAddress: 'johndoe@gmail.com', 48 | }, 49 | ], 50 | experience: [ 51 | { 52 | key: uniquid(), 53 | company: 'Apple Inc.', 54 | title: 'Senior Software Developer', 55 | startDate: 'February, 2023', 56 | endDate: 'Present', 57 | description: 58 | 'Design and build applications for the iOS platform', 59 | }, 60 | { 61 | key: uniquid(), 62 | company: 'Facebook', 63 | title: 'Junior Software Developer', 64 | startDate: 'January, 2019', 65 | endDate: 'January, 2023', 66 | description: 67 | 'Contributed to a feature on Messenger that reached 50 million users in 30 days', 68 | }, 69 | ], 70 | education: [ 71 | { 72 | key: uniquid(), 73 | school: 'Harvard University', 74 | fieldOfStudy: 'Computer Science', 75 | qualification: 'BSc', 76 | enrollmentDate: 'September, 2015', 77 | graduationDate: 'April, 2018', 78 | }, 79 | { 80 | key: uniquid(), 81 | school: 'Yale University', 82 | fieldOfStudy: 'Economics', 83 | qualification: 'BBA', 84 | enrollmentDate: 'September, 2011', 85 | graduationDate: 'April, 2014', 86 | }, 87 | ], 88 | projects: [ 89 | { 90 | key: uniquid(), 91 | title: 'CV Builder', 92 | description: 93 | 'Automatically generates a resume in a structured format that is compatible with ATS software and can be easily parsed and extracted for data analysis purposes. The resume you are reading now was created using cv builder!', 94 | liveLink: 'www.cv-builder.com', 95 | repoLink: 'www.github.com/cv-builder', 96 | }, 97 | ], 98 | skills: [ 99 | { 100 | key: uniquid(), 101 | description: 'React', 102 | }, 103 | { 104 | key: uniquid(), 105 | description: 'Typescript', 106 | }, 107 | { 108 | key: uniquid(), 109 | description: 'Tailwind', 110 | }, 111 | { 112 | key: uniquid(), 113 | description: 'Sass', 114 | }, 115 | ], 116 | }, 117 | }; 118 | 119 | const printRef = React.useRef(); 120 | const [state, setState] = useState(initialState); 121 | 122 | const handleModeChange = () => { 123 | setState((prevState) => ({ 124 | ...prevState, 125 | mode: prevState.mode === 'edit' ? 'preview' : 'edit', 126 | })); 127 | }; 128 | 129 | const handleInputChange = (e) => { 130 | const { name, value } = e.target; 131 | const component = e.target.getAttribute('data-component'); 132 | const key = e.target.form.getAttribute('data-key'); 133 | 134 | setState((prevState) => { 135 | const prevComponentArr = prevState.inputs[component]; 136 | const updatedComponentArr = prevComponentArr.map((item) => { 137 | if (item.key === key) { 138 | return { ...item, [name]: value }; 139 | } 140 | return item; 141 | }); 142 | 143 | return { 144 | ...prevState, 145 | inputs: { 146 | ...prevState.inputs, 147 | [component]: updatedComponentArr, 148 | }, 149 | }; 150 | }); 151 | }; 152 | 153 | const handleAddSection = (e) => { 154 | const component = e.target.getAttribute('data-component'); 155 | const templateObject = state.inputs[component][0]; 156 | const newObject = { key: uniquid() }; 157 | for (let prop in templateObject) { 158 | if (prop !== 'key') { 159 | newObject[prop] = ''; 160 | } 161 | } 162 | setState((prevState) => { 163 | return { 164 | ...prevState, 165 | inputs: { 166 | ...prevState.inputs, 167 | [component]: [...prevState.inputs[component], newObject], 168 | }, 169 | }; 170 | }); 171 | }; 172 | 173 | const handleDeleteSection = (e) => { 174 | const component = e.target.getAttribute('data-component'); 175 | const key = e.target.getAttribute('data-key'); 176 | 177 | const prevComponentArr = state.inputs[component]; 178 | const newComponentArr = prevComponentArr.filter( 179 | (comp) => comp.key !== key 180 | ); 181 | 182 | setState((prevState) => { 183 | return { 184 | ...prevState, 185 | inputs: { ...prevState.inputs, [component]: newComponentArr }, 186 | }; 187 | }); 188 | }; 189 | 190 | const handleReset = () => { 191 | const formFields = document.querySelectorAll('input, textarea'); 192 | 193 | formFields.forEach((field) => { 194 | field.value = ''; 195 | }); 196 | 197 | setState(initialState); 198 | }; 199 | 200 | const handleCreateSample = () => { 201 | setState(sampleData); 202 | }; 203 | 204 | const handleDownloadPdf = async () => { 205 | const element = printRef.current; 206 | const canvas = await html2canvas(element); 207 | const data = canvas.toDataURL('image/png'); 208 | 209 | const pdf = new jsPDF(); 210 | const imgProperties = pdf.getImageProperties(data); 211 | const pdfWidth = pdf.internal.pageSize.getWidth(); 212 | const pdfHeight = 213 | (imgProperties.height * pdfWidth) / imgProperties.width; 214 | 215 | pdf.addImage(data, 'PNG', 0, 0, pdfWidth, pdfHeight); 216 | pdf.save('resume.pdf'); 217 | }; 218 | 219 | return ( 220 | <> 221 | 222 | CV Builder 223 |
230 | 231 | 232 | Create Sample 233 | 234 | 235 | {state.mode === 'edit' ? 'Preview Mode' : 'Edit Mode'} 236 | 237 | 238 | Reset 239 | 240 | 241 | 242 | {state.mode === 'edit' ? ( 243 | 244 | 250 | 251 | ) : ( 252 | <> 253 | 254 | 255 | 256 | 257 | Download as PDF 258 | 259 | 260 | )} 261 |
262 | 263 | ); 264 | }; 265 | 266 | export default App; 267 | --------------------------------------------------------------------------------