├── 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 |
17 | {input.description}
18 |
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 |
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 |
18 | React
19 | Styled Components
20 | Create-react-app
21 |
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 | You need to enable JavaScript to run this app.
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 |
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 |
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 |
--------------------------------------------------------------------------------