diff --git a/frontend/src/components/Auth/Auth.js b/frontend/src/components/Auth/Auth.js index 9afdacd2afc014f6fe84e92cd137adfdfb8c128f..5c9239aa1a5d02d04041400a00ea1b1a44c864de 100644 --- a/frontend/src/components/Auth/Auth.js +++ b/frontend/src/components/Auth/Auth.js @@ -1,11 +1,182 @@ -import React from 'react' +import React, { useState, useContext } from 'react'; +import { useMutation } from '@apollo/client'; +import { useHistory } from 'react-router-dom'; +import { SIGNIN, SIGNUP } from '../../queries/queries.js'; +import { AuthContext } from '../../context/auth.js'; +import { Avatar, Button, Container, Paper, Grid, Typography } from '@material-ui/core'; +import useStyles from './styles'; +import LockOutlined from '@material-ui/icons/LockOutlined'; +import Input from './Input'; +import * as EmailValidator from 'email-validator'; + const Auth = () => { + const classes = useStyles(); + const history = useHistory(); + const context = useContext(AuthContext); + const [showPassword, setShowPassword] = useState(false); + const [isSignup, setIsSignup] = useState(false); + const initialState = { + firstName: "", + lastName: "", + email: "", + password: "", + confirmPassword: "" + }; + const [formData, setFormData] = useState(initialState); + const [signupUser, { data }] = useMutation(SIGNUP, { + update(proxy, result) { + context.signin(result.data.signup); + history.push("/"); + }, + onError(err) { + setErrorData({...errorData, email: { error: true, errorMessage: "Email already in use" }}); + } + }); + const [signinUser, { data: data2 }] = useMutation(SIGNIN, { + update(proxy, result) { + context.signin(result.data.signin); + history.push("/"); + }, + onError(err) { + setErrorData({...errorData, email: { error: true, errorMessage: "Invalid Details" }, password: { error: true, errorMessage: "Invalid Details" }}); + } + }); + + + const validationState = { + firstName: { error: false, errorMessage: ""}, + lastName: { error: false, errorMessage: ""}, + email: { error: false, errorMessage: ""}, + password: { error: false, errorMessage: ""}, + confirmPassword: { error: false, errorMessage: ""} + }; + const [errorData, setErrorData] = useState(validationState); + + const validate = () => { + let isValid = true; + let startingState = { + firstName: { error: false, errorMessage: ""}, + lastName: { error: false, errorMessage: ""}, + email: { error: false, errorMessage: ""}, + password: { error: false, errorMessage: ""}, + confirmPassword: { error: false, errorMessage: ""} + }; + + if (formData.email.trim().length === 0) { + startingState = { ...startingState, email: { error: true, errorMessage: "Required" } }; + isValid = false; + } + + if (!EmailValidator.validate(formData.email)) { + startingState = { ...startingState, email: { error: true, errorMessage: "Invalid email" } }; + isValid = false; + } + + if (formData.password.trim().length === 0) { + startingState = { ...startingState, password: { error: true, errorMessage: "Required" } }; + isValid = false; + } + + if (isSignup) { + if (formData.password.trim().length < 8) { + startingState = { ...startingState, password: { error: true, errorMessage: "Password should be at least 8 characters" } }; + isValid = false; + } + + if (formData.confirmPassword.trim().length === 0) { + startingState = { ...startingState, confirmPassword: { error: true, errorMessage: "Required" } }; + isValid = false; + } + + if (formData.password !== formData.confirmPassword) { + startingState = { ...startingState, confirmPassword: { error: true, errorMessage: "Passwords must match" } }; + isValid = false; + } + + if (formData.firstName.trim().length === 0) { + startingState = { ...startingState, firstName: { error: true, errorMessage: "Required" } }; + isValid = false; + } + + if (formData.lastName.trim().length === 0) { + startingState = { ...startingState, lastName: { error: true, errorMessage: "Required" } }; + isValid = false; + } + } + + setErrorData(startingState); + return isValid; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + if (validate()) { + if (isSignup) { + signupUser({ variables: { + name: formData.firstName + " " + formData.lastName, + email: formData.email, + password: formData.password + }}); + } else { + signinUser({ variables: { + email: formData.email, + password: formData.password + }}); + } + } + }; + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }) + } + + const handleShowPassword = () => { + setShowPassword((prevState) => !prevState); + } + + const switchMode = () => { + setIsSignup((prevState) => !prevState); + setShowPassword(false); + setErrorData(validationState); + } + return ( - <div> - TODO - </div> + <Container component="main" maxWidth="xs"> + <Paper className={classes.paper} elevation={3}> + <Avatar className={classes.avatar}> + <LockOutlined /> + </Avatar> + <Typography variant="h5">{isSignup ? "Sign Up" : "Sign In"}</Typography> + <form className={classes.form} onSubmit={handleSubmit}> + <Grid container spacing={2}> + { + isSignup && ( + <> + <Input name="firstName" label="First Name" errors={errorData.firstName} handleChange={handleChange} autoFocus half/> + <Input name="lastName" label="Last Name" errors={errorData.lastName} handleChange={handleChange} half/> + </> + ) + } + <Input name="email" label="Email Address" errors={errorData.email} handleChange={handleChange} type="email" /> + <Input name="password" label="Password" errors={errorData.password} handleChange={handleChange} handleShowPassword={handleShowPassword} type={showPassword ? "text" : "password"}/> + { isSignup && <Input name="confirmPassword" label="Confirm Password" errors={errorData.confirmPassword} handleChange={handleChange} type="password"/>} + </Grid> + <Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit} onClick={validate}> + { isSignup ? "Sign Up" : "Sign In" } + </Button> + <Grid container justify="flex-end"> + <Grid item> + <Button onClick={switchMode}> + { isSignup ? "Already have an account? Sign in" : "Dont have an account? Register"} + </Button> + </Grid> + </Grid> + </form> + </Paper> + </Container> ) } -export default Auth +export default Auth; diff --git a/frontend/src/components/Auth/Input.js b/frontend/src/components/Auth/Input.js new file mode 100644 index 0000000000000000000000000000000000000000..edb1dbf7e79c3bc5ae1225b05970b838b8f6d798 --- /dev/null +++ b/frontend/src/components/Auth/Input.js @@ -0,0 +1,34 @@ +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 Input = ({ half, name, label, errors, autoFocus, type, handleChange, handleShowPassword }) => { + return ( + <Grid item xs={12} sm={half ? 6 : 12}> + <TextField + name={name} + onChange={handleChange} + variant="outlined" + required + fullWidth + error={errors.error} + helperText={errors.errorMessage} + label={label} + autoFocus={autoFocus} + type={type} + InputProps={name === 'password' ? { + endAdornment: ( + <InputAdornment position="end"> + <IconButton onClick={handleShowPassword}> + {type === 'password' ? <Visibility/> : <VisibilityOff/>} + </IconButton> + </InputAdornment> + ) + } : null} + /> + </Grid> + ) +} + +export default Input diff --git a/frontend/src/components/Auth/styles.js b/frontend/src/components/Auth/styles.js new file mode 100644 index 0000000000000000000000000000000000000000..22793f2ebeb12efcf3421e5d1b58ec721a8cae05 --- /dev/null +++ b/frontend/src/components/Auth/styles.js @@ -0,0 +1,31 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export default makeStyles((theme) => ({ + paper: { + marginTop: theme.spacing(8), + marginBottom: theme.spacing(8), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: theme.spacing(2), + }, + root: { + '& .MuiTextField-root': { + margin: theme.spacing(1), + }, + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.primary.main, + }, + form: { + width: '100%', + marginTop: theme.spacing(3), + }, + submit: { + margin: theme.spacing(3, 0, 2), + }, + googleButton: { + marginBottom: theme.spacing(2), + }, +})); \ No newline at end of file