🚧 WIP login page

This commit is contained in:
Maxime Lalo 2023-11-24 17:27:57 +01:00
parent 6017803cee
commit 81731b72d8
7 changed files with 253 additions and 111 deletions

View File

@ -138,11 +138,16 @@ export default abstract class BaseApiService {
} }
} else { } else {
// Handle error response // Handle error response
const responseCopy = response.clone();
try { try {
const responseJson = await response.json(); const responseJson = await response.json();
return Promise.reject(responseJson); return Promise.reject(responseJson);
} catch (err) { } catch (err) {
return Promise.reject(err); const responseText = await responseCopy.text();
return Promise.reject({
http_status: response.status,
message: responseText,
});
} }
} }

View File

@ -0,0 +1,49 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
margin-bottom: 32px;
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.logo {
margin-top: 32px;
cursor: pointer;
}
.what_is_france_connect {
color: var(--light-text-action-high-blue-france, #000091);
/* 2.Corps de texte/SM - Texte détail/Desktop & Mobile - Regular */
font-family: Marianne;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
margin-top: 12px;
}
.or {
margin-top: 32px;
}
.forget-password {
margin-top: 32px;
margin-bottom: 8px;
}
.form {
margin-top: 32px;
.submit_button {
margin-top: 32px;
}
}
}

View File

