Login and register page
This commit is contained in:
@@ -17,3 +17,4 @@ Table:
|
|||||||
| 13.07.2023 | Setup frontend | 6 |
|
| 13.07.2023 | Setup frontend | 6 |
|
||||||
| 13.07.2023 | Theme and styling | 10 |
|
| 13.07.2023 | Theme and styling | 10 |
|
||||||
| 14.07.2023 | Navbar | 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 = () => {
|
const LoginPage = () => {
|
||||||
return (
|
const theme = useTheme();
|
||||||
<div>loginpage</div>
|
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;
|
export default LoginPage;
|
||||||
|
|||||||
Reference in New Issue
Block a user