Two first steps working

This commit is contained in:
Maxime Lalo 2023-11-27 10:02:43 +01:00
parent 81731b72d8
commit 386ba6c32e
4 changed files with 92 additions and 79 deletions

View File

@ -8,6 +8,15 @@ export type IMailVerifyReturn = {
partialPhoneNumber: string; partialPhoneNumber: string;
}; };
export type IVerifyTotpCodeParams = {
totpCode: string;
email: string;
};
export type IVerifyTotpCodeReturn = {
validCode: boolean;
};
export default class Auth extends BaseApiService { export default class Auth extends BaseApiService {
private static instance: Auth; private static instance: Auth;
protected readonly namespaceUrl = this.getBaseUrl().concat("/customer"); protected readonly namespaceUrl = this.getBaseUrl().concat("/customer");
@ -26,4 +35,14 @@ export default class Auth extends BaseApiService {
return Promise.reject(err); return Promise.reject(err);
} }
} }
public async verifyTotpCode(body: IVerifyTotpCodeParams): Promise<IVerifyTotpCodeReturn> {
const url = new URL(this.baseURl.concat("/verify-totp-code"));
try {
return this.postRequest<IVerifyTotpCodeReturn>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
} }

View File

@ -1,4 +1,4 @@
import React, { useCallback, useState } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import Image from "next/image"; import Image from "next/image";
@ -9,16 +9,13 @@ import franceConnectLogo from "../france-connect.svg";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Customers from "@Front/Api/Auth/Id360/Customers/Customers"; import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
import { ValidationError } from "class-validator"; import { ValidationError } from "class-validator";
import { LoginStep } from "..";
import Auth from "@Front/Api/Auth/Customer/Auth";
type IProps = { type IProps = {
setPartialPhoneNumber: (phoneNumber: string) => void; onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
setStep: (step: LoginStep) => void; validationErrors: ValidationError[];
}; };
export default function StepEmail(props: IProps) { export default function StepEmail(props: IProps) {
const { setPartialPhoneNumber, setStep } = props; const { onSubmit, validationErrors } = props;
const [validationError, setValidationError] = useState<ValidationError[]>([]);
const router = useRouter(); const router = useRouter();
const redirectCustomerOnConnection = useCallback(() => { const redirectCustomerOnConnection = useCallback(() => {
async function getCustomer() { async function getCustomer() {
@ -32,33 +29,6 @@ export default function StepEmail(props: IProps) {
getCustomer(); getCustomer();
}, [router]); }, [router]);
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["email"]) return;
const res = await Auth.getInstance().mailVerifySms({ email: values["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;
}
setValidationError([
{
property: "email",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[setPartialPhoneNumber, setStep],
);
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<Typography typo={ITypo.H1}> <Typography typo={ITypo.H1}>
@ -70,11 +40,11 @@ export default function StepEmail(props: IProps) {
<Typography className={classes["or"]} typo={ITypo.P_16}> <Typography className={classes["or"]} typo={ITypo.P_16}>
Ou Ou
</Typography> </Typography>
<Form className={classes["form"]} onSubmit={onSubmitHandler}> <Form className={classes["form"]} onSubmit={onSubmit}>
<TextField <TextField
placeholder="E-mail" placeholder="E-mail"
name="email" name="email"
validationError={validationError.find((error) => error.property === "email")} validationError={validationErrors.find((error) => error.property === "email")}
/> />
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}> <Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant Suivant

View File

@ -1,62 +1,29 @@
import React, { useCallback, useState } from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import Image from "next/image";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import franceConnectLogo from "./france-connect.svg";
import { useRouter } from "next/router";
import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
import { ValidationError } from "class-validator"; import { ValidationError } from "class-validator";
import { LoginStep } from "..";
import Auth from "@Front/Api/Auth/Customer/Auth";
type IProps = { type IProps = {
setStep: (step: LoginStep) => void; onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
setTotpCode: (code: string) => void; validationErrors: ValidationError[];
partialPhoneNumber: string; partialPhoneNumber: string;
}; };
export default function StepTotp(props: IProps) { export default function StepTotp(props: IProps) {
const { setStep, setTotpCode, partialPhoneNumber } = props; const { onSubmit, validationErrors, partialPhoneNumber } = props;
const [validationError, setValidationError] = useState<ValidationError[]>([]);
const router = useRouter();
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["email"]) return;
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
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;
}
setValidationError([
{
property: "code",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[setStep],
);
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<Typography typo={ITypo.H1}> <Typography typo={ITypo.H1}>
<div className={classes["title"]}>Votre code a é envoyé par SMS au ** ** ** {partialPhoneNumber}</div> <div className={classes["title"]}>Votre code a é envoyé par SMS au ** ** ** {partialPhoneNumber}</div>
</Typography> </Typography>
<Form className={classes["form"]} onSubmit={onSubmitHandler}> <Form className={classes["form"]} onSubmit={onSubmit}>
<TextField <TextField
placeholder="Code à usage unique" placeholder="Code à usage unique"
name="email" name="totpCode"
validationError={validationError.find((error) => error.property === "code")} validationError={validationErrors.find((error) => error.property === "totpCode")}
/> />
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}> <Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant Suivant

View File

@ -8,6 +8,8 @@ import LandingImage from "./landing-connect.jpeg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm"; import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import StepEmail from "./StepEmail"; import StepEmail from "./StepEmail";
import StepTotp from "./StepTotp"; import StepTotp from "./StepTotp";
import Auth from "@Front/Api/Auth/Customer/Auth";
import { ValidationError } from "class-validator";
export enum LoginStep { export enum LoginStep {
EMAIL, EMAIL,
@ -22,8 +24,9 @@ export default function Login() {
const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL); const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
const [totpCode, setTotpCode] = useState<string>(""); const [totpCode, setTotpCode] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>(""); const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
const openErrorModal = useCallback(() => { const openErrorModal = useCallback(() => {
setIsErrorModalOpen(true); setIsErrorModalOpen(true);
}, []); }, []);
@ -36,12 +39,66 @@ export default function Login() {
if (error === "1") openErrorModal(); if (error === "1") openErrorModal();
}, [error, openErrorModal]); }, [error, openErrorModal]);
const onEmailFormSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["email"]) return;
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
setPartialPhoneNumber(res.partialPhoneNumber);
setEmail(values["email"]);
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;
}
setValidationErrors([
{
property: "email",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[setEmail, setPartialPhoneNumber, setStep],
);
const onSmsCodeSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
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);
}
} catch (error: any) {
setValidationErrors([
{
property: "totpCode",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[email, setStep],
);
return ( return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}> <DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}> <div className={classes["root"]}>
{step === LoginStep.EMAIL && <StepEmail setPartialPhoneNumber={setPartialPhoneNumber} setStep={setStep} />} {step === LoginStep.EMAIL && <StepEmail onSubmit={onEmailFormSubmit} validationErrors={validationErrors} />}
{step === LoginStep.TOTP && ( {step === LoginStep.TOTP && (
<StepTotp setStep={setStep} setTotpCode={setTotpCode} partialPhoneNumber={partialPhoneNumber} /> <StepTotp onSubmit={onSmsCodeSubmit} validationErrors={validationErrors} partialPhoneNumber={partialPhoneNumber} />
)}
{step === LoginStep.NEW_PASSWORD && (
<StepTotp onSubmit={onSmsCodeSubmit} validationErrors={validationErrors} partialPhoneNumber={partialPhoneNumber} />
)} )}
</div> </div>
<Confirm <Confirm