- 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/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 }) => (
<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",
"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",

View File

@ -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",

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
tabIndex="0"
role="button"
onClick={onClick}
onClick={isLoading ? undefined : onClick}
className={classes}
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 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 (
<BaseModal state={state} title="Who are you?" action={action}>
<BaseModal
state={authModal}
title="Who are you?"
action={user ? loggedInAction : loggedOutAction}
>
<p>
Reactive Resume needs to know who you are so it can securely
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;

View File

@ -1,35 +1,45 @@
import React from "react";
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 styles from "./BaseModal.module.css";
import Button from "../components/shared/Button";
const BaseModal = ({ title, state, children, action }) => {
const [isOpen, setOpen] = state;
const { isOpen, setOpen } = state;
const handleClose = () => setOpen(false);
return (
<Modal open={isOpen} onClose={handleClose} className={styles.root}>
<div className={styles.modal}>
<div className={styles.title}>
<h2>{title}</h2>
<MdClose size="18" onClick={handleClose} />
<Modal
open={isOpen}
onClose={handleClose}
closeAfterTransition
className={styles.root}
BackdropComponent={Backdrop}
>
<Fade in={isOpen}>
<div className={styles.modal}>
<div className={styles.title}>
<h2>{title}</h2>
<MdClose size="18" onClick={handleClose} />
</div>
<div className={styles.body}>{children}</div>
<div className={styles.actions}>
<Button
outline
title="Cancel"
className="mr-8"
onClick={handleClose}
/>
{action}
</div>
</div>
<div className={styles.body}>{children}</div>
<div className={styles.actions}>
<Button
outline
title="Cancel"
className="mr-8"
onClick={handleClose}
/>
{action}
</div>
</div>
</Fade>
</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,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 (
<div className="container mt-24">
<div className="flex items-center">
<GatsbyImage
className="shadow-md rounded"
fixed={data.file.childImageSharp.fixed}
/>
<Wrapper>
<div className="container mt-24">
<Hero />
<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="pt-8">
<Feature title="Create a resume thats worthy of who you are.">
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, youll never have to copy and edit your
friends resume again.
</Feature>
<div className="mt-12 flex">
<Button title="Login" onClick={handleLogin} />
<AuthModal state={[isAuthModalOpen, setAuthModalOpen]} />
<Button className="ml-8" outline title="Source Code" />
</div>
<Feature title="Updating your resume shouldnt be a chore.">
The biggest problem Ive 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.
</Feature>
<Feature title="Kickstarting your career shouldnt come at a cost.">
There are brilliant alternatives to this app like{" "}
<a href="/">Novoresume</a> and <a href="/">Zety</a>, 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.
</Feature>
</div>
<footer className="my-24">
<p className="font-medium text-gray-500">
Licensed under <a href="/">MIT</a> | Made with love by{" "}
<a href="https://www.amruthpillai.com/">Amruth Pillai</a>
</p>
</footer>
</div>
<div className="pt-8">
<Feature title="Create a resume thats worthy of who you are.">
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, youll never have to copy and edit your friends
resume again.
</Feature>
<Feature title="Updating your resume shouldnt be a chore.">
The biggest problem Ive 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.
</Feature>
<Feature title="Kickstarting your career shouldnt come at a cost.">
There are brilliant alternatives to this app like{" "}
<a href="/">Novoresume</a> and <a href="/">Zety</a>, 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.
</Feature>
</div>
<footer className="my-24">
<p className="font-medium text-gray-500">
Licensed under <a href="/">MIT</a> | Made with love by{" "}
<a href="https://www.amruthpillai.com/">Amruth Pillai</a>
</p>
</footer>
</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;

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-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 {

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