diff --git a/package-lock.json b/package-lock.json index c861c5ec..50d8faa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,9 +43,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -223,9 +223,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz", - "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -234,9 +234,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", diff --git a/src/front/Api/Auth/Customer/Auth.ts b/src/front/Api/Auth/Customer/Auth.ts index 1b93a915..237df5c3 100644 --- a/src/front/Api/Auth/Customer/Auth.ts +++ b/src/front/Api/Auth/Customer/Auth.ts @@ -32,6 +32,10 @@ export type ILoginParams = { totpCode: string; }; +export type IAskNewPasswordParams = { + email: string; +}; + export default class Auth extends BaseApiService { private static instance: Auth; protected readonly namespaceUrl = this.getBaseUrl().concat("/customer"); @@ -80,4 +84,14 @@ export default class Auth extends BaseApiService { return Promise.reject(err); } } + + public async askNewPassword(body: IAskNewPasswordParams): Promise { + const url = new URL(this.baseURl.concat("/ask-new-password")); + try { + return this.postRequest(url, body); + } catch (err) { + this.onError(err); + return Promise.reject(err); + } + } } diff --git a/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/classes.module.scss b/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/classes.module.scss new file mode 100644 index 00000000..e0a34824 --- /dev/null +++ b/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/classes.module.scss @@ -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; + } + } +} diff --git a/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/index.tsx b/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/index.tsx new file mode 100644 index 00000000..1ffcd778 --- /dev/null +++ b/src/front/Components/Layouts/LoginCustomer/PasswordForgotten/index.tsx @@ -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 | null, values: { [key: string]: string }) => void; + validationErrors: ValidationError[]; +}; + +export default function PasswordForgotten(props: IProps) { + const { onSubmit, validationErrors } = props; + + return ( +
+ +
Réinitialisez votre mot de passe
+
+
+ error.property === "password")} + password + /> + + Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre. + + error.property === "confirm_password")} + password + /> + + +
+ ); +} diff --git a/src/front/Components/Layouts/LoginCustomer/StepPassword/index.tsx b/src/front/Components/Layouts/LoginCustomer/StepPassword/index.tsx index b4632b76..45888814 100644 --- a/src/front/Components/Layouts/LoginCustomer/StepPassword/index.tsx +++ b/src/front/Components/Layouts/LoginCustomer/StepPassword/index.tsx @@ -5,14 +5,29 @@ 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"; +import Confirm from "@Front/Components/DesignSystem/Modal/Confirm"; type IProps = { onSubmit: (e: React.FormEvent | null, values: { [key: string]: string }) => void; validationErrors: ValidationError[]; + onPasswordForgotClicked: () => void; }; export default function StepPassword(props: IProps) { - const { onSubmit, validationErrors } = props; + const { onSubmit, validationErrors, onPasswordForgotClicked } = props; + const [isModalOpened, setIsModalOpened] = React.useState(false); + + const closeModal = () => { + setIsModalOpened(false); + }; + + const openModal = () => { + setIsModalOpened(true); + }; + + const onModalAccept = () => { + onPasswordForgotClicked(); + setIsModalOpened(false); + }; return (
@@ -26,15 +41,29 @@ export default function StepPassword(props: IProps) { validationError={validationErrors.find((error) => error.property === "password")} password /> - +
Mot de passe oublié ? - +
+ +
+ + Un code à usage unique va vous être envoyé par sms pour réinitialiser votre mot de passe. + +
+
); } diff --git a/src/front/Components/Layouts/LoginCustomer/index.tsx b/src/front/Components/Layouts/LoginCustomer/index.tsx index 3fe19a7f..0030af15 100644 --- a/src/front/Components/Layouts/LoginCustomer/index.tsx +++ b/src/front/Components/Layouts/LoginCustomer/index.tsx @@ -15,12 +15,14 @@ import StepNewPassword from "./StepNewPassword"; import CustomerStore from "@Front/Stores/CustomerStore"; import Module from "@Front/Config/Module"; import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes"; +import PasswordForgotten from "./PasswordForgotten"; export enum LoginStep { EMAIL, TOTP, PASSWORD, NEW_PASSWORD, + PASSWORD_FORGOTTEN, } export default function Login() { const router = useRouter(); @@ -85,6 +87,8 @@ export default function Login() { // If it's first connection, show the form for first connection if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD); + // If it's password forgotten, show the form for password forgotten + else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN); // Else just login normally else setStep(LoginStep.PASSWORD); } catch (error: any) { @@ -105,7 +109,6 @@ export default function Login() { const onNewPasswordSubmit = useCallback( async (e: React.FormEvent | null, values: { [key: string]: string }) => { try { - console.log(values); if (!values["password"] || !values["confirm_password"]) return; if (values["password"] !== values["confirm_password"]) { setValidationErrors([ @@ -159,6 +162,21 @@ export default function Login() { [email, router, totpCode], ); + const onPasswordForgotClicked = useCallback(async () => { + try { + const res = await Auth.getInstance().askNewPassword({ email }); + setPartialPhoneNumber(res.partialPhoneNumber); + setStep(LoginStep.TOTP); + } catch (error: any) { + // If token already exists and is still valid redirect to the connect/register page + if (error.http_status === 425) { + setStep(LoginStep.TOTP); + return; + } + return; + } + }, [email]); + return (
@@ -166,8 +184,17 @@ export default function Login() { {step === LoginStep.TOTP && ( )} - {step === LoginStep.PASSWORD && } + {step === LoginStep.PASSWORD && ( + + )} {step === LoginStep.NEW_PASSWORD && } + {step === LoginStep.PASSWORD_FORGOTTEN && ( + + )}