- integrating sign in with google

- set up dark mode context and other services
This commit is contained in:
Amruth Pillai
2020-07-03 18:59:25 +05:30
parent 336fd22150
commit 3a1d42025f
17 changed files with 405 additions and 115 deletions

View File

@ -1,5 +1,18 @@
import "./src/styles/global.css"; import React from "react";
import "firebase/auth"; import "firebase/auth";
import "firebase/analytics"; import "firebase/analytics";
import "firebase/firestore"; 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 }) => (
<ThemeProvider>
<ModalProvider>
<UserProvider>{element}</UserProvider>
</ModalProvider>
</ThemeProvider>
);

10
package-lock.json generated
View File

@ -15207,6 +15207,16 @@
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz",
"integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==" "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": { "react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",

View File

@ -32,7 +32,8 @@
"react-firebase-hooks": "^2.2.0", "react-firebase-hooks": "^2.2.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-icons": "^3.10.0", "react-icons": "^3.10.0",
"react-loader-spinner": "^3.1.14" "react-loader-spinner": "^3.1.14",
"react-toastify": "^6.0.8"
}, },
"devDependencies": { "devDependencies": {
"prettier": "2.0.5", "prettier": "2.0.5",

View File

@ -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 (
<div className="flex items-center">
<GatsbyImage
className="shadow-md rounded"
fixed={file.childImageSharp.fixed}
/>
<div className="ml-12">
<h1 className="text-5xl font-bold">Reactive Resume</h1>
<h2 className="mt-1 text-3xl text-gray-500">
A free and open-source resume builder.
</h2>
<div className="mt-12 flex">
{user ? (
<Button
title="Go to App"
onClick={handleLogin}
isLoading={loading || authModal.isOpen}
/>
) : (
<Button
title="Login"
onClick={handleLogin}
isLoading={loading || authModal.isOpen}
/>
)}
<Button
outline
className="ml-8"
title="Source Code"
onClick={toggleDarkMode}
/>
</div>
</div>
</div>
);
};
export default Hero;

View File

@ -16,7 +16,7 @@ const Button = ({ title, isLoading, onClick, outline, className }) => {
<div <div
tabIndex="0" tabIndex="0"
role="button" role="button"
onClick={onClick} onClick={isLoading ? undefined : onClick}
className={classes} className={classes}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >

View File

@ -0,0 +1,23 @@
import React, { Fragment } from "react";
import { ToastContainer, Slide } from "react-toastify";
import ModalRegistrar from "../../modals/ModalRegistrar";
const Wrapper = ({ children }) => {
return (
<Fragment>
{children}
<ModalRegistrar />
<ToastContainer
role="alert"
hideProgressBar
transition={Slide}
closeButton={false}
position="bottom-right"
pauseOnFocusLoss={false}
/>
</Fragment>
);
};
export default Wrapper;

View File

@ -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 (
<ModalContext.Provider
value={{
authModal: { isOpen: authOpen, setOpen: setAuthOpen },
}}
>
{children}
</ModalContext.Provider>
);
};
export default ModalContext;
export { ModalProvider };

View File

@ -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 (
<ThemeContext.Provider
value={{
darkMode,
toggleDarkMode,
}}
>
{children}
</ThemeContext.Provider>
);
};
export default ThemeContext;
export { ThemeProvider };

View File

