Merge branch 'dev' into staging

This commit is contained in:
Maxime Lalo 2024-04-22 16:09:24 +02:00
commit e8e11af759
25 changed files with 403 additions and 260 deletions

6
package-lock.json generated
View File

@ -32,6 +32,7 @@
"react-toastify": "^9.1.3",
"sass": "^1.59.2",
"sharp": "^0.32.1",
"ts-pattern": "^4.3.0",
"typescript": "4.9.5",
"uuidv4": "^6.2.13"
}
@ -4910,6 +4911,11 @@
"node": ">=8.0"
}
},
"node_modules/ts-pattern": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-4.3.0.tgz",
"integrity": "sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg=="
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "PORT=5005 next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",

View File

@ -1,24 +1,25 @@
@import "@Themes/constants.scss";
.root {
padding: 40px 40px 40px 64px;
.notary-container {
display: flex;
flex-direction: column;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
max-width: 530px;
margin: auto;
.title {
margin-bottom: 24px;
}
.title {
margin: 32px 0;
text-align: center;
.forget-password {
margin-top: 24px;
margin-bottom: 8px;
}
.separator {
margin: 48px 0;
height: 1px;
background-color: var(--grey-medium);
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.forget-password {
margin-top: 32px;
margin-bottom: 8px;
}
}

View File

@ -1,214 +1,24 @@
import CoffreIcon from "@Assets/Icons/coffre.svg";
import idNoteLogo from "@Assets/Icons/id-note-logo.svg";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import Image from "next/image";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import LandingImage from "./landing-connect.jpeg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
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";
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
import PasswordForgotten from "./PasswordForgotten";
import { FrontendVariables } from "@Front/Config/VariablesFront";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Link from "next/link";
import idNoteLogo from "@Assets/Icons/id-note-logo.svg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
export enum LoginStep {
EMAIL,
TOTP,
PASSWORD,
NEW_PASSWORD,
PASSWORD_FORGOTTEN,
}
export default function Login() {
const router = useRouter();
const error = router.query["error"];
const [isErrorModalOpen, setIsErrorModalOpen] = useState(0);
const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
const [totpCodeUid, setTotpCodeUid] = useState<string>("");
const [totpCode, setTotpCode] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
const openErrorModal = useCallback(() => {
setIsErrorModalOpen(1);
}, []);
const closeErrorModal = useCallback(() => {
setIsErrorModalOpen(0);
}, []);
const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["email"]) return;
setEmail(values["email"]);
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
setPartialPhoneNumber(res.partialPhoneNumber);
setTotpCodeUid(res.totpCodeUid);
setStep(LoginStep.TOTP);
setValidationErrors([]);
} catch (error: any) {
setValidationErrors([
{
property: "email",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
}, []);
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 the code is valid setting it in state
if (res.validCode) setTotpCode(values["totpCode"]);
setValidationErrors([]);
// 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) {
setValidationErrors([
{
property: "totpCode",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[email, setStep],
);
const onNewPasswordSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
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 passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/);
if (!passwordRegex.test(values["password"])) {
setValidationErrors([
{
property: "password",
constraints: {
"400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.",
},
},
]);
return;
}
const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]);
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;
}
},
[totpCode, email, router],
);
const onPasswordSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["password"]) return;
const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]);
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
} catch (error: any) {
setValidationErrors([
{
property: "password",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[email, router, totpCode],
);
const onPasswordForgotClicked = useCallback(async () => {
try {
const res = await Auth.getInstance().askNewPassword({ email });
setPartialPhoneNumber(res.partialPhoneNumber);
setValidationErrors([]);
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]);
const onSendAnotherCode = useCallback(async () => {
try {
const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid });
setValidationErrors([]);
setPartialPhoneNumber(res.partialPhoneNumber);
setTotpCodeUid(res.totpCodeUid);
} catch (error: any) {
setValidationErrors([
{
property: "totpCode",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
}, [email, totpCodeUid]);
const redirectUserOnConnection = useCallback(() => {
const variables = FrontendVariables.getInstance();
router.push(
@ -218,6 +28,14 @@ export default function Login() {
);
}, [router]);
const openErrorModal = useCallback((index: number) => {
setIsErrorModalOpen(index);
}, []);
const closeErrorModal = useCallback(() => {
setIsErrorModalOpen(0);
}, []);
const closeNoEmailModal = useCallback(() => {
setIsErrorModalOpen(0);
router.push("https://connexion.idnot.fr/");
@ -229,49 +47,25 @@ export default function Login() {
};
useEffect(() => {
if (error === "1") openErrorModal();
openErrorModal(parseInt(error as string));
}, [error, openErrorModal]);
return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}>
{step === LoginStep.EMAIL && (
<div className={classes["notary-container"]}>
<Typography typo={ITypo.H2} className={classes["title"]}>
Connectez vous en tant que notaire
</Typography>
<Button onClick={redirectUserOnConnection} icon={idNoteLogo} iconposition={"left"}>
S'identifier avec ID.not
</Button>
<Typography typo={ITypo.P_18} className={classes["forget-password"]}>
Vous n'arrivez pas à vous connecter ?
</Typography>
<Link href="mailto:g.texier@notaires.fr">
<Button variant={EButtonVariant.LINE}>Contacter l'administrateur</Button>
</Link>
<div className={classes["separator"]} />
</div>
)}
{step === LoginStep.EMAIL && <StepEmail onSubmit={onEmailFormSubmit} validationErrors={validationErrors} />}
{step === LoginStep.TOTP && (
<StepTotp
onSubmit={onSmsCodeSubmit}
validationErrors={validationErrors}
partialPhoneNumber={partialPhoneNumber}
onSendAnotherCode={onSendAnotherCode}
/>
)}
{step === LoginStep.PASSWORD && (
<StepPassword
onSubmit={onPasswordSubmit}
validationErrors={validationErrors}
onPasswordForgotClicked={onPasswordForgotClicked}
/>
)}
{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
{step === LoginStep.PASSWORD_FORGOTTEN && (
<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
)}
<Image alt="coffre" src={CoffreIcon} />
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Connexion espace professionnel</div>
</Typography>
<Button onClick={redirectUserOnConnection} icon={idNoteLogo} iconposition={"left"}>
S'identifier avec ID.not
</Button>
<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>
<Confirm
isOpen={isErrorModalOpen === 1}

View File

@ -3,6 +3,9 @@
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;

View File

@ -3,6 +3,9 @@
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
margin-bottom: 32px;

View File

@ -31,13 +31,16 @@ export default function StepEmail(props: IProps) {
return (
<div className={classes["root"]}>
<Typography typo={ITypo.H2}>Connectez-vous en tant que client</Typography>
<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> */}
<Typography typo={ITypo.P_16}>Pour accéder à votre espace de dépôt des documents, veuillez vous identifier. </Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="E-mail"

View File

@ -3,6 +3,9 @@
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;

View File

@ -3,6 +3,9 @@
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;

View File

@ -3,6 +3,8 @@
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: 220px auto;
.title {
text-align: left;

View File

@ -0,0 +1,4 @@
@import "@Themes/constants.scss";
.root {
}

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,252 @@
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import LandingImage from "./landing-connect.jpeg";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
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";
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();
const error = router.query["error"];
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
const [totpCodeUid, setTotpCodeUid] = useState<string>("");
const [totpCode, setTotpCode] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
const openErrorModal = useCallback(() => {
setIsErrorModalOpen(true);
}, []);
const closeErrorModal = useCallback(() => {
setIsErrorModalOpen(false);
}, []);
useEffect(() => {
if (error === "1") openErrorModal();
}, [error, openErrorModal]);
const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["email"]) return;
setEmail(values["email"]);
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
setPartialPhoneNumber(res.partialPhoneNumber);
setTotpCodeUid(res.totpCodeUid);
setStep(LoginStep.TOTP);
setValidationErrors([]);
} catch (error: any) {
setValidationErrors([
{
property: "email",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
}, []);
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 the code is valid setting it in state
if (res.validCode) setTotpCode(values["totpCode"]);
setValidationErrors([]);
// 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) {
setValidationErrors([
{
property: "totpCode",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[email, setStep],
);
const onNewPasswordSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
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 passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/);
if (!passwordRegex.test(values["password"])) {
setValidationErrors([
{
property: "password",
constraints: {
"400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.",
},
},
]);
return;
}
const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]);
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;
}
},
[totpCode, email, router],
);
const onPasswordSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
if (!values["password"]) return;
const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]);
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
} catch (error: any) {
setValidationErrors([
{
property: "password",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
},
[email, router, totpCode],
);
const onPasswordForgotClicked = useCallback(async () => {
try {
const res = await Auth.getInstance().askNewPassword({ email });
setPartialPhoneNumber(res.partialPhoneNumber);
setValidationErrors([]);
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]);
const onSendAnotherCode = useCallback(async () => {
try {
const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid });
setValidationErrors([]);
setPartialPhoneNumber(res.partialPhoneNumber);
setTotpCodeUid(res.totpCodeUid);
} catch (error: any) {
setValidationErrors([
{
property: "totpCode",
constraints: {
[error.http_status]: error.message,
},
},
]);
return;
}
}, [email, totpCodeUid]);
return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}>
{step === LoginStep.EMAIL && <StepEmail onSubmit={onEmailFormSubmit} validationErrors={validationErrors} />}
{step === LoginStep.TOTP && (
<StepTotp
onSubmit={onSmsCodeSubmit}
validationErrors={validationErrors}
partialPhoneNumber={partialPhoneNumber}
onSendAnotherCode={onSendAnotherCode}
/>
)}
{step === LoginStep.PASSWORD && (
<StepPassword
onSubmit={onPasswordSubmit}
validationErrors={validationErrors}
onPasswordForgotClicked={onPasswordForgotClicked}
/>
)}
{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
{step === LoginStep.PASSWORD_FORGOTTEN && (
<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
)}
</div>
<Confirm
isOpen={isErrorModalOpen}
onClose={closeErrorModal}
showCancelButton={false}
onAccept={closeErrorModal}
closeBtn
header={"Erreur"}
confirmText={"OK"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Une erreur est survenue lors de la connexion. Veuillez réessayer.
</Typography>
</div>
</Confirm>
</DefaultDoubleSidePage>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -0,0 +1,23 @@
.root {
padding: 64px;
.content {
display: flex;
gap: 48px;
flex-direction: column;
margin-top: 48px;
.section {
display: flex;
flex-direction: column;
gap: 24px;
}
.separator {
height: 1px;
background-color: var(--grey-medium);
}
}
.bottom {
margin-top: 48px;
}
}

View File

@ -0,0 +1,47 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import classes from "./classes.module.scss";
import LandingImage from "../Login/landing-connect.jpeg";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Link from "next/link";
import Module from "@Front/Config/Module";
export default function LoginHome() {
return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}>
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Connectez-vous à votre plateforme Lecoffre.io</div>
</Typography>
<div className={classes["content"]}>
<div className={classes["section"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Je suis un notaire
</Typography>
<Link href={Module.getInstance().get().modules.pages.Login.props.path}>
<Button>Se connecter</Button>
</Link>
</div>
<div className={classes["separator"]} />
<div className={classes["section"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Je suis un client
</Typography>
<Link href={Module.getInstance().get().modules.pages.CustomersLogin.props.path}>
<Button>Se connecter</Button>
</Link>
</div>
</div>
<div className={classes["bottom"]}>
<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>
</DefaultDoubleSidePage>
);
}

View File

@ -71,7 +71,7 @@ export default function SubscriptionManageCollaborators() {
useEffect(() => {
loadSubscription();
loadCollaborators();
}, [loadCollaborators, loadSubscription]);
}, [loadSubscription]);
return (
<DefaultTemplate title="Nouvelle souscription" hasBackArrow>

View File

@ -6,14 +6,14 @@ import type { NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
// Get the JWT from the cookies
const cookies = request.cookies.get("leCoffreAccessToken");
if (!cookies) return NextResponse.redirect(new URL("/login", request.url));
if (!cookies) return NextResponse.redirect(new URL("/", request.url));
// Decode it
const userDecodedToken = jwt_decode(cookies.value) as IUserJwtPayload;
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
// If no JWT provided, redirect to login page
if (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/login", request.url));
if (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/", request.url));
// If JWT expired, redirect to login callback page to refresh tokens
const now = Math.floor(Date.now() / 1000);
@ -39,6 +39,5 @@ export const config = {
"/offices/:path*",
"/roles/:path*",
"/users/:path*",
"/",
],
};

View File

@ -1,5 +1,5 @@
import Login from "@Front/Components/Layouts/Login";
import LoginCustomer from "@Front/Components/Layouts/LoginCustomer";
export default function Route() {
return <Login />;
return <LoginCustomer />;
}

View File

@ -1,4 +1,4 @@
import LoginCustomer from "@Front/Components/Layouts/Login";
import LoginCustomer from "@Front/Components/Layouts/LoginCustomer";
export default function Route() {
return <LoginCustomer />;

View File

@ -1,5 +1,5 @@
import Login from "./login";
import LoginHome from "@Front/Components/Layouts/LoginHome";
export default function Route() {
return <Login />;
return <LoginHome />;
}