- completed design of Onyx template

This commit is contained in:
Amruth Pillai
2020-07-11 10:49:55 +05:30
parent a8c5d29858
commit 0b5653fab5
32 changed files with 848 additions and 122 deletions

View File

@ -11,11 +11,10 @@ import { ResumeProvider } from './src/contexts/ResumeContext';
import { StorageProvider } from './src/contexts/StorageContext';
import { ThemeProvider } from './src/contexts/ThemeContext';
import { UserProvider } from './src/contexts/UserContext';
import './src/styles/colors.css';
import './src/styles/global.css';
import './src/styles/shadows.css';
import './src/styles/tailwind.css';
import './src/styles/toastify.css';
import './src/styles/global.css';
const theme = createMuiTheme({
typography: {

View File

@ -86,6 +86,14 @@ module.exports = {
},
},
'gatsby-plugin-lodash',
{
resolve: `gatsby-plugin-material-ui`,
options: {
stylesProvider: {
injectFirst: true,
},
},
},
'gatsby-plugin-postcss',
{
resolve: 'gatsby-source-filesystem',

205
package-lock.json generated
View File

@ -8715,6 +8715,16 @@
}
}
},
"gatsby-plugin-material-ui": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/gatsby-plugin-material-ui/-/gatsby-plugin-material-ui-2.1.9.tgz",
"integrity": "sha512-bvfAEOugCyDgVY9ZNadnC+RP20qA/G2mMDnoj25hRf98fcTtP128s1DshoeYZF61w46Mqp6ACB+uZtrG6iWDfQ==",
"requires": {
"autoprefixer": "^9.6.1",
"clean-css": "^4.2.1",
"postcss": "^7.0.17"
}
},
"gatsby-plugin-offline": {
"version": "3.2.17",
"resolved": "https://registry.npmjs.org/gatsby-plugin-offline/-/gatsby-plugin-offline-3.2.17.tgz",
@ -10043,6 +10053,53 @@
"resolved": "https://registry.npmjs.org/html-tag-names/-/html-tag-names-1.1.5.tgz",
"integrity": "sha512-aI5tKwNTBzOZApHIynaAwecLBv8TlZTEy/P4Sj2SzzAhBrGuI8yGZ0UIXVPQzOHGS+to2mjb04iy6VWt/8+d8A=="
},
"html-to-react": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.3.tgz",
"integrity": "sha512-txe09A3vxW8yEZGJXJ1is5gGDfBEVACmZDSgwDyH5EsfRdOubBwBCg63ZThZP0xBn0UE4FyvMXZXmohusCxDcg==",
"requires": {
"domhandler": "^3.0",
"htmlparser2": "^4.1.0",
"lodash.camelcase": "^4.3.0",
"ramda": "^0.27"
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"domhandler": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
"integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
"requires": {
"domelementtype": "^2.0.1"
}
},
"domutils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz",
"integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==",
"requires": {
"dom-serializer": "^0.2.1",
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0"
}
},
"htmlparser2": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
"integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0"
}
}
}
},
"html-void-elements": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz",
@ -12330,6 +12387,21 @@
"safe-buffer": "^5.1.2"
}
},
"mdast-add-list-metadata": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz",
"integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==",
"requires": {
"unist-util-visit-parents": "1.1.2"
},
"dependencies": {
"unist-util-visit-parents": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz",
"integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q=="
}
}
},
"mdast-squeeze-paragraphs": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz",
@ -15051,6 +15123,11 @@
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz",
"integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ=="
},
"ramda": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz",
"integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA=="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -15451,6 +15528,129 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-markdown": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz",
"integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==",
"requires": {
"html-to-react": "^1.3.4",
"mdast-add-list-metadata": "1.0.1",
"prop-types": "^15.7.2",
"react-is": "^16.8.6",
"remark-parse": "^5.0.0",
"unified": "^6.1.5",
"unist-util-visit": "^1.3.0",
"xtend": "^4.0.1"
},
"dependencies": {
"is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
},
"parse-entities": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz",
"integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==",
"requires": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
}
},
"remark-parse": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz",
"integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==",
"requires": {
"collapse-white-space": "^1.0.2",
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-whitespace-character": "^1.0.0",
"is-word-character": "^1.0.0",
"markdown-escapes": "^1.0.0",
"parse-entities": "^1.1.0",
"repeat-string": "^1.5.4",
"state-toggle": "^1.0.0",
"trim": "0.0.1",
"trim-trailing-lines": "^1.0.0",
"unherit": "^1.0.4",
"unist-util-remove-position": "^1.0.0",
"vfile-location": "^2.0.0",
"xtend": "^4.0.1"
}
},
"unified": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz",
"integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==",
"requires": {
"bail": "^1.0.0",
"extend": "^3.0.0",
"is-plain-obj": "^1.1.0",
"trough": "^1.0.0",
"vfile": "^2.0.0",
"x-is-string": "^0.1.0"
}
},
"unist-util-remove-position": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz",
"integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==",
"requires": {
"unist-util-visit": "^1.1.0"
}
},
"unist-util-stringify-position": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz",
"integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ=="
},
"unist-util-visit": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz",
"integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==",
"requires": {
"unist-util-visit-parents": "^2.0.0"
}
},
"unist-util-visit-parents": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz",
"integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==",
"requires": {
"unist-util-is": "^3.0.0"
}
},
"vfile": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz",
"integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==",
"requires": {
"is-buffer": "^1.1.4",
"replace-ext": "1.0.0",
"unist-util-stringify-position": "^1.0.0",
"vfile-message": "^1.0.0"
}
},
"vfile-location": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz",
"integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA=="
},
"vfile-message": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz",
"integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==",
"requires": {
"unist-util-stringify-position": "^1.1.1"
}
}
}
},
"react-reconciler": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.25.1.tgz",
@ -19866,6 +20066,11 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
},
"x-is-string": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz",
"integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI="
},
"xdg-basedir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",

