✨ Set password first time working
This commit is contained in:
parent
386ba6c32e
commit
485b63d5ed
@ -1,4 +1,5 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
import { ICustomerTokens } from "../Id360/Customers/Customers";
|
||||
|
||||
export type IMailVerifyParams = {
|
||||
email: string;
|
||||
@ -15,6 +16,13 @@ export type IVerifyTotpCodeParams = {
|
||||
|
||||
export type IVerifyTotpCodeReturn = {
|
||||
validCode: boolean;
|
||||
firstConnection: boolean;
|
||||
};
|
||||
|
||||
export type ISetPasswordParams = {
|
||||
password: string;
|
||||
email: string;
|
||||
totpCode: string;
|
||||
};
|
||||
|
||||
export default class Auth extends BaseApiService {
|
||||
@ -45,4 +53,14 @@ export default class Auth extends BaseApiService {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async setPassword(body: ISetPasswordParams): Promise<ICustomerTokens> {
|
||||
const url = new URL(this.baseURl.concat("/set-password"));
|
||||
try {
|
||||
return this.postRequest<ICustomerTokens>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
|
||||
.password_indication {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
};
|
||||
|
||||
export default function StepNewPassword(props: IProps) {
|
||||
const { onSubmit, validationErrors } = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Configurez votre mot de passe</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Mot de passe"
|
||||
name="password"
|
||||
validationError={validationErrors.find((error) => error.property === "password")}
|
||||
password
|
||||
/>
|
||||
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.GREY} className={classes["password_indication"]}>
|
||||
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Confirmation du mot de passe"
|
||||
name="confirm_password"
|
||||
validationError={validationErrors.find((error) => error.property === "confirm_password")}
|
||||
password
|
||||
/>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Valider
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
import Link from "next/link";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
};
|
||||
|
||||
export default function StepPassword(props: IProps) {
|
||||
const { onSubmit, validationErrors } = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Entrez votre mot de passe</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Mot de passe"
|
||||
name="password"
|
||||
validationError={validationErrors.find((error) => error.property === "password")}
|
||||
password
|
||||
/>
|
||||
<Link href="/forgot-password">
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["forgot-password"]}>Mot de passe oublié ?</div>
|
||||
</Typography>
|
||||
</Link>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Valider
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -10,10 +10,15 @@ import StepEmail from "./StepEmail";
|
||||
import StepTotp from "./StepTotp";
|
||||
import Auth from "@Front/Api/Auth/Customer/Auth";
|
||||
import { ValidationError } from "class-validator";
|
||||
import StepPassword from "./StepPassword";
|
||||
import StepNewPassword from "./StepNewPassword";
|
||||
import CustomerStore from "@Front/Stores/CustomerStore";
|
||||
import Module from "@Front/Config/Module";
|
||||
|
||||
export enum LoginStep {
|
||||
EMAIL,
|
||||
TOTP,
|
||||
PASSWORD,
|
||||
NEW_PASSWORD,
|
||||
}
|
||||
export default function Login() {
|
||||
@ -27,6 +32,7 @@ export default function Login() {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
|
||||
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
|
||||
|
||||
const openErrorModal = useCallback(() => {
|
||||
setIsErrorModalOpen(true);
|
||||
}, []);
|
||||
@ -72,10 +78,14 @@ export default function Login() {
|
||||
try {
|
||||
if (!values["totpCode"]) return;
|
||||
const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email });
|
||||
if (res.validCode) {
|
||||
setTotpCode(values["totpCode"]);
|
||||
setStep(LoginStep.NEW_PASSWORD);
|
||||
}
|
||||
|
||||
// If the code is valid setting it in state
|
||||
if (res.validCode) setTotpCode(values["totpCode"]);
|
||||
|
||||
// If it's first connection, show the form for first connection
|
||||
if (res.firstConnection) setStep(LoginStep.NEW_PASSWORD);
|
||||
// Else just login normally
|
||||
else setStep(LoginStep.PASSWORD);
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
@ -90,6 +100,64 @@ export default function Login() {
|
||||
},
|
||||
[email, setStep],
|
||||
);
|
||||
|
||||
const onNewPasswordSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
console.log(values);
|
||||
if (!values["password"] || !values["confirm_password"]) return;
|
||||
if (values["password"] !== values["confirm_password"]) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "confirm_password",
|
||||
constraints: {
|
||||
"400": "Les mots de passe ne correspondent pas.",
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
|
||||
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
|
||||
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
|
||||
// If set password worked, setting the token and redirecting
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "password",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[email, totpCode, setValidationErrors],
|
||||
);
|
||||
|
||||
const onPasswordSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
if (!values["password"]) return;
|
||||
const res = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
|
||||
|
||||
// If set password worked, setting the token and redirecting
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "password",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[email, totpCode],
|
||||
);
|
||||
|
||||
return (
|
||||
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
|
||||
<div className={classes["root"]}>
|
||||
@ -97,9 +165,8 @@ export default function Login() {
|
||||
{step === LoginStep.TOTP && (
|
||||
<StepTotp onSubmit={onSmsCodeSubmit} validationErrors={validationErrors} partialPhoneNumber={partialPhoneNumber} />
|
||||
)}
|
||||
{step === LoginStep.NEW_PASSWORD && (
|
||||
<StepTotp onSubmit={onSmsCodeSubmit} validationErrors={validationErrors} partialPhoneNumber={partialPhoneNumber} />
|
||||
)}
|
||||
{step === LoginStep.PASSWORD && <StepPassword onSubmit={onPasswordSubmit} validationErrors={validationErrors} />}
|
||||
{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
|
||||
</div>
|
||||
<Confirm
|
||||
isOpen={isErrorModalOpen}
|
||||
|
Loading…
x
Reference in New Issue
Block a user