Init migration

This commit is contained in:
Anthony Janin 2025-06-24 21:50:31 +02:00
parent b6da6db0fe
commit 65f67993ba
57 changed files with 8450 additions and 5797 deletions

View File

@ -16,6 +16,7 @@ const nextConfig = {
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL, NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID, NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION, NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
}, },
serverRuntimeConfig: { serverRuntimeConfig: {
@ -31,6 +32,7 @@ const nextConfig = {
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL, NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID, NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION, NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
}, },
env: { env: {
@ -46,6 +48,7 @@ const nextConfig = {
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL, NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID, NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION, NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
}, },
// webpack: config => { // webpack: config => {

4535
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
"heroicons": "^2.1.5", "heroicons": "^2.1.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.167", "le-coffre-resources": "file:../lecoffre-ressources",
"next": "^14.2.3", "next": "^14.2.3",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"react": "18.2.0", "react": "18.2.0",

View File

@ -0,0 +1,129 @@
import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User';
import ProfileService from './ProfileService';
export default class FolderService {
private static readonly messageBus: MessageBus = MessageBus.getInstance();
private constructor() { }
public static createFolder(folderData: any, stakeholdersId: string[], customersId: string[]): Promise<any> {
const ownerId = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'folder',
isArchived: 'false',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...folderData,
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
const roles: any = {
demiurge: {
members: [ownerId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles'],
min_sig_member: 1,
},
],
storages: []
},
stakeholders: {
members: stakeholdersId,
validation_rules: [
{
quorum: 0.5,
fields: ['documents', 'motes'],
min_sig_member: 1,
},
],
storages: []
},
customers: {
members: customersId,
validation_rules: [
{
quorum: 0.0,
fields: privateFields,
min_sig_member: 0.0,
},
],
storages: []
},
apophis: {
members: [ownerId],
validation_rules: [],
storages: []
}
};
return new Promise<any>((resolve: (folderCreated: any) => void, reject: (error: string) => void) => {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getFolders(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'folder');
}
public static getFolderByUid(uid: string): Promise<any> {
return new Promise<any>((resolve: (folder: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'folder').then(async (folders: any[]) => {
if (folders.length === 0) {
resolve(null);
} else {
const folder: any = folders[0];
if (folder.processData.customers && folder.processData.customers.length > 0) {
folder.processData.customers = await new Promise<any[]>(async (resolve: (profiles: any[]) => void, reject: (error: string) => void) => {
const customers: any[] = [];
for (const uid of folder.processData.customers) {
customers.push(await ProfileService.getProfileByUid(uid));
}
resolve(customers);
});
}
resolve(folder);
}
}).catch(reject);
});
}
public static updateFolder(folder: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(folder.processId, folder.lastStateId, newData, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(folder.processId, newStateId).then(() => {
this.messageBus.validateState(folder.processId, newStateId).then((_stateValidated) => {
resolve();
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
}

View File

@ -0,0 +1,95 @@
import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User';
export default class ProfileService {
private static readonly messageBus: MessageBus = MessageBus.getInstance();
private constructor() { }
public static createProfile(profileData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'profile',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...profileData,
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles'],
min_sig_member: 1,
},
],
storages: []
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
{
quorum: 0.0,
fields: [...privateFields],
min_sig_member: 0,
},
],
storages: []
},
apophis: {
members: [ownerId],
validation_rules: [],
storages: []
}
};
return new Promise<any>((resolve: (profileCreated: any) => void, reject: (error: string) => void) => {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getProfiles(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'profile');
}
public static getProfileByUid(uid: string): Promise<any> {
return new Promise<any>((resolve: (profile: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'profile').then((profiles: any[]) => {
if (profiles.length === 0) {
resolve(null);
} else {
resolve(profiles[0]);
}
}).catch(reject);
});
}
}

View File

@ -42,7 +42,10 @@ export default class Auth extends BaseApiService {
public async getIdnotJwt(autorizationCode: string | string[]): Promise<{ accessToken: string; refreshToken: string }> { public async getIdnotJwt(autorizationCode: string | string[]): Promise<{ accessToken: string; refreshToken: string }> {
const variables = FrontendVariables.getInstance(); const variables = FrontendVariables.getInstance();
const baseBackUrl = variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
// TODO: review
const baseBackUrl = 'http://localhost:8080'//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
const url = new URL(`${baseBackUrl}/api/v1/idnot/user/${autorizationCode}`); const url = new URL(`${baseBackUrl}/api/v1/idnot/user/${autorizationCode}`);
try { try {
return await this.postRequest<{ accessToken: string; refreshToken: string }>(url); return await this.postRequest<{ accessToken: string; refreshToken: string }>(url);

View File

@ -17,6 +17,7 @@ export default function Navigation() {
const pathname = usePathname(); const pathname = usePathname();
const getAnchoringStatus = useCallback(async () => { const getAnchoringStatus = useCallback(async () => {
/* TODO: review
const anchors = await OfficeFolderAnchors.getInstance().get({ const anchors = await OfficeFolderAnchors.getInstance().get({
where: { where: {
status: { status: {
@ -27,6 +28,8 @@ export default function Navigation() {
folder: true, folder: true,
}, },
}); });
*/
const anchors = [] as any[];
try { try {
for (const anchor of anchors) { for (const anchor of anchors) {
@ -39,6 +42,8 @@ export default function Navigation() {
const getNotifications = useCallback(async () => { const getNotifications = useCallback(async () => {
//await getAnchoringStatus(); //await getAnchoringStatus();
/* TODO: review
const notifications = await Notifications.getInstance().get({ const notifications = await Notifications.getInstance().get({
where: { where: {
read: false, read: false,
@ -50,6 +55,9 @@ export default function Navigation() {
notification: { created_at: "desc" }, notification: { created_at: "desc" },
}, },
}); });
*/
const notifications = [] as any[];
notifications.forEach((notification) => { notifications.forEach((notification) => {
Toasts.getInstance().open({ Toasts.getInstance().open({
title: notification.notification.message, title: notification.notification.message,

View File

@ -35,6 +35,7 @@ export default function Header(props: IProps) {
const [cancelAt, setCancelAt] = useState<Date | null>(null); const [cancelAt, setCancelAt] = useState<Date | null>(null);
const loadSubscription = useCallback(async () => { const loadSubscription = useCallback(async () => {
/* TODO: review
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } }); const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
if (subscription[0]) { if (subscription[0]) {
@ -43,6 +44,7 @@ export default function Header(props: IProps) {
setCancelAt(new Date(stripeSubscription.cancel_at! * 1000)); setCancelAt(new Date(stripeSubscription.cancel_at! * 1000));
} }
} }
*/
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -30,7 +30,8 @@ export default function Rules(props: IProps) {
}, [props.mode, props.rules]); }, [props.mode, props.rules]);
useEffect(() => { useEffect(() => {
if (!JwtService.getInstance().decodeJwt()) return; // TODO: review
//if (!JwtService.getInstance().decodeJwt()) return;
setHasJwt(true); setHasJwt(true);
setIsShowing(getShowValue()); setIsShowing(getShowValue());
}, [getShowValue, isShowing]); }, [getShowValue, isShowing]);
@ -40,7 +41,8 @@ export default function Rules(props: IProps) {
return null; return null;
} }
if (!hasJwt || !isShowing) return null; // TODO: review
//if (!hasJwt || !isShowing) return null;
return props.children; return props.children;
} }

View File

@ -57,6 +57,14 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
useEffect(() => { useEffect(() => {
tabs.current = propsTabs; tabs.current = propsTabs;
// TODO: review
setTimeout(() => {
calculateVisibleElements();
if (tabs.current && tabs.current.length > 0 && tabs.current[0]) {
setSelectedTab(tabs.current[0].value);
}
}, 150);
}, [propsTabs]); }, [propsTabs]);
useEffect(() => { useEffect(() => {

View File

@ -15,6 +15,7 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { collaboratorUid } = router.query; const { collaboratorUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const query: IGetUsersparams = { const query: IGetUsersparams = {
@ -28,10 +29,11 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
}, },
}, },
}; };
Users.getInstance() Users.getInstance()
.get(query) .get(query)
.then((users) => setCollaborators(users)); .then((users) => setCollaborators(users));
*/
setCollaborators([]);
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -7,6 +7,8 @@ import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDas
import { DeedType } from "le-coffre-resources/dist/Notary"; import { DeedType } from "le-coffre-resources/dist/Notary";
import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes"; import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
import MessageBus from "src/sdk/MessageBus";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultDeedTypeDashboard(props: IProps) { export default function DefaultDeedTypeDashboard(props: IProps) {
@ -14,6 +16,16 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { deedTypeUid } = router.query; const { deedTypeUid } = router.query;
useEffect(() => { useEffect(() => {
// TODO: review
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
MessageBus.getInstance().getDeepTypes().then((deedTypes: any) => {
setDeedTypes(deedTypes.map((deedType: any) => deedType.processData));
});
}, 1000);
});
/*
const query: IGetDeedTypesParams = { const query: IGetDeedTypesParams = {
where: { where: {
archived_at: null, archived_at: null,
@ -26,6 +38,7 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
DeedTypes.getInstance() DeedTypes.getInstance()
.get(query) .get(query)
.then((deedTypes) => setDeedTypes(deedTypes)); .then((deedTypes) => setDeedTypes(deedTypes));
*/
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -8,6 +8,8 @@ import JwtService from "@Front/Services/JwtService/JwtService";
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import { DocumentType } from "le-coffre-resources/dist/Notary"; import { DocumentType } from "le-coffre-resources/dist/Notary";
import MessageBus from "src/sdk/MessageBus";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultDocumentTypeDashboard(props: IProps) { export default function DefaultDocumentTypeDashboard(props: IProps) {
@ -15,6 +17,18 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { documentTypeUid } = router.query; const { documentTypeUid } = router.query;
useEffect(() => { useEffect(() => {
// TODO: review
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id;
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
MessageBus.getInstance().getDocuments().then((documents: any) => {
setDocumentTypes(documents.map((document: any) => document.processData));
});
}, 1000);
});
/*
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
DocumentTypes.getInstance() DocumentTypes.getInstance()
@ -27,6 +41,7 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
}, },
}) })
.then((documentTypes) => setDocumentTypes(documentTypes)); .then((documentTypes) => setDocumentTypes(documentTypes));
*/
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -1,7 +1,7 @@
import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus"; import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
@ -9,6 +9,8 @@ import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = IPropsDashboardWithList & { type IProps = IPropsDashboardWithList & {
isArchived?: boolean; isArchived?: boolean;
}; };
@ -74,6 +76,7 @@ export default function DefaultNotaryDashboard(props: IProps) {
}, [folders, getBlocks]); }, [folders, getBlocks]);
useEffect(() => { useEffect(() => {
/* TODO: review
let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus]; let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus];
if (isArchived) targetedStatus = EFolderStatus.ARCHIVED; if (isArchived) targetedStatus = EFolderStatus.ARCHIVED;
const query: IGetFoldersParams = { const query: IGetFoldersParams = {
@ -110,6 +113,13 @@ export default function DefaultNotaryDashboard(props: IProps) {
Folders.getInstance() Folders.getInstance()
.get(query) .get(query)
.then((folders) => setFolders(folders)); .then((folders) => setFolders(folders));
*/
FolderService.getFolders().then((folders) => {
if (folders.length > 0) {
setFolders(folders.map((folder: any) => folder.processData).filter((folder: any) => folder.isArchived && folder.isArchived === (isArchived ? 'true' : 'false')));
}
});
}, [isArchived]); }, [isArchived]);
return ( return (

View File

@ -14,9 +14,12 @@ export default function DefaultOfficeDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { officeUid } = router.query; const { officeUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review
Offices.getInstance() Offices.getInstance()
.get() .get()
.then((offices) => setOffices(offices)); .then((offices) => setOffices(offices));
*/
setOffices([]);
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -14,6 +14,7 @@ export default function DefaultRoleDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { roleUid } = router.query; const { roleUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review
const query: IGetRolesParams = { const query: IGetRolesParams = {
include: { rules: true }, include: { rules: true },
}; };
@ -21,6 +22,8 @@ export default function DefaultRoleDashboard(props: IProps) {
OfficeRoles.getInstance() OfficeRoles.getInstance()
.get(query) .get(query)
.then((roles) => setRoles(roles)); .then((roles) => setRoles(roles));
*/
setRoles([]);
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -14,13 +14,15 @@ export default function DefaultUserDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { userUid } = router.query; const { userUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review
const query: IGetUsersparams = { const query: IGetUsersparams = {
include: { contact: true, office_membership: true }, include: { contact: true, office_membership: true },
}; };
Users.getInstance() Users.getInstance()
.get(query) .get(query)
.then((users) => setUsers(users)); .then((users) => setUsers(users));
*/
setUsers([]);
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {

View File

@ -18,10 +18,20 @@ export default function ContactBox(props: IProps) {
const [ribUrl, setRibUrl] = useState<string | null>(null); const [ribUrl, setRibUrl] = useState<string | null>(null);
// TODO: review
const stakeholder = {
cell_phone_number: "0606060606",
phone_number: "0606060606",
email: "test@lecoffre.fr",
};
const notaryContact = useMemo( const notaryContact = useMemo(
() => () =>
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ?? /*folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ??
folder?.stakeholders![0]!.contact, folder?.stakeholders![0]!.contact*/
// TODO: review
stakeholder,
[folder], [folder],
); );

View File

@ -0,0 +1,94 @@
import React, { useEffect, useState, useRef } from 'react';
import Modal from '@Front/Components/DesignSystem/Modal';
import Typography, { ETypo } from '@Front/Components/DesignSystem/Typography';
import Button, { EButtonstyletype, EButtonVariant } from '@Front/Components/DesignSystem/Button';
interface FormModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (file: File) => void;
}
export default function FormModal({ isOpen, onClose, onSubmit }: FormModalProps) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (!isOpen) {
setSelectedFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}
}, [isOpen]);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (files && files.length > 0 && files[0] instanceof File) {
setSelectedFile(files[0]);
}
};
const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title='Ajouter un document'
>
<div style={{ padding: '20px 0' }}>
<div style={{ marginBottom: '20px' }}>
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} className="mb-2">
Document
</Typography>
<div style={{ display: 'flex', alignItems: 'center' }}>
<input
ref={fileInputRef}
type="file"
accept="application/pdf"
onChange={handleFileChange}
style={{ display: 'none' }}
/>
<Button
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
onClick={handleButtonClick}
>
Sélectionner un fichier PDF
</Button>
{selectedFile && (
<div className="ml-3">
<Typography typo={ETypo.TEXT_MD_REGULAR}>
{selectedFile.name}
</Typography>
</div>
)}
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '20px' }}>
<Button
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
onClick={onClose}
>
Annuler
</Button>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.CONTAINED}
onClick={() => selectedFile && onSubmit(selectedFile)}
disabled={!selectedFile}
>
Enregistrer
</Button>
</div>
</div>
</Modal>
);
}

View File

@ -5,7 +5,7 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer"; import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { DocumentNotary, type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary"; import { DocumentNotary, OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -25,19 +25,76 @@ import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/Doc
import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary"; import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument"; import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
import { v4 as uuidv4 } from "uuid";
import MessageBus from "src/sdk/MessageBus";
import MapUtils from "src/sdk/MapUtils";
import AuthModal from "src/sdk/AuthModal";
import FormModal from "./FormModal";
type IProps = {}; type IProps = {};
export default function ClientDashboard(props: IProps) { export default function ClientDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
let { folderUid } = router.query; let { folderUid, profileUid } = router.query;
const [documents, setDocuments] = useState<Document[] | null>(null); const [documents, setDocuments] = useState<Document[] | null>(null);
const [customer, setCustomer] = useState<Customer | null>(null); const [customer, setCustomer] = useState<Customer | null>(null);
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null); const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
const [file, setFile] = useState<any | null>(null);
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]); const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false); const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
const fetchFolderAndCustomer = useCallback(async () => { const fetchFolderAndCustomer = useCallback(async () => {
// TODO: review
const { folder, customer, file } = await new Promise<any>((resolve) => {
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
MessageBus.getInstance().getFolders().then(async (folders) => {
const folder: any = folders.find((folder: any) => folder.processData.uid === folderUid as string);
if (folder) {
const customer = folder.processData.customers
.map((customer: any) => MapUtils.toJson(customer))
.find((customer: any) => customer.uid === profileUid as string);
const file = await new Promise<any>((resolve: (file: any) => void) => {
MessageBus.getInstance().getFiles().then((files: any) => {
if (!files || files.length === 0) {
resolve(null);
} else {
const file: any = files.find((file: any) => file.processData.folderUid === folderUid as string && file.processData.profileUid === profileUid as string);
if (file) {
resolve(file);
} else {
resolve(null);
}
}
}).catch(() => resolve(null));
});
resolve({ folder: folder.processData, customer, file: file ? file.processData : null });
}
});
}, 2500);
});
});
setCustomer(customer);
setFolder(folder);
setFile(file);
if (!file) {
setIsAuthModalOpen(true);
}
return { folder, customer };
/*
let jwt: ICustomerJwtPayload | undefined; let jwt: ICustomerJwtPayload | undefined;
if (typeof document !== "undefined") { if (typeof document !== "undefined") {
jwt = JwtService.getInstance().decodeCustomerJwt(); jwt = JwtService.getInstance().decodeCustomerJwt();
@ -73,6 +130,7 @@ export default function ClientDashboard(props: IProps) {
setCustomer(customer); setCustomer(customer);
return { folder, customer }; return { folder, customer };
*/
}, [folderUid]); }, [folderUid]);
const fetchDocuments = useCallback( const fetchDocuments = useCallback(
@ -110,9 +168,13 @@ export default function ClientDashboard(props: IProps) {
useEffect(() => { useEffect(() => {
const customerUid = customer?.uid; const customerUid = customer?.uid;
if (!folderUid || !customerUid) return; if (!folderUid || !customerUid) return;
/* TODO: review
DocumentsNotary.getInstance() DocumentsNotary.getInstance()
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } }) .get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
.then((documentsNotary) => setDocumentsNotary(documentsNotary)); .then((documentsNotary) => setDocumentsNotary(documentsNotary));
*/
}, [folderUid, customer?.uid]); }, [folderUid, customer?.uid]);
const documentsNotaryNotRead = useMemo( const documentsNotaryNotRead = useMemo(
@ -129,6 +191,21 @@ export default function ClientDashboard(props: IProps) {
setIsAddDocumentModalVisible(true); setIsAddDocumentModalVisible(true);
}, []); }, []);
const onDownloadFile = useCallback(() => {
if (!file) {
return;
}
const blob = new Blob([file.file.data], { type: file.file.type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, [file]);
const renderBox = useCallback(() => { const renderBox = useCallback(() => {
return ( return (
<DepositOtherDocument <DepositOtherDocument
@ -157,6 +234,16 @@ export default function ClientDashboard(props: IProps) {
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}> <Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)} Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
</Typography> </Typography>
{file && (
<Button
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.OUTLINED}
onClick={() => onDownloadFile()}
>
Télécharger le document
</Button>
)}
<Tag color={ETagColor.INFO} label={folder?.deed?.deed_type?.name ?? ""} /> <Tag color={ETagColor.INFO} label={folder?.deed?.deed_type?.name ?? ""} />
<div className={classes["office-container"]}> <div className={classes["office-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}> <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
@ -244,6 +331,56 @@ export default function ClientDashboard(props: IProps) {
Ajouter d'autres documents Ajouter d'autres documents
</Button> </Button>
{isAddDocumentModalVisible && renderBox()} {isAddDocumentModalVisible && renderBox()}
{isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen}
onClose={() => {
setIsAuthModalOpen(false);
setTimeout(() => {
setIsFormModalOpen(true);
}, 500);
}}
/>}
<FormModal
isOpen={isFormModalOpen}
onClose={() => setIsFormModalOpen(false)}
onSubmit={(file: any) => {
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
const arrayBuffer = event.target.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
const fileBlob = {
type: file.type,
data: uint8Array
};
const fileData = {
uid: uuidv4(),
utype_ff: 'file',
folderUid: folderUid as string,
profileUid: profileUid as string,
file: fileBlob
}
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().createFile(fileData, [], []).then((processCreated: any) => {
MessageBus.getInstance().notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
MessageBus.getInstance().validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
setIsFormModalOpen(false);
});
});
});
});
}
};
reader.readAsArrayBuffer(file);
}
}}
/>
</div> </div>
</DefaultCustomerDashboard> </DefaultCustomerDashboard>
); );

View File

@ -15,6 +15,10 @@ import { useCallback, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { validateOrReject, ValidationError } from "class-validator"; import { validateOrReject, ValidationError } from "class-validator";
import { v4 as uuidv4 } from "uuid";
import MessageBus from "src/sdk/MessageBus";
type IProps = {}; type IProps = {};
export default function DeedTypesCreate(props: IProps) { export default function DeedTypesCreate(props: IProps) {
const [hasChanged, setHasChanged] = useState<boolean>(false); const [hasChanged, setHasChanged] = useState<boolean>(false);
@ -25,12 +29,14 @@ export default function DeedTypesCreate(props: IProps) {
const onSubmitHandler = useCallback( const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try { try {
const jwt = JwtService.getInstance().decodeJwt(); // TODO: review
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id;
const deedType = DeedType.hydrate<DeedType>({ const deedType = DeedType.hydrate<DeedType>({
name: values["name"], name: values["name"],
description: values["description"], description: values["description"],
office: Office.hydrate<Office>({ office: Office.hydrate<Office>({
uid: jwt?.office_Id, uid: officeId,
}), }),
}); });
try { try {
@ -40,12 +46,27 @@ export default function DeedTypesCreate(props: IProps) {
return; return;
} }
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().createDeedType({ ...deedType, uid: uuidv4(), utype_dt: 'deedType', isDeleted: 'false' }, [], []).then((processCreated: any) => {
MessageBus.getInstance().notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
MessageBus.getInstance().validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
router.push(
Module.getInstance()
.get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", processCreated.processData.uid),
);
});
});
});
});
/*
const deedTypeCreated = await DeedTypes.getInstance().post( const deedTypeCreated = await DeedTypes.getInstance().post(
DeedType.hydrate<DeedType>({ DeedType.hydrate<DeedType>({
name: values["name"], name: values["name"],
description: values["description"], description: values["description"],
office: Office.hydrate<Office>({ office: Office.hydrate<Office>({
uid: jwt?.office_Id, uid: officeId,
}), }),
}), }),
); );
@ -55,6 +76,7 @@ export default function DeedTypesCreate(props: IProps) {
.get() .get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeCreated.uid!), .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeCreated.uid!),
); );
*/
} catch (validationErrors: Array<ValidationError> | any) { } catch (validationErrors: Array<ValidationError> | any) {
setValidationError(validationErrors as ValidationError[]); setValidationError(validationErrors as ValidationError[]);
return; return;

View File

@ -20,6 +20,9 @@ import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
import MapUtils from "src/sdk/MapUtils";
type IProps = {}; type IProps = {};
export default function DeedTypesInformations(props: IProps) { export default function DeedTypesInformations(props: IProps) {
const router = useRouter(); const router = useRouter();
@ -62,6 +65,30 @@ export default function DeedTypesInformations(props: IProps) {
useEffect(() => { useEffect(() => {
async function getDeedType() { async function getDeedType() {
if (!deedTypeUid) return; if (!deedTypeUid) return;
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getDeepTypes().then((deedTypes: any) => {
const deedType: any = deedTypes.find((deedType: any) => deedType.processData.uid === deedTypeUid);
if (deedType) {
setDeedTypeSelected(deedType.processData);
const documentsOptions: any[] = deedType.processData.document_types
?.map((documentType: any) => MapUtils.toJson(documentType))
.map((documentType: any) => {
return {
label: documentType[0].name,
id: documentType[0].uid ?? "",
};
})
.sort((a: any, b: any) => a.label.localeCompare(b.label));
setSelectedDocuments(documentsOptions);
}
});
});
/*
const deedType = await DeedTypes.getInstance().getByUid(deedTypeUid as string, { const deedType = await DeedTypes.getInstance().getByUid(deedTypeUid as string, {
q: { q: {
document_types: true, document_types: true,
@ -79,11 +106,23 @@ export default function DeedTypesInformations(props: IProps) {
}) })
.sort((a, b) => a.label.localeCompare(b.label)); .sort((a, b) => a.label.localeCompare(b.label));
setSelectedDocuments(documentsOptions); setSelectedDocuments(documentsOptions);
*/
} }
async function getDocuments() { async function getDocuments() {
// TODO: review
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
MessageBus.getInstance().getDocuments().then((documents: any) => {
setAvailableDocuments(documents.map((document: any) => document.processData));
});
}, 1000);
});
/*
const documents = await DocumentTypes.getInstance().get({}); const documents = await DocumentTypes.getInstance().get({});
setAvailableDocuments(documents); setAvailableDocuments(documents);
*/
} }
getDocuments(); getDocuments();
@ -98,11 +137,40 @@ export default function DeedTypesInformations(props: IProps) {
); );
const saveDocumentTypes = useCallback(async () => { const saveDocumentTypes = useCallback(async () => {
/*
await DeedTypes.getInstance().put(deedTypeUid as string, { await DeedTypes.getInstance().put(deedTypeUid as string, {
uid: deedTypeUid as string, uid: deedTypeUid as string,
document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })), document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })),
}); });
*/
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getDeepTypes().then((deedTypes: any) => {
const deedType: any = deedTypes.find((deedType: any) => deedType.processData.uid === deedTypeUid);
if (deedType) {
let document_types: any[] = deedType.processData.document_types;
if (!document_types) {
document_types = [];
}
document_types.push(selectedDocuments.map((document) => {
return {
uid: document.id,
name: document.label,
}
}));
MessageBus.getInstance().updateProcess(deedType.processId, deedType.lastStateId, { document_types: document_types }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
MessageBus.getInstance().notifyUpdate(deedType.processId, newStateId).then(() => {
MessageBus.getInstance().validateState(deedType.processId, newStateId).then((_stateValidated) => {
closeSaveModal(); closeSaveModal();
});
});
});
}
});
});
}, [closeSaveModal, deedTypeUid, selectedDocuments]); }, [closeSaveModal, deedTypeUid, selectedDocuments]);
const onDocumentChangeHandler = useCallback((options: IOption[] | null) => { const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {

View File

@ -14,6 +14,10 @@ import { useCallback, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { v4 as uuidv4 } from "uuid";
import MessageBus from "src/sdk/MessageBus";
type IProps = {}; type IProps = {};
export default function DocumentTypesCreate(props: IProps) { export default function DocumentTypesCreate(props: IProps) {
const [validationError, setValidationError] = useState<ValidationError[]>([]); const [validationError, setValidationError] = useState<ValidationError[]>([]);
@ -22,6 +26,38 @@ export default function DocumentTypesCreate(props: IProps) {
const onSubmitHandler = useCallback( const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try { try {
// TODO: review
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id;
const documentToCreate = DocumentType.hydrate<DocumentType>({
...values,
office: Office.hydrate<Office>({
uid: officeId,
})
});
await validateOrReject(documentToCreate, { groups: ["createDocumentType"] });
MessageBus.getInstance().isReady().then(() => {
const documentData: any = {
...values,
uid: uuidv4(),
utype_d: 'document',
isDeleted: 'false'
};
MessageBus.getInstance().createDocument(documentData, [], []).then((processCreated: any) => {
MessageBus.getInstance().notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
MessageBus.getInstance().validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
router.push(
Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", processCreated.processData.uid),
);
});
});
});
});
/*
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const office = Office.hydrate<Office>({ const office = Office.hydrate<Office>({
@ -39,6 +75,7 @@ export default function DocumentTypesCreate(props: IProps) {
.get() .get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeCreated.uid!), .modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeCreated.uid!),
); );
*/
} catch (e) { } catch (e) {
if (e instanceof Array) { if (e instanceof Array) {
setValidationError(e); setValidationError(e);

View File

@ -13,6 +13,8 @@ import { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import MessageBus from "src/sdk/MessageBus";
export default function DocumentTypesInformations() { export default function DocumentTypesInformations() {
const router = useRouter(); const router = useRouter();
let { documentTypeUid } = router.query; let { documentTypeUid } = router.query;
@ -22,11 +24,24 @@ export default function DocumentTypesInformations() {
useEffect(() => { useEffect(() => {
async function getDocument() { async function getDocument() {
if (!documentTypeUid) return; if (!documentTypeUid) return;
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getDocuments().then((documents: any) => {
const document: any = documents.find((document: any) => document.processData.uid === documentTypeUid as string);
if (document) {
setDocumentSelected(document.processData);
}
});
});
/*
const document = await DocumentTypes.getInstance().getByUid(documentTypeUid as string, { const document = await DocumentTypes.getInstance().getByUid(documentTypeUid as string, {
_count: true, _count: true,
}); });
if (!document) return; if (!document) return;
setDocumentSelected(document); setDocumentSelected(document);
*/
} }
getDocument(); getDocument();

View File

@ -19,6 +19,9 @@ import classes from "./classes.module.scss";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import ProfileService from "src/common/Api/LeCoffreApi/sdk/ProfileService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
enum ESelectedOption { enum ESelectedOption {
EXISTING_CUSTOMER = "existing_customer", EXISTING_CUSTOMER = "existing_customer",
NEW_CUSTOMER = "new_customer", NEW_CUSTOMER = "new_customer",
@ -71,11 +74,33 @@ export default function AddClientToFolder(props: IProps) {
} }
try { try {
// TODO: review
const profile: any = {
contact: values
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
ProfileService.createProfile(profile, validatorId).then((profileCreated: any) => {
FolderService.getFolderByUid(folderUid as string).then((folder: any) => {
if (folder) {
const customers: any[] = folder.processData.customers;
customers.push(profileCreated.processData.uid);
FolderService.updateFolder(folder, { customers }).then(() => {
router.push(`/folders/${folderUid}`);
});
}
});
});
/*
const customer: Customer = await Customers.getInstance().post({ const customer: Customer = await Customers.getInstance().post({
contact: values, contact: values,
}); });
if (!customer.uid) return; if (!customer.uid) return;
customersToLink?.push({ uid: customer.uid } as Partial<Customer>); customersToLink?.push({ uid: customer.uid } as Partial<Customer>);
*/
} catch (backError) { } catch (backError) {
if (!Array.isArray(backError)) return; if (!Array.isArray(backError)) return;
setValidationError(backError as ValidationError[]); setValidationError(backError as ValidationError[]);
@ -83,6 +108,7 @@ export default function AddClientToFolder(props: IProps) {
} }
} }
/*
if (customersToLink) { if (customersToLink) {
const body = OfficeFolder.hydrate<OfficeFolder>({ const body = OfficeFolder.hydrate<OfficeFolder>({
customers: customersToLink.map((customer) => { customers: customersToLink.map((customer) => {
@ -92,6 +118,7 @@ export default function AddClientToFolder(props: IProps) {
await Folders.getInstance().put(folderUid as string, body); await Folders.getInstance().put(folderUid as string, body);
router.push(`/folders/${folderUid}`); router.push(`/folders/${folderUid}`);
} }
*/
}, },
[existingCustomers, folderUid, router, selectedCustomers, selectedOption], [existingCustomers, folderUid, router, selectedCustomers, selectedOption],
); );
@ -126,6 +153,7 @@ export default function AddClientToFolder(props: IProps) {
); );
const loadCustomers = useCallback(async () => { const loadCustomers = useCallback(async () => {
/* TODO: review
const query = {}; const query = {};
const availableCustomers = await Customers.getInstance().get(query); const availableCustomers = await Customers.getInstance().get(query);
let preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string); let preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string);
@ -143,8 +171,9 @@ export default function AddClientToFolder(props: IProps) {
setAvailableCustomers(availableCustomers); setAvailableCustomers(availableCustomers);
setExistingCustomers(existingCustomers); setExistingCustomers(existingCustomers);
*/
setIsLoaded(true); setIsLoaded(true);
setSelectedOption(selectedOption); //setSelectedOption(selectedOption);
}, [folderUid, getFolderPreSelectedCustomers]); }, [folderUid, getFolderPreSelectedCustomers]);
const getSelectedOptions = useCallback((): IOption[] => { const getSelectedOptions = useCallback((): IOption[] => {

View File

@ -55,7 +55,10 @@ export default function AskDocuments() {
}, },
) => { ) => {
try { try {
// TODO: review
const documentAsked: [] = values["document_types"] as []; const documentAsked: [] = values["document_types"] as [];
/*
for (let i = 0; i < documentAsked.length; i++) { for (let i = 0; i < documentAsked.length; i++) {
await Documents.getInstance().post({ await Documents.getInstance().post({
folder: { folder: {
@ -75,6 +78,7 @@ export default function AskDocuments() {
.get() .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string), .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
); );
*/
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@ -20,9 +20,11 @@ import User from "le-coffre-resources/dist/Notary";
import { DeedType } from "le-coffre-resources/dist/Notary"; import { DeedType } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
export default function CreateFolder(): JSX.Element { export default function CreateFolder(): JSX.Element {
/** /**
* State * State
@ -48,9 +50,10 @@ export default function CreateFolder(): JSX.Element {
[key: string]: any; [key: string]: any;
}, },
) => { ) => {
const officeId = JwtService.getInstance().decodeJwt()?.office_Id; // TODO: review
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id;
const officeFolderForm = OfficeFolder.hydrate<OfficeFolder>({ const officeFolderModel = OfficeFolder.hydrate<OfficeFolder>({
folder_number: values["folder_number"], folder_number: values["folder_number"],
name: values["name"], name: values["name"],
description: values["description"], description: values["description"],
@ -67,16 +70,37 @@ export default function CreateFolder(): JSX.Element {
}); });
try { try {
await officeFolderForm.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true }); await officeFolderModel.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true });
} catch (validationErrors) { } catch (validationErrors) {
setValidationError(validationErrors as ValidationError[]); setValidationError(validationErrors as ValidationError[]);
return; //return;
} }
try { try {
/*
const newOfficeFolder = await Folders.getInstance().post(officeFolderForm); const newOfficeFolder = await Folders.getInstance().post(officeFolderForm);
if (!newOfficeFolder) return; if (!newOfficeFolder) {
return;
}
router.push(`/folders/${newOfficeFolder.uid}`); router.push(`/folders/${newOfficeFolder.uid}`);
*/
const folderData: any = {
folder_number: officeFolderModel.folder_number,
name: officeFolderModel.name,
deed: officeFolderModel.deed,
description: officeFolderModel.description,
customers: [],
documents: [],
notes: [],
office: officeFolderModel.office,
stakeholders: officeFolderModel.stakeholders
};
FolderService.createFolder(folderData, [], []).then((folderCreated: any) => {
const folderUid: string = folderCreated.processData.uid;
router.push(`/folders/${folderUid}`);
});
} catch (backError) { } catch (backError) {
if (!Array.isArray(backError)) return; if (!Array.isArray(backError)) return;
setValidationError(backError as ValidationError[]); setValidationError(backError as ValidationError[]);
@ -100,9 +124,19 @@ export default function CreateFolder(): JSX.Element {
* UseEffect * UseEffect
*/ */
useEffect(() => { useEffect(() => {
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getDeepTypes().then((deedTypes: any) => {
console.log(deedTypes.map((deedType: any) => deedType.processData));
setAvailableDeedTypes(deedTypes.map((deedType: any) => deedType.processData));
});
});
/*
DeedTypes.getInstance() DeedTypes.getInstance()
.get({ where: { archived_at: null } }) .get({ where: { archived_at: null } })
.then((deedTypes) => setAvailableDeedTypes(deedTypes)); .then((deedTypes) => setAvailableDeedTypes(deedTypes));
*/
/* TODO: review
// no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller // no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller
Users.getInstance() Users.getInstance()
.get({ .get({
@ -110,12 +144,15 @@ export default function CreateFolder(): JSX.Element {
}) })
.then((users) => { .then((users) => {
setAvailableCollaborators(users); setAvailableCollaborators(users);
/ *
// set default selected collaborators to the connected user // set default selected collaborators to the connected user
const currentUser = users.find((user) => user.uid === JwtService.getInstance().decodeJwt()?.userId); const currentUser = users.find((user) => user.uid === JwtService.getInstance().decodeJwt()?.userId);
if (currentUser) { if (currentUser) {
setSelectedCollaborators([currentUser]); setSelectedCollaborators([currentUser]);
} }
* /
}); });
*/
}, []); }, []);
/** /**

View File

@ -28,6 +28,12 @@ export default function ClientBox(props: IProps) {
const { isOpen: isDeleteModalOpen, open: openDeleteModal, close: closeDeleteModal } = useOpenable(); const { isOpen: isDeleteModalOpen, open: openDeleteModal, close: closeDeleteModal } = useOpenable();
const { isOpen: isErrorModalOpen, open: openErrorModal, close: closeErrorModal } = useOpenable(); const { isOpen: isErrorModalOpen, open: openErrorModal, close: closeErrorModal } = useOpenable();
// TODO: review
const handleOpenConnectionLink = useCallback(() => {
const url = `http://localhost:3000/client-dashboard/${folderUid}/profile/${customer.uid}`;
alert(url);
}, [customer.uid, folderUid]);
const handleDelete = useCallback( const handleDelete = useCallback(
async (customerUid: string) => { async (customerUid: string) => {
console.log(customer); console.log(customer);
@ -83,6 +89,15 @@ export default function ClientBox(props: IProps) {
</Menu> </Menu>
)} )}
</div> </div>
<div>
<Button
size={EButtonSize.SM}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.TEXT}
onClick={handleOpenConnectionLink}>
Lien de connexion
</Button>
</div>
<div> <div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}> <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Numéro de téléphone Numéro de téléphone

View File

@ -73,8 +73,10 @@ export default function DocumentTables(props: IProps) {
); );
useEffect(() => { useEffect(() => {
/* TODO: review
fetchDocuments(); fetchDocuments();
fetchDocumentsNotary(); fetchDocumentsNotary();
*/
}, [fetchDocuments, fetchDocumentsNotary]); }, [fetchDocuments, fetchDocumentsNotary]);
const openDeleteAskedDocumentModal = useCallback( const openDeleteAskedDocumentModal = useCallback(

View File

@ -41,7 +41,9 @@ export default function EmailReminder(props: IProps) {
}, [customer.uid, folderUid]); }, [customer.uid, folderUid]);
useEffect(() => { useEffect(() => {
/* TODO: review
fetchReminders(); fetchReminders();
*/
}, [fetchReminders]); }, [fetchReminders]);
const remindersLength = useMemo(() => { const remindersLength = useMemo(() => {

View File

@ -7,7 +7,7 @@ import Customer from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { AnchorStatus } from ".."; import { AnchorStatus } from "..";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -27,23 +27,34 @@ export default function ClientView(props: IProps) {
const { folder, anchorStatus } = props; const { folder, anchorStatus } = props;
const customers: ICustomer[] = useMemo( const customers: ICustomer[] = useMemo(
() => () => {
folder?.customers // TODO: review
?.map((customer) => ({ return folder?.customers?.map((customer: any) => customer.processData)
id: customer.uid ?? "", .map((customer: any) => ({
id: customer.uid ?? '',
...customer, ...customer,
})) }))
.sort((a, b) => { .sort((a: any, b: any) => {
return a.documents && return a.documents &&
a.documents.filter((document) => document.document_status === EDocumentStatus.DEPOSITED).length > 0 a.documents.filter((document: any) => document.document_status === EDocumentStatus.DEPOSITED).length > 0
? -1 ? -1
: 1; : 1;
}) ?? [], }) ?? []
},
[folder], [folder],
); );
const [customer, setCustomer] = useState<(typeof customers)[number]>(customers[0]!); const [customer, setCustomer] = useState<(typeof customers)[number]>(customers[0]!);
useEffect(() => {
// TODO: review
setTimeout(() => {
if (customers.length > 0 && customers[0]) {
setCustomer(customers[0]);
}
}, 50);
}, [customers]);
const tabs = useMemo( const tabs = useMemo(
() => () =>
customers.map((customer) => ({ customers.map((customer) => ({
@ -75,7 +86,7 @@ export default function ClientView(props: IProps) {
folder.uid, folder.uid,
OfficeFolder.hydrate<OfficeFolder>({ OfficeFolder.hydrate<OfficeFolder>({
...folder, ...folder,
customers: folder.customers?.filter((customer) => customer.uid !== customerUid), customers: folder.customers?.filter((customer: any) => customer.uid !== customerUid),
}), }),
); );
window.location.reload(); window.location.reload();
@ -110,7 +121,7 @@ export default function ClientView(props: IProps) {
anchorStatus={anchorStatus} anchorStatus={anchorStatus}
folderUid={folder.uid} folderUid={folder.uid}
onDelete={handleClientDelete} onDelete={handleClientDelete}
customerNote={folder.notes!.find((value) => value.customer?.uid === customer.uid) ?? null} customerNote={folder.notes!.find((value: any) => value.customer?.uid === customer.uid) ?? null}
/> />
<div className={classes["button-container"]}> <div className={classes["button-container"]}>
{anchorStatus === AnchorStatus.NOT_ANCHORED && ( {anchorStatus === AnchorStatus.NOT_ANCHORED && (

View File

@ -6,6 +6,8 @@ import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import MessageBus from "src/sdk/MessageBus";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
onClose?: () => void; onClose?: () => void;
@ -21,10 +23,16 @@ export default function DeleteFolderModal(props: IProps) {
if ((folder?.customers?.length ?? 0) > 0 || (folder?.documents?.length ?? 0) > 0) if ((folder?.customers?.length ?? 0) > 0 || (folder?.documents?.length ?? 0) > 0)
return console.warn("Cannot delete folder with customers or documents"); return console.warn("Cannot delete folder with customers or documents");
/* TODO: review
return Folders.getInstance() return Folders.getInstance()
.delete(folder.uid) .delete(folder.uid)
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path)) .then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
.then(onClose); .then(onClose);
*/
MessageBus.getInstance().isReady().then(() => {
//MessageBus.getInstance().deleteFolder(folder.uid).then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path)).then(onClose);
});
}, [folder, router, onClose]); }, [folder, router, onClose]);
return ( return (

View File

@ -6,6 +6,7 @@ import Image from "next/image";
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
@ -19,11 +20,37 @@ export default function AnchoringModal(props: IProps) {
const [isAnchoring, setIsAnchoring] = useState(false); const [isAnchoring, setIsAnchoring] = useState(false);
const anchor = useCallback(() => { const anchor = useCallback(() => {
setIsAnchoring(true);
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getFolders().then((folders: any) => {
const folder = folders.find((folder: any) => folder.processData.uid === folderUid);
if (folder) {
MessageBus.getInstance().updateProcess(folder.processId, folder.lastStateId, { isArchived: 'true' }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
MessageBus.getInstance().notifyUpdate(folder.processId, newStateId).then(() => {
MessageBus.getInstance().validateState(folder.processId, newStateId).then((_updatedProcess) => {
setIsAnchoring(false);
onAnchorSuccess();
if (onClose) {
onClose();
}
});
});
});
}
});
});
/*
const timeoutDelay = 9800; const timeoutDelay = 9800;
const timeoutPromise = new Promise((resolve) => { const timeoutPromise = new Promise((resolve) => {
setTimeout(resolve, timeoutDelay); setTimeout(resolve, timeoutDelay);
}); });
setIsAnchoring(true); setIsAnchoring(true);
return OfficeFolderAnchors.getInstance() return OfficeFolderAnchors.getInstance()
.post(folderUid) .post(folderUid)
.then(() => timeoutPromise) .then(() => timeoutPromise)
@ -34,6 +61,7 @@ export default function AnchoringModal(props: IProps) {
console.warn(e); console.warn(e);
setIsAnchoring(false); setIsAnchoring(false);
}); });
*/
}, [folderUid, onAnchorSuccess, onClose]); }, [folderUid, onAnchorSuccess, onClose]);
return ( return (

View File

@ -21,6 +21,8 @@ import InformationSection from "./InformationSection";
import NoClientView from "./NoClientView"; import NoClientView from "./NoClientView";
import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo"; import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
export enum AnchorStatus { export enum AnchorStatus {
"VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN", "VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN",
"ANCHORING" = "ANCHORING", "ANCHORING" = "ANCHORING",
@ -59,6 +61,8 @@ export default function FolderInformation(props: IProps) {
const fetchFolder = useCallback(async () => { const fetchFolder = useCallback(async () => {
if (!folderUid) return; if (!folderUid) return;
/*
const query = { const query = {
q: { q: {
deed: { include: { deed_type: true, document_types: true } }, deed: { include: { deed_type: true, document_types: true } },
@ -99,6 +103,17 @@ export default function FolderInformation(props: IProps) {
return Folders.getInstance() return Folders.getInstance()
.getByUid(folderUid, query) .getByUid(folderUid, query)
.then((folder) => setFolder(folder)); .then((folder) => setFolder(folder));
*/
// TODO: review
return new Promise<any>((resolve: (value: any) => void) => {
FolderService.getFolderByUid(folderUid).then((folder: any) => {
if (folder) {
setFolder(folder.processData);
resolve(folder.processData);
}
});
});
}, [folderUid]); }, [folderUid]);
const fetchAnchorStatus = useCallback(() => { const fetchAnchorStatus = useCallback(() => {
@ -113,7 +128,10 @@ export default function FolderInformation(props: IProps) {
const fetchData = useCallback(() => { const fetchData = useCallback(() => {
setIsLoading(true); setIsLoading(true);
return fetchFolder() return fetchFolder()
.then(() => fetchAnchorStatus()) .then((folder) => {
// TODO: review
//return fetchAnchorStatus()
})
.catch((e) => console.error(e)) .catch((e) => console.error(e))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, [fetchAnchorStatus, fetchFolder]); }, [fetchAnchorStatus, fetchFolder]);

View File

@ -15,9 +15,11 @@ import { Contact, Customer } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
import MapUtils from "src/sdk/MapUtils";
export default function UpdateClient() { export default function UpdateClient() {
const router = useRouter(); const router = useRouter();
const { folderUid, customerUid } = router.query; const { folderUid, customerUid } = router.query;
@ -35,6 +37,21 @@ export default function UpdateClient() {
useEffect(() => { useEffect(() => {
const fetchCustomer = async () => { const fetchCustomer = async () => {
try { try {
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getFolders().then((folders: any) => {
const folder = folders.find((folder: any) => folder.processData.uid === folderUid as string);
if (folder) {
const customers: any[] = folder.processData.customers.map((customer: any) => MapUtils.toJson(customer));
const customer: any = customers.find((customer: any) => customer.uid === customerUid as string);
if (customer) {
setCustomer(customer);
}
}
});
});
/*
const customerData = await Customers.getInstance().getByUid(customerUid as string, { const customerData = await Customers.getInstance().getByUid(customerUid as string, {
contact: { contact: {
include: { include: {
@ -45,6 +62,7 @@ export default function UpdateClient() {
if (customerData) { if (customerData) {
setCustomer(customerData); setCustomer(customerData);
} }
*/
} catch (error) { } catch (error) {
console.error("Failed to fetch customer", error); console.error("Failed to fetch customer", error);
} }
@ -71,9 +89,34 @@ export default function UpdateClient() {
}); });
try { try {
// TODO: review
MessageBus.getInstance().isReady().then(() => {
MessageBus.getInstance().getFolders().then((folders: any) => {
const folder = folders.find((folder: any) => folder.processData.uid === folderUid as string);
if (folder) {
const customers: any[] = folder.processData.customers.map((customer: any) => MapUtils.toJson(customer));
const customer: any = customers.find((customer: any) => customer.uid === customerUid as string);
if (customer) {
customer.contact = contact; // Update the contact
MessageBus.getInstance().updateProcess(folder.processId, folder.lastStateId, { customers: customers }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
MessageBus.getInstance().notifyUpdate(folder.processId, newStateId).then(() => {
MessageBus.getInstance().validateState(folder.processId, newStateId).then((_stateValidated) => {
router.push(backwardPath);
});
});
});
}
}
});
});
/*
await contact.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false }); await contact.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
await Customers.getInstance().put(customerUid as string, { contact }); await Customers.getInstance().put(customerUid as string, { contact });
router.push(backwardPath); router.push(backwardPath);
*/
} catch (validationErrors) { } catch (validationErrors) {
if (Array.isArray(validationErrors)) { if (Array.isArray(validationErrors)) {
setValidationError(validationErrors as ValidationError[]); setValidationError(validationErrors as ValidationError[]);

View File

@ -13,9 +13,10 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
export default function Folder() { export default function Folder() {
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true); const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
const router = useRouter(); const router = useRouter();
@ -23,6 +24,19 @@ export default function Folder() {
const { user: activeUser } = useUser(); const { user: activeUser } = useUser();
useEffect(() => { useEffect(() => {
// TODO: review
FolderService.getFolders().then((folders: any) => {
const foldersLive = folders.filter((folder: any) => folder.processData.isArchived === 'false');
if (foldersLive.length !== 0) {
router.push(
Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", foldersLive[foldersLive.length - 1].processData.uid)
);
}
});
/*
Folders.getInstance() Folders.getInstance()
.get({ .get({
q: { q: {
@ -38,6 +52,7 @@ export default function Folder() {
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folders[0]?.uid ?? ""), .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folders[0]?.uid ?? ""),
); );
}); });
*/
}, [router]); }, [router]);
return ( return (
<DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}> <DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>

View File

@ -41,12 +41,17 @@ export default function StepEmail(props: IProps) {
const router = useRouter(); const router = useRouter();
const error = router.query["error"]; const error = router.query["error"];
const redirectUserOnConnection = useCallback(() => { const redirectUserOnConnection = useCallback(() => {
/* TODO: review
const variables = FrontendVariables.getInstance(); const variables = FrontendVariables.getInstance();
router.push( router.push(
`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${ `${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${
variables.FRONT_APP_HOST variables.FRONT_APP_HOST
}/authorized-client&scope=openid,profile&response_type=code`, }/authorized-client&scope=openid,profile&response_type=code`,
); );
*/
router.push(
`https://qual-connexion.idnot.fr/user/IdPOAuth2/authorize/idnot_idp_v1?client_id=B3CE56353EDB15A9&redirect_uri=http://local.lecoffreio.4nkweb:3000/authorized-client&scope=openid,profile&response_type=code`,
);
}, [router]); }, [router]);
const openErrorModal = useCallback((index: number) => { const openErrorModal = useCallback((index: number) => {
@ -94,12 +99,14 @@ export default function StepEmail(props: IProps) {
Pour les clients : Pour les clients :
</Typography> </Typography>
<Form className={classes["form"]} onSubmit={onSubmit}> <Form className={classes["form"]} onSubmit={onSubmit}>
{/*
<TextField <TextField
placeholder="Renseigner votre email" placeholder="Renseigner votre email"
label="E-mail" label="E-mail"
name="email" name="email"
validationError={validationErrors.find((err) => err.property === "email")} validationError={validationErrors.find((err) => err.property === "email")}
/> />
*/}
<Button type="submit">Se connecter</Button> <Button type="submit">Se connecter</Button>
</Form> </Form>
</div> </div>

View File

@ -50,11 +50,14 @@ export default function Login() {
const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try { try {
/* TODO: review
if (!values["email"]) return; if (!values["email"]) return;
setEmail(values["email"]); setEmail(values["email"]);
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] }); const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
setPartialPhoneNumber(res.partialPhoneNumber); setPartialPhoneNumber(res.partialPhoneNumber);
setTotpCodeUid(res.totpCodeUid); setTotpCodeUid(res.totpCodeUid);
*/
setStep(LoginStep.TOTP); setStep(LoginStep.TOTP);
setValidationErrors([]); setValidationErrors([]);
} catch (error: any) { } catch (error: any) {

View File

@ -11,15 +11,25 @@ import JwtService from "@Front/Services/JwtService/JwtService";
import UserStore from "@Front/Stores/UserStore"; import UserStore from "@Front/Stores/UserStore";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import AuthModal from "src/sdk/AuthModal";
export default function LoginCallBack() { export default function LoginCallBack() {
const router = useRouter(); const router = useRouter();
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
async function getUser() { async function getUser() {
// 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:3000/authorized-client')) {
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"]; const code = router.query["code"];
if (code) { if (code) {
try { try {
@ -28,10 +38,12 @@ export default function LoginCallBack() {
await UserStore.instance.connect(token.accessToken, token.refreshToken); await UserStore.instance.connect(token.accessToken, token.refreshToken);
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
if (!jwt.rules.includes("GET folders")) { if (jwt.rules && !jwt.rules.includes("GET folders")) {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
} }
return router.push(Module.getInstance().get().modules.pages.Folder.props.path); setIsAuthModalOpen(true);
//return router.push(Module.getInstance().get().modules.pages.Folder.props.path);
return;
} catch (e: any) { } catch (e: any) {
if (e.http_status === 401 && e.message === "Email not found") { if (e.http_status === 401 && e.message === "Email not found") {
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=3"); return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=3");
@ -42,6 +54,7 @@ export default function LoginCallBack() {
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=1");
} }
} }
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken");
if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken); const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken);
@ -51,7 +64,9 @@ export default function LoginCallBack() {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
} }
if (isTokenRefreshed) { if (isTokenRefreshed) {
return router.push(Module.getInstance().get().modules.pages.Folder.props.path); setIsAuthModalOpen(true);
//return router.push(Module.getInstance().get().modules.pages.Folder.props.path);
return;
} }
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2"); return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2");
} }
@ -76,6 +91,13 @@ export default function LoginCallBack() {
description="Notre équipe de support est là pour vous aider." description="Notre équipe de support est là pour vous aider."
button={{ text: "Contacter l'administrateur", link: "mailto:support@lecoffre.io" }} button={{ text: "Contacter l'administrateur", link: "mailto:support@lecoffre.io" }}
/> />
{isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen}
onClose={() => {
setIsAuthModalOpen(false);
router.push(Module.getInstance().get().modules.pages.Folder.props.path);
}}
/>}
</div> </div>
</DefaultDoubleSidePage> </DefaultDoubleSidePage>
); );

View File

@ -34,7 +34,7 @@
"ClientDashboard": { "ClientDashboard": {
"enabled": true, "enabled": true,
"props": { "props": {
"path": "/client-dashboard/[folderUid]", "path": "/client-dashboard/[folderUid]/profile/[profileUid]",
"labelKey": "client-dashboard" "labelKey": "client-dashboard"
}, },
"pages": { "pages": {

View File

@ -34,7 +34,7 @@
"ClientDashboard": { "ClientDashboard": {
"enabled": true, "enabled": true,
"props": { "props": {
"path": "/client-dashboard/[folderUid]", "path": "/client-dashboard/[folderUid]/profile/[profileUid]",
"labelKey": "client-dashboard" "labelKey": "client-dashboard"
}, },
"pages": { "pages": {

View File

@ -34,7 +34,7 @@
"ClientDashboard": { "ClientDashboard": {
"enabled": true, "enabled": true,
"props": { "props": {
"path": "/client-dashboard/[folderUid]", "path": "/client-dashboard/[folderUid]/profile/[profileUid]",
"labelKey": "client-dashboard" "labelKey": "client-dashboard"
}, },
"pages": { "pages": {

View File

@ -34,7 +34,7 @@
"ClientDashboard": { "ClientDashboard": {
"enabled": true, "enabled": true,
"props": { "props": {
"path": "/client-dashboard/[folderUid]", "path": "/client-dashboard/[folderUid]/profile/[profileUid]",
"labelKey": "client-dashboard" "labelKey": "client-dashboard"
}, },
"pages": { "pages": {

View File

@ -27,7 +27,9 @@ export class FrontendVariables {
public HOTJAR_SITE_ID!: number; public HOTJAR_SITE_ID!: number;
public HOJAR_VERSION!: number; public HOTJAR_VERSION!: number;
public _4NK_URL!: string;
private constructor() {} private constructor() {}

View File

@ -7,6 +7,7 @@ export default function useUser() {
const [user, setUser] = useState<User | null>(); const [user, setUser] = useState<User | null>();
useEffect(() => { useEffect(() => {
/* TODO: review
const decodedJwt = JwtService.getInstance().decodeJwt(); const decodedJwt = JwtService.getInstance().decodeJwt();
if (!decodedJwt) return; if (!decodedJwt) return;
Users.getInstance() Users.getInstance()
@ -18,6 +19,7 @@ export default function useUser() {
.then((user) => { .then((user) => {
setUser(user); setUser(user);
}); });
*/
}, []); }, []);
return { return {

View File

@ -4,13 +4,15 @@ import CookieService from "@Front/Services/CookieService/CookieService";
import EventEmitter from "@Front/Services/EventEmitter"; import EventEmitter from "@Front/Services/EventEmitter";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import User from "src/sdk/User";
export default class UserStore { export default class UserStore {
public static readonly instance = new this(); public static readonly instance = new this();
protected readonly event = new EventEmitter(); protected readonly event = new EventEmitter();
public accessToken: string | null = null; public accessToken: string | null = null;
public refreshToken: string | null = null; public refreshToken: string | null = null;
private constructor() {} private constructor() { }
public isConnected(): boolean { public isConnected(): boolean {
return !!this.accessToken; return !!this.accessToken;
@ -41,6 +43,8 @@ export default class UserStore {
CookieService.getInstance().deleteCookie("leCoffreAccessToken"); CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken"); CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
User.getInstance().clear();
this.event.emit("disconnection", this.accessToken); this.event.emit("disconnection", this.accessToken);
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@ -4,16 +4,16 @@ import CookieService from "@Front/Services/CookieService/CookieService";
import EventEmitter from "@Front/Services/EventEmitter"; import EventEmitter from "@Front/Services/EventEmitter";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import User from "src/sdk/User";
export default class UserStore { export default class UserStore {
public static readonly instance = new this(); public static readonly instance = new this();
protected readonly event = new EventEmitter(); protected readonly event = new EventEmitter();
public accessToken: string | null = null;
public refreshToken: string | null = null;
private constructor() {} private constructor() {}
public isConnected(): boolean { public isConnected(): boolean {
return !!this.accessToken; return !!CookieService.getInstance().getCookie("leCoffreAccessToken");
} }
public getRole(): string | undefined { public getRole(): string | undefined {
@ -27,7 +27,7 @@ export default class UserStore {
CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken); CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken);
CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken); CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken);
this.event.emit("connection", this.accessToken); this.event.emit("connection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return false; return false;
@ -41,7 +41,9 @@ export default class UserStore {
CookieService.getInstance().deleteCookie("leCoffreAccessToken"); CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken"); CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
this.event.emit("disconnection", this.accessToken); User.getInstance().clear();
this.event.emit("disconnection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -56,4 +58,12 @@ export default class UserStore {
this.event.on("connection", callback); this.event.on("connection", callback);
return () => this.event.off("connection", callback); return () => this.event.off("connection", callback);
} }
public getAccessToken(): string {
return CookieService.getInstance().getCookie("leCoffreAccessToken") || "";
}
public getRefreshToken(): string {
return CookieService.getInstance().getCookie("leCoffreRefreshToken") || "";
}
} }

View File

@ -3,11 +3,17 @@ import { DefaultLayout } from "@Front/Components/LayoutTemplates/DefaultLayout";
import { FrontendVariables } from "@Front/Config/VariablesFront"; import { FrontendVariables } from "@Front/Config/VariablesFront";
import type { NextPage } from "next"; import type { NextPage } from "next";
import type { AppType, AppProps } from "next/app"; import type { AppType, AppProps } from "next/app";
import { useEffect, type ReactElement, type ReactNode } from "react"; import { useEffect, useState, type ReactElement, type ReactNode } from "react";
import getConfig from "next/config"; import getConfig from "next/config";
import { useRouter } from "next/router";
import { GoogleTagManager } from "@next/third-parties/google"; import { GoogleTagManager } from "@next/third-parties/google";
import { hotjar } from "react-hotjar"; import { hotjar } from "react-hotjar";
import IframeReference from "src/sdk/IframeReference";
import Iframe from "src/sdk/Iframe";
import MessageBus from "src/sdk/MessageBus";
import User from "src/sdk/User";
export type NextPageWithLayout<TProps = Record<string, unknown>, TInitialProps = TProps> = NextPage<TProps, TInitialProps> & { export type NextPageWithLayout<TProps = Record<string, unknown>, TInitialProps = TProps> = NextPage<TProps, TInitialProps> & {
getLayout?: (page: ReactElement) => ReactNode; getLayout?: (page: ReactElement) => ReactNode;
}; };
@ -28,6 +34,7 @@ type AppPropsWithLayout = AppProps & {
docaposteApiUrl: string; docaposteApiUrl: string;
hotjarSiteId: number; hotjarSiteId: number;
hotjarVersion: number; hotjarVersion: number;
_4nkUrl: string;
}; };
const { publicRuntimeConfig } = getConfig(); const { publicRuntimeConfig } = getConfig();
@ -48,6 +55,7 @@ const MyApp = (({
docaposteApiUrl, docaposteApiUrl,
hotjarSiteId, hotjarSiteId,
hotjarVersion, hotjarVersion,
_4nkUrl,
}: AppPropsWithLayout) => { }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>); const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>);
@ -64,7 +72,39 @@ const MyApp = (({
instance.FC_CLIENT_ID = fcClientId; instance.FC_CLIENT_ID = fcClientId;
instance.DOCAPOST_API_URL = docaposteApiUrl; instance.DOCAPOST_API_URL = docaposteApiUrl;
instance.HOTJAR_SITE_ID = hotjarSiteId; instance.HOTJAR_SITE_ID = hotjarSiteId;
instance.HOJAR_VERSION = hotjarVersion; instance.HOTJAR_VERSION = hotjarVersion;
instance._4NK_URL = _4nkUrl;
const router = useRouter();
const [isConnected, setIsConnected] = useState(false);
const [isReady, setIsReady] = useState(false);
IframeReference.setTargetOrigin(_4nkUrl);
useEffect(() => {
const isAuthenticated = User.getInstance().isAuthenticated();
setIsConnected(isAuthenticated);
if (isAuthenticated) {
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
setIsReady(true);
}, 50);
});
}
}, []);
useEffect(() => {
const isAuthenticated = User.getInstance().isAuthenticated();
setIsConnected(isAuthenticated);
if (isAuthenticated) {
MessageBus.getInstance().isReady().then(() => {
setTimeout(() => {
setIsReady(true);
}, 50);
});
}
}, [router]);
useEffect(() => { useEffect(() => {
if (!hotjarSiteId || !hotjarVersion) { if (!hotjarSiteId || !hotjarVersion) {
console.warn("No hotjar site id or version provided"); console.warn("No hotjar site id or version provided");
@ -78,9 +118,14 @@ const MyApp = (({
}, [hotjarSiteId, hotjarVersion]); }, [hotjarSiteId, hotjarVersion]);
return getLayout( return getLayout(
<>
{((isConnected && isReady) || !isConnected) &&
<Component {...pageProps}> <Component {...pageProps}>
<GoogleTagManager gtmId="GTM-5GLJN86P" /> <GoogleTagManager gtmId="GTM-5GLJN86P" />
</Component>, </Component>
}
{isConnected && <Iframe />}
</>
); );
}) as AppType; }) as AppType;
@ -100,6 +145,7 @@ MyApp.getInitialProps = async () => {
docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOST_API_URL, docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOST_API_URL,
hotjarSiteId: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_SITE_ID, hotjarSiteId: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_SITE_ID,
hotjarVersion: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_VERSION, hotjarVersion: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_VERSION,
_4nkUrl: publicRuntimeConfig.NEXT_PUBLIC_4NK_URL,
}; };
}; };

View File

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

150
src/sdk/AuthModal.tsx Normal file
View File

@ -0,0 +1,150 @@
import React, { useState, useEffect, useRef } from 'react';
import Modal from '@Front/Components/DesignSystem/Modal';
import Loader from '@Front/Components/DesignSystem/Loader';
import Typography, { ETypo, ETypoColor } from '@Front/Components/DesignSystem/Typography';
import IframeReference from './IframeReference';
import User from './User';
interface AuthModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
const [isIframeReady, setIsIframeReady] = useState(false);
const [showIframe, setShowIframe] = useState(false);
const [authSuccess, setAuthSuccess] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (!event.data || event.data.type === 'PassClientScriptReady') {
return;
}
if (!iframeRef.current) {
console.error('[AuthModal] handleMessage: iframeRef.current is null');
return;
}
if (event.source !== iframeRef.current.contentWindow) {
console.error('[AuthModal] handleMessage: source not match');
return;
}
const targetOrigin = IframeReference.getTargetOrigin();
if (!targetOrigin) {
console.error('[AuthModal] handleMessage: targetOrigin not found');
return;
}
if (event.origin !== targetOrigin) {
console.error('[AuthModal] handleMessage: origin not match');
return;
}
if (!event.data || typeof event.data !== 'object') {
console.error('[AuthModal] handleMessage: data not found');
return;
}
const message = event.data;
console.log('[AuthModal] handleMessage:', message);
switch (message.type) {
case 'LISTENING':
iframeRef.current.contentWindow!.postMessage({ type: 'REQUEST_LINK' }, targetOrigin);
setIsIframeReady(true);
break;
case 'LINK_ACCEPTED':
setShowIframe(false);
User.getInstance().setTokens(message.accessToken, message.refreshToken);
iframeRef.current.contentWindow!.postMessage({ type: 'GET_PAIRING_ID', accessToken: message.accessToken }, targetOrigin);
break;
case 'GET_PAIRING_ID':
User.getInstance().setPairingId(message.userPairingId);
setAuthSuccess(true);
setTimeout(() => {
setShowIframe(false);
setIsIframeReady(false);
setAuthSuccess(false);
onClose();
}, 500);
break;
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [isOpen]);
useEffect(() => {
if (isIframeReady && !showIframe) {
setShowIframe(true);
}
}, [isIframeReady, showIframe]);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title='Authentification 4nk'
>
{!isIframeReady && (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '400px',
gap: 'var(--spacing-md, 16px)'
}}>
<Loader width={40} />
<Typography typo={ETypo.TEXT_MD_SEMIBOLD}>Chargement de l'authentification...</Typography>
</div>
)}
{authSuccess ? (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '400px',
gap: '20px'
}}>
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.COLOR_SUCCESS_500}>
Authentification réussie ! Redirection en cours...
</Typography>
</div>
) : (
<div style={{
display: showIframe ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
width: '100%'
}}>
<iframe
ref={iframeRef}
src={IframeReference.getTargetOrigin()}
style={{
display: showIframe ? 'block' : 'none',
width: '400px',
height: '400px',
border: 'none',
overflow: 'hidden'
}}
/>
</div>
)}
</Modal>
);
}

33
src/sdk/EventBus.ts Normal file
View File

@ -0,0 +1,33 @@
export default class EventBus {
private static instance: EventBus;
private listeners: Record<string, Array<(...args: any[]) => void>> = {};
private constructor() { }
public static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
public on(event: string, callback: (...args: any[]) => void): () => void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
return () => {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
}
};
}
public emit(event: string, ...args: any[]): void {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
callback(...args);
});
}
}
}

36
src/sdk/Iframe.tsx Normal file
View File

@ -0,0 +1,36 @@
import { useRef, useEffect, memo } from 'react';
import IframeReference from './IframeReference';
interface IframeProps {
showIframe?: boolean;
}
function Iframe({ showIframe = false }: IframeProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
if (iframeRef.current) {
IframeReference.setIframe(iframeRef.current);
}
return () => {
IframeReference.setIframe(null);
};
}, [iframeRef.current]);
return (
<iframe
ref={iframeRef}
src={IframeReference.getTargetOrigin()}
style={{
display: showIframe ? 'block' : 'none',
width: '400px',
height: '400px',
border: 'none',
overflow: 'hidden'
}}
/>
);
}
Iframe.displayName = 'Iframe';
export default memo(Iframe);

View File

@ -0,0 +1,36 @@
export default class IframeReference {
private static targetOrigin: string | null = null;
private static iframe: HTMLIFrameElement | null = null;
private constructor() { }
public static setTargetOrigin(targetOrigin: string): void {
try {
new URL(targetOrigin);
this.targetOrigin = targetOrigin;
} catch {
throw new Error(`Invalid targetOrigin: ${targetOrigin}`);
}
}
public static getTargetOrigin(): string {
if (!this.targetOrigin) {
throw new Error("targetOrigin is not set");
}
return this.targetOrigin;
}
public static setIframe(iframe: HTMLIFrameElement | null): void {
if (iframe !== null && !(iframe instanceof HTMLIFrameElement)) {
throw new Error("setIframe expects an HTMLIFrameElement or null");
}
this.iframe = iframe;
}
public static getIframe(): HTMLIFrameElement {
if (!this.iframe) {
throw new Error("iframe is not set");
}
return this.iframe;
}
}

25
src/sdk/MapUtils.ts Normal file
View File

@ -0,0 +1,25 @@
export default class MapUtils {
private constructor() { }
public static toJson(input: any): any {
if (input instanceof Map) {
const obj: Record<string, any> = {};
for (const [key, value] of input.entries()) {
obj[key] = MapUtils.toJson(value);
}
return obj;
} else if (Array.isArray(input)) {
return input.map((item) => MapUtils.toJson(item));
} else if (input !== null && typeof input === 'object') {
const obj: Record<string, any> = {};
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
obj[key] = MapUtils.toJson(input[key]);
}
}
return obj;
}
return input;
}
}

1174
src/sdk/MessageBus.ts Normal file

File diff suppressed because it is too large Load Diff

125
src/sdk/RolesBuilder.ts Normal file
View File

@ -0,0 +1,125 @@
type Member = string;
interface ValidationRule {
quorum: number;
fields: string[];
min_sig_member: number;
}
interface Storage {
name: string;
type: string;
params?: Record<string, any>;
}
interface RoleDefinition {
members: Member[];
validation_rules: ValidationRule[];
storages: Storage[];
}
interface RolesStructure {
demiurge: RoleDefinition;
owner: RoleDefinition;
validator: RoleDefinition;
collaborator: RoleDefinition;
client: RoleDefinition;
[key: string]: RoleDefinition;
}
export default class RolesBuilder {
private static instance: RolesBuilder;
private roles: RolesStructure;
private constructor() {
this.roles = {
demiurge: {
members: [],
validation_rules: [],
storages: []
},
owner: {
members: [],
validation_rules: [
{
quorum: 0.5,
fields: ['roles'],
min_sig_member: 1,
},
],
storages: []
},
validator: {
members: [],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
],
storages: []
},
collaborator: {
members: [],
validation_rules: [],
storages: []
},
client: {
members: [],
validation_rules: [],
storages: []
}
};
}
public static getInstance(): RolesBuilder {
if (!RolesBuilder.instance) {
RolesBuilder.instance = new RolesBuilder();
}
return RolesBuilder.instance;
}
public addMember(role: string, memberId: string): RolesBuilder {
if (this.roles[role]) {
if (!this.roles[role].members.includes(memberId)) {
this.roles[role].members.push(memberId);
}
}
return this;
}
public addMembers(role: string, memberIds: string[]): RolesBuilder {
memberIds.forEach(id => this.addMember(role, id));
return this;
}
public addValidationRule(role: string, rule: ValidationRule): RolesBuilder {
if (this.roles[role]) {
this.roles[role].validation_rules.push(rule);
}
return this;
}
public addStorage(role: string, storage: Storage): RolesBuilder {
if (this.roles[role]) {
this.roles[role].storages.push(storage);
}
return this;
}
public createRole(roleId: string): RolesBuilder {
if (!this.roles[roleId]) {
this.roles[roleId] = {
members: [],
validation_rules: [],
storages: []
};
}
return this;
}
public build(): RolesStructure {
return { ...this.roles };
}
}

43
src/sdk/User.ts Normal file
View File

@ -0,0 +1,43 @@
export default class User {
private static instance: User;
private constructor() { }
public static getInstance(): User {
if (!User.instance) {
User.instance = new User();
}
return User.instance;
}
public setTokens(access: string, refresh: string): void {
sessionStorage.setItem('accessToken', access);
sessionStorage.setItem('refreshToken', refresh);
}
public getAccessToken(): string | null {
return sessionStorage.getItem('accessToken');
}
public getRefreshToken(): string | null {
return sessionStorage.getItem('refreshToken');
}
public setPairingId(pairingId: string): void {
sessionStorage.setItem('pairingId', pairingId);
}
public getPairingId(): string | null {
return sessionStorage.getItem('pairingId');
}
public isAuthenticated(): boolean {
return this.getAccessToken() !== null && this.getRefreshToken() !== null && this.getPairingId() !== null;
}
public clear(): void {
sessionStorage.removeItem('accessToken');
sessionStorage.removeItem('refreshToken');
sessionStorage.removeItem('pairingId');
}
}