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_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
},
serverRuntimeConfig: {
@ -31,6 +32,7 @@ const nextConfig = {
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_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
},
env: {
@ -46,6 +48,7 @@ const nextConfig = {
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_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
},
// 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",
"jszip": "^3.10.1",
"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",
"prettier": "^2.8.7",
"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 }> {
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}`);
try {
return await this.postRequest<{ accessToken: string; refreshToken: string }>(url);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@ import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDas
import { DeedType } from "le-coffre-resources/dist/Notary";
import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
import MessageBus from "src/sdk/MessageBus";
type IProps = IPropsDashboardWithList;
export default function DefaultDeedTypeDashboard(props: IProps) {
@ -14,6 +16,16 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
const router = useRouter();
const { deedTypeUid } = router.query;
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 = {
where: {
archived_at: null,
@ -26,6 +38,7 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
DeedTypes.getInstance()
.get(query)
.then((deedTypes) => setDeedTypes(deedTypes));
*/
}, []);
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 { DocumentType } from "le-coffre-resources/dist/Notary";
import MessageBus from "src/sdk/MessageBus";
type IProps = IPropsDashboardWithList;
export default function DefaultDocumentTypeDashboard(props: IProps) {
@ -15,6 +17,18 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
const router = useRouter();
const { documentTypeUid } = router.query;
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();
if (!jwt) return;
DocumentTypes.getInstance()
@ -27,6 +41,7 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
},
})
.then((documentTypes) => setDocumentTypes(documentTypes));
*/
}, []);
const onSelectedBlock = (block: IBlock) => {

View File

@ -1,7 +1,7 @@
import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
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 Module from "@Front/Config/Module";
@ -9,6 +9,8 @@ import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList
import { useRouter } from "next/router";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = IPropsDashboardWithList & {
isArchived?: boolean;
};
@ -74,6 +76,7 @@ export default function DefaultNotaryDashboard(props: IProps) {
}, [folders, getBlocks]);
useEffect(() => {
/* TODO: review
let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus];
if (isArchived) targetedStatus = EFolderStatus.ARCHIVED;
const query: IGetFoldersParams = {
@ -110,6 +113,13 @@ export default function DefaultNotaryDashboard(props: IProps) {
Folders.getInstance()
.get(query)
.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]);
return (

View File

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

View File

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

View File

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

View File

@ -18,10 +18,20 @@ export default function ContactBox(props: IProps) {
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(
() =>
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ??
folder?.stakeholders![0]!.contact,
/*folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ??
folder?.stakeholders![0]!.contact*/
// TODO: review
stakeholder,
[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 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 { 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 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 = {};
export default function ClientDashboard(props: IProps) {
const router = useRouter();
let { folderUid } = router.query;
let { folderUid, profileUid } = router.query;
const [documents, setDocuments] = useState<Document[] | null>(null);
const [customer, setCustomer] = useState<Customer | null>(null);
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
const [file, setFile] = useState<any | null>(null);
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isFormModalOpen, setIsFormModalOpen] = useState(false);
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;
if (typeof document !== "undefined") {
jwt = JwtService.getInstance().decodeCustomerJwt();
@ -73,6 +130,7 @@ export default function ClientDashboard(props: IProps) {
setCustomer(customer);
return { folder, customer };
*/
}, [folderUid]);
const fetchDocuments = useCallback(
@ -110,9 +168,13 @@ export default function ClientDashboard(props: IProps) {
useEffect(() => {
const customerUid = customer?.uid;
if (!folderUid || !customerUid) return;
/* TODO: review
DocumentsNotary.getInstance()
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
*/
}, [folderUid, customer?.uid]);
const documentsNotaryNotRead = useMemo(
@ -129,6 +191,21 @@ export default function ClientDashboard(props: IProps) {
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(() => {
return (
<DepositOtherDocument
@ -157,6 +234,16 @@ export default function ClientDashboard(props: IProps) {
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
</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 ?? ""} />
<div className={classes["office-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
@ -244,6 +331,56 @@ export default function ClientDashboard(props: IProps) {
Ajouter d'autres documents
</Button>
{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>
</DefaultCustomerDashboard>
);

View File

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

View File

@ -20,6 +20,9 @@ import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
import MapUtils from "src/sdk/MapUtils";
type IProps = {};
export default function DeedTypesInformations(props: IProps) {
const router = useRouter();
@ -62,6 +65,30 @@ export default function DeedTypesInformations(props: IProps) {
useEffect(() => {
async function getDeedType() {
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, {
q: {
document_types: true,
@ -79,11 +106,23 @@ export default function DeedTypesInformations(props: IProps) {
})
.sort((a, b) => a.label.localeCompare(b.label));
setSelectedDocuments(documentsOptions);
*/
}
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({});
setAvailableDocuments(documents);
*/
}
getDocuments();
@ -98,11 +137,40 @@ export default function DeedTypesInformations(props: IProps) {
);
const saveDocumentTypes = useCallback(async () => {
/*
await DeedTypes.getInstance().put(deedTypeUid as string, {
uid: deedTypeUid 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, deedTypeUid, selectedDocuments]);
const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {

View File

@ -14,6 +14,10 @@ import { useCallback, useState } from "react";
import classes from "./classes.module.scss";
import { v4 as uuidv4 } from "uuid";
import MessageBus from "src/sdk/MessageBus";
type IProps = {};
export default function DocumentTypesCreate(props: IProps) {
const [validationError, setValidationError] = useState<ValidationError[]>([]);
@ -22,6 +26,38 @@ export default function DocumentTypesCreate(props: IProps) {
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
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();
if (!jwt) return;
const office = Office.hydrate<Office>({
@ -39,6 +75,7 @@ export default function DocumentTypesCreate(props: IProps) {
.get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeCreated.uid!),
);
*/
} catch (e) {
if (e instanceof Array) {
setValidationError(e);

View File

@ -13,6 +13,8 @@ import { useEffect, useState } from "react";
import classes from "./classes.module.scss";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import MessageBus from "src/sdk/MessageBus";
export default function DocumentTypesInformations() {
const router = useRouter();
let { documentTypeUid } = router.query;
@ -22,11 +24,24 @@ export default function DocumentTypesInformations() {
useEffect(() => {
async function getDocument() {
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, {
_count: true,
});
if (!document) return;
setDocumentSelected(document);
*/
}
getDocument();

View File

@ -19,6 +19,9 @@ import classes from "./classes.module.scss";
import { useCallback, useEffect, useState } from "react";
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 {
EXISTING_CUSTOMER = "existing_customer",
NEW_CUSTOMER = "new_customer",
@ -71,11 +74,33 @@ export default function AddClientToFolder(props: IProps) {
}
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({
contact: values,
});
if (!customer.uid) return;
customersToLink?.push({ uid: customer.uid } as Partial<Customer>);
*/
} catch (backError) {
if (!Array.isArray(backError)) return;
setValidationError(backError as ValidationError[]);
@ -83,6 +108,7 @@ export default function AddClientToFolder(props: IProps) {
}
}
/*
if (customersToLink) {
const body = OfficeFolder.hydrate<OfficeFolder>({
customers: customersToLink.map((customer) => {
@ -92,6 +118,7 @@ export default function AddClientToFolder(props: IProps) {
await Folders.getInstance().put(folderUid as string, body);
router.push(`/folders/${folderUid}`);
}
*/
},
[existingCustomers, folderUid, router, selectedCustomers, selectedOption],
);
@ -126,6 +153,7 @@ export default function AddClientToFolder(props: IProps) {
);
const loadCustomers = useCallback(async () => {
/* TODO: review
const query = {};
const availableCustomers = await Customers.getInstance().get(query);
let preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string);
@ -143,8 +171,9 @@ export default function AddClientToFolder(props: IProps) {
setAvailableCustomers(availableCustomers);
setExistingCustomers(existingCustomers);
*/
setIsLoaded(true);
setSelectedOption(selectedOption);
//setSelectedOption(selectedOption);
}, [folderUid, getFolderPreSelectedCustomers]);
const getSelectedOptions = useCallback((): IOption[] => {

View File

@ -55,7 +55,10 @@ export default function AskDocuments() {
},
) => {
try {
// TODO: review
const documentAsked: [] = values["document_types"] as [];
/*
for (let i = 0; i < documentAsked.length; i++) {
await Documents.getInstance().post({
folder: {
@ -75,6 +78,7 @@ export default function AskDocuments() {
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
);
*/
} catch (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 { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react";
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 {
/**
* State
@ -48,9 +50,10 @@ export default function CreateFolder(): JSX.Element {
[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"],
name: values["name"],
description: values["description"],
@ -67,16 +70,37 @@ export default function CreateFolder(): JSX.Element {
});
try {
await officeFolderForm.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true });
await officeFolderModel.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true });
} catch (validationErrors) {
setValidationError(validationErrors as ValidationError[]);
return;
//return;
}
try {
/*
const newOfficeFolder = await Folders.getInstance().post(officeFolderForm);
if (!newOfficeFolder) return;
if (!newOfficeFolder) {
return;
}
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) {
if (!Array.isArray(backError)) return;
setValidationError(backError as ValidationError[]);
@ -100,9 +124,19 @@ export default function CreateFolder(): JSX.Element {
* 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()
.get({ where: { archived_at: null } })
.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
Users.getInstance()
.get({
@ -110,12 +144,15 @@ export default function CreateFolder(): JSX.Element {
})
.then((users) => {
setAvailableCollaborators(users);
/ *
// set default selected collaborators to the connected user
const currentUser = users.find((user) => user.uid === JwtService.getInstance().decodeJwt()?.userId);
if (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: 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(
async (customerUid: string) => {
console.log(customer);
@ -83,6 +89,15 @@ export default function ClientBox(props: IProps) {
</Menu>
)}
</div>
<div>
<Button
size={EButtonSize.SM}
variant={EButtonVariant.WARNING}
styletype={EButtonstyletype.TEXT}
onClick={handleOpenConnectionLink}>
Lien de connexion
</Button>
</div>
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
Numéro de téléphone

View File

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

View File

@ -41,7 +41,9 @@ export default function EmailReminder(props: IProps) {
}, [customer.uid, folderUid]);
useEffect(() => {
/* TODO: review
fetchReminders();
*/
}, [fetchReminders]);
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 { OfficeFolder } from "le-coffre-resources/dist/Notary";
import Link from "next/link";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { AnchorStatus } from "..";
import classes from "./classes.module.scss";
@ -27,23 +27,34 @@ export default function ClientView(props: IProps) {
const { folder, anchorStatus } = props;
const customers: ICustomer[] = useMemo(
() =>
folder?.customers
?.map((customer) => ({
id: customer.uid ?? "",
() => {
// TODO: review
return folder?.customers?.map((customer: any) => customer.processData)
.map((customer: any) => ({
id: customer.uid ?? '',
...customer,
}))
.sort((a, b) => {
.sort((a: any, b: any) => {
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;
}) ?? [],
}) ?? []
},
[folder],
);
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(
() =>
customers.map((customer) => ({
@ -75,7 +86,7 @@ export default function ClientView(props: IProps) {
folder.uid,
OfficeFolder.hydrate<OfficeFolder>({
...folder,
customers: folder.customers?.filter((customer) => customer.uid !== customerUid),
customers: folder.customers?.filter((customer: any) => customer.uid !== customerUid),
}),
);
window.location.reload();
@ -110,7 +121,7 @@ export default function ClientView(props: IProps) {
anchorStatus={anchorStatus}
folderUid={folder.uid}
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"]}>
{anchorStatus === AnchorStatus.NOT_ANCHORED && (

View File

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

View File

@ -6,6 +6,7 @@ import Image from "next/image";
import React, { useCallback, useState } from "react";
import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
type IProps = {
isOpen: boolean;
@ -19,11 +20,37 @@ export default function AnchoringModal(props: IProps) {
const [isAnchoring, setIsAnchoring] = useState(false);
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 timeoutPromise = new Promise((resolve) => {
setTimeout(resolve, timeoutDelay);
});
setIsAnchoring(true);
return OfficeFolderAnchors.getInstance()
.post(folderUid)
.then(() => timeoutPromise)
@ -34,6 +61,7 @@ export default function AnchoringModal(props: IProps) {
console.warn(e);
setIsAnchoring(false);
});
*/
}, [folderUid, onAnchorSuccess, onClose]);
return (

View File

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

View File

@ -15,9 +15,11 @@ import { Contact, Customer } from "le-coffre-resources/dist/Notary";
import Link from "next/link";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import MessageBus from "src/sdk/MessageBus";
import MapUtils from "src/sdk/MapUtils";
export default function UpdateClient() {
const router = useRouter();
const { folderUid, customerUid } = router.query;
@ -35,6 +37,21 @@ export default function UpdateClient() {
useEffect(() => {
const fetchCustomer = async () => {
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, {
contact: {
include: {
@ -45,6 +62,7 @@ export default function UpdateClient() {
if (customerData) {
setCustomer(customerData);
}
*/
} catch (error) {
console.error("Failed to fetch customer", error);
}
@ -71,9 +89,34 @@ export default function UpdateClient() {
});
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 Customers.getInstance().put(customerUid as string, { contact });
router.push(backwardPath);
*/
} catch (validationErrors) {
if (Array.isArray(validationErrors)) {
setValidationError(validationErrors as ValidationError[]);

View File

@ -13,9 +13,10 @@ import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import classes from "./classes.module.scss";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
export default function Folder() {
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
const router = useRouter();
@ -23,6 +24,19 @@ export default function Folder() {
const { user: activeUser } = useUser();
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()
.get({
q: {
@ -38,6 +52,7 @@ export default function Folder() {
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folders[0]?.uid ?? ""),
);
});
*/
}, [router]);
return (
<DefaultNotaryDashboard title={"Dossier"} mobileBackText={"Liste des dossiers"}>

View File

@ -41,12 +41,17 @@ export default function StepEmail(props: IProps) {
const router = useRouter();
const error = router.query["error"];
const redirectUserOnConnection = useCallback(() => {
/* TODO: review
const variables = FrontendVariables.getInstance();
router.push(
`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${
variables.FRONT_APP_HOST
}/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]);
const openErrorModal = useCallback((index: number) => {
@ -94,12 +99,14 @@ export default function StepEmail(props: IProps) {
Pour les clients :
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
{/*
<TextField
placeholder="Renseigner votre email"
label="E-mail"
name="email"
validationError={validationErrors.find((err) => err.property === "email")}
/>
*/}
<Button type="submit">Se connecter</Button>
</Form>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import CookieService from "@Front/Services/CookieService/CookieService";
import EventEmitter from "@Front/Services/EventEmitter";
import JwtService from "@Front/Services/JwtService/JwtService";
import User from "src/sdk/User";
export default class UserStore {
public static readonly instance = new this();
protected readonly event = new EventEmitter();
@ -41,6 +43,8 @@ export default class UserStore {
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
User.getInstance().clear();
this.event.emit("disconnection", this.accessToken);
} catch (error) {
console.error(error);

View File

@ -4,16 +4,16 @@ import CookieService from "@Front/Services/CookieService/CookieService";
import EventEmitter from "@Front/Services/EventEmitter";
import JwtService from "@Front/Services/JwtService/JwtService";
import User from "src/sdk/User";
export default class UserStore {
public static readonly instance = new this();
protected readonly event = new EventEmitter();
public accessToken: string | null = null;
public refreshToken: string | null = null;
private constructor() {}
public isConnected(): boolean {
return !!this.accessToken;
return !!CookieService.getInstance().getCookie("leCoffreAccessToken");
}
public getRole(): string | undefined {
@ -27,7 +27,7 @@ export default class UserStore {
CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken);
CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken);
this.event.emit("connection", this.accessToken);
this.event.emit("connection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) {
console.error(error);
return false;
@ -41,7 +41,9 @@ export default class UserStore {
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
this.event.emit("disconnection", this.accessToken);
User.getInstance().clear();
this.event.emit("disconnection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) {
console.error(error);
}
@ -56,4 +58,12 @@ export default class UserStore {
this.event.on("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 type { NextPage } from "next";
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 { useRouter } from "next/router";
import { GoogleTagManager } from "@next/third-parties/google";
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> & {
getLayout?: (page: ReactElement) => ReactNode;
};
@ -28,6 +34,7 @@ type AppPropsWithLayout = AppProps & {
docaposteApiUrl: string;
hotjarSiteId: number;
hotjarVersion: number;
_4nkUrl: string;
};
const { publicRuntimeConfig } = getConfig();
@ -48,6 +55,7 @@ const MyApp = (({
docaposteApiUrl,
hotjarSiteId,
hotjarVersion,
_4nkUrl,
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>);
@ -64,7 +72,39 @@ const MyApp = (({
instance.FC_CLIENT_ID = fcClientId;
instance.DOCAPOST_API_URL = docaposteApiUrl;
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(() => {
if (!hotjarSiteId || !hotjarVersion) {
console.warn("No hotjar site id or version provided");
@ -78,9 +118,14 @@ const MyApp = (({
}, [hotjarSiteId, hotjarVersion]);
return getLayout(
<>
{((isConnected && isReady) || !isConnected) &&
<Component {...pageProps}>
<GoogleTagManager gtmId="GTM-5GLJN86P" />
</Component>,
</Component>
}
{isConnected && <Iframe />}
</>
);
}) as AppType;
@ -100,6 +145,7 @@ MyApp.getInitialProps = async () => {
docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOST_API_URL,
hotjarSiteId: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_SITE_ID,
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');
}
}