@ -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 (
<UserContext.Provider
value={{
user,
loading,
logout,
loginWithGoogle,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserContext;
export { UserProvider };

View File

@ -1,10 +1,45 @@
import React from "react"; import React, { useState, useContext, Fragment } from "react";
import BaseModal from "./BaseModal"; import BaseModal from "./BaseModal";
import Button from "../components/shared/Button"; 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 = (
<Fragment>
<Button className="mr-6" title="Go to App" onClick={handleGoToApp} />
<Button title="Logout" onClick={logout} />
</Fragment>
);
const loggedOutAction = (
<Button
isLoading={isLoading}
title="Sign in with Google"
onClick={handleSignInWithGoogle}
/>
);
const AuthModal = ({ state }) => {
return ( return (
<BaseModal state={state} title="Who are you?" action={action}> <BaseModal
state={authModal}
title="Who are you?"
action={user ? loggedInAction : loggedOutAction}
>
<p> <p>
Reactive Resume needs to know who you are so it can securely Reactive Resume needs to know who you are so it can securely
authenticate you into the app and show you only your information. Once authenticate you into the app and show you only your information. Once
@ -15,6 +50,4 @@ const AuthModal = ({ state }) => {
); );
}; };
const action = <Button title="Sign in with Google" />;
export default AuthModal; export default AuthModal;

View File

@ -1,16 +1,25 @@
import React from "react"; import React from "react";
import Modal from "@material-ui/core/Modal"; import Modal from "@material-ui/core/Modal";
import Backdrop from "@material-ui/core/Backdrop";
import Fade from "@material-ui/core/Fade";
import { MdClose } from "react-icons/md"; import { MdClose } from "react-icons/md";
import styles from "./BaseModal.module.css"; import styles from "./BaseModal.module.css";
import Button from "../components/shared/Button"; import Button from "../components/shared/Button";
const BaseModal = ({ title, state, children, action }) => { const BaseModal = ({ title, state, children, action }) => {
const [isOpen, setOpen] = state; const { isOpen, setOpen } = state;
const handleClose = () => setOpen(false); const handleClose = () => setOpen(false);
return ( return (
<Modal open={isOpen} onClose={handleClose} className={styles.root}> <Modal
open={isOpen}
onClose={handleClose}
closeAfterTransition
className={styles.root}
BackdropComponent={Backdrop}
>
<Fade in={isOpen}>
<div className={styles.modal}> <div className={styles.modal}>
<div className={styles.title}> <div className={styles.title}>
<h2>{title}</h2> <h2>{title}</h2>
@ -30,6 +39,7 @@ const BaseModal = ({ title, state, children, action }) => {
{action} {action}
</div> </div>
</div> </div>
</Fade>
</Modal> </Modal>
); );
}; };

View File

@ -0,0 +1,12 @@
import React, { Fragment } from "react";
import AuthModal from "./AuthModal";
const ModalRegistrar = () => {
return (
<Fragment>
<AuthModal />
</Fragment>
);
};
export default ModalRegistrar;

12
src/pages/404.js Normal file
View File

@ -0,0 +1,12 @@
import { navigate } from "gatsby";
import { useEffect } from "react";
const NotFoundPage = () => {
useEffect(() => {
navigate("/");
}, []);
return null;
};
export default NotFoundPage;

View File

@ -1,61 +1,38 @@
import React, { useState } from "react"; import React from "react";
import { graphql } from "gatsby"; import Wrapper from "../components/shared/Wrapper";
import GatsbyImage from "gatsby-image"; import Hero from "../components/landing/Hero";
import Button from "../components/shared/Button";
import AuthModal from "../modals/AuthModal";
const Home = ({ data }) => {
const [isAuthModalOpen, setAuthModalOpen] = useState(false);
const handleLogin = () => setAuthModalOpen(true);
const Home = () => {
return ( return (
<Wrapper>
<div className="container mt-24"> <div className="container mt-24">
<div className="flex items-center"> <Hero />
<GatsbyImage
className="shadow-md rounded"
fixed={data.file.childImageSharp.fixed}
/>
<div className="ml-12">
<h1 className="text-5xl font-bold">Reactive Resume</h1>
<h2 className="mt-1 text-3xl text-gray-500">
A free and open-source resume builder.
</h2>
<div className="mt-12 flex">
<Button title="Login" onClick={handleLogin} />
<AuthModal state={[isAuthModalOpen, setAuthModalOpen]} />
<Button className="ml-8" outline title="Source Code" />
</div>
</div>
</div>
<div className="pt-8"> <div className="pt-8">
<Feature title="Create a resume thats worthy of who you are."> <Feature title="Create a resume thats worthy of who you are.">
Keep up with the latest trends in resume design without having to Keep up with the latest trends in resume design without having to
start from scratch. With new templates being designed every week and start from scratch. With new templates being designed every week and
having made it that easy to design your own templates and submit them having made it that easy to design your own templates and submit
to the community, youll never have to copy and edit your friends them to the community, youll never have to copy and edit your
resume again. friends resume again.
</Feature> </Feature>
<Feature title="Updating your resume shouldnt be a chore."> <Feature title="Updating your resume shouldnt be a chore.">
The biggest problem Ive faced was when I had to update my resume when The biggest problem Ive faced was when I had to update my resume
I learned a new skill or found a new job. The ever-shifting layouts when I learned a new skill or found a new job. The ever-shifting
and inconsistency with design over a number of years made it difficult layouts and inconsistency with design over a number of years made it
to update your own resume, but Reactive Resume makes it as easy as few difficult to update your own resume, but Reactive Resume makes it as
clicks. easy as few clicks.
</Feature> </Feature>
<Feature title="Kickstarting your career shouldnt come at a cost."> <Feature title="Kickstarting your career shouldnt come at a cost.">
There are brilliant alternatives to this app like{" "} There are brilliant alternatives to this app like{" "}
<a href="/">Novoresume</a> and <a href="/">Zety</a>, but they come at <a href="/">Novoresume</a> and <a href="/">Zety</a>, but they come
a cost, mainly because of the time the developers and the marketing at a cost, mainly because of the time the developers and the
they had to incur to make the product. This app might not be better marketing they had to incur to make the product. This app might not
than them, but it does cater to people who are just not in a position be better than them, but it does cater to people who are just not in
to pay hundreds of dollars to create a resume to bootstrap their a position to pay hundreds of dollars to create a resume to
career. bootstrap their career.
</Feature> </Feature>
</div> </div>
@ -66,6 +43,7 @@ const Home = ({ data }) => {
</p> </p>
</footer> </footer>
</div> </div>
</Wrapper>
); );
}; };
@ -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; export default Home;

View File

@ -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-loader-spinner/dist/loader/css/react-spinner-loader.css";
@import "~react-toastify/dist/ReactToastify.css";
@import "./toastify.css";
html, html,
body { body {
font-size: 12px; font-size: 12px;
@apply text-primary;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
@apply text-primary bg-inverse;
} }
p { p {

5
src/styles/tailwind.css Normal file
View File

@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

12
src/styles/toastify.css Normal file
View File

@ -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;
}