diff --git a/gatsby-browser.js b/gatsby-browser.js index a9fbc5d1..cba3bc93 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,5 +1,18 @@ -import "./src/styles/global.css"; - +import React from "react"; import "firebase/auth"; import "firebase/analytics"; import "firebase/firestore"; +import { ThemeProvider } from "./src/contexts/ThemeContext"; + +import "./src/styles/tailwind.css"; +import "./src/styles/global.css"; +import { ModalProvider } from "./src/contexts/ModalContext"; +import { UserProvider } from "./src/contexts/UserContext"; + +export const wrapRootElement = ({ element }) => ( + + + {element} + + +); diff --git a/package-lock.json b/package-lock.json index 3ff6b86a..230bded4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15207,6 +15207,16 @@ "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", "integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==" }, + "react-toastify": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-6.0.8.tgz", + "integrity": "sha512-NSqCNwv+C4IfR+c92PFZiNyeBwOJvigrP2bcRi2f6Hg3WqcHhEHOknbSQOs9QDFuqUjmK3SOrdvScQ3z63ifXg==", + "requires": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.1" + } + }, "react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", diff --git a/package.json b/package.json index 83037fe8..b460fa16 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "react-firebase-hooks": "^2.2.0", "react-helmet": "^6.1.0", "react-icons": "^3.10.0", - "react-loader-spinner": "^3.1.14" + "react-loader-spinner": "^3.1.14", + "react-toastify": "^6.0.8" }, "devDependencies": { "prettier": "2.0.5", diff --git a/src/components/landing/Hero.js b/src/components/landing/Hero.js new file mode 100644 index 00000000..5bc6e388 --- /dev/null +++ b/src/components/landing/Hero.js @@ -0,0 +1,66 @@ +import React, { useContext } from "react"; +import { useStaticQuery, graphql } from "gatsby"; +import GatsbyImage from "gatsby-image"; +import ThemeContext from "../../contexts/ThemeContext"; +import ModalContext from "../../contexts/ModalContext"; +import UserContext from "../../contexts/UserContext"; +import Button from "../shared/Button"; + +const Hero = () => { + const { user, loading } = useContext(UserContext); + const { toggleDarkMode } = useContext(ThemeContext); + const { authModal } = useContext(ModalContext); + const { file } = useStaticQuery(graphql` + query { + file(relativePath: { eq: "logo.png" }) { + childImageSharp { + fixed(width: 256, height: 256) { + ...GatsbyImageSharpFixed + } + } + } + } + `); + + const handleLogin = () => authModal.setOpen(true); + + return ( +
+ + +
+

Reactive Resume

+

+ A free and open-source resume builder. +

+ +
+ {user ? ( +
+
+
+ ); +}; + +export default Hero; diff --git a/src/components/shared/Button.js b/src/components/shared/Button.js index 086a1f0b..b15a3eba 100644 --- a/src/components/shared/Button.js +++ b/src/components/shared/Button.js @@ -16,7 +16,7 @@ const Button = ({ title, isLoading, onClick, outline, className }) => {
diff --git a/src/components/shared/Wrapper.js b/src/components/shared/Wrapper.js new file mode 100644 index 00000000..8972c68c --- /dev/null +++ b/src/components/shared/Wrapper.js @@ -0,0 +1,23 @@ +import React, { Fragment } from "react"; +import { ToastContainer, Slide } from "react-toastify"; +import ModalRegistrar from "../../modals/ModalRegistrar"; + +const Wrapper = ({ children }) => { + return ( + + {children} + + + + + ); +}; + +export default Wrapper; diff --git a/src/contexts/ModalContext.js b/src/contexts/ModalContext.js new file mode 100644 index 00000000..77cba699 --- /dev/null +++ b/src/contexts/ModalContext.js @@ -0,0 +1,25 @@ +import React, { createContext, useState } from "react"; + +const defaultState = { + authModal: {}, +}; + +const ModalContext = createContext(defaultState); + +const ModalProvider = ({ children }) => { + const [authOpen, setAuthOpen] = useState(false); + + return ( + + {children} + + ); +}; + +export default ModalContext; + +export { ModalProvider }; diff --git a/src/contexts/ThemeContext.js b/src/contexts/ThemeContext.js new file mode 100644 index 00000000..0edfbd3f --- /dev/null +++ b/src/contexts/ThemeContext.js @@ -0,0 +1,59 @@ +import React, { useState, useEffect, createContext } from "react"; + +const COLOR_CONFIG = { + light: { + "--color-primary": "#444", + "--color-primary-dark": "#333", + "--color-inverse": "#fff", + "--color-inverse-dark": "#f5f5f5", + }, + dark: { + "--color-primary": "#f5f5f5", + "--color-primary-dark": "#eee", + "--color-inverse": "#212121", + "--color-inverse-dark": "#121212", + }, +}; + +const defaultState = { + darkMode: false, + toggleDarkMode: () => {}, +}; + +const ThemeContext = createContext(defaultState); + +const ThemeProvider = ({ children }) => { + const [darkMode, setDarkMode] = useState(defaultState.darkMode); + + useEffect(() => { + const isDarkMode = JSON.parse(localStorage.getItem("darkMode")); + isDarkMode ? setDarkMode(true) : setDarkMode(false); + }, []); + + useEffect(() => { + const colorConfig = darkMode ? COLOR_CONFIG.dark : COLOR_CONFIG.light; + for (const [key, value] of Object.entries(colorConfig)) { + document.documentElement.style.setProperty(key, value); + } + }, [darkMode]); + + const toggleDarkMode = () => { + setDarkMode(!darkMode); + localStorage.setItem("darkMode", JSON.stringify(!darkMode)); + }; + + return ( + + {children} + + ); +}; + +export default ThemeContext; + +export { ThemeProvider }; diff --git a/src/contexts/UserContext.js b/src/contexts/UserContext.js new file mode 100644 index 00000000..d58b5f77 --- /dev/null +++ b/src/contexts/UserContext.js @@ -0,0 +1,52 @@ +import React, { createContext, useState, useEffect } from "react"; +import { useAuthState } from "react-firebase-hooks/auth"; +import firebase from "gatsby-plugin-firebase"; +import { toast } from "react-toastify"; + +const defaultState = { + user: null, + logout: () => {}, + loginWithGoogle: async () => {}, +}; + +const UserContext = createContext(defaultState); + +const UserProvider = ({ children }) => { + const [firebaseUser, loading] = useAuthState(firebase.auth()); + const [user, setUser] = useState(null); + + useEffect(() => { + setUser(firebaseUser); + }, [firebaseUser]); + + const loginWithGoogle = async () => { + const provider = new firebase.auth.GoogleAuthProvider(); + + try { + await firebase.auth().signInWithPopup(provider); + } catch (error) { + toast.error(error.message); + } + }; + + const logout = () => { + firebase.auth().signOut(); + }; + + return ( + + {children} + + ); +}; + +export default UserContext; + +export { UserProvider }; diff --git a/src/modals/AuthModal.js b/src/modals/AuthModal.js index 496a1506..74b17493 100644 --- a/src/modals/AuthModal.js +++ b/src/modals/AuthModal.js @@ -1,10 +1,45 @@ -import React from "react"; +import React, { useState, useContext, Fragment } from "react"; import BaseModal from "./BaseModal"; import Button from "../components/shared/Button"; +import ModalContext from "../contexts/ModalContext"; +import UserContext from "../contexts/UserContext"; + +const AuthModal = () => { + const [isLoading, setLoading] = useState(false); + const { authModal } = useContext(ModalContext); + const { user, loginWithGoogle, logout } = useContext(UserContext); + + const handleSignInWithGoogle = async () => { + setLoading(true); + await loginWithGoogle(); + setLoading(false); + }; + + const handleGoToApp = () => { + console.log("Go to App"); + }; + + const loggedInAction = ( + +
- -
{children}
- -
-
- + ); }; diff --git a/src/modals/ModalRegistrar.js b/src/modals/ModalRegistrar.js new file mode 100644 index 00000000..79ca762e --- /dev/null +++ b/src/modals/ModalRegistrar.js @@ -0,0 +1,12 @@ +import React, { Fragment } from "react"; +import AuthModal from "./AuthModal"; + +const ModalRegistrar = () => { + return ( + + + + ); +}; + +export default ModalRegistrar; diff --git a/src/pages/404.js b/src/pages/404.js new file mode 100644 index 00000000..2f7bf172 --- /dev/null +++ b/src/pages/404.js @@ -0,0 +1,12 @@ +import { navigate } from "gatsby"; +import { useEffect } from "react"; + +const NotFoundPage = () => { + useEffect(() => { + navigate("/"); + }, []); + + return null; +}; + +export default NotFoundPage; diff --git a/src/pages/index.js b/src/pages/index.js index 8258eb21..abe2a4aa 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,71 +1,49 @@ -import React, { useState } from "react"; -import { graphql } from "gatsby"; -import GatsbyImage from "gatsby-image"; -import Button from "../components/shared/Button"; -import AuthModal from "../modals/AuthModal"; - -const Home = ({ data }) => { - const [isAuthModalOpen, setAuthModalOpen] = useState(false); - - const handleLogin = () => setAuthModalOpen(true); +import React from "react"; +import Wrapper from "../components/shared/Wrapper"; +import Hero from "../components/landing/Hero"; +const Home = () => { return ( -
-
- + +
+ -
-

Reactive Resume

-

- A free and open-source resume builder. -

+
+ + Keep up with the latest trends in resume design without having to + start from scratch. With new templates being designed every week and + having made it that easy to design your own templates and submit + them to the community, you’ll never have to copy and edit your + friend’s resume again. + -
-
+ + The biggest problem I’ve faced was when I had to update my resume + when I learned a new skill or found a new job. The ever-shifting + layouts and inconsistency with design over a number of years made it + difficult to update your own resume, but Reactive Resume makes it as + easy as few clicks. + + + + There are brilliant alternatives to this app like{" "} + Novoresume and Zety, but they come + at a cost, mainly because of the time the developers and the + marketing they had to incur to make the product. This app might not + be better than them, but it does cater to people who are just not in + a position to pay hundreds of dollars to create a resume to + bootstrap their career. +
+ +
- -
- - Keep up with the latest trends in resume design without having to - start from scratch. With new templates being designed every week and - having made it that easy to design your own templates and submit them - to the community, you’ll never have to copy and edit your friend’s - resume again. - - - - The biggest problem I’ve faced was when I had to update my resume when - I learned a new skill or found a new job. The ever-shifting layouts - and inconsistency with design over a number of years made it difficult - to update your own resume, but Reactive Resume makes it as easy as few - clicks. - - - - There are brilliant alternatives to this app like{" "} - Novoresume and Zety, but they come at - a cost, mainly because of the time the developers and the marketing - they had to incur to make the product. This app might not be better - than them, but it does cater to people who are just not in a position - to pay hundreds of dollars to create a resume to bootstrap their - career. - -
- - -
+
); }; @@ -78,16 +56,4 @@ const Feature = ({ title, children }) => { ); }; -export const query = graphql` - query { - file(relativePath: { eq: "logo.png" }) { - childImageSharp { - fixed(width: 256, height: 256) { - ...GatsbyImageSharpFixed - } - } - } - } -`; - export default Home; diff --git a/src/styles/global.css b/src/styles/global.css index e05de697..8463034c 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,21 +1,12 @@ -:root { - --color-primary: #444; - --color-primary-dark: #333; - --color-inverse: #fff; - --color-inverse-dark: #f5f5f5; -} - -@tailwind base; -@tailwind components; -@tailwind utilities; - @import "~react-loader-spinner/dist/loader/css/react-spinner-loader.css"; +@import "~react-toastify/dist/ReactToastify.css"; +@import "./toastify.css"; html, body { font-size: 12px; - @apply text-primary; font-family: "Montserrat", sans-serif; + @apply text-primary bg-inverse; } p { diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css new file mode 100644 index 00000000..7f393742 --- /dev/null +++ b/src/styles/tailwind.css @@ -0,0 +1,5 @@ +@tailwind base; + +@tailwind components; + +@tailwind utilities; diff --git a/src/styles/toastify.css b/src/styles/toastify.css new file mode 100644 index 00000000..2bbc5c9f --- /dev/null +++ b/src/styles/toastify.css @@ -0,0 +1,12 @@ +.Toastify__toast { + @apply px-8 py-6 shadow-lg rounded; +} + +.Toastify__toast--default { + @apply bg-primary text-inverse; +} + +.Toastify__toast-body { + font-family: "Montserrat", sans-serif; + @apply font-medium; +}