initial commit, completed 40% of first milestone

This commit is contained in:
Amruth Pillai 2020-03-25 02:52:24 +05:30
parent dfeb67cda9
commit 25a6740c24
No known key found for this signature in database
GPG Key ID: 09959E21662F51A0
36 changed files with 55170 additions and 154 deletions

28
.eslintrc Normal file
View File

@ -0,0 +1,28 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": false,
"codeFrame": false
},
"extends": [
"airbnb",
"plugin:react/recommended",
"prettier",
"prettier/react"
],
"env": {
"browser": true,
"jest": true
},
"rules": {
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/label-has-associated-control": 0,
"react/jsx-filename-extension": 0,
"react/no-array-index-key": 0,
"no-param-reassign": 0,
"react/prop-types": 0,
"no-plusplus": 0
}
}

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"printWidth": 100,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

924
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,15 +3,21 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.9.7",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"lodash": "^4.17.15",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
"react-scripts": "3.4.1",
"uuid": "^7.0.2"
},
"scripts": {
"css": "postcss src/assets/tailwind/tailwind.src.css -o src/assets/tailwind/tailwind.css",
"prestart": "npm run css",
"start": "react-scripts start",
"prebuild": "npm run css",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
@ -30,5 +36,18 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^9.7.5",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^2.5.1",
"postcss-cli": "^7.1.0",
"tailwindcss": "^1.2.0"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [tailwindcss('./tailwind.js'), require('autoprefixer')],
};

View File

@ -1,43 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description"
content="The resume generator you've been waiting for. Completely private, secure and customizable. Pick a layout, pick colors, enter your information and voila!" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
<title>Reactive Resume</title>
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,26 +0,0 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

View File

@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,138 @@
{
"profile": {
"heading": "Profile",
"photo": "https://i.imgur.com/PziAhpG.png",
"firstName": "Amruth",
"lastName": "Pillai",
"subtitle": "UI/UX Designer & Full Stack Developer",
"address": {
"line1": "#5/A, Banashankari Nivas,",
"line2": "Brindavan Layout, Padmanabhanagar,",
"line3": "Bangalore, India - 560061"
},
"phone": "+91 98453 36113",
"website": "amruthpillai.com",
"email": "hello@amruthpillai.com"
},
"objective": {
"heading": "Objective",
"body": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quis deserunt fuga qui perspiciatis magni obcaecati libero ullam lorem, ipsum dolor sit amet consectetur adipisicing elit, voluptate dolores. Lorem ipsum dolor sit amet consectetur adipisicing elit. Assumenda maiores reiciendis, iusto cupiditate, laborum optio nesciunt corrupti, libero numquam a impedit totam magnam ipsum ducimus quas labore ea? Labore, eum."
},
"work": {
"heading": "Work Experience",
"items": [
{
"id": "e737444e-cad1-4862-a5ec-26b3103a2a9f",
"title": "GoDhiyo Solutions Pvt. Ltd.",
"role": "Full Stack Web Developer",
"start": "July 2018",
"end": "current",
"description": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consectetur tempore mollitia perspiciatis quisquam quis obcaecati ducimus architecto temporibus, optio natus fuga maxime beatae consequuntur quae a vero. Molestias, distinctio vero."
},
{
"id": "24ada227-3f81-49f9-897e-210d0c407011",
"title": "Grabhouse Pvt Ltd.",
"role": "Full Stack Web Developer",
"start": "July 2018",
"end": "current",
"description": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consectetur tempore mollitia perspiciatis quisquam quis obcaecati ducimus architecto temporibus, optio natus fuga maxime beatae consequuntur quae a vero. Molestias, distinctio vero."
}
]
},
"education": {
"heading": "Education",
"items": [
{
"id": "a5995314-0026-44c6-8505-0eed5f59dcd1",
"name": "Dayananda Sagar College of Engineering",
"major": "Bachelor of Engineering in Computer Science & Engineering",
"start": "2015",
"end": "2018",
"grade": "62%",
"description": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consectetur tempore mollitia perspiciatis quisquam quis obcaecati ducimus architecto temporibus, optio natus fuga maxime beatae consequuntur quae a vero. Molestias, distinctio vero."
},
{
"id": "ce613329-246f-446d-bad1-3a46368e3f7f",
"name": "Dayananda Sagar Institute of Technology",
"major": "Diploma in Computer Science & Engineering",
"start": "2012",
"end": "2015",
"grade": "80%",
"description": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consectetur tempore mollitia perspiciatis quisquam quis obcaecati ducimus architecto temporibus, optio natus fuga maxime beatae consequuntur quae a vero. Molestias, distinctio vero."
}
]
},
"awards": {
"heading": "Honors & Awards",
"items": [
{
"id": "011d4061-b675-412d-8943-19f2c4271099",
"title": "International Flutter Hackathon '19",
"subtitle": "1st Place, International Level"
},
{
"id": "340a979f-72e8-406d-aa4f-cc99b98e1e68",
"title": "Smart India Hackathon '17",
"subtitle": "1st Place, National Level"
}
]
},
"certifications": {
"heading": "Certifications",
"items": [
{
"id": "868a8e11-3f0f-4ab2-877d-9334edfc25cf",
"title": "The Complete React Native and Redux Course",
"subtitle": "Udemy"
},
{
"id": "056b8c6b-a376-4a9e-8008-4cc61d051c34",
"title": "Applied CS with Android",
"subtitle": "Google"
}
]
},
"skills": {
"heading": "Skills & Hobbies",
"items": [
"Photography",
"Music",
"Travel",
"Adobe Photoshop",
"Figma",
"React",
"React Native",
"NodeJS"
]
},
"extras": {
"heading": "Personal Information",
"items": [
{
"id": "b94ad832-df86-4b6c-b1ee-8f955f72b891",
"key": "Date of Birth",
"value": "6th August 1995"
},
{
"id": "76ee0ca4-2beb-49ba-9fb5-8dd7fe8d3b80",
"key": "Father's Name",
"value": "Ashokan Ramalingam"
},
{
"id": "cc159f26-07f5-4f57-9cce-1cb7346e9ea5",
"key": "Languages Known",
"value": "English, Kannada, Tamil, Hindi, German"
},
{
"id": "055ef7c2-f984-45e5-9f2b-8b2300de6be1",
"key": "Nationality",
"value": "Indian"
},
{
"id": "b36abc3e-46d0-477b-808b-8a73037dc251",
"key": "Passport Number",
"value": "R0337285"
}
]
}
}

