Login and register page

This commit is contained in:
Andrew Trieu
2023-07-16 20:14:44 +03:00
parent e96b6a8d62
commit 350517623a
3 changed files with 309 additions and 3 deletions

View File

@@ -17,3 +17,4 @@ Table:
| 13.07.2023 | Setup frontend | 6 |
| 13.07.2023 | Theme and styling | 10 |
| 14.07.2023 | Navbar | 10 |
| 16.07.2023 | Login and register page | 20 |

View File

@@ -0,0 +1,276 @@
import { useState } from "react";
import {
Box,
Typography,
Button,
TextField,
useTheme,
useMediaQuery,
} from "@mui/material";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import { Formik } from "formik";
import * as yup from "yup";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { setLogin } from "state";
import Dropzone from "react-dropzone";
import FlexBetween from "components/FlexBetween";
const registerSchema = yup.object().shape({
firstName: yup.string().required("First name is required"),
lastName: yup.string().required("Last name is required"),
email: yup.string().email("Invalid email").required("Email is required"),
password: yup.string().required("Password is required"),
location: yup.string(),
description: yup.string(),
profilePicture: yup.string(),
});
const loginSchema = yup.object().shape({
email: yup.string().email("Invalid email").required("Email is required"),
password: yup.string().required("Password is required"),
});
const initialRegisterValues = {
firstName: "",
lastName: "",
email: "",
password: "",
location: "",
description: "",
profilePicture: "",
};
const initialLoginValues = {
email: "",
password: "",
};
const Form = () => {
const [pageType, setPageType] = useState("login");
const { palette } = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
const isNotMobile = useMediaQuery("(min-width: 600px)");
const isLogin = pageType === "login";
const isRegister = pageType === "register";
const register = async (values, onSubmitProps) => {
const formData = new FormData();
for (const key in values) {
formData.append(key, values[key]);
}
formData.append("profilePicturePath", values.profilePicture.name);
const savedUserResponse = await fetch(
"http://localhost:3001/auth/register",
{
method: "POST",
body: formData,
}
);
const savedUser = await savedUserResponse.json();
onSubmitProps.resetForm();
if (savedUser) {
setPageType("login");
}
};
const login = async (values, onSubmitProps) => {
const loggedInUserResponse = await fetch(
"http://localhost:3001/auth/login",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
}
);
const loggedInUser = await loggedInUserResponse.json();
onSubmitProps.resetForm();
if (loggedInUser) {
dispatch(setLogin({ user: loggedInUser, token: loggedInUser.token }));
navigate("/home");
}
};
const handleFormSubmit = async (values, onSubmitProps) => {
if (isLogin) {
await login(values, onSubmitProps);
}
if (isRegister) {
await register(values, onSubmitProps);
}
};
return (
<Formik
onSubmit={handleFormSubmit}
initialValues={isLogin ? initialLoginValues : initialRegisterValues}
validationSchema={isLogin ? loginSchema : registerSchema}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
resetForm,
}) => (
<form onSubmit={handleSubmit}>
<Box
display="grid"
gap="30px"
gridTemplateColumns="repeat(4,minmax(0, 1fr))"
sx={{
"& > div": { gridColumn: isNotMobile ? undefined : "span 4" },
}}
>
{isRegister && (
<>
<TextField
label="First name"
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
name="firstName"
error={
Boolean(touched.firstName) && Boolean(errors.firstName)
}
helperText={touched.firstName && errors.firstName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Last name"
onBlur={handleBlur}
onChange={handleChange}
value={values.lastName}
name="lastName"
error={Boolean(touched.lastName) && Boolean(errors.lastName)}
helperText={touched.lastName && errors.lastName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Location"
onBlur={handleBlur}
onChange={handleChange}
value={values.location}
name="location"
error={Boolean(touched.location) && Boolean(errors.location)}
helperText={touched.location && errors.location}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Description"
onBlur={handleBlur}
onChange={handleChange}
value={values.description}
name="description"
error={
Boolean(touched.description) && Boolean(errors.description)
}
helperText={touched.description && errors.description}
sx={{ gridColumn: "span 4" }}
/>
<Box
gridColumn="span 4"
border={`1px solid ${palette.neutral.medium}`}
borderRadius="5px"
p="1rem"
>
<Dropzone
acceptedFiles=".jpg,.jpeg,.png"
multiple={false}
onDrop={(acceptedFiles) => {
setFieldValue("profilePicture", acceptedFiles[0]);
}}
>
{({ getRootProps, getInputProps }) => (
<Box
{...getRootProps()}
border={`2px dashed ${palette.primary.main}`}
p="1rem"
sx={{ "&:hover": { cursor: "pointer" } }}
>
<input {...getInputProps()} />
{!values.profilePicture ? (
<p>Add your profile photo here</p>
) : (
<FlexBetween>
<Typography>
{values.profilePicture.name}
</Typography>
<EditOutlinedIcon />
</FlexBetween>
)}
</Box>
)}
</Dropzone>
</Box>
</>
)}
<TextField
label="Email"
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
name="email"
error={Boolean(touched.email) && Boolean(errors.email)}
helperText={touched.email && errors.email}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Password"
type="password"
onBlur={handleBlur}
onChange={handleChange}
value={values.password}
name="password"
error={Boolean(touched.password) && Boolean(errors.password)}
helperText={touched.password && errors.password}
sx={{ gridColumn: "span 4" }}
/>
</Box>
{/* Buttons */}
<Box>
<Button
fullWidth
type="submit"
sx={{
m: "2rem 0",
p: "1rem",
backgroundColor: palette.primary.main,
color: palette.background.alt,
"&:hover": { color: palette.primary.main },
}}
>
{isLogin ? "LOGIN" : "REGISTER"}
</Button>
<Typography
onClick={() => {
setPageType(isLogin ? "register" : "login");
resetForm();
}}
sx={{
textDecoration: "underline",
color: palette.primary.main,
"&:hover": { cursor: "pointer", color: palette.primary.light },
}}
>
{isLogin
? "Don't have an account? Register here"
: "Already have an account? Login here"}
</Typography>
</Box>
</form>
)}
</Formik>
);
};
export default Form;

View File

@@ -1,7 +1,36 @@
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
import Form from "./Form";
const LoginPage = () => {
return (
<div>loginpage</div>
)
const theme = useTheme();
const isNotMobile = useMediaQuery("(min-width: 1000px)");
return (
<Box>
<Box
width="100%"
backgroundColor={theme.palette.background.alt}
p="1rem 6%"
textAlign="center"
>
<Typography fontWeight="bold" fontSize="32px" color="primary">
ChatHive
</Typography>
</Box>
<Box
width={isNotMobile ? "50%" : "95%"}
p="2rem"
margin="2rem auto"
borderRadius="1.5rem"
backgroundColor={theme.palette.background.alt}
>
<Typography fontWeight="500" variant="h5" sx={{ mb: "1.5rem" }}>
Welcome to ChatHive, where great minds meet!
</Typography>
<Form />
</Box>
</Box>
);
};
export default LoginPage;