
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 3m58s
319 lines
16 KiB
TypeScript
319 lines
16 KiB
TypeScript
import backgroundImage from "@Assets/images/background_refonte.svg";
|
|
import CoffreIcon from "@Assets/logo_small_blue.svg";
|
|
import Auth from "@Front/Api/Auth/IdNot";
|
|
import Loader from "@Front/Components/DesignSystem/Loader";
|
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
|
import HelpBox from "@Front/Components/Elements/HelpBox";
|
|
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
|
import Module from "@Front/Config/Module";
|
|
import UserStore from "@Front/Stores/UserStore";
|
|
import CookieService from "@Front/Services/CookieService/CookieService";
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/router";
|
|
import React, { useEffect, useState } from "react";
|
|
import classes from "./classes.module.scss";
|
|
|
|
// Importer les composants de UI pour les barres de progression
|
|
import { LinearProgress, Box, Typography as MuiTypography } from "@mui/material";
|
|
|
|
import AuthModal from "src/sdk/AuthModal";
|
|
|
|
import MessageBus from "src/sdk/MessageBus";
|
|
import Iframe from "src/sdk/Iframe";
|
|
|
|
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
|
import { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData";
|
|
|
|
export default function LoginCallBack() {
|
|
const router = useRouter();
|
|
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
|
|
// États pour les barres de progression
|
|
const [showProgress, setShowProgress] = useState(false);
|
|
const [progressInfo, setProgressInfo] = useState<ProgressInfo>({
|
|
globalProgress: 0,
|
|
currentStep: '',
|
|
stepProgress: 0,
|
|
description: ''
|
|
});
|
|
|
|
const waitForUserInfo = (): Promise<any> => {
|
|
return new Promise((resolve, reject) => {
|
|
if (UserStore.instance.getUser()) {
|
|
resolve(UserStore.instance.getUser());
|
|
return;
|
|
}
|
|
|
|
// Poll for userInfo every 100ms
|
|
const checkInterval = setInterval(() => {
|
|
if (UserStore.instance.getUser()) {
|
|
clearInterval(checkInterval);
|
|
resolve(UserStore.instance.getUser());
|
|
}
|
|
}, 100);
|
|
|
|
// Timeout after 60 seconds
|
|
setTimeout(() => {
|
|
clearInterval(checkInterval);
|
|
reject(new Error('Timeout waiting for user info'));
|
|
}, 60000);
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
async function getUser() {
|
|
UserStore.instance.disconnect();
|
|
|
|
// TODO: review
|
|
// HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client
|
|
// Replace with http://localhost:3000/authorized-client
|
|
if (window.location.href.startsWith('http://local.lecoffreio.4nkweb')) {
|
|
window.location.href = window.location.href.replace('http://local.lecoffreio.4nkweb:3000/authorized-client', 'http://localhost:3000/authorized-client');
|
|
return;
|
|
}
|
|
|
|
const code = router.query["code"];
|
|
if (code) {
|
|
try {
|
|
// Nettoyer l'URL pour ne garder que la racine
|
|
const rootUrl = window.location.origin;
|
|
if (window.location.href !== rootUrl) {
|
|
window.history.replaceState({}, document.title, rootUrl);
|
|
}
|
|
|
|
const user: any = await Auth.getInstance().idNotAuth(code as string);
|
|
|
|
// Extract both user data and auth token from the response
|
|
const { idNotUser, authToken } = user;
|
|
|
|
if (!authToken) {
|
|
console.error('[LoginCallback] No authToken received from backend');
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
|
|
// Store the auth token for API authentication
|
|
// TODO The authToken is just a uuid for now, it's very broken
|
|
CookieService.getInstance().setCookie("leCoffreAccessToken", authToken);
|
|
|
|
// Test that we can get user info and the authToken works
|
|
// TODO test that what's returned is identical to what we got before
|
|
const userInfoResponse = await Auth.getInstance().getIdNotUser();
|
|
console.log('[LoginCallback] userInfoResponse:', userInfoResponse);
|
|
if (!userInfoResponse || !userInfoResponse.success || !userInfoResponse.data) {
|
|
console.error('[LoginCallback] No userInfo received from backend');
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
|
|
// Store user info as a cookie
|
|
CookieService.getInstance().setCookie("leCoffreUserInfo", JSON.stringify(userInfoResponse.data));
|
|
setIsAuthModalOpen(true);
|
|
console.log('[LoginCallback] authToken stored successfully');
|
|
|
|
return;
|
|
} catch (e: any) {
|
|
if (e.http_status === 401 && e.message === "Email not found") {
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=3");
|
|
}
|
|
if (e.http_status === 409) {
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=4");
|
|
}
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
}
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2");
|
|
}
|
|
getUser();
|
|
}, [router]);
|
|
|
|
return (
|
|
<DefaultDoubleSidePage title={"Login"} image={backgroundImage}>
|
|
<div className={classes["root"]} style={showProgress ? { width: 'calc(75vw - 70px)', maxWidth: '1130px', margin: '80px 0 80px 70px' } : undefined}>
|
|
<div className={classes["title-container"]}>
|
|
<Image alt="coffre" src={CoffreIcon} width={56} />
|
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}>
|
|
Connexion à votre espace professionnel
|
|
</Typography>
|
|
</div>
|
|
|
|
{!showProgress ? (
|
|
<Loader color={"var(--secondary-default-base, #FF4617)"} width={29} />
|
|
) : (
|
|
<Box sx={{ width: '100%', mb: 4, p: 4, px: 6, borderRadius: 2, boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}>
|
|
<MuiTypography variant="h6" align="center" sx={{ mb: 2, fontWeight: 600, color: 'var(--primary-default-base, #006BE0)' }}>
|
|
Importation des données
|
|
</MuiTypography>
|
|
{/* Progression globale */}
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
|
<MuiTypography variant="body2" sx={{ fontWeight: 500, color: 'text.primary' }}>
|
|
Progression globale: <span style={{ color: 'var(--secondary-default-base, #FF4617)', fontWeight: 600 }}>{Math.round(progressInfo.globalProgress)}%</span>
|
|
</MuiTypography>
|
|
<MuiTypography variant="body2" sx={{ fontWeight: 500, color: 'text.secondary' }}>
|
|
<span style={{ fontStyle: 'italic' }}>{progressInfo.currentStep}</span>
|
|
</MuiTypography>
|
|
</Box>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={progressInfo.globalProgress}
|
|
sx={{
|
|
height: 12,
|
|
borderRadius: 6,
|
|
backgroundColor: 'rgba(255, 70, 23, 0.15)',
|
|
'& .MuiLinearProgress-bar': {
|
|
backgroundColor: 'var(--secondary-default-base, #FF4617)',
|
|
transition: 'transform 0.5s ease'
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* Progression par étape */}
|
|
<Box sx={{ mt: 2, mb: 1 }}>
|
|
<MuiTypography variant="body2" sx={{ display: 'flex', justifyContent: 'space-between', fontWeight: 500, color: 'text.primary' }}>
|
|
<span>Progression de l'étape: <span style={{ color: 'var(--primary-default-base, #006BE0)', fontWeight: 600 }}>{Math.round(progressInfo.stepProgress)}%</span></span>
|
|
<span style={{ maxWidth: '60%', textAlign: 'right', color: 'text.secondary' }}>{progressInfo.description || ''}</span>
|
|
</MuiTypography>
|
|
</Box>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={progressInfo.stepProgress}
|
|
sx={{
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: 'rgba(0, 107, 224, 0.15)',
|
|
'& .MuiLinearProgress-bar': {
|
|
backgroundColor: 'var(--primary-default-base, #006BE0)',
|
|
transition: 'transform 0.3s ease'
|
|
}
|
|
}}
|
|
/>
|
|
</Box>
|
|
)}
|
|
<div />
|
|
<HelpBox
|
|
title="Vous n'arrivez pas à vous connecter ?"
|
|
description="Notre équipe de support est là pour vous aider."
|
|
button={{ text: "Contacter l'administrateur", link: "mailto:support@lecoffre.io" }}
|
|
/>
|
|
{isAuthModalOpen && <AuthModal
|
|
isOpen={isAuthModalOpen}
|
|
onClose={() => {
|
|
setIsAuthModalOpen(false);
|
|
setIsConnected(true);
|
|
setTimeout(() => {
|
|
LoaderService.getInstance().show();
|
|
MessageBus.getInstance().initMessageListener();
|
|
MessageBus.getInstance().isReady().then(async () => {
|
|
try {
|
|
const userInfo = await waitForUserInfo();
|
|
if (!userInfo) {
|
|
console.error('[LoginCallback] No userInfo received from backend');
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
// console.log('userInfo : ', userInfo);
|
|
|
|
// Here we are now authenticated with idNot, we have our idnot user info
|
|
// We also have a device and it should be paired
|
|
// What we may not have yet is a collaborator process
|
|
// Office may not have a process too
|
|
let collaboratorProcess: { processId: string, processData: { [key: string]: any } } | null = null;
|
|
let officeProcess: { processId: string, processData: { [key: string]: any } } | null = null;
|
|
|
|
// Initialize collaborator process
|
|
try {
|
|
// Wait for pairing ID to be available before proceeding
|
|
const pairingId = await MessageBus.getInstance().getPairingId();
|
|
console.log('[LoginCallback] Pairing ID obtained:', pairingId);
|
|
// Check if we are part of the right collaborator process
|
|
const myCollaboratorProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => {
|
|
return values['utype'] === 'collaborator'
|
|
&& values['idNot'] === userInfo.idNot
|
|
&& values['isDeleted'] === 'false';
|
|
});
|
|
if (myCollaboratorProcessesData && Object.keys(myCollaboratorProcessesData).length !== 0) {
|
|
collaboratorProcess = { processId: Object.keys(myCollaboratorProcessesData)[0]!, processData: Object.values(myCollaboratorProcessesData)[0]! };
|
|
} else {
|
|
const res = await Auth.getInstance().getUserProcessByIdNot(pairingId);
|
|
if (res.success) {
|
|
collaboratorProcess = res.data;
|
|
} else {
|
|
console.error('[LoginCallback] Error getting collaborator process');
|
|
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
return;
|
|
}
|
|
// await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
// If we're on a new device, signer should notice and add us to the process
|
|
// TODO check that we're part of the collaborator process
|
|
} catch (error: any) {
|
|
console.error('[LoginCallback] Error getting collaborator process:', error);
|
|
if (error.message === 'Timeout waiting for pairing ID') {
|
|
console.error('[LoginCallback] Pairing ID not available after timeout');
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
// If we can't get collaborator process, we can't proceed
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
|
|
// Initialize office process
|
|
try {
|
|
// Wait for pairing ID to be available before proceeding
|
|
const pairingId = await MessageBus.getInstance().getPairingId();
|
|
console.log('[LoginCallback] Pairing ID obtained:', pairingId);
|
|
// Now we need to check for office process
|
|
const myOfficeProcessesData = await MessageBus.getInstance().getProcessesDecoded((processId: string, values: { [key: string]: any }) => {
|
|
return values['utype'] === 'office'
|
|
&& values['idNot'] === userInfo.office.idNot
|
|
&& values['isDeleted'] === 'false';
|
|
});
|
|
if (myOfficeProcessesData && Object.keys(myOfficeProcessesData).length !== 0) {
|
|
officeProcess = { processId: Object.keys(myOfficeProcessesData)[0]!, processData: Object.values(myOfficeProcessesData)[0]! };
|
|
} else {
|
|
const res = await Auth.getInstance().getUserProcessByIdNot(pairingId);
|
|
if (res.success) {
|
|
officeProcess = res.data;
|
|
} else {
|
|
console.error('[LoginCallback] Error getting collaborator process');
|
|
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO Check that we're part of the office process
|
|
// For now we rely on the signer to get office data, for the sake of simplicity
|
|
} catch (error: any) {
|
|
console.error('[LoginCallback] Error getting office process:', error);
|
|
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
}
|
|
|
|
// Verify both processes are initialized before proceeding
|
|
if (!collaboratorProcess || !officeProcess) {
|
|
console.error('[LoginCallback] Failed to initialize required processes');
|
|
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
return;
|
|
}
|
|
|
|
if (!UserStore.instance.connect(collaboratorProcess.processData, officeProcess.processData)) {
|
|
console.error('[LoginCallback] collaborator not connected');
|
|
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
return;
|
|
}
|
|
|
|
window.location.href = Module.getInstance().get().modules.pages.Folder.props.path;
|
|
} catch (error) {
|
|
console.error('[LoginCallback] Error waiting for user info or processing collaborator:', error);
|
|
router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
|
|
} finally {
|
|
MessageBus.getInstance().destroyMessageListener();
|
|
LoaderService.getInstance().hide();
|
|
}
|
|
return;
|
|
});
|
|
}, 100);
|
|
}}
|
|
/>}
|
|
{isConnected && <Iframe />}
|
|
</div>
|
|
</DefaultDoubleSidePage>
|
|
);
|
|
}
|