View File

@ -30,6 +30,7 @@
"gatsby-plugin-firebase": "^0.2.0-beta.4",
"gatsby-plugin-lodash": "^3.3.10",
"gatsby-plugin-manifest": "^2.4.18",
"gatsby-plugin-material-ui": "^2.1.9",
"gatsby-plugin-offline": "^3.2.17",
"gatsby-plugin-postcss": "^2.3.11",
"gatsby-plugin-prefetch-google-fonts": "^1.4.3",
@ -46,6 +47,7 @@
"react-dom": "^16.13.1",
"react-helmet": "^6.1.0",
"react-icons": "^3.10.0",
"react-markdown": "^4.3.1",
"react-scroll": "^1.7.16",
"react-toastify": "^6.0.8",
"uuid": "^8.2.0",

View File

@ -7,7 +7,7 @@ const Objective = () => {
<section>
<Heading>Objective</Heading>
<Input type="textarea" label="Objective" path="objective" />
<Input type="textarea" label="Objective" path="objective.body" />
</section>
);
};

View File

@ -38,7 +38,7 @@ const Actions = () => {
<div className={styles.container}>
<h5>Import Your Resume</h5>
<p>
<p className="leading-loose">
You can import your information from various sources like JSON Resume
or your LinkedIn to autofill most of the data for your resume.
</p>
@ -53,7 +53,7 @@ const Actions = () => {
<div className={styles.container}>
<h5>Export Your Resume</h5>
<p>
<p className="leading-loose">
Export your resume as a PDF to share with recruiters or a JSON that
you will be able to import back onto this app on another computer.
</p>
@ -73,7 +73,7 @@ const Actions = () => {
<div className={styles.container}>
<h5>Share Your Resume</h5>
<p>
<p className="leading-loose">
The link below will be accessible publicly if you choose to share it,
and viewers would see the latest version of your resume at any time.
</p>
@ -86,7 +86,7 @@ const Actions = () => {
<div className={styles.container}>
<h5>Load Demo Data</h5>
<p>
<p className="leading-loose">
Unclear on what to do with a fresh blank page? Load some demo data to
see how a resume should look and you can start editing from there.
</p>
@ -99,7 +99,7 @@ const Actions = () => {
<div className={styles.container}>
<h5>Delete Account</h5>
<p>
<p className="leading-loose">
If you would like to delete your account and erase all your resumes,
its just one button away. Please be weary as this is an irreversible
process.

View File

@ -1,4 +1,4 @@
import React, { memo } from 'react';
import React from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import { move, reorder } from '../../../../utils';
@ -37,7 +37,7 @@ const Layout = () => {
dispatch({
type: 'on_input',
payload: {
path: 'layout',
path: 'metadata.layout',
value: newState,
},
});
@ -68,11 +68,7 @@ const Layout = () => {
Block {ind + 1}
</span>
{el.map((item, index) => (
<Draggable
key={item.id}
index={index}
draggableId={item.id}
>
<Draggable key={item} index={index} draggableId={item}>
{(dragProvided) => (
<div
ref={dragProvided.innerRef}
@ -80,7 +76,7 @@ const Layout = () => {
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
{item.name}
{item}
</div>
)}
</Draggable>
@ -97,4 +93,4 @@ const Layout = () => {
);
};
export default memo(Layout);
export default Layout;

View File

@ -1,7 +1,7 @@
import { Menu, MenuItem } from '@material-ui/core';
import { navigate } from 'gatsby';
import moment from 'moment';
import React, { memo, useContext, useState } from 'react';
import React, { useContext, useState } from 'react';
import { MdMoreHoriz, MdOpenInNew } from 'react-icons/md';
import { toast } from 'react-toastify';
import DatabaseContext from '../../contexts/DatabaseContext';
@ -78,4 +78,4 @@ const ResumePreview = ({ resume }) => {
);
};
export default memo(ResumePreview);
export default ResumePreview;

View File

@ -8,6 +8,7 @@ import React, {
useState,
} from 'react';
import UserContext from './UserContext';
import initialState from '../data/initialState';
const DEBOUNCE_WAIT_TIME = 4000;
@ -46,8 +47,7 @@ const DatabaseProvider = ({ children }) => {
return snapshot.val();
};
const createResume = (resume) => {
const { id } = resume;
const createResume = ({ id, name }) => {
const createdAt = firebase.database.ServerValue.TIMESTAMP;
let firstName;
@ -61,11 +61,14 @@ const DatabaseProvider = ({ children }) => {
.database()
.ref(`users/${user.uid}/resumes/${id}`)
.set({
...initialState,
id,
name,
profile: {
...initialState.profile,
firstName: firstName || '',
lastName: lastName || '',
},
...resume,
createdAt,
updatedAt: createdAt,
});

View File

@ -0,0 +1,7 @@
import { createContext } from 'react';
const defaultState = {};
const PageContext = createContext(defaultState);
export default PageContext;

View File

@ -1,5 +1,14 @@
import arrayMove from 'array-move';
import { clone, findIndex, get, isUndefined, setWith } from 'lodash';
import {
clone,
findIndex,
get,
isUndefined,
setWith,
flatten,
concat,
times,
} from 'lodash';
import React, {
createContext,
memo,
@ -7,22 +16,8 @@ import React, {
useContext,
useReducer,
} from 'react';
import leftSections from '../data/leftSections';
import DatabaseContext from './DatabaseContext';
const initialState = {
name: '',
metadata: {
template: 'onyx',
font: 'Montserrat',
layout: [leftSections.map(({ id, name }) => ({ id, name }))],
colors: {
text: '#444444',
primary: '#5875DB',
background: '#FFFFFF',
},
},
};
import initialState from '../data/initialState';
const ResumeContext = createContext({});
@ -34,6 +29,8 @@ const ResumeProvider = ({ children }) => {
let newState;
let index;
let items;
let diff;
let temp;
switch (type) {
case 'on_add_item':
@ -85,6 +82,30 @@ const ResumeProvider = ({ children }) => {
debouncedUpdateResume(newState);
return newState;
case 'set_block_count':
items = get(state, 'metadata.layout');
if (items.length === payload) return state;
if (payload === 1) {
items = flatten(items);
}
if (items.length > payload) {
diff = items.length - payload;
temp = items.splice(Math.max(items.length - diff, 1));
items[0] = concat(items[0], flatten(temp));
}
if (items.length < payload) {
diff = payload - items.length;
times(diff, () => items.push([]));
}
newState = setWith(clone(state), 'metadata.layout', items, clone);
debouncedUpdateResume(newState);
return newState;
case 'on_input':
newState = setWith(clone(state), payload.path, payload.value, clone);
debouncedUpdateResume(newState);
@ -132,4 +153,9 @@ const useDispatch = () => {
const memoizedProvider = memo(ResumeProvider);
export { memoizedProvider as ResumeProvider, useSelector, useDispatch };
export {
ResumeContext,
memoizedProvider as ResumeProvider,
useSelector,
useDispatch,
};

View File

@ -62,11 +62,9 @@ const StorageProvider = ({ children }) => {
render:
'Your photograph was uploaded successfully... and you look great!',
progress,
autoClose: 5000,
autoClose: 2000,
hideProgressBar: true,
});
toastId.current = null;
},
);
};

86
src/data/initialState.js Normal file
View File

@ -0,0 +1,86 @@
import leftSections from './leftSections';
const initialState = {
id: '',
profile: {
heading: 'Profile',
photograph: '',
firstName: '',
lastName: '',
subtitle: '',
address: {
line1: '',
line2: '',
city: '',
pincode: '',
},
profile: '',
website: '',
email: '',
},
social: {
heading: 'Social',
visible: true,
items: [],
},
objective: {
heading: 'Objective',
visible: true,
body: '',
},
work: {
heading: 'Work Experience',
visible: true,
items: [],
},
education: {
heading: 'Education',
visible: true,
items: [],
},
awards: {
heading: 'Awards',
visible: true,
items: [],
},
certifications: {
heading: 'Certifications',
visible: true,
items: [],
},
skills: {
heading: 'Skills',
visible: true,
items: [],
},
hobbies: {
heading: 'Hobbies',
visible: true,
items: [],
},
languages: {
heading: 'Languages',
visible: true,
items: [],
},
references: {
heading: 'References',
visible: true,
items: [],
},
name: '',
metadata: {
template: 'onyx',
font: 'Montserrat',
layout: [leftSections.filter((x) => !x.fixed).map((x) => x.id)],
colors: {
text: '#444444',
primary: '#5875DB',
background: '#FFFFFF',
},
},
createdAt: new Date(),
updatedAt: new Date(),
};
export default initialState;

View File

@ -14,12 +14,14 @@ export default [
id: 'profile',
name: 'Profile',
icon: MdPerson,
fixed: true,
},
{
id: 'social',
name: 'Social Network',
icon: AiOutlineTwitter,
event: ModalEvents.SOCIAL_MODAL,
fixed: true,
},
{
id: 'objective',

View File

@ -9,7 +9,7 @@ import DataModal from '../DataModal';
const initialValues = {
name: '',
position: '',
contact: '',
phone: '',
email: '',
summary: '',
};
@ -17,7 +17,7 @@ const initialValues = {
const schema = Yup.object().shape({
name: Yup.string().required('This is a required field.'),
position: Yup.string().required('This is a required field.'),
contact: Yup.string(),
phone: Yup.string(),
email: Yup.string().email('Must be a valid email address.'),
summary: Yup.string(),
});
@ -51,7 +51,7 @@ const ReferenceModal = () => {
<Input
label="Phone Number"
placeholder="+1 (708) 756-6065"
{...getFieldProps(formik, schema, 'contact')}
{...getFieldProps(formik, schema, 'phone')}
/>
<Input

View File

@ -1,5 +1,5 @@
import firebase from 'gatsby-plugin-firebase';
import React, { useEffect, useState } from 'react';
import React, { memo, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import CreateResume from '../../components/dashboard/CreateResume';
import ResumePreview from '../../components/dashboard/ResumePreview';
@ -67,4 +67,4 @@ const Dashboard = ({ user }) => {
);
};
export default Dashboard;
export default memo(Dashboard);

View File

@ -1,12 +0,0 @@
:root {
--color-primary-50: #FFFFFF;
--color-primary-100: #FAFAFA;
--color-primary-200: #F1F0F0;
--color-primary-300: #D8D2CD;
--color-primary-400: #CDC4BA;
--color-primary-500: #ABA59D;
--color-primary-600: #8A8680;
--color-primary-700: #686663;
--color-primary-800: #484745;
--color-primary-900: #282727;
}

View File

@ -6,14 +6,6 @@ body {
@apply transition-colors duration-200 ease-in-out;
}
p {
@apply leading-loose;
}
a {
@apply text-blue-600 font-medium;
}
a:hover {
@apply underline;
}
@ -22,6 +14,10 @@ hr {
@apply w-full border-primary-200 h-1;
}
ul {
@apply list-disc list-inside;
}
section {
@apply grid grid-cols-1 gap-8;
}
@ -35,11 +31,11 @@ label > span:first-child {
}
.MuiTooltip-tooltip {
font-size: 10px !important;
font-size: 10px;
}
.MuiMenuItem-root {
justify-content: center !important;
justify-content: center;
}
.spin {

View File

@ -1,60 +1,107 @@
import React, { memo } from 'react';
import { FaGlobeAmericas, FaPhone } from 'react-icons/fa';
import { MdEmail } from 'react-icons/md';
import React, { memo, useEffect } from 'react';
import PageContext from '../contexts/PageContext';
import { useDispatch } from '../contexts/ResumeContext';
import AwardsA from './blocks/Awards/AwardsA';
import CertificationsA from './blocks/Certifications/CertificationsA';
import Contact from './blocks/Contact/ContactA';
import EducationA from './blocks/Education/EducationA';
import Heading from './blocks/Heading/HeadingA';
import HobbiesA from './blocks/Hobbies/HobbiesA';
import LanguagesA from './blocks/Languages/LanguagesA';
import ObjectiveA from './blocks/Objective/ObjectiveA';
import ReferencesA from './blocks/References/ReferencesA';
import SkillsA from './blocks/Skills/SkillsA';
import WorkA from './blocks/Work/WorkA';
const Blocks = {
objective: ObjectiveA,
work: WorkA,
education: EducationA,
awards: AwardsA,
certifications: CertificationsA,
skills: SkillsA,
hobbies: HobbiesA,
languages: LanguagesA,
references: ReferencesA,
};
const Onyx = ({ data }) => {
const { profile, metadata } = data;
const { font, colors, layout } = metadata;
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: 'set_block_count', payload: 3 });
}, []);
return (
<div
className="p-8 grid grid-cols-10 gap-4 items-center"
style={{
fontFamily: font,
color: colors.text,
backgroundColor: colors.background,
}}
>
<img
className="col-span-2 rounded"
src="https://i.imgur.com/Icr472Z.jpg"
alt="Photograph"
/>
<div className="col-span-5">
<h2 className="text-4xl font-bold" style={{ color: colors.primary }}>
{profile.firstName} {profile.lastName}
</h2>
<span className="font-medium">Customer Sales Representative</span>
<PageContext.Provider value={{ data, heading: Heading }}>
<div
id="page"
className="p-10"
style={{
fontFamily: data.metadata.font,
color: data.metadata.colors.text,
backgroundColor: data.metadata.colors.background,
}}
>
<div className="grid grid-cols-4 items-center">
<div className="col-span-3 flex items-center">
<img
className="rounded object-cover mr-4"
src={data.profile.photograph}
alt="Resume Photograph"
style={{ width: '120px', height: '120px' }}
/>
<div className="mt-5 flex flex-col">
<span>3879 Gateway Avenue,</span>
<span>Bakersfield,</span>
<span>California, USA</span>
<div>
<h1
className="font-bold text-4xl"
style={{ color: data.metadata.colors.primary }}
>
{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.city} {data.profile.address.pincode}
</span>
</div>
</div>
</div>
<Contact />
</div>
<hr
className="my-6 opacity-25"
style={{ borderColor: data.metadata.colors.text }}
/>
<div className="grid grid-cols-1 col-gap-8">
{data.metadata.layout[0] &&
data.metadata.layout[0].map((x) => {
const Component = Blocks[x];
return Component && <Component key={x} />;
})}
<div className="grid grid-cols-3 col-gap-8">
{data.metadata.layout[1] &&
data.metadata.layout[1].map((x) => {
const Component = Blocks[x];
return Component && <Component key={x} />;
})}
</div>
{data.metadata.layout[2] &&
data.metadata.layout[2].map((x) => {
const Component = Blocks[x];
return Component && <Component key={x} />;
})}
</div>
</div>
<div className="col-span-3 grid gap-4">
<div className="flex items-center">
<FaPhone size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium">+1 661-808-4188</span>
</div>
<div className="flex items-center">
<FaGlobeAmericas size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium">nancyontheweb.com</span>
</div>
<div className="flex items-center">
<MdEmail size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium">
nancyjack43@gmail.com
</span>
</div>
</div>
<hr className="my-2 col-span-10" />
{JSON.stringify(layout)}
</div>
</PageContext.Provider>
);
};

View File

@ -0,0 +1,33 @@
import moment from 'moment';
import React, { memo, useContext } from 'react';
import ReactMarkdown from 'react-markdown';
import PageContext from '../../../contexts/PageContext';
const AwardItem = (x) => (
<div key={x.id} className="mb-2">
<div className="flex justify-between items-center">
<div>
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.awarder}</p>
</div>
<h6 className="text-xs font-medium">
{moment(x.date).format('MMMM YYYY')}
</h6>
</div>
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
</div>
);
const AwardsA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.awards.visible && data.awards.items ? (
<div>
<Heading>{data.awards.heading}</Heading>
{data.awards.items.map(AwardItem)}
</div>
) : null;
};
export default memo(AwardsA);

View File

@ -0,0 +1,33 @@
import moment from 'moment';
import React, { memo, useContext } from 'react';
import ReactMarkdown from 'react-markdown';
import PageContext from '../../../contexts/PageContext';
const CertificationItem = (x) => (
<div key={x.id} className="mb-2">
<div className="flex justify-between items-center">
<div>
<h6 className="font-semibold">{x.title}</h6>
<p className="text-xs">{x.issuer}</p>
</div>
<h6 className="text-xs font-medium">
{moment(x.date).format('MMMM YYYY')}
</h6>
</div>
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
</div>
);
const CertificationsA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.certifications.visible && data.certifications.items ? (
<div>
<Heading>{data.certifications.heading}</Heading>
{data.certifications.items.map(CertificationItem)}
</div>
) : null;
};
export default memo(CertificationsA);

View File

@ -0,0 +1,66 @@
import { get } from 'lodash';
import React, { memo, useContext } from 'react';
import { FaCaretRight } from 'react-icons/fa';
import PageContext from '../../../contexts/PageContext';
import Icons from '../Icons';
const ContactItem = ({ value, icon, link }) => {
const { data } = useContext(PageContext);
const Icon = get(Icons, icon.toLowerCase(), FaCaretRight);
return value ? (
<div className="flex items-center my-3">
<Icon
size="14px"
className="mr-2"
style={{ color: data.metadata.colors.primary }}
/>
{link ? (
<a href={link} target="_blank" rel="noopener noreferrer">
<span className="font-medium break-all">{value}</span>
</a>
) : (
<span className="font-medium break-all">{value}</span>
)}
</div>
) : null;
};
const ContactA = () => {
const { data } = useContext(PageContext);
return (
<div className="col-span-1 text-xs">
<ContactItem
icon="phone"
value={data.profile.phone}
link={`tel:${data.profile.phone}`}
/>
<ContactItem
icon="website"
value={data.profile.website}
link={`http://${data.profile.website}`}
/>
<ContactItem
icon="email"
value={data.profile.email}
link={`mailto:${data.profile.email}`}
/>
{data.social.visible && data.social.items ? (
<div>
{data.social.items.map((x) => (
<ContactItem
key={x.id}
value={x.username}
icon={x.network}
link={x.url}
/>
))}
</div>
) : null}
</div>
);
};
export default memo(ContactA);

View File

@ -0,0 +1,42 @@
import React, { memo, useContext } from 'react';
import PageContext from '../../../contexts/PageContext';
import { formatDateRange } from '../../../utils';
const EducationItem = (x) => (
<div key={x.id} className="mb-2">
<div className="flex justify-between items-center">
<div>
<h6 className="font-semibold">{x.institution}</h6>
<span className="text-xs">
<strong>{x.degree}</strong> {x.field}
</span>
</div>
<div className="flex flex-col items-end">
<span className="text-xs font-medium">
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
</span>
<h6 className="text-sm font-medium">{x.gpa}</h6>
</div>
</div>
{x.courses && (
<ul className="mt-2 text-sm list-disc list-inside">
{x.courses.map((y) => (
<li key={y}>{y}</li>
))}
</ul>
)}
</div>
);
const EducationA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.education.visible && data.education.items ? (
<div>
<Heading>{data.education.heading}</Heading>
{data.education.items.map(EducationItem)}
</div>
) : null;
};
export default memo(EducationA);

View File

@ -0,0 +1,17 @@
import React, { useContext, memo } from 'react';
import PageContext from '../../../contexts/PageContext';
const HeadingA = ({ children }) => {
const { data } = useContext(PageContext);
return (
<h6
className="text-xs font-bold uppercase mt-4 mb-1"
style={{ color: data.metadata.colors.primary }}
>
{children}
</h6>
);
};
export default memo(HeadingA);

View File

@ -0,0 +1,21 @@
import React, { memo, useContext } from 'react';
import PageContext from '../../../contexts/PageContext';
const HobbyA = (x) => (
<div key={x.id} className="mb-2">
<h6 className="font-semibold">{x.name}</h6>
</div>
);
const HobbiesA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.hobbies.visible && data.hobbies.items ? (
<div>
<Heading>{data.hobbies.heading}</Heading>
{data.hobbies.items.map(HobbyA)}
</div>
) : null;
};
export default memo(HobbiesA);

View File

@ -0,0 +1,24 @@
import {
FaGlobeAmericas,
FaFacebookF,
FaTwitter,
FaLinkedinIn,
FaGithub,
FaDribbble,
FaInstagram,
} from 'react-icons/fa';
import { MdPhone, MdEmail } from 'react-icons/md';
const Icons = {
phone: MdPhone,
website: FaGlobeAmericas,
email: MdEmail,
facebook: FaFacebookF,
twitter: FaTwitter,
linkedin: FaLinkedinIn,
github: FaGithub,
dribbble: FaDribbble,
instagram: FaInstagram,
};
export default Icons;

View File

@ -0,0 +1,22 @@
import React, { memo, useContext } from 'react';
import PageContext from '../../../contexts/PageContext';
const LanguageItem = (x) => (
<div key={x.id} className="mb-2">
<h6 className="font-semibold">{x.name}</h6>
<p className="text-xs">{x.fluency}</p>
</div>
);
const LanguagesA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.languages.visible && data.languages.items ? (
<div>
<Heading>{data.languages.heading}</Heading>
{data.languages.items.map(LanguageItem)}
</div>
) : null;
};
export default memo(LanguagesA);

View File

@ -0,0 +1,16 @@
import React, { useContext, memo } from 'react';
import ReactMarkdown from 'react-markdown';
import PageContext from '../../../contexts/PageContext';
const ObjectiveA = () => {
const { data, heading: Heading } = useContext(PageContext);
return (
<div>
<Heading>{data.objective.heading}</Heading>
<ReactMarkdown className="text-sm" source={data.objective.body} />
</div>
);
};
export default memo(ObjectiveA);

View File

@ -0,0 +1,28 @@
import React, { memo, useContext } from 'react';
import ReactMarkdown from 'react-markdown';
import PageContext from '../../../contexts/PageContext';
const ReferenceItem = (x) => (
<div key={x.id} className="mb-2">
<h6 className="font-semibold">{x.name}</h6>
<p className="text-xs">{x.position}</p>
<p className="text-xs">{x.phone}</p>
<p className="text-xs">{x.email}</p>
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
</div>
);
const ReferencesA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.references.visible && data.references.items ? (
<div>
<Heading>{data.references.heading}</Heading>
<div className="grid grid-cols-3 col-gap-8">
{data.references.items.map(ReferenceItem)}
</div>
</div>
) : null;
};
export default memo(ReferencesA);

View File

@ -0,0 +1,22 @@
import React, { memo, useContext } from 'react';
import PageContext from '../../../contexts/PageContext';
const SkillItem = (x) => (
<div key={x.id} className="mb-2">
<h6 className="font-semibold">{x.name}</h6>
<p className="text-xs">{x.level}</p>
</div>
);
const SkillsA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.skills.visible && data.skills.items ? (
<div>
<Heading>{data.skills.heading}</Heading>
{data.skills.items.map(SkillItem)}
</div>
) : null;
};
export default memo(SkillsA);

View File

@ -0,0 +1,39 @@
import React, { useContext, memo } from 'react';
import ReactMarkdown from 'react-markdown';
import PageContext from '../../../contexts/PageContext';
import { formatDateRange } from '../../../utils';
const WorkItem = (x) => (
<div key={x.id} className="mb-4 last:mb-0">
<div className="flex justify-between items-center">
<div>
<h6 className="font-semibold">{x.company}</h6>
<span className="text-xs">{x.position}</span>
</div>
<span className="text-xs font-medium">
({formatDateRange({ startDate: x.startDate, endDate: x.endDate })})
</span>
</div>
<ReactMarkdown className="mt-2 text-sm" source={x.summary} />
{x.highlights && (
<ul className="mt-2 text-sm list-disc list-inside">
{x.highlights.map((y) => (
<li key={y}>{y}</li>
))}
</ul>
)}
</div>
);
const WorkA = () => {
const { data, heading: Heading } = useContext(PageContext);
return data.work.visible && data.work.items ? (
<div>
<Heading>{data.work.heading}</Heading>
<div>{data.work.items.map(WorkItem)}</div>
</div>
) : null;
};
export default memo(WorkA);

View File

@ -20,9 +20,9 @@ export const isFileImage = (file) => {
return file && acceptedImageTypes.includes(file.type);
};
export const formatDateRange = (x) =>
`${moment(x.startDate).format('MMMM Y')}${
moment(x.endDate).isValid() ? moment(x.endDate).format('MMMM Y') : 'Present'
export const formatDateRange = ({ startDate, endDate }) =>
`${moment(startDate).format('MMMM Y')}${
moment(endDate).isValid() ? moment(endDate).format('MMMM Y') : 'Present'
}`;
export const getFieldProps = (formik, schema, name) => ({