- 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 { StorageProvider } from './src/contexts/StorageContext';
import { ThemeProvider } from './src/contexts/ThemeContext'; import { ThemeProvider } from './src/contexts/ThemeContext';
import { UserProvider } from './src/contexts/UserContext'; import { UserProvider } from './src/contexts/UserContext';
import './src/styles/colors.css';
import './src/styles/global.css';
import './src/styles/shadows.css'; import './src/styles/shadows.css';
import './src/styles/tailwind.css'; import './src/styles/tailwind.css';
import './src/styles/toastify.css'; import './src/styles/toastify.css';
import './src/styles/global.css';
const theme = createMuiTheme({ const theme = createMuiTheme({
typography: { typography: {

View File

@ -86,6 +86,14 @@ module.exports = {
}, },
}, },
'gatsby-plugin-lodash', 'gatsby-plugin-lodash',
{
resolve: `gatsby-plugin-material-ui`,
options: {
stylesProvider: {
injectFirst: true,
},
},
},
'gatsby-plugin-postcss', 'gatsby-plugin-postcss',
{ {
resolve: 'gatsby-source-filesystem', 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": { "gatsby-plugin-offline": {
"version": "3.2.17", "version": "3.2.17",
"resolved": "https://registry.npmjs.org/gatsby-plugin-offline/-/gatsby-plugin-offline-3.2.17.tgz", "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", "resolved": "https://registry.npmjs.org/html-tag-names/-/html-tag-names-1.1.5.tgz",
"integrity": "sha512-aI5tKwNTBzOZApHIynaAwecLBv8TlZTEy/P4Sj2SzzAhBrGuI8yGZ0UIXVPQzOHGS+to2mjb04iy6VWt/8+d8A==" "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": { "html-void-elements": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz",
@ -12330,6 +12387,21 @@
"safe-buffer": "^5.1.2" "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": { "mdast-squeeze-paragraphs": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz",
"integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" "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": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "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", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "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": { "react-reconciler": {
"version": "0.25.1", "version": "0.25.1",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.25.1.tgz", "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", "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" "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": { "xdg-basedir": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "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-firebase": "^0.2.0-beta.4",
"gatsby-plugin-lodash": "^3.3.10", "gatsby-plugin-lodash": "^3.3.10",
"gatsby-plugin-manifest": "^2.4.18", "gatsby-plugin-manifest": "^2.4.18",
"gatsby-plugin-material-ui": "^2.1.9",
"gatsby-plugin-offline": "^3.2.17", "gatsby-plugin-offline": "^3.2.17",
"gatsby-plugin-postcss": "^2.3.11", "gatsby-plugin-postcss": "^2.3.11",
"gatsby-plugin-prefetch-google-fonts": "^1.4.3", "gatsby-plugin-prefetch-google-fonts": "^1.4.3",
@ -46,6 +47,7 @@
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-icons": "^3.10.0", "react-icons": "^3.10.0",
"react-markdown": "^4.3.1",
"react-scroll": "^1.7.16", "react-scroll": "^1.7.16",
"react-toastify": "^6.0.8", "react-toastify": "^6.0.8",
"uuid": "^8.2.0", "uuid": "^8.2.0",

View File

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

View File

@ -38,7 +38,7 @@ const Actions = () => {
<div className={styles.container}> <div className={styles.container}>
<h5>Import Your Resume</h5> <h5>Import Your Resume</h5>
<p> <p className="leading-loose">
You can import your information from various sources like JSON Resume You can import your information from various sources like JSON Resume
or your LinkedIn to autofill most of the data for your resume. or your LinkedIn to autofill most of the data for your resume.
</p> </p>
@ -53,7 +53,7 @@ const Actions = () => {
<div className={styles.container}> <div className={styles.container}>
<h5>Export Your Resume</h5> <h5>Export Your Resume</h5>
<p> <p className="leading-loose">
Export your resume as a PDF to share with recruiters or a JSON that 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. you will be able to import back onto this app on another computer.
</p> </p>
@ -73,7 +73,7 @@ const Actions = () => {
<div className={styles.container}> <div className={styles.container}>
<h5>Share Your Resume</h5> <h5>Share Your Resume</h5>
<p> <p className="leading-loose">
The link below will be accessible publicly if you choose to share it, 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. and viewers would see the latest version of your resume at any time.
</p> </p>
@ -86,7 +86,7 @@ const Actions = () => {
<div className={styles.container}> <div className={styles.container}>
<h5>Load Demo Data</h5> <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 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. see how a resume should look and you can start editing from there.
</p> </p>
@ -99,7 +99,7 @@ const Actions = () => {
<div className={styles.container}> <div className={styles.container}>
<h5>Delete Account</h5> <h5>Delete Account</h5>
<p> <p className="leading-loose">
If you would like to delete your account and erase all your resumes, 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 its just one button away. Please be weary as this is an irreversible
process. 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 { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext'; import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import { move, reorder } from '../../../../utils'; import { move, reorder } from '../../../../utils';
@ -37,7 +37,7 @@ const Layout = () => {
dispatch({ dispatch({
type: 'on_input', type: 'on_input',
payload: { payload: {
path: 'layout', path: 'metadata.layout',
value: newState, value: newState,
}, },
}); });
@ -68,11 +68,7 @@ const Layout = () => {
Block {ind + 1} Block {ind + 1}
</span> </span>
{el.map((item, index) => ( {el.map((item, index) => (
<Draggable <Draggable key={item} index={index} draggableId={item}>
key={item.id}
index={index}
draggableId={item.id}
>
{(dragProvided) => ( {(dragProvided) => (
<div <div
ref={dragProvided.innerRef} ref={dragProvided.innerRef}
@ -80,7 +76,7 @@ const Layout = () => {
{...dragProvided.draggableProps} {...dragProvided.draggableProps}
{...dragProvided.dragHandleProps} {...dragProvided.dragHandleProps}
> >
{item.name} {item}
</div> </div>
)} )}
</Draggable> </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 { Menu, MenuItem } from '@material-ui/core';
import { navigate } from 'gatsby'; import { navigate } from 'gatsby';
import moment from 'moment'; 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 { MdMoreHoriz, MdOpenInNew } from 'react-icons/md';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import DatabaseContext from '../../contexts/DatabaseContext'; 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, useState,
} from 'react'; } from 'react';
import UserContext from './UserContext'; import UserContext from './UserContext';
import initialState from '../data/initialState';
const DEBOUNCE_WAIT_TIME = 4000; const DEBOUNCE_WAIT_TIME = 4000;
@ -46,8 +47,7 @@ const DatabaseProvider = ({ children }) => {
return snapshot.val(); return snapshot.val();
}; };
const createResume = (resume) => { const createResume = ({ id, name }) => {
const { id } = resume;
const createdAt = firebase.database.ServerValue.TIMESTAMP; const createdAt = firebase.database.ServerValue.TIMESTAMP;
let firstName; let firstName;
@ -61,11 +61,14 @@ const DatabaseProvider = ({ children }) => {
.database() .database()
.ref(`users/${user.uid}/resumes/${id}`) .ref(`users/${user.uid}/resumes/${id}`)
.set({ .set({
...initialState,
id,
name,
profile: { profile: {
...initialState.profile,
firstName: firstName || '', firstName: firstName || '',
lastName: lastName || '', lastName: lastName || '',
}, },
...resume,
createdAt, createdAt,
updatedAt: 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 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, { import React, {
createContext, createContext,
memo, memo,
@ -7,22 +16,8 @@ import React, {
useContext, useContext,
useReducer, useReducer,
} from 'react'; } from 'react';
import leftSections from '../data/leftSections';
import DatabaseContext from './DatabaseContext'; import DatabaseContext from './DatabaseContext';
import initialState from '../data/initialState';
const initialState = {
name: '',
metadata: {
template: 'onyx',
font: 'Montserrat',
layout: [leftSections.map(({ id, name }) => ({ id, name }))],
colors: {
text: '#444444',
primary: '#5875DB',
background: '#FFFFFF',
},
},
};
const ResumeContext = createContext({}); const ResumeContext = createContext({});
@ -34,6 +29,8 @@ const ResumeProvider = ({ children }) => {
let newState; let newState;
let index; let index;
let items; let items;
let diff;
let temp;
switch (type) { switch (type) {
case 'on_add_item': case 'on_add_item':
@ -85,6 +82,30 @@ const ResumeProvider = ({ children }) => {
debouncedUpdateResume(newState); debouncedUpdateResume(newState);
return 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': case 'on_input':
newState = setWith(clone(state), payload.path, payload.value, clone); newState = setWith(clone(state), payload.path, payload.value, clone);
debouncedUpdateResume(newState); debouncedUpdateResume(newState);
@ -132,4 +153,9 @@ const useDispatch = () => {
const memoizedProvider = memo(ResumeProvider); 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: render:
'Your photograph was uploaded successfully... and you look great!', 'Your photograph was uploaded successfully... and you look great!',
progress, progress,
autoClose: 5000, autoClose: 2000,
hideProgressBar: true, 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', id: 'profile',
name: 'Profile', name: 'Profile',
icon: MdPerson, icon: MdPerson,
fixed: true,
}, },
{ {
id: 'social', id: 'social',
name: 'Social Network', name: 'Social Network',
icon: AiOutlineTwitter, icon: AiOutlineTwitter,
event: ModalEvents.SOCIAL_MODAL, event: ModalEvents.SOCIAL_MODAL,
fixed: true,
}, },
{ {
id: 'objective', id: 'objective',

View File

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

View File

@ -1,5 +1,5 @@
import firebase from 'gatsby-plugin-firebase'; 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 { Helmet } from 'react-helmet';
import CreateResume from '../../components/dashboard/CreateResume'; import CreateResume from '../../components/dashboard/CreateResume';
import ResumePreview from '../../components/dashboard/ResumePreview'; 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; @apply transition-colors duration-200 ease-in-out;
} }
p {
@apply leading-loose;
}
a {
@apply text-blue-600 font-medium;
}
a:hover { a:hover {
@apply underline; @apply underline;
} }
@ -22,6 +14,10 @@ hr {
@apply w-full border-primary-200 h-1; @apply w-full border-primary-200 h-1;
} }
ul {
@apply list-disc list-inside;
}
section { section {
@apply grid grid-cols-1 gap-8; @apply grid grid-cols-1 gap-8;
} }
@ -35,11 +31,11 @@ label > span:first-child {
} }
.MuiTooltip-tooltip { .MuiTooltip-tooltip {
font-size: 10px !important; font-size: 10px;
} }
.MuiMenuItem-root { .MuiMenuItem-root {
justify-content: center !important; justify-content: center;
} }
.spin { .spin {

View File

@ -1,60 +1,107 @@
import React, { memo } from 'react'; import React, { memo, useEffect } from 'react';
import { FaGlobeAmericas, FaPhone } from 'react-icons/fa'; import PageContext from '../contexts/PageContext';
import { MdEmail } from 'react-icons/md'; 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 Onyx = ({ data }) => {
const { profile, metadata } = data; const dispatch = useDispatch();
const { font, colors, layout } = metadata;
useEffect(() => {
dispatch({ type: 'set_block_count', payload: 3 });
}, []);
return ( return (
<div <PageContext.Provider value={{ data, heading: Heading }}>
className="p-8 grid grid-cols-10 gap-4 items-center" <div
style={{ id="page"
fontFamily: font, className="p-10"
color: colors.text, style={{
backgroundColor: colors.background, fontFamily: data.metadata.font,
}} color: data.metadata.colors.text,
> backgroundColor: data.metadata.colors.background,
<img }}
className="col-span-2 rounded" >
src="https://i.imgur.com/Icr472Z.jpg" <div className="grid grid-cols-4 items-center">
alt="Photograph" <div className="col-span-3 flex items-center">
/> <img
<div className="col-span-5"> className="rounded object-cover mr-4"
<h2 className="text-4xl font-bold" style={{ color: colors.primary }}> src={data.profile.photograph}
{profile.firstName} {profile.lastName} alt="Resume Photograph"
</h2> style={{ width: '120px', height: '120px' }}
<span className="font-medium">Customer Sales Representative</span> />
<div className="mt-5 flex flex-col"> <div>
<span>3879 Gateway Avenue,</span> <h1
<span>Bakersfield,</span> className="font-bold text-4xl"
<span>California, USA</span> 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> </div>
<div className="col-span-3 grid gap-4"> </PageContext.Provider>
<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>
); );
}; };

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); return file && acceptedImageTypes.includes(file.type);
}; };
export const formatDateRange = (x) => export const formatDateRange = ({ startDate, endDate }) =>
`${moment(x.startDate).format('MMMM Y')}${ `${moment(startDate).format('MMMM Y')}${
moment(x.endDate).isValid() ? moment(x.endDate).format('MMMM Y') : 'Present' moment(endDate).isValid() ? moment(endDate).format('MMMM Y') : 'Present'
}`; }`;
export const getFieldProps = (formik, schema, name) => ({ export const getFieldProps = (formik, schema, name) => ({