View File

@ -0,0 +1,10 @@
{
"font": {
"family": "Montserrat"
},
"colors": {
"background": "#FFF",
"accent": "#FF5722",
"body": "#414141"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

25
src/components/App/App.js Normal file
View File

@ -0,0 +1,25 @@
/* eslint-disable no-unused-vars */
import React from 'react';
import Onyx from '../../templates/onyx/Onyx';
import LeftSidebar from '../LeftSidebar/LeftSidebar';
const App = () => {
return (
<div className="grid grid-cols-5 items-center">
<LeftSidebar />
<div className="col-span-3">
<div id="page" className="p-12 my-auto mx-auto shadow-2xl">
<Onyx />
</div>
</div>
<div id="rightSidebar" className="h-screen bg-white col-span-1 shadow-2xl overflow-scroll">
This is the right sidebar
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,76 @@
import React, { useState, useEffect, useContext } from 'react';
import TabBar from '../../shared/TabBar';
import ProfileTab from './tabs/Profile';
import ObjectiveTab from './tabs/Objective';
import WorkTab from './tabs/Work';
import AppContext from '../../context/AppContext';
import EducationTab from './tabs/Education';
import AwardsTab from './tabs/Awards';
import CertificationsTab from './tabs/Certifications';
import SkillsTab from './tabs/Skills';
import ExtrasTab from './tabs/Extras';
const tabs = [
'Profile',
'Objective',
'Work Experience',
'Education',
'Awards',
'Certifications',
'Skills',
'Extras',
];
const LeftSidebar = () => {
const context = useContext(AppContext);
const { state, dispatch } = context;
const { data } = state;
const [currentTab, setCurrentTab] = useState('Extras');
const onChange = (key, value) => {
dispatch({
type: 'on_input',
payload: {
key,
value,
},
});
};
useEffect(() => {
dispatch({ type: 'populate_starter' });
}, [dispatch]);
const renderTabs = () => {
switch (currentTab) {
case 'Profile':
return <ProfileTab data={data} onChange={onChange} />;
case 'Objective':
return <ObjectiveTab data={data} onChange={onChange} />;
case 'Work Experience':
return <WorkTab data={data} onChange={onChange} />;
case 'Education':
return <EducationTab data={data} onChange={onChange} />;
case 'Awards':
return <AwardsTab data={data} onChange={onChange} />;
case 'Certifications':
return <CertificationsTab data={data} onChange={onChange} />;
case 'Skills':
return <SkillsTab data={data} onChange={onChange} />;
case 'Extras':
return <ExtrasTab data={data} onChange={onChange} />;
default:
return null;
}
};
return (
<div id="leftSidebar" className="h-screen bg-white col-span-1 shadow-2xl overflow-y-scroll">
<TabBar tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
<div className="px-6 pb-6">{renderTabs()}</div>
</div>
);
};
export default LeftSidebar;

View File

@ -0,0 +1,197 @@
import React, { useState, useContext } from 'react';
import { v4 as uuidv4 } from 'uuid';
import set from 'lodash/set';
import TextField from '../../../shared/TextField';
import AppContext from '../../../context/AppContext';
const AwardsTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.awards.items.map((x, index) => (
<Item
item={x}
key={x.id}
index={index}
onChange={onChange}
dispatch={dispatch}
first={index === 0}
last={index === data.awards.items.length - 1}
/>
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState({
id: uuidv4(),
title: '',
subtitle: '',
});
const onChange = (key, value) => setItem(set({ ...item }, key, value));
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'awards',
value: item,
},
});
setItem({
id: uuidv4(),
title: '',
subtitle: '',
});
setOpen(false);
};
return (
<div className="border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Award</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Title"
placeholder="Math &amp; Science Olympiad"
value={item.title}
onChange={v => onChange('title', v)}
/>
<TextField
label="Subtitle"
placeholder="First Place, International Level"
value={item.subtitle}
onChange={v => onChange('subtitle', v)}
/>
<button
type="button"
onClick={addItem}
className="mt-4 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">add</i>
<span className="text-sm">Add</span>
</div>
</button>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch, first, last }) => {
const [isOpen, setOpen] = useState(false);
const identifier = `data.awards.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'awards',
value: item,
},
});
const moveItemUp = () =>
dispatch({
type: 'move_item_up',
payload: {
key: 'awards',
value: item,
},
});
const moveItemDown = () =>
dispatch({
type: 'move_item_down',
payload: {
key: 'awards',
value: item,
},
});
return (
<div className="my-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">{item.title}</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Title"
placeholder="Math &amp; Science Olympiad"
value={item.title}
onChange={v => onChange(`${identifier}.title`, v)}
/>
<TextField
label="Subtitle"
placeholder="First Place, International Level"
value={item.subtitle}
onChange={v => onChange(`${identifier}.subtitle`, v)}
/>
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={deleteItem}
className="bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">delete</i>
<span className="text-sm">Delete</span>
</div>
</button>
<div className="flex">
{!first && (
<button
type="button"
onClick={moveItemUp}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded mr-2"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_upward</i>
</div>
</button>
)}
{!last && (
<button
type="button"
onClick={moveItemDown}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_downward</i>
</div>
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default AwardsTab;

View File

@ -0,0 +1,197 @@
import React, { useState, useContext } from 'react';
import { v4 as uuidv4 } from 'uuid';
import set from 'lodash/set';
import TextField from '../../../shared/TextField';
import AppContext from '../../../context/AppContext';
const CertificationsTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.certifications.items.map((x, index) => (
<Item
item={x}
key={x.id}
index={index}
onChange={onChange}
dispatch={dispatch}
first={index === 0}
last={index === data.certifications.items.length - 1}
/>
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState({
id: uuidv4(),
title: '',
subtitle: '',
});
const onChange = (key, value) => setItem(set({ ...item }, key, value));
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'certifications',
value: item,
},
});
setItem({
id: uuidv4(),
title: '',
subtitle: '',
});
setOpen(false);
};
return (
<div className="border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Certification</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Title"
placeholder="Android Development Nanodegree"
value={item.title}
onChange={v => onChange('title', v)}
/>
<TextField
label="Subtitle"
placeholder="Udacity"
value={item.subtitle}
onChange={v => onChange('subtitle', v)}
/>
<button
type="button"
onClick={addItem}
className="mt-4 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">add</i>
<span className="text-sm">Add</span>
</div>
</button>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch, first, last }) => {
const [isOpen, setOpen] = useState(false);
const identifier = `data.certifications.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'certifications',
value: item,
},
});
const moveItemUp = () =>
dispatch({
type: 'move_item_up',
payload: {
key: 'certifications',
value: item,
},
});
const moveItemDown = () =>
dispatch({
type: 'move_item_down',
payload: {
key: 'certifications',
value: item,
},
});
return (
<div className="my-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">{item.title}</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Title"
placeholder="Android Development Nanodegree"
value={item.title}
onChange={v => onChange(`${identifier}.title`, v)}
/>
<TextField
label="Subtitle"
placeholder="Udacity"
value={item.subtitle}
onChange={v => onChange(`${identifier}.subtitle`, v)}
/>
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={deleteItem}
className="bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">delete</i>
<span className="text-sm">Delete</span>
</div>
</button>
<div className="flex">
{!first && (
<button
type="button"
onClick={moveItemUp}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded mr-2"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_upward</i>
</div>
</button>
)}
{!last && (
<button
type="button"
onClick={moveItemDown}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_downward</i>
</div>
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default CertificationsTab;

View File

@ -0,0 +1,268 @@
import React, { useState, useContext } from 'react';
import { v4 as uuidv4 } from 'uuid';
import set from 'lodash/set';
import TextField from '../../../shared/TextField';
import TextArea from '../../../shared/TextArea';
import AppContext from '../../../context/AppContext';
const EducationTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.education.items.map((x, index) => (
<Item
item={x}
key={x.id}
index={index}
onChange={onChange}
dispatch={dispatch}
first={index === 0}
last={index === data.education.items.length - 1}
/>
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState({
id: uuidv4(),
name: '',
major: '',
start: '',
end: '',
grade: '',
description: '',
});
const onChange = (key, value) => setItem(set({ ...item }, key, value));
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'education',
value: item,
},
});
setItem({
id: uuidv4(),
title: '',
role: '',
start: '',
end: '',
grade: '',
description: '',
});
setOpen(false);
};
return (
<div className="border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Education</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Name"
placeholder="Harvard University"
value={item.name}
onChange={v => onChange('name', v)}
/>
<TextField
label="Major"
placeholder="Masters in Computer Science"
value={item.major}
onChange={v => onChange('major', v)}
/>
<TextField
label="Grade"
placeholder="7.2 CGPA"
value={item.grade}
onChange={v => onChange('grade', v)}
/>
<div className="grid grid-cols-2 col-gap-4">
<TextField
label="Start Date"
placeholder="March 2018"
value={item.start}
onChange={v => onChange('start', v)}
/>
<TextField
label="End Date"
placeholder="May 2020"
value={item.end}
onChange={v => onChange('end', v)}
/>
</div>
<TextArea
rows="5"
label="Description"
placeholder="You can write about projects or special credit classes that you took while studying at this school."
value={item.description}
onChange={v => onChange('description', v)}
/>
<button
type="button"
onClick={addItem}
className="mt-4 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">add</i>
<span className="text-sm">Add</span>
</div>
</button>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch, first, last }) => {
const [isOpen, setOpen] = useState(false);
const identifier = `data.education.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'education',
value: item,
},
});
const moveItemUp = () =>
dispatch({
type: 'move_item_up',
payload: {
key: 'education',
value: item,
},
});
const moveItemDown = () =>
dispatch({
type: 'move_item_down',
payload: {
key: 'education',
value: item,
},
});
return (
<div className="my-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">{item.name}</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Name"
placeholder="Harvard University"
value={item.name}
onChange={v => onChange(`${identifier}.name`, v)}
/>
<TextField
label="Major"
placeholder="Masters in Computer Science"
value={item.major}
onChange={v => onChange(`${identifier}.major`, v)}
/>
<TextField
label="Grade"
placeholder="7.2 CGPA"
value={item.grade}
onChange={v => onChange(`${identifier}.grade`, v)}
/>
<div className="grid grid-cols-2 col-gap-4">
<TextField
label="Start Date"
placeholder="March 2018"
value={item.start}
onChange={v => onChange(`${identifier}.start`, v)}
/>
<TextField
label="End Date"
placeholder="May 2020"
value={item.end}
onChange={v => onChange(`${identifier}.end`, v)}
/>
</div>
<TextArea
rows="5"
label="Description"
placeholder="You can write about projects or special credit classes that you took while studying at this school."
value={item.description}
onChange={v => onChange(`${identifier}.description`, v)}
/>
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={deleteItem}
className="bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">delete</i>
<span className="text-sm">Delete</span>
</div>
</button>
<div className="flex">
{!first && (
<button
type="button"
onClick={moveItemUp}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded mr-2"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_upward</i>
</div>
</button>
)}
{!last && (
<button
type="button"
onClick={moveItemDown}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_downward</i>
</div>
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default EducationTab;

View File

@ -0,0 +1,197 @@
import React, { useState, useContext } from 'react';
import { v4 as uuidv4 } from 'uuid';
import set from 'lodash/set';
import TextField from '../../../shared/TextField';
import AppContext from '../../../context/AppContext';
const ExtrasTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.extras.items.map((x, index) => (
<Item
item={x}
key={x.id}
index={index}
onChange={onChange}
dispatch={dispatch}
first={index === 0}
last={index === data.extras.items.length - 1}
/>
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState({
id: uuidv4(),
key: '',
value: '',
});
const onChange = (key, value) => setItem(items => set({ ...items }, key, value));
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'extras',
value: item,
},
});
setItem({
id: uuidv4(),
key: '',
value: '',
});
setOpen(false);
};
return (
<div className="border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Item</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Key"
placeholder="Date of Birth"
value={item.key}
onChange={v => onChange('key', v)}
/>
<TextField
label="Value"
placeholder="6th August 1995"
value={item.value}
onChange={v => onChange('value', v)}
/>
<button
type="button"
onClick={addItem}
className="mt-4 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">add</i>
<span className="text-sm">Add</span>
</div>
</button>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch, first, last }) => {
const [isOpen, setOpen] = useState(false);
const identifier = `data.extras.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'extras',
value: item,
},
});
const moveItemUp = () =>
dispatch({
type: 'move_item_up',
payload: {
key: 'extras',
value: item,
},
});
const moveItemDown = () =>
dispatch({
type: 'move_item_down',
payload: {
key: 'extras',
value: item,
},
});
return (
<div className="my-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">{item.key}</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Key"
placeholder="Date of Birth"
value={item.key}
onChange={v => onChange(`${identifier}.key`, v)}
/>
<TextField
label="Value"
placeholder="6th August 1995"
value={item.value}
onChange={v => onChange(`${identifier}.value`, v)}
/>
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={deleteItem}
className="bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">delete</i>
<span className="text-sm">Delete</span>
</div>
</button>
<div className="flex">
{!first && (
<button
type="button"
onClick={moveItemUp}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded mr-2"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_upward</i>
</div>
</button>
)}
{!last && (
<button
type="button"
onClick={moveItemDown}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_downward</i>
</div>
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default ExtrasTab;

View File

@ -0,0 +1,18 @@
import React from 'react';
import TextArea from '../../../shared/TextArea';
const ObjectiveTab = ({ data, onChange }) => {
return (
<div>
<TextArea
rows="15"
label="Objective"
placeholder="Looking for a challenging role in a reputable organization to utilize my technical, database, and management skills for the growth of the organization as well as to enhance my knowledge about new and emerging trends in the IT sector."
value={data.objective.body}
onChange={v => onChange('data.objective.body', v)}
/>
</div>
);
};
export default ObjectiveTab;

View File

@ -0,0 +1,83 @@
import React from 'react';
import TextField from '../../../shared/TextField';
const ProfileTab = ({ data, onChange }) => (
<div>
<TextField
label="Photo URL"
placeholder="https://i.imgur.com/..."
value={data.profile.photo}
onChange={v => onChange('data.profile.photo', v)}
/>
<div className="grid grid-cols-2 col-gap-4">
<TextField
label="First Name"
placeholder="Jane"
value={data.profile.firstName}
onChange={v => onChange('data.profile.firstName', v)}
/>
<TextField
label="Last Name"
placeholder="Doe"
value={data.profile.lastName}
onChange={v => onChange('data.profile.lastName', v)}
/>
</div>
<TextField
label="Subtitle"
placeholder="Full Stack Web Developer"
value={data.profile.subtitle}
onChange={v => onChange('data.profile.subtitle', v)}
/>
<hr className="my-6" />
<TextField
label="Address Line 1"
placeholder="Palladium Complex"
value={data.profile.address.line1}
onChange={v => onChange('data.profile.address.line1', v)}
/>
<TextField
label="Address Line 2"
placeholder="140 E 14th St"
value={data.profile.address.line2}
onChange={v => onChange('data.profile.address.line2', v)}
/>
<TextField
label="Address Line 3"
placeholder="New York, NY 10003 USA"
value={data.profile.address.line3}
onChange={v => onChange('data.profile.address.line3', v)}
/>
<hr className="my-6" />
<TextField
label="Phone Number"
placeholder="+1 541 754 3010"
value={data.profile.phone}
onChange={v => onChange('data.profile.phone', v)}
/>
<TextField
label="Website"
placeholder="google.com"
value={data.profile.website}
onChange={v => onChange('data.profile.website', v)}
/>
<TextField
label="Email Address"
placeholder="john.doe@example.com"
value={data.profile.email}
onChange={v => onChange('data.profile.email', v)}
/>
</div>
);
export default ProfileTab;

View File

@ -0,0 +1,110 @@
import React, { useState, useContext } from 'react';
import AppContext from '../../../context/AppContext';
const SkillsTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.skills.items.map((x, index) => (
<Item item={x} key={x} index={index} onChange={onChange} dispatch={dispatch} />
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState('');
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'skills',
value: item,
},
});
setItem('');
setOpen(false);
};
return (
<div className="mt-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Skill</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<div className="mt-4 grid grid-cols-4 col-gap-4">
<div className="col-span-3">
<input
className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
placeholder="Cooking"
value={item}
onChange={e => setItem(e.target.value)}
type="text"
/>
</div>
<button
type="button"
onClick={addItem}
className="col-span-1 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded"
>
<div className="flex justify-center items-center">
<i className="material-icons font-bold text-lg">add</i>
</div>
</button>
</div>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch }) => {
const identifier = `data.skills.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'skills',
value: item,
},
});
return (
<div className="mt-4 grid grid-cols-6">
<div className="col-span-5">
<input
className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
placeholder="Cooking"
value={item}
onChange={e => onChange(`${identifier}`, e.target.value)}
type="text"
/>
</div>
<button
type="button"
onClick={deleteItem}
className="col-span-1 text-gray-600 hover:text-red-600 text-sm font-medium"
>
<div className="flex justify-end items-center">
<i className="material-icons font-bold text-lg pr-4">close</i>
</div>
</button>
</div>
);
};
export default SkillsTab;

View File

@ -0,0 +1,252 @@
import React, { useState, useContext } from 'react';
import { v4 as uuidv4 } from 'uuid';
import set from 'lodash/set';
import TextField from '../../../shared/TextField';
import TextArea from '../../../shared/TextArea';
import AppContext from '../../../context/AppContext';
const WorkTab = ({ data, onChange }) => {
const context = useContext(AppContext);
const { dispatch } = context;
return (
<>
{data.work.items.map((x, index) => (
<Item
item={x}
key={x.id}
index={index}
onChange={onChange}
dispatch={dispatch}
first={index === 0}
last={index === data.work.items.length - 1}
/>
))}
<AddItem dispatch={dispatch} />
</>
);
};
const AddItem = ({ dispatch }) => {
const [isOpen, setOpen] = useState(false);
const [item, setItem] = useState({
id: uuidv4(),
title: '',
role: '',
start: '',
end: '',
description: '',
});
const onChange = (key, value) => setItem(set({ ...item }, key, value));
const addItem = () => {
dispatch({
type: 'add_item',
payload: {
key: 'work',
value: item,
},
});
setItem({
id: uuidv4(),
title: '',
role: '',
start: '',
end: '',
description: '',
});
setOpen(false);
};
return (
<div className="border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">Add Work Experience</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Name"
placeholder="Amazon US"
value={item.title}
onChange={v => onChange('title', v)}
/>
<TextField
label="Role"
placeholder="Frontend Web Developer"
value={item.role}
onChange={v => onChange('role', v)}
/>
<div className="grid grid-cols-2 col-gap-4">
<TextField
label="Start Date"
placeholder="March 2018"
value={item.start}
onChange={v => onChange('start', v)}
/>
<TextField
label="End Date"
placeholder="current"
value={item.end}
onChange={v => onChange('end', v)}
/>
</div>
<TextArea
rows="5"
label="Description"
placeholder="You can write about what you specialized in while working at the company and what projects you were a part of."
value={item.description}
onChange={v => onChange('description', v)}
/>
<button
type="button"
onClick={addItem}
className="mt-4 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">add</i>
<span className="text-sm">Add</span>
</div>
</button>
</div>
</div>
);
};
const Item = ({ item, index, onChange, dispatch, first, last }) => {
const [isOpen, setOpen] = useState(false);
const identifier = `data.work.items[${index}]`;
const deleteItem = () =>
dispatch({
type: 'delete_item',
payload: {
key: 'work',
value: item,
},
});
const moveItemUp = () =>
dispatch({
type: 'move_item_up',
payload: {
key: 'work',
value: item,
},
});
const moveItemDown = () =>
dispatch({
type: 'move_item_down',
payload: {
key: 'work',
value: item,
},
});
return (
<div className="my-4 border border-gray-200 rounded p-5">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() => setOpen(!isOpen)}
>
<h6 className="text-sm font-medium">{item.title}</h6>
<i className="material-icons">{isOpen ? 'expand_less' : 'expand_more'}</i>
</div>
<div className={`mt-6 ${isOpen ? 'block' : 'hidden'}`}>
<TextField
label="Name"
placeholder="Amazon US"
value={item.title}
onChange={v => onChange(`${identifier}.title`, v)}
/>
<TextField
label="Role"
placeholder="Frontend Web Developer"
value={item.role}
onChange={v => onChange(`${identifier}.role`, v)}
/>
<div className="grid grid-cols-2 col-gap-4">
<TextField
label="Start Date"
placeholder="March 2018"
value={item.start}
onChange={v => onChange(`${identifier}.start`, v)}
/>
<TextField
label="End Date"
placeholder="current"
value={item.end}
onChange={v => onChange(`${identifier}.end`, v)}
/>
</div>
<TextArea
rows="5"
label="Description"
placeholder="You can write about what you specialized in while working at the company and what projects you were a part of."
value={item.description}
onChange={v => onChange(`${identifier}.description`, v)}
/>
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={deleteItem}
className="bg-red-600 hover:bg-red-700 text-white text-sm font-medium py-2 px-5 rounded"
>
<div className="flex items-center">
<i className="material-icons mr-2 font-bold text-base">delete</i>
<span className="text-sm">Delete</span>
</div>
</button>
<div className="flex">
{!first && (
<button
type="button"
onClick={moveItemUp}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded mr-2"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_upward</i>
</div>
</button>
)}
{!last && (
<button
type="button"
onClick={moveItemDown}
className="bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium py-2 px-4 rounded"
>
<div className="flex items-center">
<i className="material-icons font-bold text-base">arrow_downward</i>
</div>
</button>
)}
</div>
</div>
</div>
</div>
);
};
export default WorkTab;

115
src/context/AppContext.js Normal file
View File

@ -0,0 +1,115 @@
/* eslint-disable no-case-declarations */
import React, { createContext, useReducer } from 'react';
import get from 'lodash/get';
import set from 'lodash/set';
import remove from 'lodash/remove';
import initialData from '../assets/starter/data.json';
import initialTheme from '../assets/starter/theme.json';
import { move } from '../utils';
const initialState = {
data: {
profile: {
heading: 'Profile',
photo: '',
firstName: '',
lastName: '',
subtitle: '',
address: {
line1: '',
line2: '',
line3: '',
},
phone: '',
website: '',
email: '',
},
objective: {
heading: 'Objective',
body: '',
},
work: {
heading: 'Work Experience',
items: [],
},
education: {
heading: 'Education',
items: [],
},
awards: {
heading: 'Honors & Awards',
items: [],
},
certifications: {
heading: 'Certifications',
items: [],
},
skills: {
heading: 'Skills & Hobbies',
items: [],
},
extras: {
heading: 'Personal Information',
items: [],
},
},
theme: {
font: {
family: '',
},
colors: {
background: '',
accent: '',
body: '',
},
},
};
const reducer = (state, { type, payload }) => {
let items;
switch (type) {
case 'add_item':
items = get({ ...state }, `data.${payload.key}.items`, []);
items.push(payload.value);
return set({ ...state }, `data.${payload.key}.items`, items);
case 'delete_item':
items = get({ ...state }, `data.${payload.key}.items`, []);
remove(items, x => x === payload.value);
return set({ ...state }, `data.${payload.key}.items`, items);
case 'move_item_up':
items = get({ ...state }, `data.${payload.key}.items`, []);
move(items, payload.value, -1);
return set({ ...state }, `data.${payload.key}.items`, items);
case 'move_item_down':
items = get({ ...state }, `data.${payload.key}.items`, []);
move(items, payload.value, 1);
return set({ ...state }, `data.${payload.key}.items`, items);
case 'on_input':
return set({ ...state }, payload.key, payload.value);
case 'populate_starter':
return {
...state,
data: initialData,
theme: initialTheme,
};
case 'reset':
return initialState;
default:
throw state;
}
};
const AppContext = createContext(initialState);
const { Provider } = AppContext;
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
export const AppProvider = StateProvider;
export const AppConsumer = AppContext.Consumer;
export default AppContext;

View File

@ -1,13 +1,61 @@
html,
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100%;
color: #2d3748;
background-color: #f5f5f5;
font-size: 14px;
font-family: 'Montserrat', sans-serif;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
@media screen {
#sidebar {
top: 0;
left: 0;
bottom: 0;
}
#tabs::-webkit-scrollbar {
width: 0px;
background: transparent; /* make scrollbar transparent */
}
#tabs li:first-child {
padding-left: 1.5em;
}
#tabs li:last-child {
padding-right: 1.5em;
}
#page {
width: 21cm;
height: 29.7cm;
zoom: 0.8;
background-color: white;
overflow: scroll;
}
}
@media print {
html,
body,
body * {
-webkit-print-color-adjust: exact;
background-color: white;
visibility: hidden;
}
#page,
#page * {
visibility: visible;
}
#page {
margin: 0;
padding: 0;
box-shadow: none;
position: absolute;
left: 0;
top: 0;
}
}

View File

@ -1,17 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './assets/tailwind/tailwind.css';
import * as serviceWorker from './serviceWorker';
import './index.css';
import { AppProvider } from './context/AppContext';
import App from './components/App/App';
ReactDOM.render(
<React.StrictMode>
<App />
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
document.getElementById('root'),
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -15,9 +15,7 @@ const isLocalhost = Boolean(
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
);
export function register(config) {
@ -43,7 +41,7 @@ export function register(config) {
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
'worker. To learn more, visit https://bit.ly/CRA-PWA',
);
});
} else {
@ -71,7 +69,7 @@ function registerValidSW(swUrl, config) {
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
);
// Execute callback
@ -122,9 +120,7 @@ function checkValidServiceWorker(swUrl, config) {
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
console.log('No internet connection found. App is running in offline mode.');
});
}

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

28
src/shared/TabBar.js Normal file
View File

@ -0,0 +1,28 @@
import React from 'react';
const TabBar = ({ tabs, currentTab, setCurrentTab }) => {
return (
<ul id="tabs" className="my-4 flex items-center overflow-x-scroll">
{tabs.map(tab =>
currentTab === tab ? (
<li key={tab} className="mx-1">
<div className="whitespace-no-wrap bg-gray-700 text-white rounded-md text-sm py-2 px-6 font-medium">
{tab}
</div>
</li>
) : (
<li key={tab} className="mx-1">
<div
className="bg-white whitespace-no-wrap rounded-md cursor-pointer text-sm py-2 px-6 font-medium hover:bg-gray-200"
onClick={() => setCurrentTab(tab)}
>
{tab}
</div>
</li>
),
)}
</ul>
);
};
export default TabBar;

18
src/shared/TextArea.js Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
const TextArea = ({ label, placeholder, value, onChange, rows = 5 }) => (
<div className="my-4 w-full flex flex-col">
<label className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-2">
{label}
</label>
<textarea
className="appearance-none block leading-7 w-full bg-gray-200 text-gray-800 border border-gray-200 rounded py-3 px-4 focus:outline-none focus:bg-white focus:border-gray-500"
rows={rows}
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
/>
</div>
);
export default TextArea;

18
src/shared/TextField.js Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
const TextField = ({ label, placeholder, value, onChange }) => (
<div className="my-4 w-full flex flex-col">
<label className="uppercase tracking-wide text-gray-600 text-xs font-semibold mb-2">
{label}
</label>
<input
className="appearance-none block w-full bg-gray-200 text-gray-800 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
type="text"
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
/>
</div>
);
export default TextField;

172
src/templates/onyx/Onyx.js Normal file
View File

@ -0,0 +1,172 @@
import React, { useContext } from 'react';
import AppContext from '../../context/AppContext';
const Onyx = () => {
const context = useContext(AppContext);
const { state } = context;
const { data, theme } = state;
return (
<div
style={{
fontFamily: theme.font.family,
backgroundColor: theme.colors.background,
color: theme.colors.body,
}}
>
<div className="grid grid-cols-4 items-center">
<div className="col-span-3 flex items-center">
{data.profile.photo && (
<img
className="rounded object-cover mr-4"
src={data.profile.photo}
alt="Resume Photograph"
style={{ width: '120px', height: '120px' }}
/>
)}
<div>
<h1 className="font-bold text-4xl" style={{ color: theme.colors.accent }}>
{data.profile.firstName} {data.profile.lastName}
</h1>
<h6 className="font-medium text-sm">{data.profile.subtitle}</h6>
<div className="flex flex-col mt-4 text-xs">
<span>{data.profile.address.line1}</span>
<span>{data.profile.address.line2}</span>
<span>{data.profile.address.line3}</span>
</div>
</div>
</div>
<div className="col-span-1 text-xs">
<div className="flex items-center my-3">
<span className="material-icons text-lg mr-2" style={{ color: theme.colors.accent }}>
phone
</span>
<span className="font-medium">{data.profile.phone}</span>
</div>
<div className="flex items-center my-3">
<span className="material-icons text-lg mr-2" style={{ color: theme.colors.accent }}>
language
</span>
<span className="font-medium">{data.profile.website}</span>
</div>
<div className="flex items-center my-3">
<span className="material-icons text-lg mr-2" style={{ color: theme.colors.accent }}>
alternate_email
</span>
<span className="font-medium">{data.profile.email}</span>
</div>
</div>
</div>
<hr className="my-6" />
<h6 className="text-xs font-bold uppercase mt-6 mb-2" style={{ color: theme.colors.accent }}>
{data.objective.heading}
</h6>
<p className="text-sm">{data.objective.body}</p>
<h6 className="text-xs font-bold uppercase mt-6 mb-2" style={{ color: theme.colors.accent }}>
{data.work.heading}
</h6>
{data.work.items.map(exp => (
<div key={exp.title} className="mt-3">
<div className="flex justify-between">
<div>
<h6 className="font-semibold">{exp.title}</h6>
<p className="text-xs">{exp.role}</p>
</div>
<span className="text-xs font-medium">
({exp.start} - {exp.end})
</span>
</div>
<p className="mt-2 text-sm">{exp.description}</p>
</div>
))}
<h6 className="text-xs font-bold uppercase mt-6 mb-2" style={{ color: theme.colors.accent }}>
{data.education.heading}
</h6>
{data.education.items.map(edu => (
<div key={edu.name} className="mt-3">
<div className="flex justify-between">
<div>
<h6 className="font-semibold">{edu.name}</h6>
<p className="text-xs">{edu.major}</p>
</div>
<div className="flex flex-col items-end">
<span className="text-sm font-bold">{edu.grade}</span>
<span className="text-xs font-medium">
({edu.start} - {edu.end})
</span>
</div>
</div>
<p className="mt-2 text-sm">{edu.description}</p>
</div>
))}
<div className="grid grid-cols-2">
<div>
<h6
className="text-xs font-bold uppercase mt-6 mb-2"
style={{ color: theme.colors.accent }}
>
{data.awards.heading}
</h6>
{data.awards.items.map(x => (
<div key={x.title} className="mt-3">
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.subtitle}</p>
</div>
))}
</div>
<div>
<h6
className="text-xs font-bold uppercase mt-6 mb-2"
style={{ color: theme.colors.accent }}
>
{data.certifications.heading}
</h6>
{data.certifications.items.map(x => (
<div key={x.title} className="mt-3">
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.subtitle}</p>
</div>
))}
</div>
</div>
<h6 className="text-xs font-bold uppercase mt-6 mb-2" style={{ color: theme.colors.accent }}>
{data.skills.heading}
</h6>
<div className="mt-1 flex flex-wrap">
{data.skills.items.map(x => (
<span
key={x}
className="text-xs rounded-full px-3 py-1 font-medium my-2 mr-2"
style={{ backgroundColor: theme.colors.body, color: theme.colors.background }}
>
{x}
</span>
))}
</div>
<h6 className="text-xs font-bold uppercase mt-6 mb-2" style={{ color: theme.colors.accent }}>
{data.extras.heading}
</h6>
<table className="w-2/3 table-auto">
<tbody>
{data.extras.items.map(x => (
<tr key={x.key}>
<td className="border font-medium px-4 py-2 text-sm">{x.key}</td>
<td className="border px-4 py-2 text-sm">{x.value}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default Onyx;

11
src/utils/index.js Normal file
View File

@ -0,0 +1,11 @@
/* eslint-disable import/prefer-default-export */
const move = (array, element, delta) => {
const index = array.indexOf(element);
const newIndex = index + delta;
if (newIndex < 0 || newIndex === array.length) return;
const indexes = [index, newIndex].sort((a, b) => a - b);
array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]);
};
export { move };

9
tailwind.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
theme: {
container: {
center: true,
},
},
variants: {},
plugins: [],
};