diff --git a/gatsby-browser.js b/gatsby-browser.js index aee72655..bffbf171 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -3,6 +3,7 @@ import "firebase/analytics"; import "firebase/auth"; import "firebase/firestore"; import React from "react"; +import { DashboardProvider } from "./src/contexts/DashboardContext"; import { ModalProvider } from "./src/contexts/ModalContext"; import { ResumeProvider } from "./src/contexts/ResumeContext"; import { ThemeProvider } from "./src/contexts/ThemeContext"; @@ -25,7 +26,9 @@ export const wrapRootElement = ({ element }) => ( - {element} + + {element} + diff --git a/src/components/builder/center/Artboard.js b/src/components/builder/center/Artboard.js new file mode 100644 index 00000000..2b95974a --- /dev/null +++ b/src/components/builder/center/Artboard.js @@ -0,0 +1,8 @@ +import React from "react"; +import styles from "./Artboard.module.css"; + +const Artboard = () => { + return
; +}; + +export default Artboard; diff --git a/src/components/builder/center/Artboard.module.css b/src/components/builder/center/Artboard.module.css new file mode 100644 index 00000000..30fbacee --- /dev/null +++ b/src/components/builder/center/Artboard.module.css @@ -0,0 +1,6 @@ +.container { + width: 400px; + height: 600px; + box-shadow: var(--shadow); + @apply bg-white; +} diff --git a/src/components/builder/left/LeftNavbar.js b/src/components/builder/left/LeftNavbar.js index 610f4f79..d578d7a8 100644 --- a/src/components/builder/left/LeftNavbar.js +++ b/src/components/builder/left/LeftNavbar.js @@ -1,5 +1,6 @@ import { Link } from "gatsby"; import React from "react"; +import { MdPerson } from "react-icons/md"; import Avatar from "../../shared/Avatar"; import Logo from "../../shared/Logo"; import styles from "./LeftNavbar.module.css"; @@ -11,9 +12,16 @@ const LeftNavbar = () => { -
+
-
+
+ +
+ +
diff --git a/src/components/builder/left/LeftNavbar.module.css b/src/components/builder/left/LeftNavbar.module.css index 76be3678..7c6bb9e9 100644 --- a/src/components/builder/left/LeftNavbar.module.css +++ b/src/components/builder/left/LeftNavbar.module.css @@ -2,5 +2,5 @@ width: 75px; z-index: 20; box-shadow: var(--left-shadow); - @apply p-4 h-screen flex flex-col items-center; + @apply px-4 py-6 h-screen flex flex-col items-center; } diff --git a/src/components/builder/left/LeftSidebar.js b/src/components/builder/left/LeftSidebar.js index b66d4dc4..b6ce284b 100644 --- a/src/components/builder/left/LeftSidebar.js +++ b/src/components/builder/left/LeftSidebar.js @@ -1,5 +1,5 @@ import React from "react"; -import Input from "../../shared/Input"; +import Profile from "../sections/Profile"; import LeftNavbar from "./LeftNavbar"; import styles from "./LeftSidebar.module.css"; @@ -9,13 +9,7 @@ const LeftSidebar = () => {
-
-

Profile

-
-
- -
-
+
); diff --git a/src/components/builder/left/LeftSidebar.module.css b/src/components/builder/left/LeftSidebar.module.css index 12374d54..3e6abd80 100644 --- a/src/components/builder/left/LeftSidebar.module.css +++ b/src/components/builder/left/LeftSidebar.module.css @@ -3,10 +3,3 @@ box-shadow: var(--left-shadow); @apply w-full h-screen p-8; } - -.circle { - width: 60px; - height: 60px; - flex: 0 0 60px; - @apply bg-gray-300 rounded-full; -} diff --git a/src/components/builder/right/RightNavbar.js b/src/components/builder/right/RightNavbar.js new file mode 100644 index 00000000..f483f551 --- /dev/null +++ b/src/components/builder/right/RightNavbar.js @@ -0,0 +1,18 @@ +import React from "react"; +import { MdPerson } from "react-icons/md"; +import styles from "./RightNavbar.module.css"; + +const RightNavbar = () => { + return ( +
+
+ +
+
+ ); +}; + +export default RightNavbar; diff --git a/src/components/builder/right/RightNavbar.module.css b/src/components/builder/right/RightNavbar.module.css new file mode 100644 index 00000000..b5f24230 --- /dev/null +++ b/src/components/builder/right/RightNavbar.module.css @@ -0,0 +1,6 @@ +.container { + width: 75px; + z-index: 20; + box-shadow: var(--right-shadow); + @apply px-4 py-6 h-screen flex flex-col items-center; +} diff --git a/src/components/builder/right/RightSidebar.js b/src/components/builder/right/RightSidebar.js new file mode 100644 index 00000000..1790bed5 --- /dev/null +++ b/src/components/builder/right/RightSidebar.js @@ -0,0 +1,18 @@ +import React from "react"; +import Profile from "../sections/Profile"; +import RightNavbar from "./RightNavbar"; +import styles from "./RightSidebar.module.css"; + +const RightSidebar = () => { + return ( +
+
+ +
+ + +
+ ); +}; + +export default RightSidebar; diff --git a/src/components/builder/right/RightSidebar.module.css b/src/components/builder/right/RightSidebar.module.css new file mode 100644 index 00000000..9465ae61 --- /dev/null +++ b/src/components/builder/right/RightSidebar.module.css @@ -0,0 +1,5 @@ +.container { + z-index: 10; + box-shadow: var(--right-shadow); + @apply w-full h-screen p-8; +} diff --git a/src/components/builder/sections/Profile.js b/src/components/builder/sections/Profile.js new file mode 100644 index 00000000..e4326376 --- /dev/null +++ b/src/components/builder/sections/Profile.js @@ -0,0 +1,27 @@ +import React from "react"; +import { MdFileUpload } from "react-icons/md"; +import Heading from "../../shared/Heading"; +import Input from "../../shared/Input"; +import styles from "./Profile.module.css"; + +const Profile = () => { + return ( +
+ Profile +
+
+ +
+ + +
+ +
+ + +
+
+ ); +}; + +export default Profile; diff --git a/src/components/builder/sections/Profile.module.css b/src/components/builder/sections/Profile.module.css new file mode 100644 index 00000000..c9346816 --- /dev/null +++ b/src/components/builder/sections/Profile.module.css @@ -0,0 +1,6 @@ +.circle { + width: 60px; + height: 60px; + flex: 0 0 60px; + @apply flex items-center justify-center bg-secondary rounded-full; +} diff --git a/src/components/dashboard/CreateResume.module.css b/src/components/dashboard/CreateResume.module.css index a783e025..59104ed0 100644 --- a/src/components/dashboard/CreateResume.module.css +++ b/src/components/dashboard/CreateResume.module.css @@ -12,7 +12,7 @@ .resume > .page { max-width: 184px; height: 260px; - @apply rounded absolute w-full bg-white; + @apply rounded absolute w-full bg-inverse; @apply transition-opacity duration-200 ease-in-out; @apply cursor-pointer absolute text-gray-500 flex justify-center items-center; } diff --git a/src/components/dashboard/ResumePreview.js b/src/components/dashboard/ResumePreview.js index c65baf83..cbdc1516 100644 --- a/src/components/dashboard/ResumePreview.js +++ b/src/components/dashboard/ResumePreview.js @@ -4,14 +4,14 @@ import moment from "moment"; import React, { useContext, useState } from "react"; import { MdMoreHoriz, MdOpenInNew } from "react-icons/md"; import { toast } from "react-toastify"; +import DashboardContext from "../../contexts/DashboardContext"; import ModalContext from "../../contexts/ModalContext"; -import ResumeContext from "../../contexts/ResumeContext"; import styles from "./ResumePreview.module.css"; const ResumePreview = ({ resume }) => { const [anchorEl, setAnchorEl] = useState(null); const { createResumeModal } = useContext(ModalContext); - const { deleteResume } = useContext(ResumeContext); + const { deleteResume } = useContext(DashboardContext); const handleOpen = () => navigate(`/app/builder/${resume.id}`); diff --git a/src/components/shared/Heading.js b/src/components/shared/Heading.js new file mode 100644 index 00000000..d9bba1ea --- /dev/null +++ b/src/components/shared/Heading.js @@ -0,0 +1,7 @@ +import React from "react"; + +const Heading = ({ children }) => { + return

{children}

; +}; + +export default Heading; diff --git a/src/components/shared/Input.js b/src/components/shared/Input.js index e34af5a0..b89c5141 100644 --- a/src/components/shared/Input.js +++ b/src/components/shared/Input.js @@ -1,11 +1,14 @@ import cx from "classnames"; -import React from "react"; +import { get } from "lodash"; +import React, { useContext } from "react"; import { v4 as uuidv4 } from "uuid"; +import ResumeContext from "../../contexts/ResumeContext"; import styles from "./Input.module.css"; const Input = ({ - label, name, + path, + label, value, error, onChange, @@ -14,6 +17,20 @@ const Input = ({ type = "text", }) => { const uuid = uuidv4(); + const { state, dispatch } = useContext(ResumeContext); + + const inputProps = (path) => ({ + value: get(state, path), + onChange: (e) => { + dispatch({ + type: "on_input", + payload: { + path, + value: e.target.value, + }, + }); + }, + }); return (
@@ -26,8 +43,9 @@ const Input = ({ value={value} onChange={onChange} placeholder={placeholder} + {...(path && inputProps(path))} /> -

{error}

+

{error}

); diff --git a/src/components/shared/Input.module.css b/src/components/shared/Input.module.css index cb62014f..e0f65f55 100644 --- a/src/components/shared/Input.module.css +++ b/src/components/shared/Input.module.css @@ -7,13 +7,21 @@ } .container > label > span { - @apply mb-1 text-gray-600 font-medium text-sm uppercase; + @apply mb-1 text-secondary-dark font-medium text-xs uppercase; } .container > label > input { - @apply py-4 px-4 rounded bg-gray-200 border border-gray-200; + @apply py-3 px-4 rounded bg-secondary text-primary border border-secondary; +} + +.container > label > input::placeholder { + @apply text-primary opacity-50; } .container > label > input:focus { - @apply outline-none border-gray-400; + @apply outline-none border-secondary-dark; +} + +.container > label > p { + @apply mt-1 text-red-600 text-xs; } diff --git a/src/contexts/DashboardContext.js b/src/contexts/DashboardContext.js new file mode 100644 index 00000000..57e8f8cd --- /dev/null +++ b/src/contexts/DashboardContext.js @@ -0,0 +1,81 @@ +import firebase from "gatsby-plugin-firebase"; +import React, { createContext, useContext, useEffect, useState } from "react"; +import { v4 as uuidv4 } from "uuid"; +import { transformCollectionSnapshot } from "../utils"; +import UserContext from "./UserContext"; + +const defaultState = { + resumes: [], + createResume: async () => {}, + deleteResume: async () => {}, +}; + +const DashboardContext = createContext(defaultState); + +const DashboardProvider = ({ children }) => { + const [resumes, setResumes] = useState([null]); + const [collectionRef, setCollectionRef] = useState(null); + const { user } = useContext(UserContext); + + useEffect(() => { + if (user) { + setCollectionRef(`users/${user.uid}/resumes`); + } + }, [user]); + + useEffect(() => { + if (collectionRef) { + firebase + .firestore() + .collection(collectionRef) + .onSnapshot((snapshot) => + transformCollectionSnapshot(snapshot, setResumes) + ); + } + }, [collectionRef]); + + const createResume = async ({ name }) => { + const id = uuidv4(); + const createdAt = firebase.firestore.FieldValue.serverTimestamp(); + await firebase.firestore().collection(collectionRef).doc(id).set({ + id, + name, + createdAt, + updatedAt: createdAt, + }); + }; + + const updateResume = async (resume) => { + const { id, name } = resume; + + if (resumes.find((x) => x.id === id) === resume) return; + + await firebase.firestore().collection(collectionRef).doc(id).update({ + id, + name, + updatedAt: firebase.firestore.FieldValue.serverTimestamp(), + }); + }; + + const deleteResume = async (resume) => { + const { id } = resume; + await firebase.firestore().collection(collectionRef).doc(id).delete(); + }; + + return ( + + {children} + + ); +}; + +export default DashboardContext; + +export { DashboardProvider }; diff --git a/src/contexts/ResumeContext.js b/src/contexts/ResumeContext.js index fd818fa2..e3523986 100644 --- a/src/contexts/ResumeContext.js +++ b/src/contexts/ResumeContext.js @@ -1,76 +1,28 @@ -import firebase from "gatsby-plugin-firebase"; -import React, { createContext, useContext, useEffect, useState } from "react"; -import { v4 as uuidv4 } from "uuid"; -import { transformCollectionSnapshot } from "../utils"; -import UserContext from "./UserContext"; +import { set } from "lodash"; +import React, { createContext, useReducer } from "react"; -const defaultState = { - resumes: [], - createResume: async () => {}, - deleteResume: async () => {}, +const initialState = { + profile: { + photograph: "", + firstName: "", + lastName: "", + }, }; -const ResumeContext = createContext(defaultState); +const ResumeContext = createContext(initialState); const ResumeProvider = ({ children }) => { - const [resumes, setResumes] = useState([null]); - const [collectionRef, setCollectionRef] = useState(null); - const { user } = useContext(UserContext); - - useEffect(() => { - if (user) { - setCollectionRef(`users/${user.uid}/resumes`); + const [state, dispatch] = useReducer((state, { type, payload }) => { + switch (type) { + case "on_input": + return set({ ...state }, payload.path, payload.value); + default: + throw new Error(); } - }, [user]); - - useEffect(() => { - if (collectionRef) { - firebase - .firestore() - .collection(collectionRef) - .onSnapshot((snapshot) => - transformCollectionSnapshot(snapshot, setResumes) - ); - } - }, [collectionRef]); - - const createResume = async ({ name }) => { - const id = uuidv4(); - const createdAt = firebase.firestore.FieldValue.serverTimestamp(); - await firebase.firestore().collection(collectionRef).doc(id).set({ - id, - name, - createdAt, - updatedAt: createdAt, - }); - }; - - const updateResume = async (resume) => { - const { id, name } = resume; - - if (resumes.find((x) => x.id === id) === resume) return; - - await firebase.firestore().collection(collectionRef).doc(id).update({ - id, - name, - updatedAt: firebase.firestore.FieldValue.serverTimestamp(), - }); - }; - - const deleteResume = async (resume) => { - const { id } = resume; - await firebase.firestore().collection(collectionRef).doc(id).delete(); - }; + }, initialState); return ( - + {children} ); diff --git a/src/contexts/ThemeContext.js b/src/contexts/ThemeContext.js index 6acd83d6..15efeb72 100644 --- a/src/contexts/ThemeContext.js +++ b/src/contexts/ThemeContext.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, createContext } from "react"; +import React, { createContext, useEffect, useState } from "react"; const COLOR_CONFIG = { light: { @@ -6,12 +6,16 @@ const COLOR_CONFIG = { "--color-primary-dark": "#333", "--color-inverse": "#fff", "--color-inverse-dark": "#f5f5f5", + "--color-secondary": "#edf2f7", + "--color-secondary-dark": "#718096", }, dark: { "--color-primary": "#f5f5f5", - "--color-primary-dark": "#eee", + "--color-primary-dark": "#eeeeee", "--color-inverse": "#212121", "--color-inverse-dark": "#181818", + "--color-secondary": "#444", + "--color-secondary-dark": "#888", }, }; diff --git a/src/modals/CreateResumeModal.js b/src/modals/CreateResumeModal.js index 085869d3..92d60d02 100644 --- a/src/modals/CreateResumeModal.js +++ b/src/modals/CreateResumeModal.js @@ -3,8 +3,8 @@ import React, { useContext, useEffect, useRef, useState } from "react"; import * as Yup from "yup"; import Button from "../components/shared/Button"; import Input from "../components/shared/Input"; +import DashboardContext from "../contexts/DashboardContext"; import ModalContext from "../contexts/ModalContext"; -import ResumeContext from "../contexts/ResumeContext"; import { getModalText } from "../utils"; import BaseModal from "./BaseModal"; @@ -18,7 +18,7 @@ const CreateResumeModal = ({ data }) => { const modalRef = useRef(null); const [isEditMode, setEditMode] = useState(false); const { createResumeModal } = useContext(ModalContext); - const { createResume, updateResume } = useContext(ResumeContext); + const { createResume, updateResume } = useContext(DashboardContext); const formik = useFormik({ initialValues: { diff --git a/src/pages/app/builder.js b/src/pages/app/builder.js index 535f7e68..2bd4a355 100644 --- a/src/pages/app/builder.js +++ b/src/pages/app/builder.js @@ -1,16 +1,22 @@ import React from "react"; +import Artboard from "../../components/builder/center/Artboard"; import LeftSidebar from "../../components/builder/left/LeftSidebar"; +import RightSidebar from "../../components/builder/right/RightSidebar"; import Wrapper from "../../components/shared/Wrapper"; const Builder = ({ id }) => { return ( -
+
-
-
+
+ +
+
+ +
); diff --git a/src/pages/app/dashboard.js b/src/pages/app/dashboard.js index 3cbe90c3..480e9b0e 100644 --- a/src/pages/app/dashboard.js +++ b/src/pages/app/dashboard.js @@ -1,12 +1,12 @@ import React, { useContext } from "react"; -import Wrapper from "../../components/shared/Wrapper"; import CreateResume from "../../components/dashboard/CreateResume"; import ResumePreview from "../../components/dashboard/ResumePreview"; import TopNavbar from "../../components/dashboard/TopNavbar"; -import ResumeContext from "../../contexts/ResumeContext"; +import Wrapper from "../../components/shared/Wrapper"; +import DashboardContext from "../../contexts/DashboardContext"; const Dashboard = () => { - const { resumes } = useContext(ResumeContext); + const { resumes } = useContext(DashboardContext); return ( diff --git a/src/styles/colors.css b/src/styles/colors.css index 38af36e8..c6a86344 100644 --- a/src/styles/colors.css +++ b/src/styles/colors.css @@ -3,4 +3,6 @@ --color-primary-dark: #333; --color-inverse: #fff; --color-inverse-dark: #f5f5f5; + --color-secondary: #edf2f7; + --color-secondary-dark: #718096; } diff --git a/src/styles/global.css b/src/styles/global.css index ce6e0b48..4ee1e7ff 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -24,5 +24,9 @@ a:hover { } hr { - @apply w-full h-1; + @apply w-full border-secondary h-1; +} + +section { + @apply grid grid-cols-1 gap-8; } diff --git a/src/styles/shadows.css b/src/styles/shadows.css index db471320..772084b4 100644 --- a/src/styles/shadows.css +++ b/src/styles/shadows.css @@ -1,6 +1,7 @@ :root { - --shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04); - --shadow-strong: 0 0 8px 0 rgba(0, 0, 0, 0.08); - --left-shadow: 8px 0 8px -4px rgba(0, 0, 0, 0.04); - --bottom-shadow: 0 8px 8px -4px rgba(0, 0, 0, 0.04); + --shadow: 0 0 6px 0 rgba(0, 0, 0, 0.05); + --shadow-strong: 0 0 6px 0 rgba(0, 0, 0, 0.1); + --left-shadow: 6px 0 6px -6px rgba(0, 0, 0, 0.05); + --right-shadow: -6px 0 6px -6px rgba(0, 0, 0, 0.05); + --bottom-shadow: 0 6px 6px -6px rgba(0, 0, 0, 0.05); } diff --git a/tailwind.config.js b/tailwind.config.js index 394d98c6..811e7a0d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,6 +10,8 @@ module.exports = { "primary-dark": "var(--color-primary-dark)", inverse: "var(--color-inverse)", "inverse-dark": "var(--color-inverse-dark)", + secondary: "var(--color-secondary)", + "secondary-dark": "var(--color-secondary-dark)", }, }, },