Added authentication step 2 / 2
This commit is contained in:
1 parent
d68ef08f6d
commit
939ff749e5
3 files changed
+220
-221
No files matched your search
+207
-219
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Box, Divider, FormControl, Stack, Typography } from "@mui/material";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { credentials } from "../../Validation/validation";
|
||||
import { login } from "../../Features/Auth/authSlice";
|
||||
@@ -16,6 +16,148 @@ import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const LandingPage = ({ onContinue }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap={theme.gap.large} alignItems="center" textAlign="center">
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>We are pleased to see you again!</Typography>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Continue with Email"
|
||||
img={<Mail />}
|
||||
onClick={onContinue}
|
||||
sx={{
|
||||
width: "100%",
|
||||
"& svg": {
|
||||
mr: theme.gap.small,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box maxWidth={400}>
|
||||
<Typography className="tos-p">
|
||||
By continuing, you agree to our{" "}
|
||||
<Typography component="span">Terms of Service</Typography> and{" "}
|
||||
<Typography component="span">Privacy Policy.</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap={theme.gap.large} textAlign="center">
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>Enter your email address</Typography>
|
||||
</Box>
|
||||
<Box textAlign="left">
|
||||
<form noValidate spellCheck={false} onSubmit={onSubmit}>
|
||||
<Field
|
||||
type="email"
|
||||
id="login-email-input"
|
||||
label="Email"
|
||||
isRequired={true}
|
||||
placeholder="john.doe@domain.com"
|
||||
autoComplete="email"
|
||||
value={form.email}
|
||||
onChange={onChange}
|
||||
error={errors.email}
|
||||
/>
|
||||
</form>
|
||||
</Box>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Back"
|
||||
animate="slideLeft"
|
||||
img={<ArrowBackRoundedIcon />}
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mb: theme.gap.medium,
|
||||
px: theme.gap.ml,
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.gap.xs,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="primary"
|
||||
label="Continue"
|
||||
onClick={onSubmit}
|
||||
disabled={errors.email && true}
|
||||
sx={{ width: "30%" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap={theme.gap.large} textAlign="center">
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>Enter your password</Typography>
|
||||
</Box>
|
||||
<Box textAlign="left">
|
||||
<form noValidate spellCheck={false} onSubmit={onSubmit}>
|
||||
<Field
|
||||
type="password"
|
||||
id="login-password-input"
|
||||
label="Password"
|
||||
isRequired={true}
|
||||
placeholder="••••••••••"
|
||||
autoComplete="current-password"
|
||||
value={form.password}
|
||||
onChange={onChange}
|
||||
error={errors.password}
|
||||
/>
|
||||
</form>
|
||||
</Box>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Back"
|
||||
animate="slideLeft"
|
||||
img={<ArrowBackRoundedIcon />}
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
mb: theme.gap.medium,
|
||||
px: theme.gap.ml,
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.gap.xs,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="primary"
|
||||
label="Continue"
|
||||
onClick={onSubmit}
|
||||
disabled={errors.email && true}
|
||||
sx={{ width: "30%" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
@@ -45,47 +187,6 @@ const Login = () => {
|
||||
});
|
||||
}, [navigate]);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { error } = credentials.validate(form, { abortEarly: false });
|
||||
|
||||
if (error) {
|
||||
// validation errors
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
createToast({
|
||||
body:
|
||||
error.details && error.details.length > 0
|
||||
? error.details[0].message
|
||||
: "Error validating data.",
|
||||
});
|
||||
} else {
|
||||
const action = await dispatch(login(form));
|
||||
if (action.payload.success) {
|
||||
navigate("/monitors");
|
||||
createToast({
|
||||
body: "Welcome back! You're successfully logged in.",
|
||||
});
|
||||
} else {
|
||||
if (action.payload) {
|
||||
// dispatch errors
|
||||
createToast({
|
||||
body: action.payload.msg,
|
||||
});
|
||||
} else {
|
||||
// unknown errors
|
||||
createToast({
|
||||
body: "Unknown error.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
const name = idMap[id];
|
||||
@@ -107,114 +208,62 @@ const Login = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const [step, setStep] = useState(0);
|
||||
const LandingPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Stack gap={theme.gap.large} alignItems="center" textAlign="center">
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>We are pleased to see you again!</Typography>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Continue with Email"
|
||||
img={<Mail />}
|
||||
onClick={() => setStep(1)}
|
||||
sx={{
|
||||
width: "100%",
|
||||
"& svg": {
|
||||
mr: theme.gap.small,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <Divider sx={{ marginY: theme.spacing(1) }}>or</Divider>
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Continue with Google"
|
||||
onClick={() => setStep(1)}
|
||||
sx={{ width: "100%" }}
|
||||
/> */}
|
||||
</Box>
|
||||
<Box maxWidth={400}>
|
||||
<Typography className="tos-p">
|
||||
By continuing, you agree to our{" "}
|
||||
<Typography component="span">Terms of Service</Typography> and{" "}
|
||||
<Typography component="span">Privacy Policy.</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StepOne = () => {
|
||||
const submit = (event) => {
|
||||
event.preventDefault();
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (step === 1) {
|
||||
const { error } = credentials.validate(
|
||||
{ email: form.email },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
if (error) {
|
||||
setErrors((prev) => ({ ...prev, email: error.details[0].message }));
|
||||
createToast({ body: "Invalid email address." });
|
||||
createToast({ body: error.details[0].message });
|
||||
} else {
|
||||
setStep(2);
|
||||
}
|
||||
};
|
||||
} else if (step === 2) {
|
||||
const { error } = credentials.validate(form, { abortEarly: false });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack gap={theme.gap.large} textAlign="center">
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>Enter your email address</Typography>
|
||||
</Box>
|
||||
<Box textAlign="left">
|
||||
<form onSubmit={submit}>
|
||||
<Field
|
||||
type="email"
|
||||
id="login-email-input"
|
||||
label="Email"
|
||||
isRequired={true}
|
||||
placeholder="john.doe@domain.com"
|
||||
autoComplete="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
error={errors.email}
|
||||
/>
|
||||
</form>
|
||||
</Box>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Button
|
||||
level="secondary"
|
||||
label="Back"
|
||||
animate="slideLeft"
|
||||
img={<ArrowBackRoundedIcon />}
|
||||
onClick={() => setStep(0)}
|
||||
sx={{
|
||||
mb: theme.gap.medium,
|
||||
px: theme.gap.ml,
|
||||
"& svg.MuiSvgIcon-root": {
|
||||
mr: theme.gap.xs,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
level="primary"
|
||||
label="Continue"
|
||||
onClick={submit}
|
||||
disabled={errors.email && true}
|
||||
sx={{ width: "30%" }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
if (error) {
|
||||
// validation errors
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
createToast({
|
||||
body:
|
||||
error.details && error.details.length > 0
|
||||
? error.details[0].message
|
||||
: "Error validating data.",
|
||||
});
|
||||
} else {
|
||||
const action = await dispatch(login(form));
|
||||
if (action.payload.success) {
|
||||
navigate("/monitors");
|
||||
createToast({
|
||||
body: "Welcome back! You're successfully logged in.",
|
||||
});
|
||||
} else {
|
||||
if (action.payload) {
|
||||
// dispatch errors
|
||||
createToast({
|
||||
body: action.payload.msg,
|
||||
});
|
||||
} else {
|
||||
// unknown errors
|
||||
createToast({
|
||||
body: "Unknown error.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
return (
|
||||
<Stack className="login-page" overflow="hidden">
|
||||
<img
|
||||
@@ -252,7 +301,27 @@ const Login = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{step === 0 ? <LandingPage /> : step === 1 ? <StepOne /> : ""}
|
||||
{step === 0 ? (
|
||||
<LandingPage onContinue={() => setStep(1)} />
|
||||
) : step === 1 ? (
|
||||
<StepOne
|
||||
form={form}
|
||||
errors={errors}
|
||||
onSubmit={handleSubmit}
|
||||
onChange={handleChange}
|
||||
onBack={() => setStep(0)}
|
||||
/>
|
||||
) : (
|
||||
step === 2 && (
|
||||
<StepTwo
|
||||
form={form}
|
||||
errors={errors}
|
||||
onSubmit={handleSubmit}
|
||||
onChange={handleChange}
|
||||
onBack={() => setStep(1)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
<Box textAlign="center" p={theme.gap.large}>
|
||||
<Typography display="inline-block">Don't have an account? -</Typography>
|
||||
@@ -267,87 +336,6 @@ const Login = () => {
|
||||
Sign Up
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* <form className="login-form" onSubmit={handleSubmit}>
|
||||
<Stack gap={theme.gap.small} alignItems="center">
|
||||
<Typography component="h1" sx={{ mt: theme.gap.xl }}>
|
||||
Log In
|
||||
</Typography>
|
||||
<Typography>We are pleased to see you again!</Typography>
|
||||
</Stack>
|
||||
<Stack gap={theme.gap.large} sx={{ mt: `calc(${theme.gap.ml}*2)` }}>
|
||||
<Field
|
||||
type="email"
|
||||
id="login-email-input"
|
||||
label="Email"
|
||||
isRequired={true}
|
||||
placeholder="Enter your email"
|
||||
autoComplete="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
error={errors.email}
|
||||
/>
|
||||
<Field
|
||||
type="password"
|
||||
id="login-password-input"
|
||||
label="Password"
|
||||
isRequired={true}
|
||||
placeholder="••••••••••"
|
||||
autoComplete="current-password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
error={errors.password}
|
||||
/>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
sx={{
|
||||
padding: 0,
|
||||
"& .MuiTouchRipple-root": {
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Remember me"
|
||||
/>
|
||||
<Typography
|
||||
component="span"
|
||||
onClick={() => navigate("/forgot-password")}
|
||||
>
|
||||
Forgot password
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack gap={theme.gap.ml} mt={theme.gap.large}>
|
||||
<Button
|
||||
type="submit"
|
||||
level="primary"
|
||||
label="Continue"
|
||||
disabled={Object.keys(errors).length !== 0 && true}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
gap="5px"
|
||||
mt={theme.gap.ml}
|
||||
>
|
||||
<Typography sx={{ alignSelf: "center" }}>
|
||||
Don't have an account?
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form> */}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
font-size: var(--env-var-font-size-large);
|
||||
color: var(--env-var-color-2-light);
|
||||
}
|
||||
.login-page .field h3.MuiTypography-root,
|
||||
.login-page .field input {
|
||||
color: var(--env-var-color-5);
|
||||
}
|
||||
.login-page .tos-p {
|
||||
font-size: var(--env-var-font-size-medium-plus);
|
||||
}
|
||||
@@ -156,9 +160,16 @@
|
||||
line-height: 1;
|
||||
}
|
||||
.login-page button:not(.MuiIconButton-root),
|
||||
.field .MuiInputBase-root:has(input) {
|
||||
.login-page .field .MuiInputBase-root:has(input) {
|
||||
height: 40px;
|
||||
}
|
||||
.login-page .field svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.login-page .field .MuiIconButton-root{
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.login-page > .MuiStack-root:nth-of-type(1) {
|
||||
height: var(--env-var-nav-bar-height);
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.MuiInputAdornment-outlined .MuiIconButton-root svg {
|
||||
.maintenance-options .MuiInputAdornment-outlined .MuiIconButton-root svg {
|
||||
width: 20px;
|
||||
}
|
||||
Reference in new issue
Block a user