diff --git a/client/src/App.js b/client/src/App.js index d69802b..78a86ab 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -7,7 +7,7 @@ import Invoices from './components/Invoices/Invoices'; import InvoiceDetails from './components/InvoiceDetails/InvoiceDetails' import ClientList from './components/Clients/ClientList' import NavBar from './components/NavBar/NavBar'; -import Login from './components/Login/Login' +import Auth from './components/Auth' import Dashboard from './components/Dashboard/Dashboard'; import Footer from './components/Footer/Footer'; import Header from './components/Header/Header'; @@ -31,7 +31,7 @@ function App() { - + diff --git a/client/src/components/Auth/Field.js b/client/src/components/Auth/Field.js new file mode 100644 index 0000000..0eb21b6 --- /dev/null +++ b/client/src/components/Auth/Field.js @@ -0,0 +1,52 @@ +import React, { useState } from "react"; +import { TextField, Grid, InputAdornment, IconButton } from "@material-ui/core"; + +import Visibility from "@material-ui/icons/Visibility"; +import VisibilityOff from "@material-ui/icons/VisibilityOff"; + +const Field = ({ + name, + handleChange, + label, + half, + autoFocus, + type, + placeholder, +}) => { + const [showPass, setShowPass] = useState(false); + + const togglePasswordVisibility = () => { + setShowPass((show) => !show); + }; + + return ( + + + + {!showPass ? : } + + + ), + } + : null + } + /> + + ); +}; + +export default Field; diff --git a/client/src/components/Login/Google.js b/client/src/components/Auth/Google.js similarity index 100% rename from client/src/components/Login/Google.js rename to client/src/components/Auth/Google.js diff --git a/client/src/components/Login/Icon.js b/client/src/components/Auth/Icon.js similarity index 100% rename from client/src/components/Login/Icon.js rename to client/src/components/Auth/Icon.js diff --git a/client/src/components/Login/Login.module.css b/client/src/components/Auth/Login.module.css similarity index 100% rename from client/src/components/Login/Login.module.css rename to client/src/components/Auth/Login.module.css diff --git a/client/src/components/Auth/auth-form.js b/client/src/components/Auth/auth-form.js new file mode 100644 index 0000000..09a203b --- /dev/null +++ b/client/src/components/Auth/auth-form.js @@ -0,0 +1,182 @@ +import { Grid, Typography, Avatar, Paper, Button } from "@material-ui/core"; +import { useState } from "react"; +import Field from "./Field"; +import { GoogleLogin } from "react-google-login"; +// import ProgressButton from "react-progress-button"; +import LockOutlinedIcon from "@material-ui/icons/LockOutlined"; +import { Link } from "react-router-dom"; +import useStyles from "./styles"; +import styles from "./Login.module.css"; +import { createProfile } from "../../actions/profile"; +import { useDispatch } from "react-redux"; +import CircularProgress from "@material-ui/core/CircularProgress"; + +const initialState = { + firstName: "", + lastName: "", + email: "", + password: "", + confirmPassword: "", + profilePicture: "", + bio: "", +}; + +const AuthForm = ({ isSignup = false, onSubmit }) => { + const classes = useStyles(); + const [formData, setFormData] = useState(initialState); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const dispatch = useDispatch(); + const [isLoading, setIsLoading] = useState(false); + + const googleSuccess = async (res) => { + console.log(res); + const result = res?.profileObj; + const token = res?.tokenId; + dispatch( + createProfile({ + name: result?.name, + email: result?.email, + userId: result?.googleId, + phoneNumber: "", + businessName: "", + contactAddress: "", + logo: result?.imageUrl, + website: "", + }) + ); + + try { + dispatch({ type: "AUTH", data: { result, token } }); + + window.location.href = "/dashboard"; + } catch (error) { + console.log(error); + } + }; + + const googleError = (error) => { + console.log(error); + console.log("Google Sign In was unseccassful. Try again later"); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (typeof onSubmit === "function") { + try { + setIsLoading(true); + await Promise.resolve(onSubmit(formData)); + setIsLoading(false); + } catch (err) { + // handle error + setIsLoading(false); + } + } + }; + + return ( + + + + + + {isSignup ? "Sign up" : "Sign in"} + +
+ + {isSignup && ( + <> + + + + )} + + + {isSignup && ( + + )} + +
+
+ {isLoading ? ( + + ) : ( + + )} +
+
+ ( + + )} + onSuccess={googleSuccess} + onFailure={googleError} + cookiePolicy="single_host_origin" + /> +
+
+ + + {isSignup ? ( + Already have an account? Sign in + ) : ( + Don't have an account? Sign Up + )} + + + +

+ Forgotten Password? +

+ +
+
+ ); +}; + +export default AuthForm; diff --git a/client/src/components/Auth/index.js b/client/src/components/Auth/index.js new file mode 100644 index 0000000..881e741 --- /dev/null +++ b/client/src/components/Auth/index.js @@ -0,0 +1,33 @@ +import React from "react"; +import { Container } from "@material-ui/core"; +import { Switch, Route, useHistory } from "react-router-dom"; +import AuthForm from "./auth-form"; +import { useDispatch } from "react-redux"; +import { signin, signup } from "../../actions/auth"; + +const Auth = () => { + const history = useHistory(); + // eslint-disable-next-line + const user = JSON.parse(localStorage.getItem("profile")); + + if (user) { + history.push("/dashboard"); + } + + const dispatch = useDispatch(); + + return ( + + + + dispatch(signin())} /> + + + dispatch(signup())} /> + + + + ); +}; + +export default Auth; diff --git a/client/src/components/Login/styles.js b/client/src/components/Auth/styles.js similarity index 100% rename from client/src/components/Login/styles.js rename to client/src/components/Auth/styles.js diff --git a/client/src/components/Header/Header.js b/client/src/components/Header/Header.js index 1248f48..53b2b12 100644 --- a/client/src/components/Header/Header.js +++ b/client/src/components/Header/Header.js @@ -1,64 +1,54 @@ -import React, { useState, useEffect } from 'react' -import { useHistory, useLocation } from 'react-router-dom' -import { useDispatch } from 'react-redux' -import decode from 'jwt-decode' -import styles from './Header.module.css' - -import Button from '@material-ui/core/Button'; -import ClickAwayListener from '@material-ui/core/ClickAwayListener'; -import Grow from '@material-ui/core/Grow'; -import Paper from '@material-ui/core/Paper'; -import Popper from '@material-ui/core/Popper'; -import MenuItem from '@material-ui/core/MenuItem'; -import MenuList from '@material-ui/core/MenuList'; -import { makeStyles } from '@material-ui/core/styles'; -import Avatar from '@material-ui/core/Avatar'; - +import React, { useState, useEffect } from "react"; +import { useHistory, useLocation } from "react-router-dom"; +import { useDispatch } from "react-redux"; +import decode from "jwt-decode"; +import styles from "./Header.module.css"; + +import Button from "@material-ui/core/Button"; +import ClickAwayListener from "@material-ui/core/ClickAwayListener"; +import Grow from "@material-ui/core/Grow"; +import Paper from "@material-ui/core/Paper"; +import Popper from "@material-ui/core/Popper"; +import MenuItem from "@material-ui/core/MenuItem"; +import MenuList from "@material-ui/core/MenuList"; +import { makeStyles } from "@material-ui/core/styles"; +import Avatar from "@material-ui/core/Avatar"; const useStyles = makeStyles((theme) => ({ root: { - display: 'flex', + display: "flex", }, paper: { marginRight: theme.spacing(2), }, })); - - const Header = () => { - const dispatch = useDispatch() - const [user, setUser] = useState(JSON.parse(localStorage.getItem('profile'))) - const history = useHistory() - const location = useLocation() - - - useEffect(() => { - setUser(JSON.parse(localStorage.getItem('profile'))) - },[location]) - - - const logout =() => { - dispatch({ type: 'LOGOUT' }) - history.push('/') - setUser(null) - } - - - useEffect(()=> { - const token = user?.token - // setUser(JSON.parse(localStorage.getItem('profile'))) - //If token expires, logout the user - if(token) { - const decodedToken = decode(token) - if(decodedToken.exp * 1000 < new Date().getTime()) logout() - } - // eslint-disable-next-line - }, [location, user]) //when location changes, set the user - - - + const dispatch = useDispatch(); + const [user, setUser] = useState(JSON.parse(localStorage.getItem("profile"))); + const history = useHistory(); + const location = useLocation(); + + useEffect(() => { + setUser(JSON.parse(localStorage.getItem("profile"))); + }, [location]); + + const logout = () => { + dispatch({ type: "LOGOUT" }); + history.push("/"); + setUser(null); + }; + useEffect(() => { + const token = user?.token; + // setUser(JSON.parse(localStorage.getItem('profile'))) + //If token expires, logout the user + if (token) { + const decodedToken = decode(token); + if (decodedToken.exp * 1000 < new Date().getTime()) logout(); + } + // eslint-disable-next-line + }, [location, user]); //when location changes, set the user const classes = useStyles(); const [open, setOpen] = React.useState(false); @@ -68,7 +58,7 @@ const Header = () => { setOpen((prevOpen) => !prevOpen); }; - const handleClose = (event ) => { + const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { return; } @@ -76,14 +66,13 @@ const Header = () => { setOpen(false); }; - - const openLink =(link) => { - history.push(`/${link}`) - setOpen(false); - } + const openLink = (link) => { + history.push(`/${link}`); + setOpen(false); + }; function handleListKeyDown(event) { - if (event.key === 'Tab') { + if (event.key === "Tab") { event.preventDefault(); setOpen(false); } @@ -99,50 +88,73 @@ const Header = () => { prevOpen.current = open; }, [open]); - - - - if(!user) return ( -
- history.push('/')} src="https://i.postimg.cc/C5fxh51H/Arc-Invoice-Logo2.png" alt="arc-invoice" /> - -
- ) + if (!user) return ( -
-
-
- - - {({ TransitionProps, placement }) => ( - - - - - openLink('settings') }>{(user?.result?.name).split(" ")[0]} - logout()} >Logout - - - - - )} - + Get started +
-
- - + ); + return ( +
+
+
+ + + {({ TransitionProps, placement }) => ( + + + + + openLink("settings")}> + {(user?.result?.name).split(" ")[0]} + + logout()}>Logout + + + + + )} +
- ) -} +
+
+ ); +}; -export default Header +export default Header; diff --git a/client/src/components/Login/Field.js b/client/src/components/Login/Field.js deleted file mode 100644 index b12ee73..0000000 --- a/client/src/components/Login/Field.js +++ /dev/null @@ -1,55 +0,0 @@ -// import React from 'react' -// import styles from './Login.module.css' - -// const Field = ({ name, placeholder, type, handleChange }) => { - -// return ( -//
-// -//
-// ) -// } - -// export default Field - - - -import React from 'react'; -import { TextField, Grid, InputAdornment, IconButton } from '@material-ui/core'; - -import Visibility from '@material-ui/icons/Visibility'; -import VisibilityOff from '@material-ui/icons/VisibilityOff'; - -const Field = ({ name, handleChange, label, half, autoFocus, type, handleShowPassword, placeholder }) => ( - - - - {type === 'password' ? : } - - - ), - } : null} - /> - -); - -export default Field diff --git a/client/src/components/Login/Login.js b/client/src/components/Login/Login.js deleted file mode 100644 index 4b28821..0000000 --- a/client/src/components/Login/Login.js +++ /dev/null @@ -1,126 +0,0 @@ -import React, { useState } from 'react' -import Field from './Field' -import useStyles from './styles' -import styles from './Login.module.css' -import { GoogleLogin } from 'react-google-login' -import {useDispatch} from 'react-redux' -import { useHistory, Link } from 'react-router-dom' -import { signup, signin } from '../../actions/auth' -import { Avatar, Button, Paper, Grid, Typography, Container } from '@material-ui/core' -import LockOutlinedIcon from '@material-ui/icons/LockOutlined' -import { createProfile } from '../../actions/profile' -// import Google from './Google' -import { useSnackbar } from 'react-simple-snackbar' -import ProgressButton from 'react-progress-button' - - - -const initialState ={ firstName: '', lastName: '', email: '', password: '', confirmPassword: '', profilePicture: '', bio: ''} - -const Login = () => { - - const classes = useStyles(); - const [formData, setFormData] = useState(initialState) - const [isSignup, setIsSignup] = useState(false) - const dispatch = useDispatch() - const history = useHistory() - const [showPassword, setShowPassword] = useState(false); - // eslint-disable-next-line - const [openSnackbar, closeSnackbar] = useSnackbar() - const user = JSON.parse(localStorage.getItem('profile')) - - const handleShowPassword = () => setShowPassword(!showPassword); - const handleChange =(e)=> { - setFormData( {...formData, [e.target.name] : e.target.value} ) - } - - const handleSubmit =(e) => { - e.preventDefault() - if(isSignup) { - dispatch(signup(formData, openSnackbar)) - } else { - dispatch(signin(formData, openSnackbar)) - } - } - - - const switchMode =() => { - setIsSignup((prevState) => !prevState) - } - - const googleSuccess = async (res) => { - console.log(res) - const result = res?.profileObj - const token = res?.tokenId - dispatch(createProfile({name: result?.name, email: result?.email, userId: result?.googleId, phoneNumber: '', businessName: '', contactAddress: '', logo: result?.imageUrl, website: ''})) - - try { - dispatch({ type: "AUTH", data: {result, token}}) - - window.location.href='/dashboard' - - } catch (error) { - console.log(error) - } - } - const googleError =(error) => { - console.log(error) - console.log("Google Sign In was unseccassful. Try again later") - } - - - if(user) { - history.push('/dashboard') - } - - return ( - - - - - - { isSignup ? 'Sign up' : 'Sign in' } -
- - { isSignup && ( - <> - - - - )} - - - { isSignup && } - -
-
- {/* */} - { isSignup ? 'Sign Up' : 'Sign In' } -
-
- ( - - )} - onSuccess={googleSuccess} - onFailure={googleError} - cookiePolicy="single_host_origin" - /> -
-
- - - - - -

Forgotten Password?

-
-
-
- ) -} - -export default Login diff --git a/client/src/components/Password/Forgot.js b/client/src/components/Password/Forgot.js index abce865..c8a27dc 100644 --- a/client/src/components/Password/Forgot.js +++ b/client/src/components/Password/Forgot.js @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom' import { Button, Paper, Typography, Container, Grid } from '@material-ui/core'; import useStyles from './styles'; -import Field from '../Login/Field'; +import Field from '../Auth/Field'; import { forgot } from '../../actions/auth'; import styles from './Password.module.css' diff --git a/client/src/components/Password/Reset.js b/client/src/components/Password/Reset.js index 8939be7..372f3b8 100644 --- a/client/src/components/Password/Reset.js +++ b/client/src/components/Password/Reset.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { Button, Paper, Typography, Container, Grid } from '@material-ui/core'; import useStyles from './styles'; -import Field from '../Login/Field'; +import Field from '../Auth/Field'; import { useParams, useHistory } from 'react-router-dom' import { reset } from '../../actions/auth';