Login and register page
This commit is contained in:
@@ -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 |
|
||||
|
||||
276
client/src/scenes/loginPage/Form.jsx
Normal file
276
client/src/scenes/loginPage/Form.jsx
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user