- refactor sections

- combine resume and metadata contexts
This commit is contained in:
Amruth Pillai
2020-07-09 19:18:04 +05:30
parent c00d7a9eef
commit 3b252476c4
41 changed files with 309 additions and 235 deletions

View File

@ -1,11 +1,11 @@
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'; import { createMuiTheme, MuiThemeProvider } from '@material-ui/core';
import 'animate.css';
import 'firebase/analytics'; import 'firebase/analytics';
import 'firebase/auth'; import 'firebase/auth';
import 'firebase/database'; import 'firebase/database';
import 'firebase/storage'; import 'firebase/storage';
import React from 'react'; import React from 'react';
import { DatabaseProvider } from './src/contexts/DatabaseContext'; import { DatabaseProvider } from './src/contexts/DatabaseContext';
import { MetadataProvider } from './src/contexts/MetadataContext';
import { ModalProvider } from './src/contexts/ModalContext'; import { ModalProvider } from './src/contexts/ModalContext';
import { ResumeProvider } from './src/contexts/ResumeContext'; import { ResumeProvider } from './src/contexts/ResumeContext';
import { StorageProvider } from './src/contexts/StorageContext'; import { StorageProvider } from './src/contexts/StorageContext';
@ -32,9 +32,7 @@ export const wrapRootElement = ({ element }) => (
<UserProvider> <UserProvider>
<DatabaseProvider> <DatabaseProvider>
<ResumeProvider> <ResumeProvider>
<MetadataProvider> <StorageProvider>{element}</StorageProvider>
<StorageProvider>{element}</StorageProvider>
</MetadataProvider>
</ResumeProvider> </ResumeProvider>
</DatabaseProvider> </DatabaseProvider>
</UserProvider> </UserProvider>

89
package-lock.json generated
View File

@ -2700,6 +2700,11 @@
"resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM="
}, },
"animate.css": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.0.tgz",
"integrity": "sha512-0aVcfWDeU9ykV6vjn1P67ZSs01jxoUQZCGaYbkk0SIIelIG8kUdLrIkua1+VabHfTtsSivDRMMn0ILPvZum2gw=="
},
"ansi-align": { "ansi-align": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
@ -8225,9 +8230,9 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
}, },
"gatsby": { "gatsby": {
"version": "2.24.0", "version": "2.24.1",
"resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.24.0.tgz", "resolved": "https://registry.npmjs.org/gatsby/-/gatsby-2.24.1.tgz",
"integrity": "sha512-l/9ueKdnHNB3+nbzZRaj2mYWJMAay7PwMN+M0VfdIgA2K6d/lz3xI8W5BRNmNJyuk8itykB64r7NFNxsnTChRw==", "integrity": "sha512-aqFfx+Vj3kBhS17tgL1LrkMzip2Xctd3lCj+pogCGV9GCkg6wOqW2uOEqWeoiCNq09sPuwv3GNB8sbEFoQ/2DA==",
"requires": { "requires": {
"@babel/code-frame": "^7.10.3", "@babel/code-frame": "^7.10.3",
"@babel/core": "^7.10.3", "@babel/core": "^7.10.3",
@ -8543,11 +8548,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
}, },
"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="
},
"lru-cache": { "lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -8556,49 +8556,11 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"mini-css-extract-plugin": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz",
"integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==",
"requires": {
"loader-utils": "^1.1.0",
"normalize-url": "1.9.1",
"schema-utils": "^1.0.0",
"webpack-sources": "^1.1.0"
}
},
"node-fetch": { "node-fetch": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
}, },
"normalize-url": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
"integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
"requires": {
"object-assign": "^4.0.1",
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
}
}
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"regexpp": { "regexpp": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
@ -8612,29 +8574,11 @@
"glob": "^7.1.3" "glob": "^7.1.3"
} }
}, },
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"requires": {
"ajv": "^6.1.0",
"ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0"
}
},
"semver": { "semver": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
}, },
"sort-keys": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
"requires": {
"is-plain-obj": "^1.0.0"
}
},
"source-map": { "source-map": {
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
@ -12667,10 +12611,9 @@
} }
}, },
"mini-css-extract-plugin": { "mini-css-extract-plugin": {
"version": "0.9.0", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz",
"integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", "integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==",
"dev": true,
"requires": { "requires": {
"loader-utils": "^1.1.0", "loader-utils": "^1.1.0",
"normalize-url": "1.9.1", "normalize-url": "1.9.1",
@ -12681,14 +12624,12 @@
"is-plain-obj": { "is-plain-obj": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
"dev": true
}, },
"normalize-url": { "normalize-url": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
"integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
"dev": true,
"requires": { "requires": {
"object-assign": "^4.0.1", "object-assign": "^4.0.1",
"prepend-http": "^1.0.0", "prepend-http": "^1.0.0",
@ -12699,14 +12640,12 @@
"prepend-http": { "prepend-http": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
"dev": true
}, },
"query-string": { "query-string": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dev": true,
"requires": { "requires": {
"object-assign": "^4.1.0", "object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0" "strict-uri-encode": "^1.0.0"
@ -12716,7 +12655,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"dev": true,
"requires": { "requires": {
"ajv": "^6.1.0", "ajv": "^6.1.0",
"ajv-errors": "^1.0.0", "ajv-errors": "^1.0.0",
@ -12727,7 +12665,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
"dev": true,
"requires": { "requires": {
"is-plain-obj": "^1.0.0" "is-plain-obj": "^1.0.0"
} }

View File

@ -18,12 +18,13 @@
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@reach/router": "^1.3.4", "@reach/router": "^1.3.4",
"animate.css": "^4.1.0",
"array-move": "^2.2.2", "array-move": "^2.2.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"firebase": "^7.15.5", "firebase": "^7.15.5",
"formik": "^2.1.4", "formik": "^2.1.4",
"gatsby": "^2.24.0", "gatsby": "^2.24.1",
"gatsby-image": "^2.4.13", "gatsby-image": "^2.4.13",
"gatsby-plugin-create-client-paths": "^2.3.10", "gatsby-plugin-create-client-paths": "^2.3.10",
"gatsby-plugin-firebase": "^0.2.0-beta.4", "gatsby-plugin-firebase": "^0.2.0-beta.4",

View File

@ -1,14 +1,13 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useSelector as useMetadataSelector } from '../../../contexts/MetadataContext'; import { useSelector } from '../../../contexts/ResumeContext';
import { useSelector as useResumeSelector } from '../../../contexts/ResumeContext';
import Onyx from '../../../templates/Onyx'; import Onyx from '../../../templates/Onyx';
import styles from './Artboard.module.css'; import styles from './Artboard.module.css';
const Artboard = () => { const Artboard = () => {
const { template, layout, colors } = useMetadataSelector((store) => store); const state = useSelector();
const state = useResumeSelector((store) => store); const { id, name, metadata } = state;
const { id, name } = state; const { template } = metadata;
return ( return (
<div> <div>
@ -18,9 +17,7 @@ const Artboard = () => {
</Helmet> </Helmet>
<div id="artboard" className={styles.container}> <div id="artboard" className={styles.container}>
{template === 'Onyx' && ( {template === 'onyx' && <Onyx data={state} />}
<Onyx data={state} layout={layout} colors={colors} />
)}
</div> </div>
</div> </div>
); );

View File

@ -14,7 +14,7 @@ const LeftNavbar = () => (
<hr className="my-6" /> <hr className="my-6" />
<div className="grid grid-cols-1 gap-5 text-secondary-dark"> <div className="grid grid-cols-1 gap-4 text-secondary-dark">
{sections.map((x) => ( {sections.map((x) => (
<SectionIcon <SectionIcon
key={x.id} key={x.id}

View File

@ -1,17 +1,17 @@
import React, { Fragment, memo } from 'react'; import React, { Fragment, memo } from 'react';
import { Element } from 'react-scroll'; import { Element } from 'react-scroll';
import sections from '../../../data/leftSections'; import sections from '../../../data/leftSections';
import Awards from '../sections/Awards'; import Awards from './sections/Awards';
import Certifications from '../sections/Certifications'; import Certifications from './sections/Certifications';
import Education from '../sections/Education'; import Education from './sections/Education';
import Hobbies from '../sections/Hobbies'; import Hobbies from './sections/Hobbies';
import Languages from '../sections/Languages'; import Languages from './sections/Languages';
import Objective from '../sections/Objective'; import Objective from './sections/Objective';
import Profile from '../sections/Profile'; import Profile from './sections/Profile';
import References from '../sections/References'; import References from './sections/References';
import Skills from '../sections/Skills'; import Skills from './sections/Skills';
import Social from '../sections/Social'; import Social from './sections/Social';
import Work from '../sections/Work'; import Work from './sections/Work';
import LeftNavbar from './LeftNavbar'; import LeftNavbar from './LeftNavbar';
import styles from './LeftSidebar.module.css'; import styles from './LeftSidebar.module.css';

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Awards = ({ id, name, event }) => { const Awards = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Certifications = ({ id, name, event }) => { const Certifications = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Education = ({ id, name, event }) => { const Education = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Hobbies = ({ id, name, event }) => { const Hobbies = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Languages = ({ id, name, event }) => { const Languages = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import Input from '../../shared/Input'; import Input from '../../../shared/Input';
const Objective = () => { const Objective = () => {
return ( return (

View File

@ -1,7 +1,7 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import Input from '../../shared/Input'; import Input from '../../../shared/Input';
import PhotoUpload from '../../shared/PhotoUpload'; import PhotoUpload from '../../../shared/PhotoUpload';
const Profile = () => { const Profile = () => {
return ( return (

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const References = ({ id, name, event }) => { const References = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Skills = ({ id, name, event }) => { const Skills = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Social = ({ id, name, event }) => { const Social = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import List from '../lists/List'; import List from '../../lists/List';
const Work = ({ id, name, event }) => { const Work = ({ id, name, event }) => {
const path = `${id}.items`; const path = `${id}.items`;

View File

@ -20,7 +20,7 @@ const List = ({
hasDate, hasDate,
event, event,
}) => { }) => {
const items = useSelector((state) => get(state, path, [])); const items = useSelector(path, []);
const { emitter } = useContext(ModalContext); const { emitter } = useContext(ModalContext);
const handleAdd = () => emitter.emit(event); const handleAdd = () => emitter.emit(event);

View File

@ -82,7 +82,7 @@ const ListItem = ({
size="18px" size="18px"
aria-haspopup="true" aria-haspopup="true"
onClick={handleClick} onClick={handleClick}
className="cursor-pointer" className="cursor-context-menu"
/> />
<Menu <Menu
keepMounted keepMounted

View File

@ -7,7 +7,7 @@ import SyncIndicator from './SyncIndicator';
const RightNavbar = () => { const RightNavbar = () => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className="grid grid-cols-1 gap-6 text-secondary-dark"> <div className="grid grid-cols-1 gap-4 text-secondary-dark">
{sections.map((x) => ( {sections.map((x) => (
<SectionIcon <SectionIcon
key={x.id} key={x.id}

View File

@ -1,12 +1,15 @@
import React, { Fragment, memo } from 'react'; import React, { Fragment, memo } from 'react';
import { Element } from 'react-scroll'; import { Element } from 'react-scroll';
import sections from '../../../data/rightSections'; import sections from '../../../data/rightSections';
import Layout from '../sections/Layout';
import RightNavbar from './RightNavbar'; import RightNavbar from './RightNavbar';
import styles from './RightSidebar.module.css'; import styles from './RightSidebar.module.css';
import Layout from './sections/Layout';
import Templates from './sections/Templates';
const getComponent = (id) => { const getComponent = (id) => {
switch (id) { switch (id) {
case 'templates':
return Templates;
case 'layout': case 'layout':
return Layout; return Layout;
default: default:

View File

@ -4,7 +4,7 @@
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none; scrollbar-width: none;
@apply w-full h-screen overflow-scroll p-8; @apply w-full h-screen overflow-scroll p-8;
@apply grid gap-6; @apply grid gap-8;
} }
.container::-webkit-scrollbar { .container::-webkit-scrollbar {

View File

@ -0,0 +1,47 @@
import React, { memo } from 'react';
import { useDispatch } from '../../../../contexts/ResumeContext';
import Heading from '../../../shared/Heading';
import Input from '../../../shared/Input';
const Colors = () => {
const dispatch = useDispatch();
const handleClick = (value) => {
dispatch({
type: 'on_input',
payload: {
path: 'metadata.colors.primary',
value,
},
});
};
return (
<section>
<Heading>Colors</Heading>
<Input
name="primary"
label="Primary Color"
placeholder="#FF4081"
path="metadata.colors.primary"
/>
<Input
name="text"
label="Text Color"
placeholder="#444444"
path="metadata.colors.text"
/>
<Input
name="background"
label="Background Color"
placeholder="#FFFFFF"
path="metadata.colors.background"
/>
</section>
);
};
export default memo(Colors);

View File

@ -1,12 +1,12 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { useDispatch, useSelector } from '../../../contexts/MetadataContext'; import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import { move, reorder } from '../../../utils'; import { move, reorder } from '../../../../utils';
import Heading from '../../shared/Heading'; import Heading from '../../../shared/Heading';
import styles from './Layout.module.css'; import styles from './Layout.module.css';
const Layout = () => { const Layout = () => {
const blocks = useSelector((state) => state.layout); const blocks = useSelector('metadata.layout');
const dispatch = useDispatch(); const dispatch = useDispatch();
const onDragEnd = (result) => { const onDragEnd = (result) => {
@ -22,13 +22,25 @@ const Layout = () => {
const items = reorder(blocks[sInd], source.index, destination.index); const items = reorder(blocks[sInd], source.index, destination.index);
const newState = [...blocks]; const newState = [...blocks];
newState[sInd] = items; newState[sInd] = items;
dispatch({ type: 'set_layout', payload: newState }); dispatch({
type: 'on_input',
payload: {
path: 'metadata.layout',
value: newState,
},
});
} else { } else {
const newResult = move(blocks[sInd], blocks[dInd], source, destination); const newResult = move(blocks[sInd], blocks[dInd], source, destination);
const newState = [...blocks]; const newState = [...blocks];
newState[sInd] = newResult[sInd]; newState[sInd] = newResult[sInd];
newState[dInd] = newResult[dInd]; newState[dInd] = newResult[dInd];
dispatch({ type: 'set_layout', payload: newState }); dispatch({
type: 'on_input',
payload: {
path: 'layout',
value: newState,
},
});
} }
}; };
@ -58,8 +70,8 @@ const Layout = () => {
{el.map((item, index) => ( {el.map((item, index) => (
<Draggable <Draggable
key={item.id} key={item.id}
draggableId={item.id}
index={index} index={index}
draggableId={item.id}
> >
{(dragProvided) => ( {(dragProvided) => (
<div <div

View File

@ -0,0 +1,48 @@
import cx from 'classnames';
import React, { memo } from 'react';
import { useDispatch, useSelector } from '../../../../contexts/ResumeContext';
import templates from '../../../../data/templates';
import { handleKeyUp } from '../../../../utils';
import Heading from '../../../shared/Heading';
import styles from './Templates.module.css';
const Templates = () => {
const dispatch = useDispatch();
const template = useSelector('metadata.template');
const handleClick = (value) => {
dispatch({
type: 'on_input',
payload: {
path: 'metadata.template',
value,
},
});
};
return (
<section>
<Heading>Templates</Heading>
<div className="grid grid-cols-2 gap-8">
{templates.map((x) => (
<div
key={x.id}
tabIndex="0"
role="button"
onKeyUp={(e) => handleKeyUp(e, () => handleClick(x.id))}
onClick={() => handleClick(x.id)}
className={cx(styles.template, {
[styles.selected]: template === x.id,
})}
>
<img loading="lazy" height="240px" src={x.preview} alt={x.name} />
<span>{x.name}</span>
</div>
))}
</div>
</section>
);
};
export default memo(Templates);

View File

@ -0,0 +1,26 @@
.template {
@apply w-full rounded flex flex-col items-center;
}
.template:focus {
@apply outline-none;
}
.template img {
height: 240px;
@apply w-full object-cover border-2 border-transparent rounded;
@apply transition-opacity duration-200 ease-in-out;
}
.template.selected img {
@apply border-2 border-primary;
}
.template:hover img {
@apply cursor-pointer opacity-75;
@apply transition-opacity duration-200 ease-in-out;
}
.template span {
@apply mt-1 text-center text-sm font-semibold;
}

View File

@ -1,5 +1,5 @@
import cx from 'classnames'; import cx from 'classnames';
import { get, isFunction } from 'lodash'; import { isFunction } from 'lodash';
import React, { memo, useEffect, useState } from 'react'; import React, { memo, useEffect, useState } from 'react';
import { FaAngleDown } from 'react-icons/fa'; import { FaAngleDown } from 'react-icons/fa';
import { MdClose } from 'react-icons/md'; import { MdClose } from 'react-icons/md';
@ -26,14 +26,14 @@ const Input = ({
type = 'text', type = 'text',
}) => { }) => {
const [uuid, setUuid] = useState(null); const [uuid, setUuid] = useState(null);
const stateValue = useSelector((state) => get(state, path)); const stateValue = useSelector(path, '');
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
setUuid(uuidv4()); setUuid(uuidv4());
}, []); }, []);
value = isFunction(onChange) ? value : stateValue; value = path ? stateValue : value;
onChange = isFunction(onChange) onChange = isFunction(onChange)
? onChange ? onChange
: (e) => { : (e) => {

View File

@ -14,10 +14,16 @@
@apply text-primary opacity-50; @apply text-primary opacity-50;
} }
.container label input:hover,
.container label textarea:hover,
.container label select:hover {
@apply outline-none border-secondary-dark;
}
.container label input:focus, .container label input:focus,
.container label textarea:focus, .container label textarea:focus,
.container label select:focus { .container label select:focus {
@apply outline-none border-gray-500; @apply outline-none border-primary;
} }
.container label > p { .container label > p {

View File

@ -3,4 +3,14 @@
height: 60px; height: 60px;
flex: 0 0 60px; flex: 0 0 60px;
@apply flex items-center justify-center cursor-pointer bg-secondary text-secondary-dark rounded-full; @apply flex items-center justify-center cursor-pointer bg-secondary text-secondary-dark rounded-full;
@apply transition-opacity duration-200 ease-in-out;
}
.circle:focus {
@apply outline-none;
}
.circle:hover {
@apply opacity-75;
@apply transition-opacity duration-200 ease-in-out;
} }

View File

@ -19,9 +19,9 @@ const SectionIcon = ({ section, containerId, tooltipPlacement }) => {
duration={500} duration={500}
containerId={containerId} containerId={containerId}
activeClass="text-primary" activeClass="text-primary"
className="py-2 cursor-pointer focus:outline-none focus:text-primary hover:text-primary" className="py-2 cursor-pointer focus:outline-none focus:text-primary hover:text-primary animate__animated animate__fadeIn"
> >
<Icon size="16px" /> <Icon size="18px" />
</Link> </Link>
</Tooltip> </Tooltip>
); );

View File

@ -1,70 +0,0 @@
import { clone, setWith } from 'lodash';
import React, {
createContext,
memo,
useCallback,
useContext,
useReducer,
} from 'react';
import leftSections from '../data/leftSections';
import DatabaseContext from './DatabaseContext';
import { useSelector as useResumeSelector } from './ResumeContext';
const initialState = {
template: 'Onyx',
font: 'Montserrat',
layout: [leftSections.map(({ id, name }) => ({ id, name }))],
colors: {
textColor: '#444444',
primaryColor: '#5875DB',
backgroundColor: '#FFFFFF',
},
};
const MetadataContext = createContext({});
const MetadataProvider = ({ children }) => {
const id = useResumeSelector((state) => state.id);
const { debouncedUpdateMetadata } = useContext(DatabaseContext);
const memoizedReducer = useCallback(
(state, { type, payload }) => {
let newState;
switch (type) {
case 'set_layout':
newState = setWith(clone(state), 'layout', payload, clone);
debouncedUpdateMetadata(id, newState);
return newState;
default:
throw new Error();
}
},
[id, debouncedUpdateMetadata],
);
const [state, dispatch] = useReducer(memoizedReducer, initialState);
const selectValue = (callback) => callback(state);
return (
<MetadataContext.Provider value={{ selectValue, dispatch }}>
{children}
</MetadataContext.Provider>
);
};
const useSelector = (callback) => {
const { selectValue } = useContext(MetadataContext);
return selectValue(callback);
};
const useDispatch = () => {
const { dispatch } = useContext(MetadataContext);
return dispatch;
};
const memoizedProvider = memo(MetadataProvider);
export { memoizedProvider as MetadataProvider, useSelector, useDispatch };

View File

@ -1,5 +1,5 @@
import arrayMove from 'array-move'; import arrayMove from 'array-move';
import { clone, findIndex, get, setWith } from 'lodash'; import { clone, findIndex, get, isUndefined, setWith } from 'lodash';
import React, { import React, {
createContext, createContext,
memo, memo,
@ -7,9 +7,22 @@ import React, {
useContext, useContext,
useReducer, useReducer,
} from 'react'; } from 'react';
import leftSections from '../data/leftSections';
import DatabaseContext from './DatabaseContext'; import DatabaseContext from './DatabaseContext';
const 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({});
@ -89,18 +102,22 @@ const ResumeProvider = ({ children }) => {
const [state, dispatch] = useReducer(memoizedReducer, initialState); const [state, dispatch] = useReducer(memoizedReducer, initialState);
const selectValue = (callback) => callback(state);
return ( return (
<ResumeContext.Provider value={{ selectValue, dispatch }}> <ResumeContext.Provider value={{ state, dispatch }}>
{children} {children}
</ResumeContext.Provider> </ResumeContext.Provider>
); );
}; };
const useSelector = (callback) => { const useSelector = (path, fallback) => {
const { selectValue } = useContext(ResumeContext); const { state } = useContext(ResumeContext);
return selectValue(callback); let value = get(state, path);
if (isUndefined(value)) {
value = isUndefined(fallback) ? state : fallback;
}
return value;
}; };
const useDispatch = () => { const useDispatch = () => {

View File

@ -16,7 +16,7 @@ const StorageProvider = ({ children }) => {
const { user } = useContext(UserContext); const { user } = useContext(UserContext);
const id = useSelector((state) => state.id); const id = useSelector('id');
const dispatch = useDispatch(); const dispatch = useDispatch();
const uploadPhotograph = async (file) => { const uploadPhotograph = async (file) => {

View File

@ -1,6 +1,11 @@
import { MdDashboard } from 'react-icons/md'; import { MdDashboard, MdStyle } from 'react-icons/md';
export default [ export default [
{
id: 'templates',
name: 'Templates',
icon: MdStyle,
},
{ {
id: 'layout', id: 'layout',
name: 'Layout', name: 'Layout',

14
src/data/templates.js Normal file
View File

@ -0,0 +1,14 @@
const templates = [
{
id: 'onyx',
name: 'Onyx',
preview: 'https://source.unsplash.com/random/300x500',
},
{
id: 'pikachu',
name: 'Pikachu',
preview: 'https://source.unsplash.com/random/301x501',
},
];
export default templates;

View File

@ -6,9 +6,20 @@ import ModalEvents from '../constants/ModalEvents';
import DatabaseContext from '../contexts/DatabaseContext'; import DatabaseContext from '../contexts/DatabaseContext';
import { getFieldProps } from '../utils'; import { getFieldProps } from '../utils';
import DataModal from './DataModal'; import DataModal from './DataModal';
import leftSections from '../data/leftSections';
const initialValues = { const initialValues = {
name: '', name: '',
metadata: {
template: 'onyx',
font: 'Montserrat',
layout: [leftSections.map(({ id, name }) => ({ id, name }))],
colors: {
text: '#444444',
primary: '#5875DB',
background: '#FFFFFF',
},
},
}; };
const schema = Yup.object().shape({ const schema = Yup.object().shape({

View File

@ -1,5 +1,5 @@
import firebase from 'gatsby-plugin-firebase'; import firebase from 'gatsby-plugin-firebase';
import React, { memo, useEffect, useState } from 'react'; import React, { 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';
@ -27,6 +27,15 @@ const Dashboard = ({ user }) => {
setLoading(false); setLoading(false);
}); });
firebase
.database()
.ref(ref)
.on('child_removed', (snapshot) => {
if (snapshot.val()) {
setResumes(resumes.filter((x) => x.id === snapshot.val().id));
}
});
return () => { return () => {
firebase.database().ref(ref).off(); firebase.database().ref(ref).off();
}; };
@ -58,4 +67,4 @@ const Dashboard = ({ user }) => {
); );
}; };
export default memo(Dashboard); export default Dashboard;

View File

@ -2,13 +2,16 @@ import React, { memo } from 'react';
import { FaGlobeAmericas, FaPhone } from 'react-icons/fa'; import { FaGlobeAmericas, FaPhone } from 'react-icons/fa';
import { MdEmail } from 'react-icons/md'; import { MdEmail } from 'react-icons/md';
const Onyx = ({ data, layout, colors }) => { const Onyx = ({ data }) => {
const { profile, metadata } = data;
const { colors, layout } = metadata;
return ( return (
<div <div
className="p-8 grid grid-cols-10 gap-4 items-center" className="p-8 grid grid-cols-10 gap-4 items-center"
style={{ style={{
color: colors.textColor, color: colors.text,
backgroundColor: colors.backgroundColor, backgroundColor: colors.background,
}} }}
> >
<img <img
@ -17,11 +20,8 @@ const Onyx = ({ data, layout, colors }) => {
alt="Photograph" alt="Photograph"
/> />
<div className="col-span-5"> <div className="col-span-5">
<h2 <h2 className="text-4xl font-bold" style={{ color: colors.primary }}>
className="text-4xl font-bold" {profile.firstName} {profile.lastName}
style={{ color: colors.primaryColor }}
>
{data.profile.firstName} {data.profile.lastName}
</h2> </h2>
<span className="font-medium">Customer Sales Representative</span> <span className="font-medium">Customer Sales Representative</span>
@ -33,17 +33,17 @@ const Onyx = ({ data, layout, colors }) => {
</div> </div>
<div className="col-span-3 grid gap-4"> <div className="col-span-3 grid gap-4">
<div className="flex items-center"> <div className="flex items-center">
<FaPhone size="14" style={{ color: colors.primaryColor }} /> <FaPhone size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium">+1 661-808-4188</span> <span className="ml-4 text-sm font-medium">+1 661-808-4188</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<FaGlobeAmericas size="14" style={{ color: colors.primaryColor }} /> <FaGlobeAmericas size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium">nancyontheweb.com</span> <span className="ml-4 text-sm font-medium">nancyontheweb.com</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<MdEmail size="14" style={{ color: colors.primaryColor }} /> <MdEmail size="14" style={{ color: colors.primary }} />
<span className="ml-4 text-sm font-medium"> <span className="ml-4 text-sm font-medium">
nancyjack43@gmail.com nancyjack43@gmail.com
</span> </span>

View File

@ -5,6 +5,9 @@ module.exports = {
center: true, center: true,
}, },
extend: { extend: {
cursor: {
'context-menu': 'context-menu',
},
colors: { colors: {
primary: 'var(--color-primary)', primary: 'var(--color-primary)',
'primary-dark': 'var(--color-primary-dark)', 'primary-dark': 'var(--color-primary-dark)',