@ -0,0 +1,91 @@
import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import Image from "next/image";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
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 { LoginStep } from "..";
import Auth from "@Front/Api/Auth/Customer/Auth";
type IProps = {
setPartialPhoneNumber: (phoneNumber: string) => void;
setStep: (step: LoginStep) => void;
};
export default function StepEmail(props: IProps) {
const { setPartialPhoneNumber, setStep } = props;
const [validationError, setValidationError] = useState<ValidationError[]>([]);
const router = useRouter();
const redirectCustomerOnConnection = useCallback(() => {
async function getCustomer() {
try {
const loginRes = await Customers.getInstance().login();
router.push(loginRes.enrollment.franceConnectUrl);
} catch (e) {
console.error(e);
}
}
getCustomer();
}, [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 (
<div className={classes["root"]}>
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Identifiez-vous</div>
</Typography>
<Typography typo={ITypo.P_16}>Pour accéder à votre espace de dépôt des documents, veuillez vous identifier.</Typography>
<Image alt="france-connect" src={franceConnectLogo} onClick={redirectCustomerOnConnection} className={classes["logo"]} />
<div className={classes["what_is_france_connect"]}>Qu'est ce que FranceConnect ?</div>
<Typography className={classes["or"]} typo={ITypo.P_16}>
Ou
</Typography>
<Form className={classes["form"]} onSubmit={onSubmitHandler}>
<TextField
placeholder="E-mail"
name="email"
validationError={validationError.find((error) => error.property === "email")}
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant
</Button>
</Form>
{/* <Typography typo={ITypo.P_18}>
<div className={classes["forget-password"]}>Vous n'arrivez pas à vous connecter ?</div>
</Typography>
<Link href="mailto:g.texier@notaires.fr">
<Button variant={EButtonVariant.LINE}>Contacter l'administrateur</Button>
</Link> */}
</div>
);
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,67 @@
import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import Image from "next/image";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
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 { LoginStep } from "..";
import Auth from "@Front/Api/Auth/Customer/Auth";
type IProps = {
setStep: (step: LoginStep) => void;
setTotpCode: (code: string) => void;
partialPhoneNumber: string;
};
export default function StepTotp(props: IProps) {
const { setStep, setTotpCode, 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 (
<div className={classes["root"]}>
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Votre code a é envoyé par SMS au ** ** ** {partialPhoneNumber}</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmitHandler}>
<TextField
placeholder="Code à usage unique"
name="email"
validationError={validationError.find((error) => error.property === "code")}
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant
</Button>
</Form>
</div>
);
}

View File

@ -1,49 +1,4 @@
@import "@Themes/constants.scss"; @import "@Themes/constants.scss";
.root { .root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
margin-bottom: 32px;
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.logo {
margin-top: 32px;
cursor: pointer;
}
.what_is_france_connect {
color: var(--light-text-action-high-blue-france, #000091);
/* 2.Corps de texte/SM - Texte détail/Desktop & Mobile - Regular */
font-family: Marianne;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
margin-top: 12px;
}
.or {
margin-top: 32px;
}
.forget-password {
margin-top: 32px;
margin-bottom: 8px;
}
.form {
margin-top: 32px;
.submit_button {
margin-top: 32px;
}
}
} }

View File

@ -1,37 +1,28 @@
import franceConnectLogo from "./france-connect.svg";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import LandingImage from "./landing-connect.jpeg"; import LandingImage from "./landing-connect.jpeg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm"; import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import Form from "@Front/Components/DesignSystem/Form"; import StepEmail from "./StepEmail";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import StepTotp from "./StepTotp";
import Module from "@Front/Config/Module";
import { ValidationError } from "class-validator";
import Auth from "@Front/Api/Auth/Customer/Auth";
export enum LoginStep {
EMAIL,
TOTP,
NEW_PASSWORD,
}
export default function Login() { export default function Login() {
const router = useRouter(); const router = useRouter();
const error = router.query["error"]; const error = router.query["error"];
const [validationError, setValidationError] = useState<ValidationError[]>([]);
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
const redirectCustomerOnConnection = useCallback(() => { const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
async function getCustomer() { const [totpCode, setTotpCode] = useState<string>("");
try { const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
const loginRes = await Customers.getInstance().login();
router.push(loginRes.enrollment.franceConnectUrl);
} catch (e) {
console.error(e);
}
}
getCustomer();
}, [router]);
const openErrorModal = useCallback(() => { const openErrorModal = useCallback(() => {
setIsErrorModalOpen(true); setIsErrorModalOpen(true);
@ -45,53 +36,13 @@ export default function Login() {
if (error === "1") openErrorModal(); if (error === "1") openErrorModal();
}, [error, openErrorModal]); }, [error, openErrorModal]);
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
console.log(values);
if (!values["email"]) return;
const phoneNumber = await Auth.getInstance().mailVerifySms({ email: values["email"] });
/* router.push(
Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", "1"),
); */
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
if (validationErrors.length > 0) {
setValidationError(validationErrors as ValidationError[]);
}
return;
}
},
[router],
);
return ( return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}> <DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}> <div className={classes["root"]}>
<Typography typo={ITypo.H1}> {step === LoginStep.EMAIL && <StepEmail setPartialPhoneNumber={setPartialPhoneNumber} setStep={setStep} />}
<div className={classes["title"]}>Identifiez-vous</div> {step === LoginStep.TOTP && (
</Typography> <StepTotp setStep={setStep} setTotpCode={setTotpCode} partialPhoneNumber={partialPhoneNumber} />
<Typography typo={ITypo.P_16}>Pour accéder à votre espace de dépôt des documents, veuillez vous identifier.</Typography> )}
<Image alt="france-connect" src={franceConnectLogo} onClick={redirectCustomerOnConnection} className={classes["logo"]} />
<div className={classes["what_is_france_connect"]}>Qu'est ce que FranceConnect ?</div>
<Typography className={classes["or"]} typo={ITypo.P_16}>
Ou
</Typography>
<Form className={classes["form"]} onSubmit={onSubmitHandler}>
<TextField
placeholder="E-mail"
name="email"
validationError={validationError.find((error) => error.property === "email")}
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Suivant
</Button>
</Form>
{/* <Typography typo={ITypo.P_18}>
<div className={classes["forget-password"]}>Vous n'arrivez pas à vous connecter ?</div>
</Typography>
<Link href="mailto:g.texier@notaires.fr">
<Button variant={EButtonVariant.LINE}>Contacter l'administrateur</Button>
</Link> */}
</div> </div>
<Confirm <Confirm
isOpen={isErrorModalOpen} isOpen={isErrorModalOpen}