Merge branch 'ajanin' of https://git.4nkweb.com/4nk/lecoffre-front into ajanin
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 4m10s

This commit is contained in:
omaroughriss 2025-09-11 20:08:54 +02:00
commit 4d4afb59c4
107 changed files with 11268 additions and 7022 deletions

41
.github/workflows/dev.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Build and Push to Registry
on:
push:
branches: [ dev ]
env:
REGISTRY: git.4nkweb.com
IMAGE_NAME: 4nk/lecoffre-front
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.USER }}
password: ${{ secrets.TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
ssh: default
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ gitea.sha }}

View File

@ -1,44 +0,0 @@
# Install dependencies only when needed
FROM node:19-alpine AS deps
WORKDIR leCoffre-front
COPY package.json ./
RUN apk update && apk add openssh-client git
COPY id_rsa /root/.ssh/id_rsa
RUN chmod 600 ~/.ssh/id_rsa
RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa
RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts
RUN npm install --frozen-lockfile
# Rebuild the source code only when needed
FROM node:19-alpine AS builder
WORKDIR leCoffre-front
COPY --from=deps leCoffre-front/node_modules ./node_modules
COPY --from=deps leCoffre-front/package.json package.json
COPY tsconfig.json tsconfig.json
COPY src src
RUN npm run build
# Production image, copy all the files and run next
FROM node:19-alpine AS production
WORKDIR leCoffre-front
RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
COPY public ./public
COPY --from=builder --chown=lecoffreuser leCoffre-front/node_modules ./node_modules
COPY --from=builder --chown=lecoffreuser leCoffre-front/.next ./.next
COPY --from=builder --chown=lecoffreuser leCoffre-front/package.json ./package.json
USER lecoffreuser
CMD ["npm", "run", "start"]
EXPOSE 3000

View File

@ -2,6 +2,9 @@
const nextConfig = { const nextConfig = {
reactStrictMode: false, reactStrictMode: false,
typescript: {
ignoreBuildErrors: true,
},
publicRuntimeConfig: { publicRuntimeConfig: {
// Will be available on both server and client // Will be available on both server and client
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL, NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
@ -17,6 +20,7 @@ const nextConfig = {
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, NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
}, },
serverRuntimeConfig: { serverRuntimeConfig: {
@ -33,6 +37,7 @@ const nextConfig = {
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, NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
}, },
env: { env: {
@ -49,6 +54,7 @@ const nextConfig = {
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, NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
}, },
// webpack: config => { // webpack: config => {

11573
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "NEXT_TELEMETRY_DISABLED=1 next build --no-lint",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"format": "prettier --write src" "format": "prettier --write src"
@ -30,8 +30,9 @@
"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": "file:../lecoffre-ressources", "le-coffre-resources": "git+ssh://git@git.4nkweb.com/4nk/lecoffre-ressources.git#v2.167",
"next": "^14.2.3", "next": "^14.2.3",
"pdf-lib": "^1.17.1",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",

View File

@ -0,0 +1,73 @@
import MessageBus from 'src/sdk/MessageBus';
export default abstract class AbstractService {
protected static readonly messageBus: MessageBus = MessageBus.getInstance();
private static readonly CACHE_TTL = 60 * 60 * 1000; // 60 minutes cache TTL
protected constructor() { }
protected static setItem(key: string, process: any): void {
const list: any[] = JSON.parse(sessionStorage.getItem(key) || '[]');
const index: number = list.findIndex((item: any) => item.process.processData.uid === process.processData.uid);
if (index !== -1) {
list[index] = {
process: process,
timestamp: Date.now()
};
} else {
list.push({
process: process,
timestamp: Date.now()
});
}
sessionStorage.setItem(key, JSON.stringify(list));
}
protected static getItem(key: string, uid: string): any {
const list: any[] = JSON.parse(sessionStorage.getItem(key) || '[]');
if (list.length === 0) {
return null;
}
const item: any = list.find((item: any) => item.process.processData.uid === uid);
if (!item) {
return null;
}
const now: number = Date.now();
if ((now - item.timestamp) < this.CACHE_TTL) {
return item.process;
}
return null;
}
protected static getItems(key: string): any[] {
const list: any[] = JSON.parse(sessionStorage.getItem(key) || '[]');
const now: number = Date.now();
const items: any[] = [];
for (const item of list) {
if (now - item.timestamp < this.CACHE_TTL) {
items.push(item.process);
}
}
return items;
}
protected static removeItem(key: string, uid: string): void {
const list: any[] = JSON.parse(sessionStorage.getItem(key) || '[]');
const index: number = list.findIndex((item: any) => item.process.processData.uid === uid);
if (index !== -1) {
list.splice(index, 1);
}
sessionStorage.setItem(key, JSON.stringify(list));
}
}

View File

@ -0,0 +1,316 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import OfficeService from './OfficeService';
import RoleService from './RoleService';
import OfficeRoleService from './OfficeRoleService';
export default class CollaboratorService extends AbstractService {
private constructor() {
super();
}
public static createCollaborator(collaboratorData: any, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'collaborator',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...collaboratorData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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) => {
this.getCollaboratorByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getCollaborators(callback: (processes: any[]) => void, waitForAll: boolean = false): void {
// Check if we have valid cache
const items: any[] = this.getItems('_collaborators_');
if (items.length > 0 && !waitForAll) {
setTimeout(() => callback([...items]), 0);
}
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'collaborator' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
if (waitForAll) {
callback([...items]);
}
return;
}
const updatedItems: any[] = [...items];
for (let processIndex = 0; processIndex < processes.length; processIndex++) {
let process = processes[processIndex];
if (!waitForAll) {
process = await this.completeCollaborator(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeCollaborator(process);
}
// Update cache
this.setItem('_collaborators_', process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid);
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
}
if (waitForAll) {
callback([...updatedItems]);
}
});
}
public static getCollaboratorsBy(whereClause: { [path: string]: any }): Promise<any[]> {
return new Promise<any[]>((resolve: (collaborators: any[]) => void) => {
this.getCollaborators((processes: any[]) => {
if (processes.length === 0) {
resolve([]);
} else {
resolve(processes.filter((process: any) => {
const collaborator: any = process.processData;
for (const path in whereClause) {
const paths: string[] = path.split('.');
let value: any = collaborator;
for (let i = 0; i < paths.length; i++) {
const currentPath = paths[i];
if (!currentPath || value === undefined || value === null) {
break;
}
value = value[currentPath];
}
if (value !== whereClause[path]) {
return false;
}
}
return true;
}));
}
}, true);
});
}
public static getCollaboratorBy(whereClause: { [path: string]: any }): Promise<any | null> {
return new Promise<any | null>((resolve: (collaborator: any | null) => void) => {
this.getCollaborators((processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
resolve(processes.find((process: any) => {
const collaborator: any = process.processData;
for (const path in whereClause) {
const paths: string[] = path.split('.');
let value: any = collaborator;
for (let i = 0; i < paths.length; i++) {
const currentPath = paths[i];
if (!currentPath || value === undefined || value === null) {
break;
}
value = value[currentPath];
}
if (value !== whereClause[path]) {
return false;
}
}
return true;
}));
}
}, true);
});
}
public static getCollaboratorByUid(uid: string, forceRefresh: boolean = false): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_collaborators_', uid);
if (item && !forceRefresh) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'collaborator' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeCollaborator(process);
// Update cache
this.setItem('_collaborators_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateCollaborator(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const collaboratorUid: string = process.processData.uid;
this.removeItem('_collaborators_', collaboratorUid);
this.getCollaboratorByUid(collaboratorUid, true).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
private static async completeCollaborator(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.office) {
const office: any = (await OfficeService.getOfficeByUid(process.processData.office.uid)).processData;
process.processData.office = {
uid: office.uid,
idNot: office.idNot,
crpcen: office.crpcen,
name: office.name,
office_status: office.office_status
};
if (progressCallback) {
progressiveProcess.processData.office = process.processData.office;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.role) {
const role: any = (await RoleService.getRoleByUid(process.processData.role.uid)).processData;
process.processData.role = {
uid: role.uid,
name: role.name
};
if (progressCallback) {
progressiveProcess.processData.role = process.processData.role;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.office_role) {
const officeRole: any = (await OfficeRoleService.getOfficeRoleByUid(process.processData.office_role.uid)).processData;
process.processData.office_role = {
uid: officeRole.uid,
name: officeRole.name,
rules: officeRole.rules?.map((rule: any) => {
return {
uid: rule.uid,
name: rule.name
};
})
};
if (progressCallback) {
progressiveProcess.processData.office_role = process.processData.office_role;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
return process;
}
}

View File

@ -1,16 +1,17 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
export default class CustomerService { import AbstractService from './AbstractService';
private static readonly messageBus: MessageBus = MessageBus.getInstance(); export default class CustomerService extends AbstractService {
private constructor() { } private constructor() {
super();
}
public static createCustomer(customerData: any, validatorId: string): Promise<any> { public static createCustomer(customerData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +19,7 @@ export default class CustomerService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...customerData, ...customerData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -70,7 +71,7 @@ export default class CustomerService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getCustomerByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
@ -78,16 +79,55 @@ export default class CustomerService {
} }
public static getCustomers(): Promise<any[]> { public static getCustomers(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'customer' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_customers_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'customer' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then((processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_customers_', process);
items.push(process);
}
return items;
}
});
} }
public static getCustomerByUid(uid: string): Promise<any> { public static getCustomerByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_customers_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'customer' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'customer' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then((processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; const process: any = processes[0];
// Update cache
this.setItem('_customers_', process);
resolve(process); resolve(process);
} }
}).catch(reject); }).catch(reject);
@ -100,7 +140,10 @@ export default class CustomerService {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve(); const customerUid: string = process.processData.uid;
this.removeItem('_customers_', customerUid);
this.getCustomerByUid(customerUid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);

View File

@ -0,0 +1,52 @@
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
export default class DatabaseService {
// Empêcher l'instanciation de cette classe utilitaire
private constructor() { }
/**
* Récupère les données d'une table avec pagination
* @param tableName Nom de la table à consulter
* @param page Numéro de page (commence à 1)
* @param limit Nombre d'éléments par page
* @returns Données de la table avec pagination
*/
public static async getTableData(tableName: string, page: number = 1, limit: number = 10): Promise<any> {
// Vérification des paramètres
if (!tableName) {
throw new Error('Le nom de la table est requis');
}
// Validation du nom de la table (par sécurité)
const tableNameRegex = /^[a-zA-Z0-9_]+$/;
if (!tableNameRegex.test(tableName)) {
throw new Error('Nom de table invalide');
}
try {
// Construction de l'URL avec paramètres de pagination
const url = `${publicRuntimeConfig.NEXT_PUBLIC_API_URL}/db/${tableName}?page=${page}&limit=${limit}`;
// Appel à l'API REST
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Erreur lors de la récupération des données');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Erreur lors de l\'accès à la base de données:', error);
throw error;
}
}
}

View File

@ -1,18 +1,19 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import DocumentTypeService from './DocumentTypeService'; import DocumentTypeService from './DocumentTypeService';
export default class DeedTypeService { export default class DeedTypeService extends AbstractService {
private static readonly messageBus: MessageBus = MessageBus.getInstance(); private constructor() {
super();
private constructor() { } }
public static createDeedType(deedTypeData: any, validatorId: string): Promise<any> { public static createDeedType(deedTypeData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -20,7 +21,7 @@ export default class DeedTypeService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...deedTypeData, ...deedTypeData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -72,37 +73,102 @@ export default class DeedTypeService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getDeedTypeByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}); });
} }
public static getDeedTypes(): Promise<any[]> { public static getDeedTypes(callback: (processes: any[]) => void, waitForAll: boolean = false): void {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'deedType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_deed_types_');
if (items.length > 0 && !waitForAll) {
setTimeout(() => callback([...items]), 0);
}
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'deedType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
if (waitForAll) {
callback([...items]);
}
return;
}
const updatedItems: any[] = [...items];
for (let processIndex = 0; processIndex < processes.length; processIndex++) {
let process = processes[processIndex];
if (!waitForAll) {
process = await this.completeDeedType(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeDeedType(process);
}
// Update cache
this.setItem('_deed_types_', process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid);
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
}
if (waitForAll) {
callback([...updatedItems]);
}
});
} }
public static getDeedTypeByUid(uid: string, includeDocumentTypes: boolean = true): Promise<any> { public static getDeedTypeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_deed_types_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'deedType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then(async (processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'deedType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; let process: any = processes[0];
process = await this.completeDeedType(process);
if (includeDocumentTypes && process.processData.document_types && process.processData.document_types.length > 0) { // Update cache
process.processData.document_types = await new Promise<any[]>(async (resolve: (document_types: any[]) => void) => { this.setItem('_deed_types_', process);
let document_types: any[] = [];
for (const document_type of process.processData.document_types) {
const p: any = await DocumentTypeService.getDocumentTypeByUid(document_type.uid);
document_types.push(p.processData);
}
// Remove duplicates
document_types = document_types.filter((item: any, index: number) => document_types.findIndex((t: any) => t.uid === item.uid) === index);
resolve(document_types);
});
}
resolve(process); resolve(process);
} }
@ -111,15 +177,46 @@ export default class DeedTypeService {
} }
public static updateDeedType(process: any, newData: any): Promise<void> { public static updateDeedType(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => { return new Promise<void>((resolve: () => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => { this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then(() => {
// Update cache
this.setItem('_deed_types_', process);
resolve(); resolve();
}).catch(reject); }).catch((error) => console.error('Failed to validate state', error));
}).catch(reject); }).catch((error) => console.error('Failed to notify update', error));
}).catch(reject); }).catch((error) => console.error('Failed to update', error));
}); });
} }
private static async completeDeedType(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.document_types && process.processData.document_types.length > 0) {
progressiveProcess.processData.document_types = [];
if (progressCallback) {
progressCallback(progressiveProcess);
}
for (const document_type of process.processData.document_types) {
const documentTypeData = (await DocumentTypeService.getDocumentTypeByUid(document_type.uid)).processData;
progressiveProcess.processData.document_types.push(documentTypeData);
// Remove duplicates
progressiveProcess.processData.document_types = progressiveProcess.processData.document_types
.filter((item: any, index: number) => progressiveProcess.processData.document_types.findIndex((t: any) => t.uid === item.uid) === index);
if (progressCallback) {
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
process.processData.document_types = progressiveProcess.processData.document_types;
}
return process;
}
} }

View File

@ -1,16 +1,17 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
export default class DocumentService { import AbstractService from './AbstractService';
private static readonly messageBus: MessageBus = MessageBus.getInstance(); export default class DocumentService extends AbstractService {
private constructor() { } private constructor() {
super();
}
public static createDocument(documentData: any, validatorId: string): Promise<any> { public static createDocument(documentData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +19,7 @@ export default class DocumentService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...documentData, ...documentData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -70,7 +71,7 @@ export default class DocumentService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getDocumentByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
@ -78,16 +79,55 @@ export default class DocumentService {
} }
public static getDocuments(): Promise<any[]> { public static getDocuments(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'document' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_documents_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'document' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then((processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_documents_', process);
items.push(process);
}
return items;
}
});
} }
public static getDocumentByUid(uid: string): Promise<any> { public static getDocumentByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_documents_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'document' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'document' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then((processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; const process: any = processes[0];
// Update cache
this.setItem('_documents_', process);
resolve(process); resolve(process);
} }
}).catch(reject); }).catch(reject);
@ -100,7 +140,10 @@ export default class DocumentService {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve(); const documentUid: string = process.processData.uid;
this.removeItem('_documents_', documentUid);
this.getDocumentByUid(documentUid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);

View File

@ -1,16 +1,17 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
export default class DocumentTypeService { import AbstractService from './AbstractService';
private static readonly messageBus: MessageBus = MessageBus.getInstance(); export default class DocumentTypeService extends AbstractService {
private constructor() { } private constructor() {
super();
}
public static createDocumentType(documentTypeData: any, validatorId: string): Promise<any> { public static createDocumentType(documentTypeData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +19,7 @@ export default class DocumentTypeService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...documentTypeData, ...documentTypeData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -70,7 +71,7 @@ export default class DocumentTypeService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getDocumentTypeByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
@ -78,16 +79,48 @@ export default class DocumentTypeService {
} }
public static getDocumentTypes(): Promise<any[]> { public static getDocumentTypes(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'documentType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_document_types_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'documentType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_document_types_', process);
items.push(process);
}
return items;
}
});
} }
public static getDocumentTypeByUid(uid: string): Promise<any> { public static getDocumentTypeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_document_types_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'documentType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'documentType' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; const process: any = processes[0];
// Update cache
this.setItem('_document_types_', process);
resolve(process); resolve(process);
} }
}).catch(reject); }).catch(reject);
@ -100,7 +133,10 @@ export default class DocumentTypeService {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve(); const documentTypeUid: string = process.processData.uid;
this.removeItem('_document_types_', documentTypeUid);
this.getDocumentTypeByUid(documentTypeUid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);

View File

@ -3,14 +3,16 @@ import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus'; import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
import { FileData } from '../../../../front/Api/Entities/types';
export default class FileService { export default class FileService {
private static readonly messageBus: MessageBus = MessageBus.getInstance(); private static readonly messageBus: MessageBus = MessageBus.getInstance();
private constructor() { } private constructor() { }
public static createFile(fileData: any, validatorId: string): Promise<any> { public static createFile(fileData: FileData, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +20,7 @@ export default class FileService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...fileData, ...fileData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);

View File

@ -1,19 +1,24 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import CustomerService from './CustomerService'; import CustomerService from './CustomerService';
import CollaboratorService from './CollaboratorService';
import DeedTypeService from './DeedTypeService'; import DeedTypeService from './DeedTypeService';
import DocumentTypeService from './DocumentTypeService';
import DocumentService from './DocumentService';
import FileService from './FileService';
import NoteService from './NoteService';
export default class FolderService { export default class FolderService extends AbstractService {
private static readonly messageBus: MessageBus = MessageBus.getInstance(); private constructor() {
super();
private constructor() { } }
public static createFolder(folderData: any, stakeholdersId: string[], customersId: string[]): Promise<any> { public static createFolder(folderData: any, stakeholdersId: string[], customersId: string[]): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -21,7 +26,7 @@ export default class FolderService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...folderData, ...folderData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -79,42 +84,135 @@ export default class FolderService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getFolderByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}); });
} }
public static getFolders(): Promise<any[]> { public static getFolders(callback: (processes: any[]) => void, waitForAll: boolean = false): void {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'folder' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_folders_');
if (items.length > 0 && !waitForAll) {
setTimeout(() => callback([...items]), 0);
}
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'folder' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
if (waitForAll) {
callback([...items]);
}
return;
}
const updatedItems: any[] = [...items];
for (let processIndex = 0; processIndex < processes.length; processIndex++) {
let process = processes[processIndex];
if (!waitForAll) {
process = await this.completeFolder(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeFolder(process);
}
// Update cache
this.setItem('_folders_', process);
const existingIndex: number = updatedItems.findIndex(item => item.processData?.uid === process.processData?.uid);
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
}
if (waitForAll) {
callback([...updatedItems]);
}
});
} }
public static getFolderByUid(uid: string, includeCustomers: boolean = true, includeDeedType: boolean = true): Promise<any> { public static getFoldersBy(whereClause: { [path: string]: any }): Promise<any[]> {
return new Promise<any[]>((resolve: (folders: any[]) => void) => {
this.getFolders((processes: any[]) => {
if (processes.length === 0) {
resolve([]);
} else {
resolve(processes.filter((process: any) => {
const folder: any = process.processData;
for (const path in whereClause) {
const paths: string[] = path.split('.');
let value: any = folder;
for (let i = 0; i < paths.length; i++) {
const currentPath = paths[i];
if (!currentPath || value === undefined || value === null) {
break;
}
value = value[currentPath];
}
if (value !== whereClause[path]) {
return false;
}
}
return true;
}));
}
}, true);
});
}
public static getFolderByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_folders_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'folder' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then(async (processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'folder' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; let process: any = processes[0];
process = await this.completeFolder(process);
if (includeCustomers && process.processData.customers && process.processData.customers.length > 0) { // Update cache
process.processData.customers = await new Promise<any[]>(async (resolve: (customers: any[]) => void) => { this.setItem('_folders_', process);
const customers: any[] = [];
for (const uid of process.processData.customers) {
const p: any = await CustomerService.getCustomerByUid(uid);
customers.push(p.processData);
}
resolve(customers);
});
}
if (includeDeedType && process.processData.deed && process.processData.deed.deed_type) {
const p: any = await DeedTypeService.getDeedTypeByUid(process.processData.deed.deed_type.uid);
process.processData.deed.deed_type = p.processData;
// Remove duplicates
process.processData.deed.document_types = p.processData.document_types.filter((item: any, index: number) => p.processData.document_types.findIndex((t: any) => t.uid === item.uid) === index);
}
resolve(process); resolve(process);
} }
@ -128,10 +226,103 @@ export default class FolderService {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve(); const folderUid: string = process.processData.uid;
this.removeItem('_folders_', folderUid);
this.getFolderByUid(folderUid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}); });
} }
public static refreshFolderByUid(uid: string): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.getFolderByUid(uid).then((process: any) => {
this.updateFolder(process, {}).then(resolve).catch(reject);
}).catch(reject);
});
}
private static async completeFolder(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.customers && process.processData.customers.length > 0) {
process.processData.customers = await new Promise<any[]>(async (resolve: (customers: any[]) => void) => {
const customers: any[] = [];
for (const customer of process.processData.customers) {
customers.push((await CustomerService.getCustomerByUid(customer.uid)).processData);
}
resolve(customers);
});
if (process.processData.customers && process.processData.customers.length > 0) {
const documents: any[] = (await DocumentService.getDocuments()).map((process: any) => process.processData);
for (const customer of process.processData.customers) {
customer.documents = documents.filter((document: any) => (document.depositor && document.depositor.uid === customer.uid) || (document.customer && document.customer.uid === customer.uid));
for (const document of customer.documents) {
if (document.document_type) {
document.document_type = (await DocumentTypeService.getDocumentTypeByUid(document.document_type.uid)).processData;
}
if (document.files && document.files.length > 0) {
const files: any[] = [];
for (const file of document.files) {
files.push({ uid: file.uid, file_name: (await FileService.getFileByUid(file.uid)).processData.file_name });
}
document.files = files;
}
}
}
}
if (progressCallback) {
progressiveProcess.processData.customers = process.processData.customers;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.stakeholders && process.processData.stakeholders.length > 0) {
process.processData.stakeholders = await new Promise<any[]>(async (resolve: (stakeholders: any[]) => void) => {
const stakeholders: any[] = [];
for (const stakeholder of process.processData.stakeholders) {
stakeholders.push((await CollaboratorService.getCollaboratorByUid(stakeholder.uid)).processData);
}
resolve(stakeholders);
});
if (progressCallback) {
progressiveProcess.processData.stakeholders = process.processData.stakeholders;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
if (process.processData.deed && process.processData.deed.deed_type) {
const deed_type: any = (await DeedTypeService.getDeedTypeByUid(process.processData.deed.deed_type.uid)).processData;
process.processData.deed.deed_type = deed_type;
if (deed_type.document_types && deed_type.document_types.length > 0) {
// Remove duplicates
process.processData.deed.document_types = deed_type.document_types.filter((item: any, index: number) => deed_type.document_types.findIndex((t: any) => t.uid === item.uid) === index);
}
if (progressCallback) {
progressiveProcess.processData.deed = process.processData.deed;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
const notes: any[] = (await NoteService.getNotes()).map((process: any) => process.processData);
if (notes.length > 0) {
process.processData.notes = notes.filter((note: any) => note.folder.uid === process.processData.uid);
if (progressCallback) {
progressiveProcess.processData.notes = process.processData.notes;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
return process;
}
} }

View File

@ -0,0 +1,379 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import MessageBus from 'src/sdk/MessageBus';
import DatabaseService from './DatabaseService';
import RuleService from './RuleService';
import RuleGroupService from './RuleGroupService';
import RoleService from './RoleService';
import OfficeRoleService from './OfficeRoleService';
/**
* Type pour le callback de progression
*/
export interface ProgressInfo {
/** Progression globale (0-100) */
globalProgress: number;
/** Nom de l'étape en cours */
currentStep: string;
/** Progression de l'étape en cours (0-100) */
stepProgress: number;
/** Description optionnelle de l'action en cours */
description?: string;
}
export default class ImportData {
protected static readonly messageBus: MessageBus = MessageBus.getInstance();
public static async import(office: any, validatorId: string, onProgress?: (info: ProgressInfo) => void): Promise<void> {
// Définir les étapes d'importation dynamiquement
const importSteps = [
{
name: 'Règles',
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
await this.importRules(progressCallback)
},
{
name: 'Groupes de règles',
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
await this.importRuleGroups(progressCallback)
},
{
name: 'Rôles',
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
await this.importRoles(progressCallback)
},
{
name: 'Rôles d\'office',
function: async (progressCallback?: (subProgress: number, description?: string) => void, prevResults?: any[]) =>
await this.importOfficeRoles(office, prevResults![1], progressCallback)
}
];
// Calculer la part de progression pour chaque étape
const totalSteps = importSteps.length;
const stepWeight = 100 / totalSteps;
// Appel du callback avec 0% au début
onProgress?.({
globalProgress: 0,
currentStep: 'Initialisation',
stepProgress: 0,
description: 'Début de l\'importation des données'
});
// Exécuter chaque étape d'importation séquentiellement
const results: any[] = [];
for (let i = 0; i < importSteps.length; i++) {
const step = importSteps[i];
if (!step) continue; // S'assurer que l'étape existe
const startProgress = i * stepWeight;
// Créer un callback de progression pour cette étape
const stepProgressCallback = (subProgress: number, description?: string) => {
onProgress?.({
globalProgress: startProgress + (subProgress * stepWeight / 100),
currentStep: step.name,
stepProgress: subProgress,
description
});
};
// Exécuter l'étape et stocker le résultat si nécessaire
const result = await step.function(stepProgressCallback, results);
if (result) {
results.push(result);
}
}
if (!await this.isDone()) {
await this.done(validatorId);
}
}
public static async isDone(): Promise<boolean> {
return await this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'importData' &&
publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => processes.length > 0);
}
private static async done(validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'importData',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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<void>((resolve: () => 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(() => {
resolve();
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
private static async importRules(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
const rules: any[] = [];
const INIT_PROGRESS = 0;
const FETCH_PROGRESS = 30;
const CREATE_END_PROGRESS = 90;
const FINAL_PROGRESS = 100;
onProgress?.(INIT_PROGRESS, 'Initialisation');
let page = 1;
let limit = 10;
let totalPages = 1;
onProgress?.(FETCH_PROGRESS, 'Récupération des règles existantes');
let result = await DatabaseService.getTableData('rules', page, limit);
if (result && result.success && result.pagination) {
totalPages = result.pagination.totalPages || 1;
}
const FETCH_PAGE_PROGRESS_START = FETCH_PROGRESS;
const FETCH_PAGE_PROGRESS_END = 60;
const CREATE_START_PROGRESS = 60;
while (result && result.success) {
const fetchPageProgress = FETCH_PAGE_PROGRESS_START + ((page / totalPages) * (FETCH_PAGE_PROGRESS_END - FETCH_PAGE_PROGRESS_START));
onProgress?.(fetchPageProgress, `Page ${page}/${totalPages} : Récupération des règles`);
const existingRules: any[] = (await RuleService.getRules()).map((process: any) => process.processData);
const filteredRules: any[] = result.data.filter((rule: any) => !existingRules.some((existingRule: any) => existingRule.uid === rule.uid));
const totalFilteredRules = filteredRules.length;
for (let i = 0; i < totalFilteredRules; i++) {
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
rules.push((await RuleService.createRule(filteredRules[i], validatorId)).processData);
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
const ruleProgressIncrement = progressRange / (totalFilteredRules * totalPages);
const progress = CREATE_START_PROGRESS + ((page - 1) * totalFilteredRules + i + 1) * ruleProgressIncrement;
onProgress?.(progress, `Page ${page}/${totalPages} : Création de la règle ${i + 1}/${totalFilteredRules} - ${filteredRules[i].label}`);
}
if (!result.pagination.hasNextPage) {
break;
}
page++;
result = await DatabaseService.getTableData('rules', page, limit);
}
onProgress?.(FINAL_PROGRESS, 'Importation des règles terminée');
return rules;
}
private static async importRuleGroups(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
const ruleGroups: any[] = [];
const INIT_PROGRESS = 0;
const FETCH_PROGRESS = 30;
const CREATE_END_PROGRESS = 90;
const FINAL_PROGRESS = 100;
onProgress?.(INIT_PROGRESS, 'Initialisation');
let page = 1;
let limit = 10;
let totalPages = 1;
onProgress?.(FETCH_PROGRESS, 'Récupération des groupes de règles existants');
let result = await DatabaseService.getTableData('rules_groups', page, limit);
if (result && result.success && result.pagination) {
totalPages = result.pagination.totalPages || 1;
}
const FETCH_PAGE_PROGRESS_START = FETCH_PROGRESS;
const FETCH_PAGE_PROGRESS_END = 60;
const CREATE_START_PROGRESS = 60;
while (result && result.success) {
const fetchPageProgress = FETCH_PAGE_PROGRESS_START + ((page / totalPages) * (FETCH_PAGE_PROGRESS_END - FETCH_PAGE_PROGRESS_START));
onProgress?.(fetchPageProgress, `Page ${page}/${totalPages} : Récupération du groupe de règles`);
const existingRuleGroups: any[] = (await RuleGroupService.getRuleGroups()).map((process: any) => process.processData);
const filteredRuleGroups: any[] = result.data.filter((rule: any) => !existingRuleGroups.some((existingRule: any) => existingRule.uid === rule.uid));
const totalFilteredRuleGroups = filteredRuleGroups.length;
for (let i = 0; i < totalFilteredRuleGroups; i++) {
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
ruleGroups.push((await RuleGroupService.createRuleGroup(filteredRuleGroups[i], validatorId)).processData);
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
const ruleProgressIncrement = progressRange / (totalFilteredRuleGroups * totalPages);
const progress = CREATE_START_PROGRESS + ((page - 1) * totalFilteredRuleGroups + i + 1) * ruleProgressIncrement;
onProgress?.(progress, `Page ${page}/${totalPages} : Création du groupe de règles ${i + 1}/${totalFilteredRuleGroups} - ${filteredRuleGroups[i].label}`);
}
if (!result.pagination.hasNextPage) {
break;
}
page++;
result = await DatabaseService.getTableData('rules_groups', page, limit);
}
onProgress?.(FINAL_PROGRESS, 'Importation des groupes de règles terminée');
return ruleGroups;
}
private static async importRoles(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
// Constantes de progression - pourraient être paramétrées
const INIT_PROGRESS = 0;
const FETCH_PROGRESS = 30;
const CREATE_START_PROGRESS = FETCH_PROGRESS;
const CREATE_END_PROGRESS = 90;
const FINAL_PROGRESS = 100;
onProgress?.(INIT_PROGRESS, 'Initialisation');
return await new Promise<any[]>((resolve: (roles: any[]) => void) => {
const defaultRoles: any[] = [
{
name: 'super-admin',
label: 'Super administrateur'
},
{
name: 'admin',
label: 'Administrateur'
},
{
name: 'notary',
label: 'Notaire'
},
{
name: 'default',
label: 'Utilisateur'
}
];
RoleService.getRoles().then(async (processes: any[]) => {
onProgress?.(FETCH_PROGRESS, 'Récupération des rôles existants');
const roles: any[] = processes.map((process: any) => process.processData);
if (roles.length === 0) {
const totalRoles = defaultRoles.length;
for (let i = 0; i < totalRoles; i++) {
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
roles.push((await RoleService.createRole(defaultRoles[i], validatorId)).processData);
// Progression dynamique pendant la création des rôles
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
const progress = CREATE_START_PROGRESS + ((i + 1) / totalRoles) * progressRange;
onProgress?.(progress, `Création du rôle ${i + 1}/${totalRoles} : ${defaultRoles[i].label}`);
}
}
onProgress?.(FINAL_PROGRESS, 'Importation des rôles terminée');
resolve(roles);
});
});
}
private static async importOfficeRoles(office: any, ruleGroups: any[], onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
// Constantes de progression - pourraient être paramétrées
const INIT_PROGRESS = 0;
const FETCH_PROGRESS = 30;
const CREATE_START_PROGRESS = FETCH_PROGRESS;
const CREATE_END_PROGRESS = 90;
const FINAL_PROGRESS = 100;
onProgress?.(INIT_PROGRESS, 'Initialisation');
return await new Promise<any[]>((resolve: (roles: any[]) => void) => {
const collaboratorRules: any[] = ruleGroups
.map((ruleGroup: any) => ruleGroup.rules)
.reduce((acc: any, curr: any) => [...acc, ...curr], [])
.map((rule: any) => ({ uid: rule.uid }));
const defaultOfficeRoles: any[] = [
{
name: 'Notaire',
office: {
uid: office.uid
},
rules: collaboratorRules
},
{
name: 'Collaborateur',
office: {
uid: office.uid
}
}
];
OfficeRoleService.getOfficeRoles().then(async (processes: any[]) => {
onProgress?.(FETCH_PROGRESS, 'Récupération des rôles d\'office existants');
const officeRoles: any[] = processes.map((process: any) => process.processData);
if (officeRoles.length === 0) {
const totalOfficeRoles = defaultOfficeRoles.length;
for (let i = 0; i < totalOfficeRoles; i++) {
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
officeRoles.push((await OfficeRoleService.createOfficeRole(defaultOfficeRoles[i], validatorId)).processData);
// Progression dynamique pendant la création des rôles d'office
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
const progress = CREATE_START_PROGRESS + ((i + 1) / totalOfficeRoles) * progressRange;
onProgress?.(progress, `Création du rôle d'office ${i + 1}/${totalOfficeRoles} : ${defaultOfficeRoles[i].name}`);
}
}
onProgress?.(FINAL_PROGRESS, 'Importation des rôles d\'office terminée');
resolve(officeRoles);
});
});
}
}

View File

@ -10,7 +10,7 @@ export default class NoteService {
private constructor() { } private constructor() { }
public static createNote(noteData: any, validatorId: string): Promise<any> { public static createNote(noteData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +18,7 @@ export default class NoteService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...noteData, ...noteData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);

View File

@ -0,0 +1,98 @@
import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User';
import { FileData } from '../../../../front/Api/Entities/types';
export default class OfficeRibService {
private static readonly messageBus: MessageBus = MessageBus.getInstance();
private constructor() { }
public static createOfficeRib(fileData: FileData, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'officeRib',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...fileData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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 getOfficeRib(): Promise<any> {
return this.messageBus.getFileByUtype('officeRib');
}
public static updateOfficeRib(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve();
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
}

View File

@ -0,0 +1,179 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import OfficeService from './OfficeService';
import RuleService from './RuleService';
export default class OfficeRoleService extends AbstractService {
private constructor() {
super();
}
public static createOfficeRole(roleData: any, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'officeRole',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...roleData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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) => {
this.getOfficeRoleByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getOfficeRoles(): Promise<any[]> {
// Check if we have valid cache
const items: any[] = this.getItems('_office_roles_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'officeRole' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (let process of processes) {
process = await this.completeOfficeRole(process);
// Update cache
this.setItem('_office_roles_', process);
items.push(process);
}
return items;
}
});
}
public static getOfficeRoleByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_office_roles_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'officeRole' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeOfficeRole(process);
// Update cache
this.setItem('_office_roles_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateOfficeRole(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const roleUid: string = process.processData.uid;
this.removeItem('_office_roles_', roleUid);
this.getOfficeRoleByUid(roleUid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
private static async completeOfficeRole(process: any): Promise<any> {
if (process.processData.office) {
process.processData.office = await new Promise<any>(async (resolve: (office: any) => void) => {
const office: any = (await OfficeService.getOfficeByUid(process.processData.office.uid)).processData;
resolve(office);
});
}
if (process.processData.rules && process.processData.rules.length > 0) {
process.processData.rules = await new Promise<any[]>(async (resolve: (rules: any[]) => void) => {
const rules: any[] = [];
for (const rule of process.processData.rules) {
rules.push((await RuleService.getRuleByUid(rule.uid)).processData);
}
resolve(rules);
});
}
return process;
}
}

View File

@ -0,0 +1,152 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
export default class OfficeService extends AbstractService {
private constructor() {
super();
}
public static createOffice(officeData: any, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'office',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...officeData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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) => {
this.getOfficeByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getOffices(): Promise<any[]> {
// Check if we have valid cache
const items: any[] = this.getItems('_offices_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'office' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then((processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_offices_', process);
items.push(process);
}
return items;
}
});
}
public static getOfficeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_offices_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'office' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then((processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
const process: any = processes[0];
// Update cache
this.setItem('_offices_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateDocument(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const officeUid: string = process.processData.uid;
this.removeItem('_offices_', officeUid);
this.getOfficeByUid(officeUid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
}

View File

@ -1,16 +1,17 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import MessageBus from 'src/sdk/MessageBus';
import User from 'src/sdk/User'; import User from 'src/sdk/User';
export default class RoleService { import AbstractService from './AbstractService';
private static readonly messageBus: MessageBus = MessageBus.getInstance(); export default class RoleService extends AbstractService {
private constructor() { } private constructor() {
super();
}
public static createRole(roleData: any, validatorId: string): Promise<any> { public static createRole(roleData: any, validatorId: string): Promise<any> {
const ownerId = User.getInstance().getPairingId()!; const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = { const processData: any = {
uid: uuidv4(), uid: uuidv4(),
@ -18,7 +19,7 @@ export default class RoleService {
isDeleted: 'false', isDeleted: 'false',
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
...roleData, ...roleData
}; };
const privateFields: string[] = Object.keys(processData); const privateFields: string[] = Object.keys(processData);
@ -70,7 +71,7 @@ export default class RoleService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => { this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => { 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) => { this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
resolve(processCreated); this.getRoleByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
@ -78,16 +79,55 @@ export default class RoleService {
} }
public static getRoles(): Promise<any[]> { public static getRoles(): Promise<any[]> {
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'role' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'); // Check if we have valid cache
const items: any[] = this.getItems('_roles_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'role' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_roles_', process);
items.push(process);
}
return items;
}
});
} }
public static getRoleByUid(uid: string): Promise<any> { public static getRoleByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_roles_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => { return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'role' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then(async (processes: any[]) => { this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'role' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) { if (processes.length === 0) {
resolve(null); resolve(null);
} else { } else {
const process: any = processes[0]; const process: any = processes[0];
// Update cache
this.setItem('_roles_', process);
resolve(process); resolve(process);
} }
}).catch(reject); }).catch(reject);
@ -100,7 +140,10 @@ export default class RoleService {
const newStateId: string = processUpdated.diffs[0]?.state_id; const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => { this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => { this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
resolve(); const roleUid: string = process.processData.uid;
this.removeItem('_roles_', roleUid);
this.getRoleByUid(roleUid).then(resolve).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);
}).catch(reject); }).catch(reject);

View File

@ -0,0 +1,171 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import RuleService from './RuleService';
export default class RuleGroupService extends AbstractService {
private constructor() {
super();
}
public static createRuleGroup(ruleGroupData: any, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'ruleGroup',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...ruleGroupData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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) => {
this.getRuleGroupByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getRuleGroups(): Promise<any[]> {
// Check if we have valid cache
const items: any[] = this.getItems('_rule_groups_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'ruleGroup' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (let process of processes) {
process = await this.completeRuleGroup(process);
// Update cache
this.setItem('_rule_groups_', process);
items.push(process);
}
return items;
}
});
}
public static getRuleGroupByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_rule_groups_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'ruleGroup' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeRuleGroup(process);
// Update cache
this.setItem('_rule_groups_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateRuleGroup(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const ruleUid: string = process.processData.uid;
this.removeItem('_rule_groups_', ruleUid);
this.getRuleGroupByUid(ruleUid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
private static async completeRuleGroup(process: any): Promise<any> {
if (process.processData.rules && process.processData.rules.length > 0) {
process.processData.rules = await new Promise<any[]>(async (resolve: (rules: any[]) => void) => {
const rules: any[] = [];
for (const rule of process.processData.rules) {
rules.push((await RuleService.getRuleByUid(rule.uid)).processData);
}
resolve(rules);
});
}
return process;
}
}

View File

@ -0,0 +1,152 @@
import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
export default class RuleService extends AbstractService {
private constructor() {
super();
}
public static createRule(ruleData: any, validatorId: string): Promise<any> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
uid: uuidv4(),
utype: 'rule',
isDeleted: 'false',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
...ruleData
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
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: (processCreated: 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) => {
this.getRuleByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getRules(): Promise<any[]> {
// Check if we have valid cache
const items: any[] = this.getItems('_rules_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'rule' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then((processes: any[]) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_rules_', process);
items.push(process);
}
return items;
}
});
}
public static getRuleByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_rules_', uid);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'rule' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then((processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
const process: any = processes[0];
// Update cache
this.setItem('_rules_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateRule(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const ruleUid: string = process.processData.uid;
this.removeItem('_rules_', ruleUid);
this.getRuleByUid(ruleUid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
}

View File

@ -40,15 +40,15 @@ export default class Auth extends BaseApiService {
} }
} }
public async getIdnotJwt(autorizationCode: string | string[]): Promise<{ accessToken: string; refreshToken: string }> { public async getIdNotUser(autorizationCode: string | string[]): Promise<{ idNotUser: any }> {
const variables = FrontendVariables.getInstance(); // const variables = FrontendVariables.getInstance();
// TODO: review // TODO: review
const baseBackUrl = 'http://localhost:8080'//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST; 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<{ idNotUser: any }>(url);
} catch (err) { } catch (err) {
this.onError(err); this.onError(err);
return Promise.reject(err); return Promise.reject(err);

View File

@ -56,7 +56,7 @@ export default abstract class BaseApiService {
} }
protected async postRequest<T>(url: URL, body: { [key: string]: unknown } = {}, token?: string) { protected async postRequest<T>(url: URL, body: { [key: string]: unknown } = {}, token?: string) {
await this.checkJwtToken(); //await this.checkJwtToken();
return this.sendRequest<T>( return this.sendRequest<T>(
async () => async () =>
await fetch(url, { await fetch(url, {

View File

@ -0,0 +1,2 @@
export * from './types';
export * from './rule';

View File

@ -0,0 +1,21 @@
/**
* FileBlob interface representing a file's binary data and metadata
* Used for file transmission and storage in the application
*/
export interface FileBlob {
/** MIME type of the file (e.g., "application/pdf", "image/jpeg") */
type: string;
/** Binary data of the file as Uint8Array */
data: Uint8Array;
}
/**
* FileData interface representing a complete file object with blob and metadata
* Used when creating or updating files in the system
*/
export interface FileData {
/** The file blob containing type and binary data */
file_blob: FileBlob;
/** The name of the file */
file_name: string;
}

View File

@ -88,10 +88,15 @@ export default class Customers extends BaseNotary {
} }
} }
public async sendReminder(uid: string, documentsUid: string[]): Promise<void> { public async sendReminder(office: any, customer: any): Promise<void> {
const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`)); // TODO: review
const baseBackUrl = 'http://localhost:8080';//variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
const url = new URL(`${baseBackUrl}/api/send_reminder`);
//const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`));
try { try {
await this.postRequest<void>(url, { documentsUid }); await this.postRequest<void>(url, { office, customer });
} catch (err) { } catch (err) {
this.onError(err); this.onError(err);
return Promise.reject(err); return Promise.reject(err);

View File

@ -16,9 +16,12 @@ import Button, { EButtonstyletype, EButtonVariant } from "../Button";
import Confirm from "../OldModal/Confirm"; import Confirm from "../OldModal/Confirm";
import Alert from "../OldModal/Alert"; import Alert from "../OldModal/Alert";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import WatermarkService from "@Front/Services/WatermarkService";
type IProps = { type IProps = {
onChange?: (files: File[]) => void; onChange?: (files: File[]) => void;
@ -238,46 +241,65 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
return; return;
} }
const customer: any = await new Promise<void>((resolve: (customer: any) => void) => {
CustomerService.getCustomerByUid(this.props.customer_uid).then((process: any) => {
if (process) {
const customer: any = process.processData;
resolve(customer);
}
});
});
for (let i = 0; i < filesArray.length; i++) { for (let i = 0; i < filesArray.length; i++) {
const file = filesArray[i]!.file; const file = filesArray[i]!.file;
await new Promise<void>((resolve: () => void) => { await new Promise<void>((resolve: () => void) => {
const reader = new FileReader(); // Add watermark to the file before processing
reader.onload = (event) => { WatermarkService.getInstance().addWatermark(file).then(async (watermarkedFile) => {
if (event.target?.result) { const reader = new FileReader();
const arrayBuffer = event.target.result as ArrayBuffer; reader.onload = (event) => {
const uint8Array = new Uint8Array(arrayBuffer); if (event.target?.result) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
const fileBlob: any = { const fileName: string = `${file.name.split('.')[0]}_${strDate}.${file.name.split('.').pop()}`;
type: file.type,
data: uint8Array
};
const fileData: any = { const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
file_blob: fileBlob, const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
file_name: file.name
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => { const fileBlob: any = {
const fileUid: string = processCreated.processData.uid; type: watermarkedFile.type,
data: uint8Array
};
DocumentService.getDocumentByUid(documentCreated.uid).then((process: any) => { const fileData: any = {
if (process) { file_blob: fileBlob,
const document: any = process.processData; file_name: fileName
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
let files: any[] = document.files; FileService.createFile(fileData, validatorId).then((processCreated: any) => {
if (!files) { const fileUid: string = processCreated.processData.uid;
files = [];
DocumentService.getDocumentByUid(documentCreated.uid).then((process: any) => {
if (process) {
const document: any = process.processData;
let files: any[] = document.files;
if (!files) {
files = [];
}
files.push({ uid: fileUid });
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
});
} }
files.push({ uid: fileUid }); });
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => resolve());
}
}); });
}); }
} };
}; reader.readAsArrayBuffer(watermarkedFile);
reader.readAsArrayBuffer(file); });
}); });
} }

View File

@ -213,9 +213,9 @@ export default function DragAndDrop(props: IProps) {
</div> </div>
{documentFiles.length > 0 && ( {documentFiles.length > 0 && (
<div className={classes["documents"]}> <div className={classes["documents"]}>
{documentFiles.map((documentFile) => ( {documentFiles.map((documentFile, index) => (
<DocumentFileElement <DocumentFileElement
key={documentFile.id} key={documentFile.uid || `${documentFile.id}-${index}`}
isLoading={documentFile.isLoading} isLoading={documentFile.isLoading}
file={documentFile.file} file={documentFile.file}
onRemove={() => handleRemove(documentFile)} onRemove={() => handleRemove(documentFile)}

View File

@ -1,6 +1,4 @@
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
import Notifications from "@Front/Api/LeCoffreApi/Notary/Notifications/Notifications";
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import Toasts from "@Front/Stores/Toasts"; import Toasts from "@Front/Stores/Toasts";
@ -29,7 +27,7 @@ export default function Navigation() {
}, },
}); });
*/ */
const anchors = [] as any[]; // const anchors = [] as any[];
/* TODO: review /* TODO: review
try { try {

View File

@ -8,6 +8,7 @@ import MenuItem from "@Front/Components/DesignSystem/Menu/MenuItem";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
closeModal: () => void; closeModal: () => void;
isCustomer?: boolean;
}; };
type IState = {}; type IState = {};
@ -19,29 +20,31 @@ export default class ProfileModal extends React.Component<IProps, IState> {
<> <>
<div className={classes["background"]} onClick={this.props.closeModal} /> <div className={classes["background"]} onClick={this.props.closeModal} />
<div className={classes["root"]}> <div className={classes["root"]}>
<MenuItem {!this.props.isCustomer && <MenuItem
item={{ item={{
text: "Mon compte", text: "Mon compte",
link: Module.getInstance().get().modules.pages.MyAccount.props.path, link: Module.getInstance().get().modules.pages.MyAccount.props.path,
}} }}
/> />}
<MenuItem {!this.props.isCustomer && <MenuItem
item={{ item={{
text: "Guide de Prise en Main", text: "Guide de Prise en Main",
link: "https://ressources.lecoffre.io/", link: "https://ressources.lecoffre.io/",
target: "_blank", target: "_blank",
}} }}
/> />}
<MenuItem
{!this.props.isCustomer && <MenuItem
item={{ item={{
text: "CGU", text: "CGU",
link: "/CGU_LeCoffre_io.pdf", link: "/CGU_LeCoffre_io.pdf",
hasSeparator: true, hasSeparator: true,
}} }}
/> />}
<LogOutButton />
<LogOutButton isCustomer={this.props.isCustomer} />
</div> </div>
</> </>
); );

View File

@ -9,7 +9,7 @@ import ProfileModal from "./ProfileModal";
const headerBreakpoint = 1023; const headerBreakpoint = 1023;
export default function Profile() { export default function Profile(props: { isCustomer?: boolean }) {
const { isOpen, toggle, close } = useOpenable(); const { isOpen, toggle, close } = useOpenable();
useEffect(() => { useEffect(() => {
@ -27,7 +27,7 @@ export default function Profile() {
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<IconButton icon={<UserIcon />} onClick={toggle} /> <IconButton icon={<UserIcon />} onClick={toggle} />
<ProfileModal isOpen={isOpen} closeModal={close} /> <ProfileModal isOpen={isOpen} closeModal={close} isCustomer={props.isCustomer} />
</div> </div>
); );
} }

View File

@ -1,17 +1,16 @@
import LogoIcon from "@Assets/logo_standard_neutral.svg"; import LogoIcon from "@Assets/logo_standard_neutral.svg";
import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe"; // import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions"; // import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService"; // import JwtService from "@Front/Services/JwtService/JwtService";
import { InformationCircleIcon, LifebuoyIcon } from "@heroicons/react/24/outline"; import { LifebuoyIcon } from "@heroicons/react/24/outline";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; 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 React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect } from "react";
import IconButton from "../IconButton"; import IconButton from "../IconButton";
import Typography, { ETypo, ETypoColor } from "../Typography";
import BurgerMenu from "./BurgerMenu"; import BurgerMenu from "./BurgerMenu";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import LogoCielNatureIcon from "./logo-ciel-notaires.jpeg"; import LogoCielNatureIcon from "./logo-ciel-notaires.jpeg";
@ -21,18 +20,19 @@ import Profile from "./Profile";
type IProps = { type IProps = {
isUserConnected: boolean; isUserConnected: boolean;
isCustomer?: boolean;
}; };
const headerHeight = 75; const headerHeight = 75;
export default function Header(props: IProps) { export default function Header(props: IProps) {
const { isUserConnected } = props; const { isUserConnected, isCustomer } = props;
const router = useRouter(); const router = useRouter();
const { pathname } = router; const { pathname } = router;
const isOnCustomerLoginPage = Module.getInstance().get().modules.pages.CustomersLogin.props.path === pathname; const isOnCustomerLoginPage = Module.getInstance().get().modules.pages.CustomersLogin.props.path === pathname;
const [cancelAt, setCancelAt] = useState<Date | null>(null); // const [cancelAt, setCancelAt] = useState<Date | null>(null);
const loadSubscription = useCallback(async () => { const loadSubscription = useCallback(async () => {
/* TODO: review /* TODO: review
@ -63,7 +63,7 @@ export default function Header(props: IProps) {
<Image src={LogoIcon} alt="logo" className={classes["logo"]} /> <Image src={LogoIcon} alt="logo" className={classes["logo"]} />
</Link> </Link>
</div> </div>
{isUserConnected && ( {isUserConnected && !isCustomer && (
<> <>
<div className={classes["desktop"]}> <div className={classes["desktop"]}>
<Navigation /> <Navigation />
@ -84,9 +84,14 @@ export default function Header(props: IProps) {
</div> </div>
</> </>
)} )}
{isCustomer && (
<div className={classes["desktop"]}>
<Profile isCustomer={isCustomer} />
</div>
)}
{isOnCustomerLoginPage && <Image width={70} height={70} alt="ciel-nature" src={LogoCielNatureIcon}></Image>} {isOnCustomerLoginPage && <Image width={70} height={70} alt="ciel-nature" src={LogoCielNatureIcon}></Image>}
</div> </div>
{cancelAt && ( {/* {cancelAt && (
<div className={classes["subscription-line"]}> <div className={classes["subscription-line"]}>
<InformationCircleIcon height="24" /> <InformationCircleIcon height="24" />
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}> <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}>
@ -94,7 +99,7 @@ export default function Header(props: IProps) {
{cancelAt.toLocaleDateString()}. {cancelAt.toLocaleDateString()}.
</Typography> </Typography>
</div> </div>
)} )} */}
</> </>
); );
} }

View File

@ -6,14 +6,18 @@ import React, { useCallback } from "react";
import MenuItem from "../Menu/MenuItem"; import MenuItem from "../Menu/MenuItem";
export default function LogOut() { export default function LogOut(props: { isCustomer?: boolean }) {
const router = useRouter(); const router = useRouter();
const variables = FrontendVariables.getInstance(); const variables = FrontendVariables.getInstance();
const disconnect = useCallback(() => { const disconnect = useCallback(() => {
UserStore.instance if (!props.isCustomer) {
.disconnect() UserStore.instance
.then(() => router.push(`https://qual-connexion.idnot.fr/user/auth/logout?sourceURL=${variables.FRONT_APP_HOST}`)); .disconnect()
.then(() => router.push(`https://qual-connexion.idnot.fr/user/auth/logout?sourceURL=${variables.FRONT_APP_HOST}`));
} else {
router.push("/");
}
}, [router, variables.FRONT_APP_HOST]); }, [router, variables.FRONT_APP_HOST]);
return <MenuItem item={{ text: "Déconnexion", icon: <PowerIcon />, onClick: disconnect }} />; return <MenuItem item={{ text: "Déconnexion", icon: <PowerIcon />, onClick: disconnect }} />;

View File

@ -4,6 +4,8 @@ import { IAppRule } from "@Front/Api/Entities/rule";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import UserStore from "@Front/Stores/UserStore";
export enum RulesMode { export enum RulesMode {
OPTIONAL = "optional", OPTIONAL = "optional",
NECESSARY = "necessary", NECESSARY = "necessary",
@ -20,19 +22,23 @@ export default function Rules(props: IProps) {
const router = useRouter(); const router = useRouter();
const [isShowing, setIsShowing] = React.useState(false); const [isShowing, setIsShowing] = React.useState(false);
const [hasJwt, setHasJwt] = React.useState(false); // const [hasJwt, setHasJwt] = React.useState(false);
const getShowValue = useCallback(() => { const getShowValue = useCallback(() => {
//const user: any = UserStore.instance.getUser();
if (props.mode === RulesMode.NECESSARY) { if (props.mode === RulesMode.NECESSARY) {
//return user.isAdmin && user.isAdmin === 'true';
return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action)); return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
} }
//const ruleNames: string[] = props.rules.map((rule: any) => rule.name);
//return user.role.rules.map((rule: any) => rule.name).filter((ruleName: string) => ruleNames.includes(ruleName)).length > 0;
return props.rules.length === 0 || !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action)); return props.rules.length === 0 || !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
}, [props.mode, props.rules]); }, [props.mode, props.rules]);
useEffect(() => { useEffect(() => {
// TODO: review // TODO: review
//if (!JwtService.getInstance().decodeJwt()) return; //if (!JwtService.getInstance().decodeJwt()) return;
setHasJwt(true); // setHasJwt(true);
setIsShowing(getShowValue()); setIsShowing(getShowValue());
}, [getShowValue, isShowing]); }, [getShowValue, isShowing]);

View File

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

View File

@ -2,11 +2,12 @@ import React, { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import UserStore from "@Front/Stores/UserStore";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
import JwtService from "@Front/Services/JwtService/JwtService";
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/Admin/Users/Users"; import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
@ -33,7 +34,20 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
.get(query) .get(query)
.then((users) => setCollaborators(users)); .then((users) => setCollaborators(users));
*/ */
setCollaborators([]);
const user: any = UserStore.instance.getUser();
const officeId: string = user.office.uid;
CollaboratorService.getCollaborators((processes: any[]) => {
if (processes.length > 0) {
let collaborators: any[] = processes.map((process: any) => process.processData);
// FilterBy office.uid
collaborators = collaborators.filter((collaborator: any) => collaborator.office.uid === officeId);
setCollaborators(collaborators);
}
});
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {
@ -49,10 +63,10 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
blocks={ blocks={
collaborators collaborators
? collaborators.map((collaborator) => ({ ? collaborators.map((collaborator) => ({
id: collaborator.uid!, id: collaborator.uid!,
primaryText: collaborator.contact?.first_name + " " + collaborator.contact?.last_name, primaryText: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
isActive: collaborator.uid === collaboratorUid, isActive: collaborator.uid === collaboratorUid,
})) }))
: [] : []
} }
/> />

View File

@ -9,7 +9,9 @@ import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDas
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = IPropsDashboardWithList & {}; type IProps = IPropsDashboardWithList & {
isReady: boolean;
};
export default function DefaultCustomerDashboard(props: IProps) { export default function DefaultCustomerDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
@ -17,8 +19,8 @@ export default function DefaultCustomerDashboard(props: IProps) {
const [folders, setFolders] = useState<OfficeFolder[]>([]); const [folders, setFolders] = useState<OfficeFolder[]>([]);
useEffect(() => { useEffect(() => {
const jwt = JwtService.getInstance().decodeCustomerJwt(); //const jwt = JwtService.getInstance().decodeCustomerJwt();
if (!jwt) return; //if (!jwt) return;
/* /*
Folders.getInstance() Folders.getInstance()
@ -46,13 +48,19 @@ export default function DefaultCustomerDashboard(props: IProps) {
.then((folders) => setFolders(folders)); .then((folders) => setFolders(folders));
*/ */
FolderService.getFolders().then((processes: any[]) => { if (props.isReady) {
if (processes.length > 0) { FolderService.getFolders((processes: any[]) => {
const folders: any[] = processes.map((process: any) => process.processData); if (processes.length > 0) {
setFolders(folders); let folders: any[] = processes.map((process: any) => process.processData);
}
}); // Filter By customer.uid
}, []); folders = folders.filter((folder: any) => folder.customers.some((customer: any) => customer.uid === profileUid));
setFolders(folders);
}
});
}
}, [props.isReady]);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {
const folder = folders.find((folder) => folder.uid === block.id); const folder = folders.find((folder) => folder.uid === block.id);
@ -65,7 +73,7 @@ export default function DefaultCustomerDashboard(props: IProps) {
.replace("[profileUid]", profileUid as string ?? ""), .replace("[profileUid]", profileUid as string ?? ""),
); );
}; };
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false} />; return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false} isCustomer={true} />;
function getBlocks(folders: OfficeFolder[]): IBlock[] { function getBlocks(folders: OfficeFolder[]): IBlock[] {
return folders.map((folder) => { return folders.map((folder) => {

View File

@ -15,6 +15,7 @@ export type IPropsDashboardWithList = {
mobileBackText?: string; mobileBackText?: string;
headerConnected?: boolean; headerConnected?: boolean;
noPadding?: boolean; noPadding?: boolean;
isCustomer?: boolean;
}; };
type IProps = IPropsDashboardWithList & ISearchBlockListProps; type IProps = IPropsDashboardWithList & ISearchBlockListProps;
@ -29,11 +30,12 @@ export default function DefaultDashboardWithList(props: IProps) {
headerConnected = true, headerConnected = true,
bottomButton, bottomButton,
noPadding = false, noPadding = false,
isCustomer = false,
} = props; } = props;
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<Header isUserConnected={headerConnected} /> <Header isUserConnected={headerConnected} isCustomer={isCustomer} />
<div className={classes["content"]}> <div className={classes["content"]}>
<SearchBlockList blocks={blocks} onSelectedBlock={onSelectedBlock} bottomButton={bottomButton} /> <SearchBlockList blocks={blocks} onSelectedBlock={onSelectedBlock} bottomButton={bottomButton} />
<div className={classes["right-side"]} data-no-padding={noPadding}> <div className={classes["right-side"]} data-no-padding={noPadding}>

View File

@ -11,21 +11,24 @@ import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultDeedTypeDashboard(props: IProps) { export default function DefaultDeedTypeDashboard(props: IProps) {
const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
const router = useRouter(); const router = useRouter();
const { deedTypeUid } = router.query; const { deedTypeUid } = router.query;
const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
useEffect(() => { useEffect(() => {
DeedTypeService.getDeedTypes().then((processes: any) => { DeedTypeService.getDeedTypes((processes: any[]) => {
let deedTypes = processes.map((process: any) => process.processData); if (processes.length > 0) {
let deedTypes = processes.map((process: any) => process.processData);
// FilterBy archived_at = null or not defined // FilterBy archived_at = null or not defined
deedTypes = deedTypes.filter((deedType: any) => !deedType.archived_at); deedTypes = deedTypes.filter((deedType: any) => !deedType.archived_at);
// OrderBy name asc // OrderBy name asc
deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name)); deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name));
setDeedTypes(deedTypes); setDeedTypes(deedTypes);
}
}); });
}, []); }, []);

View File

@ -7,6 +7,7 @@ import JwtService from "@Front/Services/JwtService/JwtService";
import { DocumentType } from "le-coffre-resources/dist/Notary"; import { DocumentType } from "le-coffre-resources/dist/Notary";
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService"; import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
import UserStore from "@Front/Stores/UserStore";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
@ -15,11 +16,8 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
const router = useRouter(); const router = useRouter();
const { documentTypeUid } = router.query; const { documentTypeUid } = router.query;
useEffect(() => { useEffect(() => {
const jwt = JwtService.getInstance().decodeJwt(); const user: any = UserStore.instance.getUser();
if (!jwt) return; const officeId: string = user.office.uid;
// TODO: review
const officeId = 'demo_notary_office_id'; // jwt.office_Id;
DocumentTypeService.getDocumentTypes().then((processes: any[]) => { DocumentTypeService.getDocumentTypes().then((processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {

View File

@ -115,7 +115,7 @@ export default function DefaultNotaryDashboard(props: IProps) {
.then((folders) => setFolders(folders)); .then((folders) => setFolders(folders));
*/ */
FolderService.getFolders().then((processes: any[]) => { FolderService.getFolders((processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {
let folders: any[] = processes.map((process: any) => process.processData); let folders: any[] = processes.map((process: any) => process.processData);

View File

@ -1,25 +1,26 @@
import { Office } from "le-coffre-resources/dist/SuperAdmin";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import Offices from "@Front/Api/LeCoffreApi/SuperAdmin/Offices/Offices";
import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultOfficeDashboard(props: IProps) { export default function DefaultOfficeDashboard(props: IProps) {
const [offices, setOffices] = React.useState<Office[] | null>(null); const [offices, setOffices] = React.useState<any[] | null>(null);
const router = useRouter(); const router = useRouter();
const { officeUid } = router.query; const { officeUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review OfficeService.getOffices().then((processes: any[]) => {
Offices.getInstance() if (processes.length > 0) {
.get() const offices: any[] = processes.map((process: any) => process.processData);
.then((offices) => setOffices(offices)); setOffices(offices);
*/ }
setOffices([]); });
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {
@ -33,11 +34,11 @@ export default function DefaultOfficeDashboard(props: IProps) {
blocks={ blocks={
offices offices
? offices.map((office) => ({ ? offices.map((office) => ({
id: office.uid!, id: office.uid!,
primaryText: office.name, primaryText: office.name,
isActive: office.uid === officeUid, isActive: office.uid === officeUid,
secondaryText: office.crpcen, secondaryText: office.crpcen,
})) }))
: [] : []
} }
/> />

View File

@ -4,32 +4,41 @@ import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import { OfficeRole } from "le-coffre-resources/dist/Notary";
import OfficeRoles, { IGetRolesParams } from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService"; import UserStore from "@Front/Stores/UserStore";
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultRoleDashboard(props: IProps) { export default function DefaultRoleDashboard(props: IProps) {
const [roles, setRoles] = React.useState<OfficeRole[] | null>(null); const [roles, setRoles] = React.useState<any[] | null>(null);
const router = useRouter(); const router = useRouter();
const { roleUid } = router.query; const { roleUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review const user: any = UserStore.instance.getUser();
const query: IGetRolesParams = { if (!user) {
include: { rules: true }, return;
}; }
OfficeRoles.getInstance() const office: any = user.office;
.get(query) if (!office) {
.then((roles) => setRoles(roles)); return;
*/ }
RoleService.getRoles().then((processes: any[]) => { OfficeRoleService.getOfficeRoles().then(async (processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {
const roles: any[] = processes.map((process: any) => process.processData); let officeRoles: any[] = processes.map((process: any) => process.processData);
setRoles(roles);
// FilterBy office.uid
officeRoles = officeRoles.filter((officeRole: any) => officeRole.office.uid === office.uid);
// OrderBy name
officeRoles = officeRoles.sort((a: any, b: any) => a.name.localeCompare(b.name));
setRoles(officeRoles);
} }
}); });
}, []); }, []);

View File

@ -1,28 +1,34 @@
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
import User from "le-coffre-resources/dist/SuperAdmin";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList"; import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
import UserStore from "@Front/Stores/UserStore";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
type IProps = IPropsDashboardWithList; type IProps = IPropsDashboardWithList;
export default function DefaultUserDashboard(props: IProps) { export default function DefaultUserDashboard(props: IProps) {
const [users, setUsers] = React.useState<User[] | null>(null); const [users, setUsers] = React.useState<any[] | null>(null);
const router = useRouter(); const router = useRouter();
const { userUid } = router.query; const { userUid } = router.query;
useEffect(() => { useEffect(() => {
/* TODO: review const user: any = UserStore.instance.getUser();
const query: IGetUsersparams = { if (!user) return;
include: { contact: true, office_membership: true }, const officeId: string = user.office.uid;
};
Users.getInstance() CollaboratorService.getCollaborators((processes: any[]) => {
.get(query) if (processes.length > 0) {
.then((users) => setUsers(users)); let collaborators: any[] = processes.map((process: any) => process.processData);
*/
setUsers([]); // FilterBy office.uid
collaborators = collaborators.filter((collaborator: any) => collaborator.office.uid === officeId);
setUsers(collaborators);
}
});
}, []); }, []);
const onSelectedBlock = (block: IBlock) => { const onSelectedBlock = (block: IBlock) => {
@ -36,11 +42,11 @@ export default function DefaultUserDashboard(props: IProps) {
blocks={ blocks={
users users
? users.map((user) => ({ ? users.map((user) => ({
id: user.uid!, id: user.uid!,
primaryText: user.contact?.first_name + " " + user.contact?.last_name, primaryText: user.contact?.first_name + " " + user.contact?.last_name,
isActive: user.uid === userUid, isActive: user.uid === userUid,
secondaryText: user.office_membership?.crpcen + " - " + user.office_membership?.name, secondaryText: user.office?.crpcen + " - " + user.office?.name,
})) }))
: [] : []
} }
/> />

View File

@ -6,7 +6,8 @@ import { OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Not
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
import OfficeRibService from "src/common/Api/LeCoffreApi/sdk/OfficeRibService";
type IProps = { type IProps = {
folder: OfficeFolderNotary; folder: OfficeFolderNotary;
@ -18,20 +19,8 @@ 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![0]!.contact,
/*folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ??
folder?.stakeholders![0]!.contact*/
// TODO: review
stakeholder,
[folder], [folder],
); );
@ -47,11 +36,13 @@ export default function ContactBox(props: IProps) {
useEffect(() => { useEffect(() => {
if (!folder?.office?.uid) return; if (!folder?.office?.uid) return;
/* TODO: review OfficeRibService.getOfficeRib().then((process: any) => {
OfficeRib.getInstance() if (process) {
.getRibStream(folder.office.uid) const officeRib: any = process.processData;
.then((blob) => setRibUrl(URL.createObjectURL(blob))); const fileBlob: Blob = new Blob([officeRib.file_blob.data], { type: officeRib.file_blob.type });
*/ setRibUrl(URL.createObjectURL(fileBlob));
}
});
}, [folder]); }, [folder]);
const downloadRib = useCallback(async () => { const downloadRib = useCallback(async () => {

View File

@ -7,16 +7,21 @@ import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster"; import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import { FileBlob, FileData } from "@Front/Api/Entities/types";
import WatermarkService from "@Front/Services/WatermarkService";
type IProps = { type IProps = {
document: any; document: any;
customer: any;
onChange: () => void; onChange: () => void;
}; };
export default function DepositDocumentComponent(props: IProps) { export default function DepositDocumentComponent(props: IProps) {
const { document, onChange } = props; const { document, customer, onChange } = props;
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [refused_reason, setRefusedReason] = useState<string | null>(null); const [refused_reason, setRefusedReason] = useState<string | null>(null);
@ -30,50 +35,115 @@ export default function DepositDocumentComponent(props: IProps) {
}, [document.files]); }, [document.files]);
const addFile = useCallback( const addFile = useCallback(
(file: File) => { async (file: File) => {
return new Promise<void>( try {
(resolve: () => void) => { // Add watermark to the file before processing
const reader = new FileReader(); const watermarkedFile = await WatermarkService.getInstance().addWatermark(file);
reader.onload = (event) => {
if (event.target?.result) {
const arrayBuffer = event.target.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
const fileBlob: any = { return new Promise<void>(
type: file.type, (resolve: () => void) => {
data: uint8Array const reader = new FileReader();
}; reader.onload = (event) => {
if (event.target?.result) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
const fileData: any = { const fileName: string = `${document.document_type.name}-${customer.contact.last_name}_${strDate}.${file.name.split('.').pop()}`;
file_blob: fileBlob,
file_name: file.name
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => { const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
const fileUid: string = processCreated.processData.uid; const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
DocumentService.getDocumentByUid(document.uid!).then((process: any) => { const fileBlob: FileBlob = {
if (process) { type: watermarkedFile.type,
const document: any = process.processData; data: uint8Array
};
let files: any[] = document.files; const fileData: FileData = {
if (!files) { file_blob: fileBlob,
files = []; file_name: fileName
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => {
const fileUid: string = processCreated.processData.uid;
DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
if (process) {
const document: any = process.processData;
let files: any[] = document.files;
if (!files) {
files = [];
}
files.push({ uid: fileUid });
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
});
} }
files.push({ uid: fileUid }); });
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => resolve());
}
}); });
}); }
} };
}; reader.readAsArrayBuffer(watermarkedFile);
reader.readAsArrayBuffer(file); })
}) .then(onChange)
.then(onChange) .then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" })) .catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message })); } catch (error) {
console.error('Error processing file with watermark:', error);
// If watermarking fails, proceed with original file
return new Promise<void>(
(resolve: () => void) => {
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
const fileName: string = `${document.document_type.name}-${customer.contact.last_name}_${strDate}.${file.name.split('.').pop()}`;
const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
const fileBlob: FileBlob = {
type: file.type,
data: uint8Array
};
const fileData: FileData = {
file_blob: fileBlob,
file_name: fileName
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => {
const fileUid: string = processCreated.processData.uid;
DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
if (process) {
const document: any = process.processData;
let files: any[] = document.files;
if (!files) {
files = [];
}
files.push({ uid: fileUid });
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
});
}
});
});
}
};
reader.readAsArrayBuffer(file);
})
.then(onChange)
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
}
}, },
[document.uid, onChange], [document.uid, onChange],
); );
@ -82,9 +152,9 @@ export default function DepositDocumentComponent(props: IProps) {
(fileUid: string) => { (fileUid: string) => {
return new Promise<void>( return new Promise<void>(
(resolve: () => void) => { (resolve: () => void) => {
FileService.getFileByUid(fileUid).then((process: any) => { FileService.getFileByUid(fileUid).then((res: any) => {
if (process) { if (res) {
FileService.updateFile(process, { isDeleted: 'true' }).then(() => { FileService.updateFile(res.processId, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
DocumentService.getDocumentByUid(document.uid!).then((process: any) => { DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
if (process) { if (process) {
const document: any = process.processData; const document: any = process.processData;
@ -95,7 +165,9 @@ export default function DepositDocumentComponent(props: IProps) {
} }
files = files.filter((file: any) => file.uid !== fileUid); files = files.filter((file: any) => file.uid !== fileUid);
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.ASKED }).then(() => resolve()); DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.ASKED }).then(() => {
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
});
} }
}); });
}); });

View File

@ -6,7 +6,7 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService"; // import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
import { ArrowDownTrayIcon, EyeIcon } from "@heroicons/react/24/outline"; import { ArrowDownTrayIcon, EyeIcon } from "@heroicons/react/24/outline";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import JSZip from "jszip"; import JSZip from "jszip";
@ -16,6 +16,7 @@ import classes from "./classes.module.scss";
import Link from "next/link"; import Link from "next/link";
import Customer from "le-coffre-resources/dist/Customer"; import Customer from "le-coffre-resources/dist/Customer";
import { DocumentNotary } from "le-coffre-resources/dist/Notary"; import { DocumentNotary } from "le-coffre-resources/dist/Notary";
import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
@ -44,10 +45,10 @@ export default function ReceivedDocuments() {
const [customer, setCustomer] = useState<Customer | null>(null); const [customer, setCustomer] = useState<Customer | null>(null);
const fetchFolderAndCustomer = useCallback(async () => { const fetchFolderAndCustomer = useCallback(async () => {
let jwt: ICustomerJwtPayload | undefined; // let jwt: ICustomerJwtPayload | undefined;
if (typeof document !== "undefined") { // if (typeof document !== "undefined") {
jwt = JwtService.getInstance().decodeCustomerJwt(); // jwt = JwtService.getInstance().decodeCustomerJwt();
} // }
// TODO: review // TODO: review
LoaderService.getInstance().show(); LoaderService.getInstance().show();
@ -115,7 +116,7 @@ export default function ReceivedDocuments() {
let documents: any[] = processes.map((process: any) => process.processData); let documents: any[] = processes.map((process: any) => process.processData);
// FilterBy folder.uid & customer.uid // FilterBy folder.uid & customer.uid
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.customer && document.customer.uid === customerUid); documents = documents.filter((document: any) => document.folder.uid === folderUid && document.customer /*&& document.customer.uid === customerUid*/);
for (const document of documents) { for (const document of documents) {
if (document.files && document.files.length > 0) { if (document.files && document.files.length > 0) {
@ -133,10 +134,24 @@ export default function ReceivedDocuments() {
}); });
}, [folderUid, customer]); }, [folderUid, customer]);
const onDownload = useCallback((doc: any) => { const onDownload = useCallback(async (doc: any) => {
const file = doc.files?.[0]; const file = doc.files?.[0];
if (!file) return; if (!file) return;
if (doc.document_status !== EDocumentNotaryStatus.DOWNLOADED) {
await new Promise<void>((resolve: () => void) => {
LoaderService.getInstance().show();
DocumentService.getDocumentByUid(doc.uid).then((process: any) => {
if (process) {
DocumentService.updateDocument(process, { document_status: EDocumentNotaryStatus.DOWNLOADED }).then(() => {
LoaderService.getInstance().hide();
resolve();
});
}
});
});
}
return new Promise<void>((resolve: () => void) => { return new Promise<void>((resolve: () => void) => {
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type }); const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@ -158,9 +173,23 @@ export default function ReceivedDocuments() {
const zip = new JSZip(); const zip = new JSZip();
const folder = zip.folder("documents") || zip; const folder = zip.folder("documents") || zip;
documentsNotary.map((doc: any) => { documentsNotary.map(async (doc: any) => {
const file = doc.files?.[0]; const file = doc.files?.[0];
if (file) { if (file) {
if (doc.document_status !== EDocumentNotaryStatus.DOWNLOADED) {
await new Promise<void>((resolve: () => void) => {
LoaderService.getInstance().show();
DocumentService.getDocumentByUid(doc.uid).then((process: any) => {
if (process) {
DocumentService.updateDocument(process, { document_status: EDocumentNotaryStatus.DOWNLOADED }).then(() => {
LoaderService.getInstance().hide();
resolve();
});
}
});
});
}
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type }); const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
folder.file(file.file_name ?? "file", blob); folder.file(file.file_name ?? "file", blob);
} }
@ -208,7 +237,7 @@ function buildRows(
): IRowProps[] { ): IRowProps[] {
return documentsNotary.map((documentNotary) => ({ return documentsNotary.map((documentNotary) => ({
key: documentNotary.uid ?? "", key: documentNotary.uid ?? "",
name: formatName(documentNotary.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_", name: documentNotary.files?.[0]?.file_name?.split(".")?.[0] || "_",
sentAt: new Date(documentNotary.created_at!).toLocaleDateString(), sentAt: new Date(documentNotary.created_at!).toLocaleDateString(),
// actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />, // actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
actions: { actions: {
@ -228,7 +257,3 @@ function buildRows(
}, },
})); }));
} }
function formatName(text: string): string {
return text.replace(/[^a-zA-Z0-9 ]/g, "");
}

View File

@ -7,6 +7,7 @@ import { DocumentNotary } from "le-coffre-resources/dist/Notary";
import Image from "next/image"; import Image from "next/image";
import { NextRouter, useRouter } from "next/router"; import { NextRouter, useRouter } from "next/router";
import React from "react"; import React from "react";
import { FileBlob } from "@Front/Api/Entities/types";
import BasePage from "../../Base"; import BasePage from "../../Base";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -28,8 +29,8 @@ type IState = {
isValidateModalVisible: boolean; isValidateModalVisible: boolean;
refuseText: string; refuseText: string;
selectedFileIndex: number; selectedFileIndex: number;
selectedFile: any; selectedFile: { uid: string; file_name: string; file_blob: FileBlob } | null;
documentNotary: DocumentNotary | null; documentNotary: any | null;
fileBlob: Blob | null; fileBlob: Blob | null;
isLoading: boolean; isLoading: boolean;
}; };
@ -150,7 +151,7 @@ class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
{ {
documentNotary, documentNotary,
selectedFileIndex: 0, selectedFileIndex: 0,
selectedFile: documentNotary.files![0]!, selectedFile: documentNotary.files![0] as any,
isLoading: false, isLoading: false,
}, },
() => { () => {
@ -167,7 +168,7 @@ class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
private async getFilePreview(): Promise<void> { private async getFilePreview(): Promise<void> {
try { try {
const fileBlob: Blob = new Blob([this.state.selectedFile.file_blob.data], { type: this.state.selectedFile.file_blob.type }); const fileBlob: Blob = new Blob([this.state.selectedFile!.file_blob.data], { type: this.state.selectedFile!.file_blob.type });
this.setState({ this.setState({
fileBlob, fileBlob,
}); });

View File

@ -22,9 +22,6 @@ import ContactBox from "./ContactBox";
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 Modal from "@Front/Components/DesignSystem/Modal";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import AuthModal from "src/sdk/AuthModal"; import AuthModal from "src/sdk/AuthModal";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
@ -46,31 +43,8 @@ export default function ClientDashboard(props: IProps) {
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 [isReady, setIsReady] = useState(false);
const [isSmsModalOpen, setIsSmsModalOpen] = useState(false); const [isAuthModalOpen, setIsAuthModalOpen] = useState(true);
const [smsCode, setSmsCode] = useState("");
const [smsError, setSmsError] = useState("");
const verifySmsCode = useCallback(() => {
if (smsCode === "1234") {
setIsSmsModalOpen(false);
} else {
setSmsError("Code incorrect. Le code valide est 1234.");
}
}, [smsCode]);
useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === "Enter" && isSmsModalOpen) {
verifySmsCode();
}
};
window.addEventListener("keypress", handleKeyPress);
return () => {
window.removeEventListener("keypress", handleKeyPress);
};
}, [isSmsModalOpen, smsCode, verifySmsCode]);
const fetchFolderAndCustomer = useCallback(async () => { const fetchFolderAndCustomer = useCallback(async () => {
let jwt: ICustomerJwtPayload | undefined; let jwt: ICustomerJwtPayload | undefined;
@ -97,7 +71,6 @@ export default function ClientDashboard(props: IProps) {
setCustomer(customer); setCustomer(customer);
setFolder(folder); setFolder(folder);
setIsAuthModalOpen(true);
LoaderService.getInstance().hide(); LoaderService.getInstance().hide();
return { folder, customer }; return { folder, customer };
@ -138,6 +111,7 @@ export default function ClientDashboard(props: IProps) {
const fetchDocuments = useCallback( const fetchDocuments = useCallback(
async (customerUid: string | undefined) => { async (customerUid: string | undefined) => {
setDocuments([]);
LoaderService.getInstance().show(); LoaderService.getInstance().show();
return new Promise<void>((resolve: () => void) => { return new Promise<void>((resolve: () => void) => {
DocumentService.getDocuments().then(async (processes: any[]) => { DocumentService.getDocuments().then(async (processes: any[]) => {
@ -171,14 +145,16 @@ export default function ClientDashboard(props: IProps) {
[folderUid], [folderUid],
); );
/*
useEffect(() => { useEffect(() => {
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid)); fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
}, [fetchDocuments, fetchFolderAndCustomer]); }, [fetchDocuments, fetchFolderAndCustomer]);
*/
useEffect(() => { useEffect(() => {
setDocumentsNotary([]);
const customerUid = customer?.uid; const customerUid = customer?.uid;
if (!folderUid || !customerUid) return; if (!folderUid || !customerUid) return;
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DocumentService.getDocuments().then(async (processes: any[]) => { DocumentService.getDocuments().then(async (processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {
@ -235,8 +211,8 @@ export default function ClientDashboard(props: IProps) {
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument, folder]); }, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument, folder]);
return ( return (
<DefaultCustomerDashboard> <DefaultCustomerDashboard isReady={isReady}>
<div className={classes["root"]}> {isReady && (<div className={classes["root"]}>
<div className={classes["top"]}> <div className={classes["top"]}>
<div className={classes["folder-info-container"]}> <div className={classes["folder-info-container"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}> <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
@ -315,6 +291,7 @@ export default function ClientDashboard(props: IProps) {
<DepositDocumentComponent <DepositDocumentComponent
key={document.uid} key={document.uid}
document={document} document={document}
customer={customer}
onChange={() => fetchDocuments(customer?.uid)} onChange={() => fetchDocuments(customer?.uid)}
/> />
))} ))}
@ -332,58 +309,16 @@ export default function ClientDashboard(props: IProps) {
Ajouter d'autres documents Ajouter d'autres documents
</Button> </Button>
{isAddDocumentModalVisible && renderBox()} {isAddDocumentModalVisible && renderBox()}
</div>)}
{isAuthModalOpen && <AuthModal {isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen} isOpen={isAuthModalOpen}
onClose={() => { onClose={() => {
setIsAuthModalOpen(false); setIsReady(true);
setIsSmsModalOpen(true); setIsAuthModalOpen(false);
}} fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
/>} }}
/>}
{isSmsModalOpen && (
<Modal
isOpen={isSmsModalOpen}
onClose={() => setIsSmsModalOpen(false)}
title="Vérification SMS"
>
<div className={classes["sms-modal-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_PRIMARY}>
Veuillez saisir le code à 4 chiffres que vous avez reçu par SMS
</Typography>
<TextField
name="smsCode"
placeholder="Code SMS à 4 chiffres"
value={smsCode}
onChange={(e) => {
const value = e.target.value;
// Only allow digits
if (value === "" || /^\d+$/.test(value)) {
setSmsCode(value);
setSmsError("");
}
}}
/>
{smsError && (
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.TEXT_ACCENT}>
{smsError}
</Typography>
)}
<div style={{ marginTop: "20px" }}>
<Button
variant={EButtonVariant.PRIMARY}
onClick={verifySmsCode}
>
Vérifier
</Button>
</div>
</div>
</Modal>
)}
</div>
</DefaultCustomerDashboard> </DefaultCustomerDashboard>
); );
} }

View File

@ -1,14 +1,10 @@
import { ChevronLeftIcon } from "@heroicons/react/24/solid"; import { ChevronLeftIcon } from "@heroicons/react/24/solid";
import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import Roles from "@Front/Api/LeCoffreApi/Admin/Roles/Roles";
import Users from "@Front/Api/LeCoffreApi/Admin/Users/Users";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import Switch from "@Front/Components/DesignSystem/Switch"; import Switch from "@Front/Components/DesignSystem/Switch";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard"; import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import User, { OfficeRole } from "le-coffre-resources/dist/Admin";
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";
@ -18,12 +14,17 @@ import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/Dr
import { getLabel } from "@Front/Components/DesignSystem/Dropdown"; import { getLabel } from "@Front/Components/DesignSystem/Dropdown";
import SelectField from "@Front/Components/DesignSystem/Form/SelectField"; import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
type IProps = {}; type IProps = {};
export default function CollaboratorInformations(props: IProps) { export default function CollaboratorInformations(props: IProps) {
const router = useRouter(); const router = useRouter();
let { collaboratorUid } = router.query; let { collaboratorUid } = router.query;
const [userSelected, setUserSelected] = useState<User | null>(null); const [userSelected, setUserSelected] = useState<any | null>(null);
const [availableRoles, setAvailableRoles] = useState<IOption[]>([]); const [availableRoles, setAvailableRoles] = useState<IOption[]>([]);
const [roleModalOpened, setRoleModalOpened] = useState<boolean>(false); const [roleModalOpened, setRoleModalOpened] = useState<boolean>(false);
@ -35,7 +36,7 @@ export default function CollaboratorInformations(props: IProps) {
useEffect(() => { useEffect(() => {
if (!userSelected) return; if (!userSelected) return;
setIsAdminChecked(userSelected.role?.name === "admin"); setIsAdminChecked(userSelected.role.name === "admin");
}, [userSelected]); }, [userSelected]);
const handleRoleChange = useCallback((option: IOption) => { const handleRoleChange = useCallback((option: IOption) => {
@ -46,60 +47,54 @@ export default function CollaboratorInformations(props: IProps) {
const closeRoleModal = useCallback(() => { const closeRoleModal = useCallback(() => {
setRoleModalOpened(false); setRoleModalOpened(false);
setSelectedOption({ setSelectedOption({
id: (userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid) ?? "", id: userSelected?.role?.uid ?? "",
label: userSelected?.office_role ? userSelected?.office_role?.name : "Utilisateur restreint", label: userSelected?.role?.name ?? "Utilisateur restreint"
}); });
}, [userSelected?.office_role, userSelected?.role?.uid]); }, [userSelected?.role]);
const changeRole = useCallback(async () => { const changeRole = useCallback(async () => {
await Users.getInstance().put( LoaderService.getInstance().show();
userSelected?.uid as string, CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then((process: any) => {
User.hydrate<User>({ if (process) {
uid: userSelected?.uid as string, CollaboratorService.updateCollaborator(process, { office_role: { uid: selectedOption?.id as string } }).then(() => {
office_role: OfficeRole.hydrate<OfficeRole>({ LoaderService.getInstance().hide();
uid: selectedOption?.id as string, setRoleModalOpened(false);
}), });
}), }
); });
setRoleModalOpened(false);
}, [selectedOption, userSelected]); }, [selectedOption, userSelected]);
const changeAdmin = useCallback(async () => { const changeAdmin = useCallback(async () => {
try { try {
if (adminRoleType === "add") { if (adminRoleType === "add") {
const adminRole = await Roles.getInstance().getOne({ LoaderService.getInstance().show();
where: { CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
name: "admin", if (process) {
}, const role: any = (await RoleService.getRoles())
}); .map((process: any) => process.processData)
.filter((role: any) => role.name === "admin")[0];
if (!adminRole) return; CollaboratorService.updateCollaborator(process, { role: { uid: role.uid } }).then(() => {
await Users.getInstance().put( LoaderService.getInstance().hide();
userSelected?.uid as string, setAdminModalOpened(false);
User.hydrate<User>({ });
uid: userSelected?.uid as string, }
office_role: undefined, });
role: adminRole,
}),
);
} else { } else {
const defaultRole = await Roles.getInstance().getOne({ LoaderService.getInstance().show();
where: { CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
name: "default", if (process) {
}, const role: any = (await RoleService.getRoles())
}); .map((process: any) => process.processData)
.filter((role: any) => role.name === "default")[0];
if (!defaultRole) return; CollaboratorService.updateCollaborator(process, { role: { uid: role.uid } }).then(() => {
await Users.getInstance().put( LoaderService.getInstance().hide();
userSelected?.uid as string, setAdminModalOpened(false);
User.hydrate<User>({ });
uid: userSelected?.uid as string, }
office_role: undefined, });
role: defaultRole,
}),
);
} }
setAdminModalOpened(false);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -113,37 +108,29 @@ export default function CollaboratorInformations(props: IProps) {
}, []); }, []);
const closeAdminModal = useCallback(() => { const closeAdminModal = useCallback(() => {
setIsAdminChecked(userSelected?.role?.name === "admin" && !userSelected.office_role); setIsAdminChecked(userSelected?.role.name === "admin");
setAdminModalOpened(false); setAdminModalOpened(false);
}, [userSelected]); }, [userSelected]);
useEffect(() => { useEffect(() => {
async function getUser() { async function getUser() {
if (!collaboratorUid) return; if (!collaboratorUid) return;
const user = await Users.getInstance().getByUid(collaboratorUid as string, { LoaderService.getInstance().show();
q: { CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
contact: true, if (process) {
office_role: true, const collaborator: any = process.processData;
role: true,
seats: {
include: {
subscription: true,
},
},
},
});
if (!user) return;
const roles = await OfficeRoles.getInstance().get(); const officeRoles: any[] = (await OfficeRoleService.getOfficeRoles())
if (!roles) return; .map((process: any) => process.processData);
setAvailableRoles(roles.map((role) => ({ id: role.uid ?? "", label: role.name })));
setUserSelected(user); setUserSelected(collaborator);
setSelectedOption({ setAvailableRoles(officeRoles.map((officeRole: any) => ({ id: officeRole.uid, label: officeRole.name })));
id: (user?.office_role ? user?.office_role?.uid : user?.role?.uid) ?? "", setSelectedOption({ id: collaborator.office_role.uid, label: collaborator.office_role.name });
label: user?.office_role ? user?.office_role?.name : "Utilisateur restreint",
LoaderService.getInstance().hide();
}
}); });
} }
getUser(); getUser();
}, [collaboratorUid]); }, [collaboratorUid]);
@ -154,7 +141,7 @@ export default function CollaboratorInformations(props: IProps) {
<Typography typo={ETypo.TITLE_H1}> <Typography typo={ETypo.TITLE_H1}>
{userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name} {userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name}
</Typography> </Typography>
{userSelected && userSelected.seats?.some((seat) => new Date(seat.subscription!.end_date) >= new Date()) && ( {userSelected && userSelected.seats?.some((seat: any) => new Date(seat.subscription!.end_date) >= new Date()) && (
<div className={classes["subscription-active"]}> <div className={classes["subscription-active"]}>
<div className={classes["subscription-active-dot"]} /> <div className={classes["subscription-active-dot"]} />
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_SUCCESS_600}> <Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_SUCCESS_600}>

View File

@ -7,7 +7,6 @@ import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDeedTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDeedTypeDashboard"; import DefaultDeedTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDeedTypeDashboard";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster"; import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService";
import { DeedType, Office } from "le-coffre-resources/dist/Admin"; import { DeedType, Office } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@ -15,8 +14,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 DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService"; import UserStore from "@Front/Stores/UserStore";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
type IProps = {}; type IProps = {};
export default function DeedTypesCreate(props: IProps) { export default function DeedTypesCreate(props: IProps) {
@ -28,8 +29,8 @@ 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 {
// TODO: review const user: any = UserStore.instance.getUser();
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id; const officeId: string = user.office.uid;
const deedType = DeedType.hydrate<DeedType>({ const deedType = DeedType.hydrate<DeedType>({
name: values["name"], name: values["name"],
@ -60,12 +61,12 @@ export default function DeedTypesCreate(props: IProps) {
title: "Succès !", title: "Succès !",
description: "Type d'acte créé avec succès" description: "Type d'acte créé avec succès"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", processCreated.processData.uid), .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", processCreated.processData.uid),
); );
LoaderService.getInstance().hide();
}); });
} catch (validationErrors: Array<ValidationError> | any) { } catch (validationErrors: Array<ValidationError> | any) {
setValidationError(validationErrors as ValidationError[]); setValidationError(validationErrors as ValidationError[]);

View File

@ -36,7 +36,7 @@ export default function DeedTypesEdit() {
const deedType: any = process.processData; const deedType: any = process.processData;
setDeedTypeSelected(deedType); setDeedTypeSelected(deedType);
} }
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
}); });
} }
@ -63,16 +63,25 @@ export default function DeedTypesEdit() {
} }
try { try {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => { DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => {
if (process) { if (process) {
DeedTypeService.updateDeedType(process, { name: values["name"], description: values["description"] }).then(() => { // New data
setTimeout(() => LoaderService.getInstance().hide(), 2000); const newData: any = {
router.push( name: values["name"],
Module.getInstance() description: values["description"]
.get() };
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string),
); // Merge process data with new data & update process
}); process.processData.name = newData.name;
process.processData.description = newData.description;
await DeedTypeService.updateDeedType(process, newData);
router.push(
Module.getInstance()
.get()
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string),
);
LoaderService.getInstance().hide();
} }
}); });
} catch (validationErrors) { } catch (validationErrors) {

View File

@ -52,12 +52,25 @@ export default function DeedTypesInformations(props: IProps) {
const deleteDeedType = useCallback(async () => { const deleteDeedType = useCallback(async () => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => { DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => {
if (process) { if (process) {
DeedTypeService.updateDeedType(process, { archived_at: new Date().toISOString() }).then(() => { // New data
setTimeout(() => LoaderService.getInstance().hide(), 2000); const newData: any = {
router.push(Module.getInstance().get().modules.pages.DeedTypes.props.path); isDeleted: 'true',
}); archived_at: new Date().toISOString()
};
// Merge process data with new data & update process
process.processData.isDeleted = newData.isDeleted;
process.processData.archived_at = newData.archived_at;
await DeedTypeService.updateDeedType(process, newData);
router.push(
Module.getInstance()
.get()
.modules.pages.DeedTypes.props.path
);
LoaderService.getInstance().hide();
} }
}); });
}, [deedTypeUid, router]); }, [deedTypeUid, router]);
@ -66,6 +79,7 @@ export default function DeedTypesInformations(props: IProps) {
async function getDeedType() { async function getDeedType() {
if (!deedTypeUid) return; if (!deedTypeUid) return;
setSelectedDocuments([]);
DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => { DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then((process: any) => {
if (process) { if (process) {
const deedType: any = process.processData; const deedType: any = process.processData;
@ -86,6 +100,7 @@ export default function DeedTypesInformations(props: IProps) {
} }
async function getDocuments() { async function getDocuments() {
setAvailableDocuments([]);
DocumentTypeService.getDocumentTypes().then((processes: any[]) => { DocumentTypeService.getDocumentTypes().then((processes: any[]) => {
if (processes.length) { if (processes.length) {
const documents: any[] = processes.map((process: any) => process.processData); const documents: any[] = processes.map((process: any) => process.processData);
@ -107,7 +122,7 @@ export default function DeedTypesInformations(props: IProps) {
const saveDocumentTypes = useCallback(() => { const saveDocumentTypes = useCallback(() => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DeedTypeService.getDeedTypeByUid(deedTypeUid as string, false).then((process: any) => { DeedTypeService.getDeedTypeByUid(deedTypeUid as string).then(async (process: any) => {
if (process) { if (process) {
const deedType: any = process.processData; const deedType: any = process.processData;
@ -115,13 +130,20 @@ export default function DeedTypesInformations(props: IProps) {
if (!document_types) { if (!document_types) {
document_types = []; document_types = [];
} }
selectedDocuments.map((selectedDocument: any) => ({ uid: selectedDocument.id as string })) selectedDocuments.map((selectedDocument: any) => selectedDocument.id as string)
.forEach((selectedDocument: any) => document_types.push(selectedDocument)); .forEach((uid: any) => document_types.push(availableDocuments.find((document: any) => document.uid === uid)));
DeedTypeService.updateDeedType(process, { document_types: document_types }).then(() => { // New data
setTimeout(() => LoaderService.getInstance().hide(), 2000); const newData: any = {
closeSaveModal(); document_types: document_types
}); };
// Merge process data with new data & update process
process.processData.document_types = newData.document_types;
await DeedTypeService.updateDeedType(process, newData);
LoaderService.getInstance().hide();
closeSaveModal();
} }
}); });
}, [closeSaveModal, deedTypeUid, selectedDocuments]); }, [closeSaveModal, deedTypeUid, selectedDocuments]);

View File

@ -16,6 +16,7 @@ import classes from "./classes.module.scss";
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService"; import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import UserStore from "@Front/Stores/UserStore";
type IProps = {}; type IProps = {};
export default function DocumentTypesCreate(props: IProps) { export default function DocumentTypesCreate(props: IProps) {
@ -25,11 +26,8 @@ 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 {
const jwt = JwtService.getInstance().decodeJwt(); const user: any = UserStore.instance.getUser();
if (!jwt) return; const officeId: string = user.office.uid;
// TODO: review
const officeId = 'demo_notary_office_id'; // jwt.office_Id;
const documentFormModel = DocumentType.hydrate<DocumentType>({ const documentFormModel = DocumentType.hydrate<DocumentType>({
...values, ...values,
@ -53,12 +51,12 @@ export default function DocumentTypesCreate(props: IProps) {
title: "Succès !", title: "Succès !",
description: "Type de document créé avec succès" description: "Type de document créé avec succès"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", processCreated.processData.uid), .modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", processCreated.processData.uid),
); );
LoaderService.getInstance().hide();
}); });
} catch (e) { } catch (e) {
if (e instanceof Array) { if (e instanceof Array) {

View File

@ -31,7 +31,7 @@ export default function DocumentTypesEdit() {
const documentType: any = process.processData; const documentType: any = process.processData;
setDocumentTypeSelected(documentType); setDocumentTypeSelected(documentType);
} }
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
}); });
} }
@ -56,7 +56,6 @@ export default function DocumentTypesEdit() {
DocumentTypeService.getDocumentTypeByUid(documentTypeUid as string).then((process: any) => { DocumentTypeService.getDocumentTypeByUid(documentTypeUid as string).then((process: any) => {
if (process) { if (process) {
DocumentTypeService.updateDocumentType(process, values).then(() => { DocumentTypeService.updateDocumentType(process, values).then(() => {
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
@ -65,6 +64,7 @@ export default function DocumentTypesEdit() {
documentTypeUid as string ?? "", documentTypeUid as string ?? "",
) )
); );
LoaderService.getInstance().hide();
}); });
} }
}); });

View File

@ -82,21 +82,21 @@ export default function AddClientToFolder(props: IProps) {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
CustomerService.createCustomer(customerData, validatorId).then((processCreated: any) => { CustomerService.createCustomer(customerData, validatorId).then((processCreated: any) => {
FolderService.getFolderByUid(folderUid as string, false, false).then((process: any) => { FolderService.getFolderByUid(folderUid as string).then((process: any) => {
if (process) { if (process) {
let customers: any[] = process.processData.customers; const customers: any[] = [];
if (!customers) { for (const customerUid of process.processData.customers.map((customer: any) => customer.uid)) {
customers = []; customers.push({ uid: customerUid });
} }
customers.push(processCreated.processData.uid); customers.push({ uid: processCreated.processData.uid });
FolderService.updateFolder(process, { customers: customers }).then(() => { FolderService.updateFolder(process, { customers: customers }).then(() => {
ToasterService.getInstance().success({ ToasterService.getInstance().success({
title: "Succès !", title: "Succès !",
description: "Client ajouté avec succès au dossier" description: "Client ajouté avec succès au dossier"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push(`/folders/${folderUid}`); router.push(`/folders/${folderUid}`);
LoaderService.getInstance().hide();
}); });
} }
}); });
@ -108,17 +108,20 @@ export default function AddClientToFolder(props: IProps) {
} }
} else { } else {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
FolderService.getFolderByUid(folderUid as string, false, false).then((process: any) => { FolderService.getFolderByUid(folderUid as string).then((process: any) => {
if (process) { if (process) {
const customers: any[] = customersToLink.map((customer: any) => customer.uid); const customers: any[] = [];
for (const customerUid of customersToLink.map((customer: any) => customer.uid)) {
customers.push({ uid: customerUid });
}
FolderService.updateFolder(process, { customers: customers }).then(() => { FolderService.updateFolder(process, { customers: customers }).then(() => {
ToasterService.getInstance().success({ ToasterService.getInstance().success({
title: "Succès !", title: "Succès !",
description: selectedCustomers.length > 1 ? "Clients associés avec succès au dossier" : "Client associé avec succès au dossier" description: selectedCustomers.length > 1 ? "Clients associés avec succès au dossier" : "Client associé avec succès au dossier"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push(`/folders/${folderUid}`); router.push(`/folders/${folderUid}`);
LoaderService.getInstance().hide();
}); });
} }
}); });

View File

@ -97,14 +97,21 @@ export default function ParameterDocuments(props: IProps) {
const oldDocumentsType = props.folder.deed?.document_types!; const oldDocumentsType = props.folder.deed?.document_types!;
await new Promise<void>((resolve: () => void) => { await new Promise<void>((resolve: () => void) => {
DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then((process: any) => { DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then(async (process: any) => {
if (process) { if (process) {
DeedTypeService.updateDeedType(process, { // New data
const newData: any = {
document_types: [ document_types: [
...oldDocumentsType.map((document: any) => ({ uid: document.uid })), ...oldDocumentsType.map((document: any) => ({ uid: document.uid })),
{ uid: documentType.uid } { uid: documentType.uid }
] ]
}).then(() => resolve()); };
// Merge process data with new data & update process
process.processData.document_types = newData.document_types;
await DeedTypeService.updateDeedType(process, newData);
resolve();
} }
}); });
}); });
@ -131,14 +138,21 @@ export default function ParameterDocuments(props: IProps) {
const oldDocumentsType = props.folder.deed?.document_types!; const oldDocumentsType = props.folder.deed?.document_types!;
await new Promise<void>((resolve: () => void) => { await new Promise<void>((resolve: () => void) => {
DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then((process: any) => { DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then(async (process: any) => {
if (process) { if (process) {
DeedTypeService.updateDeedType(process, { // New data
const newData: any = {
document_types: [ document_types: [
...oldDocumentsType.map((document: any) => ({ uid: document.uid })), ...oldDocumentsType.map((document: any) => ({ uid: document.uid })),
...selectedDocuments.map((document: any) => ({ uid: document.id as string })) ...selectedDocuments.map((document: any) => ({ uid: document.id as string }))
] ]
}).then(() => resolve()); };
// Merge process data with new data & update process
process.processData.document_types = newData.document_types;
await DeedTypeService.updateDeedType(process, newData);
resolve();
} }
}); });
}); });

View File

@ -62,27 +62,35 @@ export default function AskDocuments() {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
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++) {
const documentTypeUid = documentAsked[i];
if (!documentTypeUid) continue;
const documentData: any = { const documentData: any = {
folder: { folder: {
uid: folderUid, uid: folderUid as string,
}, },
depositor: { depositor: {
uid: customerUid, uid: customerUid as string,
}, },
document_type: { document_type: {
uid: documentAsked[i] uid: documentTypeUid
}, },
document_status: EDocumentStatus.ASKED document_status: EDocumentStatus.ASKED,
file_uid: null,
}; };
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0'; const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
await DocumentService.createDocument(documentData, validatorId); await DocumentService.createDocument(documentData, validatorId);
} }
router.push(
Module.getInstance() FolderService.refreshFolderByUid(folderUid as string).then(() => {
.get() LoaderService.getInstance().hide();
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string), router.push(
); Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
);
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -101,7 +109,7 @@ export default function AskDocuments() {
// If those UIDs are already asked, filter them to not show them in the list and only // If those UIDs are already asked, filter them to not show them in the list and only
// show the documents that are not asked yet // show the documents that are not asked yet
const documentTypes = folder.deed!.document_types!.filter((documentType) => { const documentTypes = folder.deed?.document_types?.filter((documentType) => {
if (userDocumentTypesUids.includes(documentType!.uid!)) return false; if (userDocumentTypesUids.includes(documentType!.uid!)) return false;
return true; return true;
}); });

View File

@ -11,7 +11,6 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
import { ToasterService } from "@Front/Components/DesignSystem/Toaster"; import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import JwtService from "@Front/Services/JwtService/JwtService";
import { ValidationError } from "class-validator/types/validation/ValidationError"; import { ValidationError } from "class-validator/types/validation/ValidationError";
import { Deed, Office, OfficeFolder } from "le-coffre-resources/dist/Notary"; import { Deed, Office, OfficeFolder } from "le-coffre-resources/dist/Notary";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
@ -21,9 +20,12 @@ 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 UserStore from "@Front/Stores/UserStore";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService"; import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
export default function CreateFolder(): JSX.Element { export default function CreateFolder(): JSX.Element {
/** /**
@ -50,9 +52,8 @@ export default function CreateFolder(): JSX.Element {
[key: string]: any; [key: string]: any;
}, },
) => { ) => {
// TODO: review const user: any = UserStore.instance.getUser();
console.log(JwtService.getInstance().decodeJwt()); const officeId: string = user.office.uid;
const officeId = 'demo_notary_office_id'; //JwtService.getInstance().decodeJwt()?.office_Id;
const officeFolderModel = OfficeFolder.hydrate<OfficeFolder>({ const officeFolderModel = OfficeFolder.hydrate<OfficeFolder>({
folder_number: values["folder_number"], folder_number: values["folder_number"],
@ -93,7 +94,7 @@ export default function CreateFolder(): JSX.Element {
customers: [], customers: [],
documents: [], documents: [],
notes: [], notes: [],
stakeholders: folderAccessType === "whole_office" ? availableCollaborators : selectedCollaborators, stakeholders: (folderAccessType === "whole_office" ? availableCollaborators : selectedCollaborators).map((collaborator: any) => ({ uid: collaborator.uid })),
status: EFolderStatus.LIVE status: EFolderStatus.LIVE
}; };
@ -103,9 +104,9 @@ export default function CreateFolder(): JSX.Element {
title: "Succès !", title: "Succès !",
description: "Dossier créé avec succès" description: "Dossier créé avec succès"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
const folderUid: string = processCreated.processData.uid; const folderUid: string = processCreated.processData.uid;
router.push(`/folders/${folderUid}`); router.push(`/folders/${folderUid}`);
LoaderService.getInstance().hide();
}); });
} catch (backError) { } catch (backError) {
if (!Array.isArray(backError)) return; if (!Array.isArray(backError)) return;
@ -130,34 +131,25 @@ export default function CreateFolder(): JSX.Element {
* UseEffect * UseEffect
*/ */
useEffect(() => { useEffect(() => {
DeedTypeService.getDeedTypes().then((processes: any[]) => { DeedTypeService.getDeedTypes((processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {
let deedTypes: any[] = processes.map((process: any) => process.processData); const deedTypes: any[] = processes.map((process: any) => process.processData);
// FilterBy archived_at = null or not defined
deedTypes = deedTypes.filter((deedType: any) => !deedType.archived_at);
setAvailableDeedTypes(deedTypes); setAvailableDeedTypes(deedTypes);
} }
}); });
/* TODO: review CollaboratorService.getCollaborators((processes: any[]) => {
// no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller if (processes.length > 0) {
Users.getInstance() const collaborators: any[] = processes.map((process: any) => process.processData);
.get({ setAvailableCollaborators(collaborators);
include: { contact: true },
}) const user: any = UserStore.instance.getUser();
.then((users) => { const currentUser: any = collaborators.find((collaborator: any) => collaborator.uid === user.uid);
setAvailableCollaborators(users);
/ *
// set default selected collaborators to the connected user
const currentUser = users.find((user) => user.uid === JwtService.getInstance().decodeJwt()?.userId);
if (currentUser) { if (currentUser) {
setSelectedCollaborators([currentUser]); setSelectedCollaborators([currentUser]);
} }
* / }
}); });
*/
}, []); }, []);
/** /**

View File

@ -0,0 +1,171 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl, 40px);
max-width: 800px;
margin: 0 auto;
padding: var(--spacing-xl, 32px);
.header {
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
text-align: center;
}
.content {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl, 40px);
.drag-drop-container {
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
@media (min-width: $screen-m) {
flex-direction: row;
gap: var(--spacing-lg, 24px);
}
.drag-drop-box {
flex: 1;
min-height: 200px;
display: flex;
flex-direction: column;
gap: var(--spacing-sm, 8px);
// Force the DragAndDrop component to take full width and height
> div {
width: 100% !important;
height: 100% !important;
min-height: 200px !important;
display: flex !important;
flex-direction: column !important;
// Override the fit-content width from DragAndDrop
&.root {
width: 100% !important;
height: 100% !important;
min-height: 200px !important;
}
}
.file-info {
padding: var(--spacing-sm, 8px) var(--spacing-md, 16px);
background-color: var(--color-success-50, #f0f9ff);
border: 1px solid var(--color-success-200, #bae6fd);
border-radius: var(--radius-md, 8px);
margin-top: var(--spacing-sm, 8px);
}
}
}
.warning {
text-align: center;
padding: var(--spacing-md, 16px);
background-color: var(--color-warning-50, #fffbeb);
border: 1px solid var(--color-warning-200, #fde68a);
border-radius: var(--radius-md, 8px);
}
.verification-result {
text-align: center;
padding: var(--spacing-lg, 24px);
border-radius: var(--radius-md, 8px);
border: 2px solid;
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
&.success {
background-color: var(--color-success-50, #f0fdf4);
border-color: var(--color-success-200, #bbf7d0);
}
&.error {
background-color: var(--color-error-50, #fef2f2);
border-color: var(--color-error-200, #fecaca);
}
.verification-details {
background-color: rgba(0, 0, 0, 0.05);
padding: var(--spacing-md, 16px);
border-radius: var(--radius-sm, 4px);
font-family: monospace;
white-space: pre-line;
text-align: left;
max-width: 100%;
overflow-x: auto;
}
.merkle-proof-section {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-md, 16px);
padding: var(--spacing-lg, 24px);
background-color: rgba(0, 0, 0, 0.02);
border-radius: var(--radius-md, 8px);
border: 1px solid rgba(0, 0, 0, 0.1);
}
}
.qr-container {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-sm, 8px);
.qr-code {
width: 150px;
height: 150px;
border: 2px solid var(--color-neutral-200, #e5e7eb);
border-radius: var(--radius-sm, 4px);
padding: var(--spacing-sm, 8px);
background-color: white;
}
.qr-description {
text-align: center;
max-width: 200px;
}
}
.qr-loading {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
height: 150px;
border: 2px dashed var(--color-neutral-300, #d1d5db);
border-radius: var(--radius-sm, 4px);
background-color: var(--color-neutral-50, #f9fafb);
}
.qr-error {
display: flex;
justify-content: center;
align-items: center;
width: 150px;
height: 150px;
border: 2px solid var(--color-error-200, #fecaca);
border-radius: var(--radius-sm, 4px);
background-color: var(--color-error-50, #fef2f2);
}
.actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--spacing-lg, 24px);
@media (max-width: $screen-s) {
flex-direction: column;
gap: var(--spacing-md, 16px);
}
}
}
}

View File

@ -0,0 +1,260 @@
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module";
import PdfService from "@Front/Services/PdfService";
import { FileBlob } from "@Front/Api/Entities/types";
import { ShieldCheckIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";
import React, { useState } from "react";
import MessageBus from "src/sdk/MessageBus";
import classes from "./classes.module.scss";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
type IProps = {
folderUid: string;
};
/**
* Convert a File object to FileBlob
* @param file - The File object to convert
* @returns Promise<FileBlob> - The converted FileBlob
*/
const convertFileToFileBlob = async (file: File): Promise<FileBlob> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const arrayBuffer = reader.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
resolve({
type: file.type,
data: uint8Array
});
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsArrayBuffer(file);
});
};
export default function DocumentVerification(props: IProps) {
const { folderUid } = props;
const router = useRouter();
const [documentToVerify, setDocumentToVerify] = useState<File | null>(null);
const [validationCertificate, setValidationCertificate] = useState<File | null>(null);
const [isVerifying, setIsVerifying] = useState(false);
const [verificationResult, setVerificationResult] = useState<{
success: boolean;
message: string;
details?: string;
merkleProof?: string;
} | null>(null);
const handleDocumentToVerifyChange = (files: File[]) => {
if (files.length > 0 && files[0]) {
setDocumentToVerify(files[0]);
} else {
setDocumentToVerify(null);
}
};
const handleValidationCertificateChange = (files: File[]) => {
if (files.length > 0 && files[0]) {
setValidationCertificate(files[0]);
} else {
setValidationCertificate(null);
}
};
const handleVerifyDocuments = async () => {
if (!documentToVerify || !validationCertificate) {
console.error("Both documents are required for verification");
return;
}
setIsVerifying(true);
setVerificationResult(null);
const messageBus = MessageBus.getInstance();
try {
// Here the things we need to verify:
// - we can produce the same hash from the document provided than what is in the validation certificate
// - the merkle proof is valid with that hash
// - the root of the merkle tree is a state id from a commited state in the process
// - that process is a file process linked to the right folder
// Step 1: Parse the validation certificate
const validationData = await PdfService.getInstance().parseCertificate(validationCertificate);
// Step 2: Convert File to FileBlob and hash the document using MessageBus
const fileBlob = await convertFileToFileBlob(documentToVerify);
await messageBus.isReady();
const documentHash = await messageBus.hashDocument(fileBlob, validationData.commitmentId);
// Step 3: Compare hashes
const hashesMatch = documentHash.toLowerCase() === validationData.documentHash.toLowerCase();
if (!hashesMatch) {
throw new Error('Hash du document invalide, le document fourni n\'est pas celui qui a été certifié');
}
// Step 4: Verify the merkle proof
const merkleProof = validationData.merkleProof;
const merkleProofValid = await messageBus.verifyMerkleProof(merkleProof, documentHash);
if (!merkleProofValid) {
throw new Error('Preuve de Merkle invalide, le document n\'a pas été certifié là où le certificat le prétend');
}
// Step 5: Verify that this file process depends on the right folder process
// First pin all the validated documents related to the folder
const documentProcesses = await DocumentService.getDocuments();
const documents = documentProcesses.filter((process: any) =>
process.processData.document_status === "VALIDATED" &&
process.processData.folder.uid === folderUid
);
if (!documents || documents.length === 0) {
throw new Error(`Aucune demande de document trouvé pour le dossier ${folderUid}`);
}
// Step 6: verify that the merkle proof match the last commited state for the file process
const stateId = JSON.parse(validationData.merkleProof)['root'];
let stateIdExists = false;
for (const doc of documents) {
const processData = doc.processData;
for (const file of processData.files) {
const fileUid = file.uid;
const fileProcess = await FileService.getFileByUid(fileUid);
const lastUpdatedStateId = fileProcess.lastUpdatedFileState.state_id;
stateIdExists = lastUpdatedStateId === stateId; // we assume that last state is the validated document, that seems reasonable
if (stateIdExists) break;
}
if (stateIdExists) break;
}
if (!stateIdExists) {
throw new Error('La preuve fournie ne correspond à aucun document demandé pour ce dossier.');
}
setVerificationResult({
success: true,
message: "✅ Vérification réussie ! Le document est authentique et intègre.",
});
} catch (error) {
console.error("Verification failed:", error);
setVerificationResult({
success: false,
message: `❌ Erreur lors de la vérification: ${error}`,
});
} finally {
setIsVerifying(false);
}
};
const handleBackToFolder = () => {
const folderPath = Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid);
router.push(folderPath);
};
const bothDocumentsPresent = documentToVerify && validationCertificate;
return (
<div className={classes["root"]}>
<div className={classes["header"]}>
<Typography typo={ETypo.TITLE_H2} color={ETypoColor.TEXT_PRIMARY}>
Vérification de Documents
</Typography>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Vérifiez l'intégrité et l'authenticité de vos documents
</Typography>
</div>
<div className={classes["content"]}>
<div className={classes["drag-drop-container"]}>
<div className={classes["drag-drop-box"]}>
<DragAndDrop
title="Document à valider"
description="Glissez-déposez ou cliquez pour sélectionner le document que vous souhaitez vérifier"
onChange={handleDocumentToVerifyChange}
/>
{documentToVerify && (
<div className={classes["file-info"]}>
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_SUCCESS_500}>
{documentToVerify.name}
</Typography>
</div>
)}
</div>
<div className={classes["drag-drop-box"]}>
<DragAndDrop
title="Certificat de validation"
description="Glissez-déposez ou cliquez pour sélectionner le certificat de validation correspondant"
onChange={handleValidationCertificateChange}
/>
{validationCertificate && (
<div className={classes["file-info"]}>
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_SUCCESS_500}>
{validationCertificate.name}
</Typography>
</div>
)}
</div>
</div>
{!bothDocumentsPresent && (
<div className={classes["warning"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_WARNING_500}>
Veuillez sélectionner les deux documents pour procéder à la vérification
</Typography>
</div>
)}
{verificationResult && (
<div className={`${classes["verification-result"]} ${classes[verificationResult.success ? "success" : "error"]}`}>
<Typography typo={ETypo.TEXT_LG_REGULAR} color={verificationResult.success ? ETypoColor.COLOR_SUCCESS_500 : ETypoColor.COLOR_ERROR_500}>
{verificationResult.message}
</Typography>
</div>
)}
<div className={classes["actions"]}>
<Button
variant={EButtonVariant.SECONDARY}
styletype={EButtonstyletype.TEXT}
size={EButtonSize.LG}
onClick={handleBackToFolder}
disabled={isVerifying}
>
Retour au dossier
</Button>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.CONTAINED}
size={EButtonSize.LG}
onClick={handleVerifyDocuments}
rightIcon={<ShieldCheckIcon />}
disabled={!bothDocumentsPresent || isVerifying}
isLoading={isVerifying}
>
{isVerifying ? "Vérification en cours..." : "Vérifier les documents"}
</Button>
</div>
</div>
</div>
);
}

View File

@ -30,12 +30,6 @@ 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(
(customerUid: string) => { (customerUid: string) => {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
@ -100,15 +94,6 @@ 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

@ -4,6 +4,7 @@ import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
type IProps = { type IProps = {
@ -23,7 +24,10 @@ export default function DeleteAskedDocumentModal(props: IProps) {
(resolve: () => void) => { (resolve: () => void) => {
DocumentService.getDocumentByUid(documentUid).then((process: any) => { DocumentService.getDocumentByUid(documentUid).then((process: any) => {
if (process) { if (process) {
DocumentService.updateDocument(process, { isDeleted: 'true' }).then(() => resolve()); const document: any = process.processData;
DocumentService.updateDocument(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
});
} }
}); });
}) })

View File

@ -4,6 +4,7 @@ import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
type IProps = { type IProps = {
@ -21,8 +22,10 @@ export default function DeleteSentDocumentModal(props: IProps) {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
DocumentService.getDocumentByUid(documentUid).then((process: any) => { DocumentService.getDocumentByUid(documentUid).then((process: any) => {
if (process) { if (process) {
DocumentService.updateDocument(process, { isDeleted: 'true' }) const document: any = process.processData;
DocumentService.updateDocument(process, { isDeleted: 'true', archived_at: new Date().toISOString() })
.then(() => onDeleteSuccess(documentUid)) .then(() => onDeleteSuccess(documentUid))
.then(() => FolderService.refreshFolderByUid(document.folder.uid))
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." })) .then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
.then(() => LoaderService.getInstance().hide()) .then(() => LoaderService.getInstance().hide())
.then(onClose); .then(onClose);

View File

@ -1,8 +1,12 @@
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import React from "react"; import React from "react";
import { FileBlob } from "@Front/Api/Entities/types";
type IProps = { type IProps = {
file: any; file: {
uid: string;
file_blob: FileBlob;
};
url: string; url: string;
isOpen: boolean; isOpen: boolean;
onClose?: () => void; onClose?: () => void;

View File

@ -6,7 +6,7 @@ import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import useOpenable from "@Front/Hooks/useOpenable"; import useOpenable from "@Front/Hooks/useOpenable";
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline"; import { ArrowDownTrayIcon, EyeIcon, TrashIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { useMediaQuery } from "@mui/material"; import { useMediaQuery } from "@mui/material";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import DocumentNotary, { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary"; import DocumentNotary, { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
@ -18,9 +18,14 @@ import classes from "./classes.module.scss";
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal"; import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
import DeleteSentDocumentModal from "./DeleteSentDocumentModal"; import DeleteSentDocumentModal from "./DeleteSentDocumentModal";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import PdfService, { CertificateData, Metadata } from "@Front/Services/PdfService";
import MessageBus from "src/sdk/MessageBus";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
type IProps = { type IProps = {
@ -53,33 +58,20 @@ export default function DocumentTables(props: IProps) {
const fetchDocuments = useCallback( const fetchDocuments = useCallback(
() => { () => {
LoaderService.getInstance().show(); setDocuments([]);
DocumentService.getDocuments().then(async (processes: any[]) => { FolderService.getFolderByUid(folderUid).then((process: any) => {
if (processes.length > 0) { if (process) {
let documents: any[] = processes.map((process: any) => process.processData); const folder: any = process.processData;
const customer: any = folder.customers.find((customer: any) => customer.uid === customerUid);
// FilterBy folder.uid & depositor.uid if (customer && customer.documents) {
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.depositor && document.depositor.uid === customerUid); const documents: any[] = customer.documents.filter((document: any) => document.depositor);
setDocuments(documents);
for (const document of documents) { } else {
if (document.document_type) { setDocuments([]);
document.document_type = (await DocumentTypeService.getDocumentTypeByUid(document.document_type.uid)).processData;
}
if (document.files && document.files.length > 0) {
const files: any[] = [];
for (const file of document.files) {
files.push((await FileService.getFileByUid(file.uid)).processData);
}
document.files = files;
}
} }
setDocuments(documents);
} else { } else {
setDocuments([]); setDocuments([]);
} }
LoaderService.getInstance().hide();
}); });
}, },
[customerUid, folderUid], [customerUid, folderUid],
@ -87,29 +79,20 @@ export default function DocumentTables(props: IProps) {
const fetchDocumentsNotary = useCallback( const fetchDocumentsNotary = useCallback(
() => { () => {
LoaderService.getInstance().show(); setDocumentsNotary([]);
DocumentService.getDocuments().then(async (processes: any[]) => { FolderService.getFolderByUid(folderUid).then((process: any) => {
if (processes.length > 0) { if (process) {
let documents: any[] = processes.map((process: any) => process.processData); const folder: any = process.processData;
const customer: any = folder.customers.find((customer: any) => customer.uid === customerUid);
// FilterBy folder.uid & customer.uid if (customer && customer.documents) {
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.customer && document.customer.uid === customerUid); const documents: any[] = customer.documents.filter((document: any) => document.customer);
setDocumentsNotary(documents);
for (const document of documents) { } else {
if (document.files && document.files.length > 0) { setDocumentsNotary([]);
const files: any[] = [];
for (const file of document.files) {
files.push((await FileService.getFileByUid(file.uid)).processData);
}
document.files = files;
}
} }
setDocumentsNotary(documents);
} else { } else {
setDocumentsNotary([]); setDocumentsNotary([]);
} }
LoaderService.getInstance().hide();
}); });
}, },
[customerUid, folderUid], [customerUid, folderUid],
@ -142,7 +125,13 @@ export default function DocumentTables(props: IProps) {
const file = doc.files?.[0]; const file = doc.files?.[0];
if (!file) return; if (!file) return;
return new Promise<void>((resolve: () => void) => { return new Promise<void>(async (resolve: () => void) => {
if (!file.file_blob) {
LoaderService.getInstance().show();
file.file_blob = (await FileService.getFileByUid(file.uid)).processData.file_blob;
LoaderService.getInstance().hide();
}
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type }); const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
@ -161,7 +150,12 @@ export default function DocumentTables(props: IProps) {
const file = doc.files?.[0]; const file = doc.files?.[0];
if (!file) return; if (!file) return;
return new Promise<void>((resolve: () => void) => { return new Promise<void>(async (resolve: () => void) => {
if (!file.file_blob) {
LoaderService.getInstance().show();
file.file_blob = (await FileService.getFileByUid(file.uid)).processData.file_blob;
LoaderService.getInstance().hide();
}
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type }); const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
@ -176,6 +170,66 @@ export default function DocumentTables(props: IProps) {
}).catch((e) => console.warn(e)); }).catch((e) => console.warn(e));
}, []); }, []);
const onDownloadCertificate = useCallback(async (doc: any) => {
try {
const certificateData: CertificateData = {
customer: {
firstName: doc.depositor.first_name || doc.depositor.firstName || "N/A",
lastName: doc.depositor.last_name || doc.depositor.lastName || "N/A",
postalAddress: doc.depositor.postal_address || doc.depositor.address || doc.depositor.postalAddress || "N/A",
email: doc.depositor.email || "N/A"
},
notary: {
name: "N/A"
},
folderUid: folderUid,
documentHash: "N/A",
metadata: {
fileName: "N/A",
isDeleted: false,
updatedAt: new Date(),
commitmentId: "N/A",
createdAt: new Date(),
documentUid: "N/A",
documentType: "N/A",
merkleProof: "N/A"
}
};
// Get the specific document that was clicked
const documentProcess = await DocumentService.getDocumentByUid(doc.uid);
if (!documentProcess) {
throw new Error('Document not found');
}
// Process only the files for this specific document
for (const file of documentProcess.processData.files) {
const fileProcess = await FileService.getFileByUid(file.uid);
const hash = fileProcess.lastUpdatedFileState.pcd_commitment.file_blob;
certificateData.documentHash = hash;
const proof = await MessageBus.getInstance().generateMerkleProof(fileProcess.lastUpdatedFileState, 'file_blob');
const metadata: Metadata = {
fileName: fileProcess.processData.file_name,
isDeleted: false,
updatedAt: new Date(fileProcess.processData.updated_at),
commitmentId: fileProcess.lastUpdatedFileState.commited_in,
createdAt: new Date(fileProcess.processData.created_at),
documentUid: doc.uid,
documentType: doc.document_type.name,
merkleProof: proof
};
certificateData.metadata = metadata;
}
await PdfService.getInstance().downloadCertificate(certificateData);
} catch (error) {
console.error('Error downloading certificate:', error);
}
}, [folderUid, customerUid]);
const askedDocuments: IRowProps[] = useMemo( const askedDocuments: IRowProps[] = useMemo(
() => () =>
documents documents
@ -183,7 +237,7 @@ export default function DocumentTables(props: IProps) {
if (document.document_status !== EDocumentStatus.ASKED) return null; if (document.document_status !== EDocumentStatus.ASKED) return null;
return { return {
key: document.uid, key: document.uid,
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "_" }, document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
document_status: { document_status: {
sx: { width: 107 }, sx: { width: 107 },
content: ( content: (
@ -223,7 +277,7 @@ export default function DocumentTables(props: IProps) {
if (document.document_status !== EDocumentStatus.DEPOSITED) return null; if (document.document_status !== EDocumentStatus.DEPOSITED) return null;
return { return {
key: document.uid, key: document.uid,
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "_" }, document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
document_status: { document_status: {
sx: { width: 107 }, sx: { width: 107 },
content: ( content: (
@ -240,7 +294,7 @@ export default function DocumentTables(props: IProps) {
}, },
file: { file: {
sx: { width: 120 }, sx: { width: 120 },
content: document.files?.[0]?.file_name ?? "_", content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
}, },
actions: { actions: {
sx: { width: 76 }, sx: { width: 76 },
@ -263,13 +317,13 @@ export default function DocumentTables(props: IProps) {
); );
const validatedDocuments: IRowProps[] = useMemo( const validatedDocuments: IRowProps[] = useMemo(
() => () => {
documents const validated = documents
.map((document) => { .map((document) => {
if (document.document_status !== EDocumentStatus.VALIDATED) return null; if (document.document_status !== EDocumentStatus.VALIDATED) return null;
return { return {
key: document.uid, key: document.uid,
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "_" }, document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
document_status: { document_status: {
sx: { width: 107 }, sx: { width: 107 },
content: ( content: (
@ -286,7 +340,7 @@ export default function DocumentTables(props: IProps) {
}, },
file: { file: {
sx: { width: 120 }, sx: { width: 120 },
content: document.files?.[0]?.file_name ?? "_", content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
}, },
actions: { actions: {
sx: { width: 76 }, sx: { width: 76 },
@ -300,13 +354,17 @@ export default function DocumentTables(props: IProps) {
<IconButton icon={<EyeIcon />} /> <IconButton icon={<EyeIcon />} />
</Link> </Link>
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} /> <IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
<IconButton onClick={() => onDownloadCertificate(document)} icon={<DocumentTextIcon />} />
</div> </div>
), ),
}, },
}; };
}) })
.filter((document) => document !== null) as IRowProps[], .filter((document) => document !== null) as IRowProps[];
[documents, folderUid, onDownload],
return validated;
},
[documents, folderUid, onDownload, onDownloadCertificate],
); );
const refusedDocuments: IRowProps[] = useMemo( const refusedDocuments: IRowProps[] = useMemo(
@ -316,7 +374,7 @@ export default function DocumentTables(props: IProps) {
if (document.document_status !== EDocumentStatus.REFUSED) return null; if (document.document_status !== EDocumentStatus.REFUSED) return null;
return { return {
key: document.uid, key: document.uid,
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "_" }, document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
document_status: { document_status: {
sx: { width: 107 }, sx: { width: 107 },
content: ( content: (
@ -333,7 +391,7 @@ export default function DocumentTables(props: IProps) {
}, },
file: { file: {
sx: { width: 120 }, sx: { width: 120 },
content: document.files?.[0]?.file_name ?? "_", content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
}, },
actions: { sx: { width: 76 }, content: "" }, actions: { sx: { width: 76 }, content: "" },
}; };
@ -350,7 +408,7 @@ export default function DocumentTables(props: IProps) {
key: document.uid, key: document.uid,
document_type: { document_type: {
sx: { width: 300 }, sx: { width: 300 },
content: formatName(document.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_", content: "Autre document",
}, },
document_status: { document_status: {
sx: { width: 107 }, sx: { width: 107 },
@ -362,7 +420,7 @@ export default function DocumentTables(props: IProps) {
}, },
file: { file: {
sx: { width: 120 }, sx: { width: 120 },
content: document.files?.[0]?.file_name ?? "_", content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
}, },
actions: { actions: {
sx: { width: 76 }, sx: { width: 76 },
@ -380,10 +438,11 @@ export default function DocumentTables(props: IProps) {
); );
const progress = useMemo(() => { const progress = useMemo(() => {
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length; // Exclude refused documents from total - only count documents that are still in progress
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length;
if (total === 0) return 0; if (total === 0) return 0;
return (validatedDocuments.length / total) * 100; return (validatedDocuments.length / total) * 100;
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]); }, [askedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
if (documents.length === 0 && documentsNotary.length === 0) return <NoDocument />; if (documents.length === 0 && documentsNotary.length === 0) return <NoDocument />;
@ -461,10 +520,6 @@ function getHeader(dateColumnTitle: string, isMobile: boolean): IHead[] {
]; ];
} }
function formatName(text: string): string {
return text.replace(/[^a-zA-Z0-9 ]/g, "");
}
function getTagForSentDocument(status: EDocumentNotaryStatus) { function getTagForSentDocument(status: EDocumentNotaryStatus) {
if (status === EDocumentNotaryStatus.SENT) { if (status === EDocumentNotaryStatus.SENT) {
return ( return (

View File

@ -11,6 +11,11 @@ import React, { useCallback, useMemo, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import UserStore from "@Front/Stores/UserStore";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
onClose?: () => void; onClose?: () => void;
@ -23,9 +28,24 @@ export default function ReminderModal(props: IProps) {
const [selectedOptions, setSelectedOptions] = useState<IOption[]>([]); const [selectedOptions, setSelectedOptions] = useState<IOption[]>([]);
const [isAllSelected, setIsAllSelected] = useState(false); const [isAllSelected, setIsAllSelected] = useState(false);
const onRemind = useCallback(() => { const onRemind = useCallback(async () => {
LoaderService.getInstance().show();
const documentUids: string[] = selectedOptions.map((option) => option.value) as string[];
const user: any = UserStore.instance.getUser();
const _office: any = user.office;
const _documents: any[] = [];
for (const documentUid of documentUids) {
_documents.push((await DocumentService.getDocumentByUid(documentUid)).processData);
}
const _customer = (await CustomerService.getCustomerByUid(customer.uid!)).processData;
LoaderService.getInstance().hide();
Customers.getInstance() Customers.getInstance()
.sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[]) .sendReminder(_office, _customer)
.then(onRemindSuccess) .then(onRemindSuccess)
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "La relance a été envoyée avec succès." })) .then(() => ToasterService.getInstance().success({ title: "Succès !", description: "La relance a été envoyée avec succès." }))
.then(onClose); .then(onClose);

View File

@ -64,7 +64,7 @@ export default function ClientView(props: IProps) {
(customerUid: string) => { (customerUid: string) => {
if (!folder.uid) return; if (!folder.uid) return;
LoaderService.getInstance().show(); LoaderService.getInstance().show();
FolderService.getFolderByUid(folder.uid, false, false).then((process: any) => { FolderService.getFolderByUid(folder.uid).then((process: any) => {
if (process) { if (process) {
const folder: any = process.processData; const folder: any = process.processData;
@ -72,7 +72,7 @@ export default function ClientView(props: IProps) {
const customers = folder.customers.filter((uid: string) => uid !== customerUid); const customers = folder.customers.filter((uid: string) => uid !== customerUid);
FolderService.updateFolder(process, { customers: customers }).then(() => { FolderService.updateFolder(process, { customers: customers }).then(() => {
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
window.location.reload(); window.location.reload();
}); });
} }

View File

@ -5,11 +5,12 @@ import { IItem } from "@Front/Components/DesignSystem/Menu/MenuItem";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag"; import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PaperAirplaneIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline"; import { ArchiveBoxIcon, DocumentTextIcon, EllipsisHorizontalIcon, PaperAirplaneIcon, PencilSquareIcon, ShieldCheckIcon, UsersIcon } from "@heroicons/react/24/outline";
import classNames from "classnames"; import classNames from "classnames";
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 { useMemo } from "react"; import { useMemo } from "react";
import { useRouter } from "next/router";
import { AnchorStatus } from ".."; import { AnchorStatus } from "..";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -20,10 +21,12 @@ type IProps = {
onArchive: () => void; onArchive: () => void;
anchorStatus: AnchorStatus; anchorStatus: AnchorStatus;
isArchived: boolean; isArchived: boolean;
onDownloadAllCertificates: () => void;
}; };
export default function InformationSection(props: IProps) { export default function InformationSection(props: IProps) {
const { folder, progress, onArchive, anchorStatus, isArchived } = props; const { folder, progress, onArchive, anchorStatus, isArchived, onDownloadAllCertificates } = props;
const router = useRouter();
const menuItemsDekstop = useMemo(() => { const menuItemsDekstop = useMemo(() => {
let elements: IItem[] = []; let elements: IItem[] = [];
@ -43,6 +46,24 @@ export default function InformationSection(props: IProps) {
link: Module.getInstance() link: Module.getInstance()
.get() .get()
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""), .modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true,
};
const verifyDocumentElement = {
icon: <ShieldCheckIcon />,
text: "Vérifier le document",
onClick: () => {
const verifyPath = Module.getInstance()
.get()
.modules.pages.Folder.pages.VerifyDocuments.props.path.replace("[folderUid]", folder?.uid ?? "");
router.push(verifyPath);
},
hasSeparator: true,
};
const downloadAllCertificatesElement = {
icon: <DocumentTextIcon />,
text: "Télécharger tous les certificats",
onClick: () => onDownloadAllCertificates(),
hasSeparator: false, hasSeparator: false,
}; };
@ -52,8 +73,12 @@ export default function InformationSection(props: IProps) {
elements.push(modifyInformationsElement); elements.push(modifyInformationsElement);
} }
// Add verify document option
elements.push(verifyDocumentElement);
elements.push(downloadAllCertificatesElement);
return elements; return elements;
}, [anchorStatus, folder?.uid]); }, [anchorStatus, folder?.uid, router]);
const menuItemsMobile = useMemo(() => { const menuItemsMobile = useMemo(() => {
let elements: IItem[] = []; let elements: IItem[] = [];
@ -75,6 +100,24 @@ export default function InformationSection(props: IProps) {
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""), .modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
hasSeparator: true, hasSeparator: true,
}; };
const verifyDocumentElement = {
icon: <ShieldCheckIcon />,
text: "Vérifier le document",
onClick: () => {
const verifyPath = Module.getInstance()
.get()
.modules.pages.Folder.pages.VerifyDocuments.props.path.replace("[folderUid]", folder?.uid ?? "");
router.push(verifyPath);
},
hasSeparator: true,
};
const downloadAllCertificatesElement = {
icon: <DocumentTextIcon />,
text: "Télécharger tous les certificats",
onClick: () => onDownloadAllCertificates(),
hasSeparator: false,
};
// If the folder is not anchored, we can modify the collaborators and the informations // If the folder is not anchored, we can modify the collaborators and the informations
if (anchorStatus === AnchorStatus.NOT_ANCHORED) { if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
@ -82,6 +125,10 @@ export default function InformationSection(props: IProps) {
elements.push(modifyInformationsElement); elements.push(modifyInformationsElement);
} }
// Add verify document option
elements.push(verifyDocumentElement);
elements.push(downloadAllCertificatesElement);
elements.push({ elements.push({
icon: <PaperAirplaneIcon />, icon: <PaperAirplaneIcon />,
text: "Envoyer des documents", text: "Envoyer des documents",
@ -101,7 +148,7 @@ export default function InformationSection(props: IProps) {
} }
return elements; return elements;
}, [anchorStatus, folder?.uid, isArchived, onArchive]); }, [anchorStatus, folder?.uid, isArchived, onArchive, onDownloadAllCertificates, router]);
return ( return (
<section className={classes["root"]}> <section className={classes["root"]}>
<div className={classes["info-box1"]}> <div className={classes["info-box1"]}>

View File

@ -28,8 +28,8 @@ export default function DeleteFolderModal(props: IProps) {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
FolderService.getFolderByUid(folder.uid!).then((process: any) => { FolderService.getFolderByUid(folder.uid!).then((process: any) => {
if (process) { if (process) {
FolderService.updateFolder(process, { isDeleted: 'true' }).then(() => { FolderService.updateFolder(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
resolve(); resolve();
}); });
} }

View File

@ -1,6 +1,6 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert"; import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button"; import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
import { LockClosedIcon } from "@heroicons/react/24/outline"; import { ArchiveBoxIcon } from "@heroicons/react/24/outline";
type IProps = { type IProps = {
onAnchor: () => void; onAnchor: () => void;
@ -10,12 +10,12 @@ export default function AnchoringAlertInfo(props: IProps) {
const { onAnchor } = props; const { onAnchor } = props;
return ( return (
<Alert <Alert
title="Validation et Certification du Dossier" title="Dossier complet"
description="Votre dossier est désormais complet à 100%. Vous pouvez maintenant procéder à la validation et à l'ancrage des documents dans la blockchain. Cette étape garantit la sécurité et l'authenticité de vos documents." description="Vous pouvez maintenant archiver le dossier."
firstButton={{ firstButton={{
children: "Ancrer et certifier", children: "Archiver",
styletype: EButtonstyletype.CONTAINED, styletype: EButtonstyletype.CONTAINED,
rightIcon: <LockClosedIcon />, rightIcon: <ArchiveBoxIcon />,
onClick: onAnchor, onClick: onAnchor,
}} }}
variant={EAlertVariant.INFO} variant={EAlertVariant.INFO}

View File

@ -1,11 +1,14 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert"; import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import { EButtonstyletype } from "@Front/Components/DesignSystem/Button"; import { EButtonstyletype } from "@Front/Components/DesignSystem/Button";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { ArchiveBoxArrowDownIcon, ArchiveBoxIcon, ArrowDownOnSquareIcon } from "@heroicons/react/24/outline"; import { ArchiveBoxArrowDownIcon, ArchiveBoxIcon, ArrowDownOnSquareIcon } from "@heroicons/react/24/outline";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback } from "react"; import { useCallback } from "react";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = { type IProps = {
onDownloadAnchoringProof: () => void; onDownloadAnchoringProof: () => void;
folderUid: string; folderUid: string;
@ -17,12 +20,15 @@ export default function ArchiveAlertWarning(props: IProps) {
const router = useRouter(); const router = useRouter();
const restoreArchive = useCallback(() => { const restoreArchive = useCallback(() => {
Folders.getInstance() LoaderService.getInstance().show();
.restore(folderUid) FolderService.getFolderByUid(folderUid).then((process: any) => {
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path)) if (process) {
.catch((e) => { FolderService.updateFolder(process, { archived_at: null, archived_description: null, status: EFolderStatus.LIVE })
console.warn(e); .then(() => LoaderService.getInstance().hide())
}); .then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
.catch((e) => console.error(e));
}
});
}, [folderUid, router]); }, [folderUid, router]);
return ( return (

View File

@ -1,4 +1,3 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
@ -6,6 +5,10 @@ import Module from "@Front/Config/Module";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
@ -19,14 +22,16 @@ export default function ArchiveModal(props: IProps) {
const archive = useCallback(() => { const archive = useCallback(() => {
const description = (document.querySelector("textarea[name='archived_description']") as HTMLTextAreaElement).value ?? ""; const description = (document.querySelector("textarea[name='archived_description']") as HTMLTextAreaElement).value ?? "";
LoaderService.getInstance().show();
Folders.getInstance() FolderService.getFolderByUid(folderUid).then((process: any) => {
.archive(folderUid, description) if (process) {
.then(onClose) FolderService.updateFolder(process, { archived_at: new Date().toISOString(), archived_description: description, status: EFolderStatus.ARCHIVED })
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path)) .then(() => LoaderService.getInstance().hide())
.catch((e) => { .then(onClose)
console.warn(e); .then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
}); .catch((e) => console.error(e));
}
});
}, [folderUid, onClose, router]); }, [folderUid, onClose, router]);
return ( return (

View File

@ -1,4 +1,3 @@
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors"; import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
import Loader from "@Front/Components/DesignSystem/Loader"; import Loader from "@Front/Components/DesignSystem/Loader";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
@ -22,9 +21,14 @@ import NoClientView from "./NoClientView";
import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo"; import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import NoteService from "src/common/Api/LeCoffreApi/sdk/NoteService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import PdfService from "@Front/Services/PdfService";
import MessageBus from "src/sdk/MessageBus";
import { saveAs } from "file-saver";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
export enum AnchorStatus { export enum AnchorStatus {
"VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN", "VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN",
@ -38,7 +42,7 @@ export default function FolderInformation(props: IProps) {
const { isArchived = false } = props; const { isArchived = false } = props;
const [anchorStatus, setAnchorStatus] = useState<AnchorStatus>(AnchorStatus.NOT_ANCHORED); const [anchorStatus, setAnchorStatus] = useState<AnchorStatus>(AnchorStatus.NOT_ANCHORED);
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [folder, setFolder] = useState<OfficeFolder | null>(null); const [folder, setFolder] = useState<any>(null);
const anchoringModal = useOpenable(); const anchoringModal = useOpenable();
const downloadAnchoringProofModal = useOpenable(); const downloadAnchoringProofModal = useOpenable();
const requireAnchoringModal = useOpenable(); const requireAnchoringModal = useOpenable();
@ -50,10 +54,12 @@ export default function FolderInformation(props: IProps) {
const progress = useMemo(() => { const progress = useMemo(() => {
let total = 0; let total = 0;
let validatedDocuments = 0; let validatedDocuments = 0;
folder?.customers?.forEach((customer) => { folder?.customers?.forEach((customer: any) => {
const documents = customer.documents; const documents = customer.documents;
total += documents?.length ?? 0; // Only count documents that are not refused (still in progress)
validatedDocuments += documents?.filter((document) => document.document_status === EDocumentStatus.VALIDATED).length ?? 0; const activeDocuments = documents?.filter((document: any) => document.document_status !== EDocumentStatus.REFUSED) ?? [];
total += activeDocuments.length;
validatedDocuments += activeDocuments.filter((document: any) => document.document_status === EDocumentStatus.VALIDATED).length;
}); });
if (total === 0) return 0; if (total === 0) return 0;
const percentage = (validatedDocuments / total) * 100; const percentage = (validatedDocuments / total) * 100;
@ -109,41 +115,10 @@ export default function FolderInformation(props: IProps) {
*/ */
// TODO: review // TODO: review
LoaderService.getInstance().show();
return FolderService.getFolderByUid(folderUid).then(async (process: any) => { return FolderService.getFolderByUid(folderUid).then(async (process: any) => {
if (process) { if (process) {
const folder: any = process.processData; const folder: any = process.processData;
await new Promise<void>((resolve: () => void) => {
NoteService.getNotes().then((processes: any) => {
if (processes.length > 0) {
let notes: any[] = processes.map((process: any) => process.processData);
// FilterBy folder.uid
notes = notes.filter((note: any) => note.folder.uid === folderUid);
if (notes.length > 0) {
folder.notes = notes;
}
}
resolve();
});
});
await new Promise<void>((resolve: () => void) => {
DocumentService.getDocuments().then((processes: any[]) => {
if (processes.length > 0) {
const documents: any[] = processes.map((process: any) => process.processData);
for (const customer of folder.customers) {
customer.documents = documents.filter((document: any) => document.depositor && document.depositor.uid === customer.uid);
}
}
resolve();
});
});
setFolder(folder); setFolder(folder);
setTimeout(() => LoaderService.getInstance().hide(), 2000);
} }
}); });
}, [folderUid]); }, [folderUid]);
@ -173,10 +148,125 @@ export default function FolderInformation(props: IProps) {
}, [fetchData]); }, [fetchData]);
const onArchive = useCallback(() => { const onArchive = useCallback(() => {
if (anchorStatus === AnchorStatus.NOT_ANCHORED) return requireAnchoringModal.open(); //if (anchorStatus === AnchorStatus.NOT_ANCHORED) return requireAnchoringModal.open();
archiveModal.open(); archiveModal.open();
}, [anchorStatus, archiveModal, requireAnchoringModal]); }, [anchorStatus, archiveModal, requireAnchoringModal]);
const onDownloadAllCertificates = useCallback(async () => {
if (!folder?.uid) return;
try {
LoaderService.getInstance().show();
// Get all documents for this folder
const allDocuments = await DocumentService.getDocuments();
const folderDocuments = allDocuments
.map((process: any) => process.processData)
.filter((doc: any) =>
doc.folder?.uid === folder.uid &&
doc.document_status === EDocumentStatus.VALIDATED
);
if (folderDocuments.length === 0) {
ToasterService.getInstance().warning({
title: "Aucun certificat",
description: "Aucun document validé trouvé pour ce dossier."
});
LoaderService.getInstance().hide();
return;
}
// Generate certificates for all validated documents
const certificates: any[] = [];
for (const doc of folderDocuments) {
try {
// Get customer info
const customer = doc.depositor || doc.customer;
const certificateData: any = {
customer: {
firstName: customer?.first_name || customer?.firstName || "N/A",
lastName: customer?.last_name || customer?.lastName || "N/A",
postalAddress: customer?.postal_address || customer?.address || customer?.postalAddress || "N/A",
email: customer?.email || "N/A"
},
notary: {
name: "N/A"
},
folderUid: folder.uid,
documentHash: "N/A",
metadata: {
fileName: "N/A",
isDeleted: false,
updatedAt: new Date(),
commitmentId: "N/A",
createdAt: new Date(),
documentUid: "N/A",
documentType: "N/A",
merkleProof: "N/A"
}
};
// Process files for this document
if (doc.files && doc.files.length > 0) {
for (const file of doc.files) {
const fileProcess = await FileService.getFileByUid(file.uid);
if (fileProcess.lastUpdatedFileState?.pcd_commitment?.file_blob) {
const hash = fileProcess.lastUpdatedFileState.pcd_commitment.file_blob;
certificateData.documentHash = hash;
const proof = await MessageBus.getInstance().generateMerkleProof(
fileProcess.lastUpdatedFileState,
'file_blob'
);
const metadata: any = {
fileName: fileProcess.processData.file_name,
isDeleted: false,
updatedAt: new Date(fileProcess.processData.updated_at),
commitmentId: fileProcess.lastUpdatedFileState.commited_in,
createdAt: new Date(fileProcess.processData.created_at),
documentUid: doc.uid,
documentType: doc.document_type?.name || "N/A",
merkleProof: proof
};
certificateData.metadata = metadata;
break; // Use first file for now
}
}
}
certificates.push(certificateData);
} catch (error) {
console.error(`Error processing document ${doc.uid}:`, error);
}
}
// Generate combined PDF
const combinedPdf = await PdfService.getInstance().generateCombinedCertificates(certificates, folder);
// Download the combined PDF
const filename = `certificats_${folder.folder_number || folder.uid}_${new Date().toISOString().split('T')[0]}.pdf`;
saveAs(combinedPdf, filename);
ToasterService.getInstance().success({
title: "Succès !",
description: `${certificates.length} certificat(s) téléchargé(s) avec succès.`
});
} catch (error) {
console.error('Error downloading all certificates:', error);
ToasterService.getInstance().error({
title: "Erreur",
description: "Une erreur est survenue lors du téléchargement des certificats."
});
} finally {
LoaderService.getInstance().hide();
}
}, [folder?.uid]);
return ( return (
<DefaultNotaryDashboard title={"Dossier"} isArchived={isArchived} mobileBackText="Retour aux dossiers"> <DefaultNotaryDashboard title={"Dossier"} isArchived={isArchived} mobileBackText="Retour aux dossiers">
{!isLoading && ( {!isLoading && (
@ -187,8 +277,9 @@ export default function FolderInformation(props: IProps) {
onArchive={onArchive} onArchive={onArchive}
anchorStatus={anchorStatus} anchorStatus={anchorStatus}
isArchived={isArchived} isArchived={isArchived}
onDownloadAllCertificates={onDownloadAllCertificates}
/> />
{progress === 100 && anchorStatus === AnchorStatus.NOT_ANCHORED && ( {progress === 100 && /*anchorStatus === AnchorStatus.NOT_ANCHORED*/ folder.status !== EFolderStatus.ARCHIVED && (
<AnchoringAlertInfo onAnchor={anchoringModal.open} /> <AnchoringAlertInfo onAnchor={anchoringModal.open} />
)} )}
{!isArchived && anchorStatus === AnchorStatus.VERIFIED_ON_CHAIN && ( {!isArchived && anchorStatus === AnchorStatus.VERIFIED_ON_CHAIN && (

View File

@ -22,6 +22,8 @@ import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService"; import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
import WatermarkService from "@Front/Services/WatermarkService";
enum EClientSelection { enum EClientSelection {
ALL_CLIENTS = "all_clients", ALL_CLIENTS = "all_clients",
@ -65,48 +67,67 @@ export default function SendDocuments() {
LoaderService.getInstance().show(); LoaderService.getInstance().show();
for (const selectedClient of selectedClients) { for (const selectedClient of selectedClients) {
const customer: any = await new Promise<void>((resolve: (customer: any) => void) => {
CustomerService.getCustomerByUid(selectedClient as string).then((process: any) => {
if (process) {
const customer: any = process.processData;
resolve(customer);
}
});
});
for (const file of files) { for (const file of files) {
await new Promise<void>((resolve: () => void) => { await new Promise<void>((resolve: () => void) => {
const reader = new FileReader(); // Add watermark to the file before processing
reader.onload = (event) => { WatermarkService.getInstance().addWatermark(file).then(async (watermarkedFile) => {
if (event.target?.result) { const reader = new FileReader();
const arrayBuffer = event.target.result as ArrayBuffer; reader.onload = (event) => {
const uint8Array = new Uint8Array(arrayBuffer); if (event.target?.result) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
const fileBlob: any = { const fileName: string = `aplc_${customer.contact.last_name}_${strDate}.${file.name.split('.').pop()}`;
type: file.type,
data: uint8Array
};
const fileData: any = { const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
file_blob: fileBlob, const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
file_name: file.name
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => { const fileBlob: any = {
const fileUid: string = processCreated.processData.uid; type: watermarkedFile.type,
data: uint8Array
const documentData: any = {
folder: {
uid: folderUid as string
},
customer: {
uid: selectedClient as string
},
files: [
{
uid: fileUid
}
],
document_status: EDocumentNotaryStatus.SENT
}; };
DocumentService.createDocument(documentData, validatorId).then(() => resolve()); const fileData: any = {
}); file_blob: fileBlob,
} file_name: fileName
}; };
reader.readAsArrayBuffer(file); const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
FileService.createFile(fileData, validatorId).then((processCreated: any) => {
const fileUid: string = processCreated.processData.uid;
const documentData: any = {
folder: {
uid: folderUid as string
},
customer: {
uid: selectedClient as string
},
files: [
{
uid: fileUid
}
],
document_status: EDocumentNotaryStatus.SENT
};
DocumentService.createDocument(documentData, validatorId).then(() => {
FolderService.refreshFolderByUid(folderUid as string).then(() => resolve());
});
});
}
};
reader.readAsArrayBuffer(watermarkedFile);
});
}); });
} }
} }

View File

@ -41,7 +41,7 @@ export default function UpdateClient() {
if (process) { if (process) {
const customer: any = process.processData; const customer: any = process.processData;
setCustomer(customer); setCustomer(customer);
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
} }
}); });
} catch (error) { } catch (error) {
@ -77,8 +77,8 @@ export default function UpdateClient() {
if (process) { if (process) {
// TODO: review - address // TODO: review - address
CustomerService.updateCustomer(process, { contact: contact }).then(() => { CustomerService.updateCustomer(process, { contact: contact }).then(() => {
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push(backwardPath); router.push(backwardPath);
LoaderService.getInstance().hide();
}); });
} }
}); });

View File

@ -54,10 +54,10 @@ export default function UpdateFolderMetadata() {
FolderService.getFolderByUid(folderUid).then((process: any) => { FolderService.getFolderByUid(folderUid).then((process: any) => {
if (process) { if (process) {
FolderService.updateFolder(process, { ...values, deed: { uid: values["deed"] } }).then(() => { FolderService.updateFolder(process, { ...values, deed: { uid: values["deed"] } }).then(() => {
setTimeout(() => LoaderService.getInstance().hide(), 2000);
router.push(Module.getInstance() router.push(Module.getInstance()
.get() .get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid)); .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid));
LoaderService.getInstance().hide();
}); });
} }
}); });
@ -75,7 +75,7 @@ export default function UpdateFolderMetadata() {
if (process) { if (process) {
const folder: any = process.processData; const folder: any = process.processData;
setSelectedFolder(folder); setSelectedFolder(folder);
setTimeout(() => LoaderService.getInstance().hide(), 2000); LoaderService.getInstance().hide();
} }
}); });
}, [folderUid]); }, [folderUid]);

View File

@ -6,7 +6,6 @@ import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { Document } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Image from "next/image"; import Image from "next/image";
import { NextRouter, useRouter } from "next/router"; import { NextRouter, useRouter } from "next/router";
@ -17,8 +16,12 @@ import classes from "./classes.module.scss";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import MessageBox from "@Front/Components/Elements/MessageBox"; import MessageBox from "@Front/Components/Elements/MessageBox";
import { FileBlob } from "@Front/Api/Entities/types";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService"; import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService"; import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
type IProps = {}; type IProps = {};
type IPropsClass = { type IPropsClass = {
@ -32,9 +35,9 @@ type IState = {
isValidateModalVisible: boolean; isValidateModalVisible: boolean;
refuseText: string; refuseText: string;
selectedFileIndex: number; selectedFileIndex: number;
selectedFile: any; // File | null; TODO: review selectedFile: { uid: string; file_name: string; file_blob: FileBlob } | null;
validatedPercentage: number; validatedPercentage: number;
document: Document | null; document: any | null;
fileBlob: Blob | null; fileBlob: Blob | null;
isLoading: boolean; isLoading: boolean;
}; };
@ -237,6 +240,8 @@ class ViewDocumentsClass extends BasePage<IPropsClass, IState> {
private async getFilePreview(): Promise<void> { private async getFilePreview(): Promise<void> {
try { try {
if (!this.state.selectedFile) return;
const fileBlob: Blob = new Blob([this.state.selectedFile.file_blob.data], { type: this.state.selectedFile.file_blob.type }); const fileBlob: Blob = new Blob([this.state.selectedFile.file_blob.data], { type: this.state.selectedFile.file_blob.type });
this.setState({ this.setState({
fileBlob, fileBlob,
@ -247,7 +252,7 @@ class ViewDocumentsClass extends BasePage<IPropsClass, IState> {
} }
private downloadFile() { private downloadFile() {
if (!this.state.fileBlob) return; if (!this.state.fileBlob || !this.state.selectedFile) return;
const url = URL.createObjectURL(this.state.fileBlob); const url = URL.createObjectURL(this.state.fileBlob);
const a = document.createElement('a'); const a = document.createElement('a');
@ -319,16 +324,20 @@ class ViewDocumentsClass extends BasePage<IPropsClass, IState> {
private async refuseDocument() { private async refuseDocument() {
try { try {
LoaderService.getInstance().show();
DocumentService.getDocumentByUid(this.props.documentUid).then((process: any) => { DocumentService.getDocumentByUid(this.props.documentUid).then((process: any) => {
if (process) { if (process) {
DocumentService.updateDocument(process, { document_status: EDocumentStatus.REFUSED, refused_reason: this.state.refuseText }).then(() => { DocumentService.updateDocument(process, { document_status: EDocumentStatus.REFUSED, refused_reason: this.state.refuseText }).then(() => {
this.props.router.push( FolderService.refreshFolderByUid(this.props.folderUid).then(() => {
Module.getInstance() LoaderService.getInstance().hide();
.get() this.props.router.push(
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid) + Module.getInstance()
"?customerUid=" + .get()
this.state.document?.depositor?.uid, .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid) +
); "?customerUid=" +
this.state.document?.depositor?.uid,
);
});
}); });
} }
}); });
@ -339,16 +348,34 @@ class ViewDocumentsClass extends BasePage<IPropsClass, IState> {
private async validateDocument() { private async validateDocument() {
try { try {
DocumentService.getDocumentByUid(this.props.documentUid).then((process: any) => { LoaderService.getInstance().show();
DocumentService.getDocumentByUid(this.props.documentUid).then(async (process: any) => {
if (process) { if (process) {
await new Promise<void>((resolve: () => void) => {
FileService.getFileByUid(process.processData.files[0].uid).then((p) => {
if (p) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
const fileName: string = p.processData.file_name;
const extension: string = fileName.substring(fileName.lastIndexOf('.'));
FileService.updateFile(p, { file_name: `aplc_${fileName.substring(0, fileName.lastIndexOf('_'))}_${strDate}${extension}` }).then(resolve);
}
});
});
DocumentService.updateDocument(process, { document_status: EDocumentStatus.VALIDATED }).then(() => { DocumentService.updateDocument(process, { document_status: EDocumentStatus.VALIDATED }).then(() => {
this.props.router.push( FolderService.refreshFolderByUid(this.props.folderUid).then(() => {
Module.getInstance() LoaderService.getInstance().hide();
.get() this.props.router.push(
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid) + Module.getInstance()
"?customerUid=" + .get()
this.state.document?.depositor?.uid, .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid) +
); "?customerUid=" +
this.state.document?.depositor?.uid,
);
});
}); });
} }
}); });

View File

@ -25,12 +25,12 @@ export default function Folder() {
useEffect(() => { useEffect(() => {
// TODO: review // TODO: review
FolderService.getFolders().then((processes: any[]) => { FolderService.getFoldersBy({ status: EFolderStatus.LIVE }).then((processes: any[]) => {
if (processes.length > 0) { if (processes.length > 0) {
let folders: any[] = processes.map((process: any) => process.processData); let folders: any[] = processes.map((process: any) => process.processData);
// FilterBy status // OrderBy created_at desc
folders = folders.filter((folder: any) => folder.status === EFolderStatus.LIVE); folders = folders.sort((a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
if (folders.length > 0) { if (folders.length > 0) {
router.push( router.push(

View File

@ -25,6 +25,7 @@ type IProps = {
export default function StepEmail(props: IProps) { export default function StepEmail(props: IProps) {
const { onSubmit, validationErrors } = props; const { onSubmit, validationErrors } = props;
const [isErrorModalOpen, setIsErrorModalOpen] = useState(0); const [isErrorModalOpen, setIsErrorModalOpen] = useState(0);
/* const router = useRouter(); /* const router = useRouter();
const redirectCustomerOnConnection = useCallback(() => { const redirectCustomerOnConnection = useCallback(() => {
async function getCustomer() { async function getCustomer() {
@ -88,7 +89,7 @@ export default function StepEmail(props: IProps) {
<div className={classes["content"]}> <div className={classes["content"]}>
<div className={classes["section"]}> <div className={classes["section"]}>
<Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT} className={classes["section-title"]}> <Typography typo={ETypo.TITLE_H6} color={ETypoColor.TEXT_ACCENT} className={classes["section-title"]}>
Pour les notaires et les colaborateurs : Pour les notaires et les collaborateurs :
</Typography> </Typography>
<Button onClick={redirectUserOnConnection} rightIcon={<Image alt="id-not-logo" src={idNoteLogo} />}> <Button onClick={redirectUserOnConnection} rightIcon={<Image alt="id-not-logo" src={idNoteLogo} />}>
S'identifier avec ID.not S'identifier avec ID.not
@ -99,14 +100,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

@ -16,6 +16,14 @@ import Module from "@Front/Config/Module";
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes"; import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
import PasswordForgotten from "./PasswordForgotten"; import PasswordForgotten from "./PasswordForgotten";
import backgroundImage from "@Assets/images/background_refonte.svg"; import backgroundImage from "@Assets/images/background_refonte.svg";
import UserStore from "@Front/Stores/UserStore";
import AuthModal from "src/sdk/AuthModal";
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
import MessageBus from "src/sdk/MessageBus";
import { resolve } from "path";
export enum LoginStep { export enum LoginStep {
EMAIL, EMAIL,
TOTP, TOTP,
@ -23,6 +31,7 @@ export enum LoginStep {
NEW_PASSWORD, NEW_PASSWORD,
PASSWORD_FORGOTTEN, PASSWORD_FORGOTTEN,
} }
export default function Login() { export default function Login() {
const router = useRouter(); const router = useRouter();
// const error = router.query["error"]; // const error = router.query["error"];
@ -34,7 +43,9 @@ export default function Login() {
const [totpCode, setTotpCode] = useState<string>(""); const [totpCode, setTotpCode] = useState<string>("");
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>(""); const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
const [sessionId, setSessionId] = useState<string>("");
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]); const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
// const openErrorModal = useCallback(() => { // const openErrorModal = useCallback(() => {
// setIsErrorModalOpen(true); // setIsErrorModalOpen(true);
@ -50,9 +61,10 @@ 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"]);
/* TODO: review
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);
@ -77,18 +89,33 @@ export default function Login() {
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => { async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try { try {
if (!values["totpCode"]) return; if (!values["totpCode"]) return;
/*
const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email }); const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email });
// If the code is valid setting it in state // If the code is valid setting it in state
if (res.validCode) setTotpCode(values["totpCode"]); if (res.validCode) {
setTotpCode(values["totpCode"]);
setSessionId(res.sessionId); // Store the session ID
}
*/
if ('1234' === values["totpCode"]) {
setTotpCode(values["totpCode"]);
// For testing, set a mock session ID
setSessionId("mock-session-id-123");
}
setValidationErrors([]); setValidationErrors([]);
/*
// If it's first connection, show the form for first connection // If it's first connection, show the form for first connection
if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD); if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD);
// If it's password forgotten, show the form for password forgotten // If it's password forgotten, show the form for password forgotten
else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN); else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN);
// Else just login normally // Else just login normally
else setStep(LoginStep.PASSWORD); else setStep(LoginStep.PASSWORD);
*/
setIsAuthModalOpen(true);
} catch (error: any) { } catch (error: any) {
setValidationErrors([ setValidationErrors([
{ {
@ -133,7 +160,10 @@ export default function Login() {
return; return;
} }
const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] }); const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
// TODO: review
//CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]); setValidationErrors([]);
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
// If set password worked, setting the token and redirecting // If set password worked, setting the token and redirecting
@ -157,7 +187,10 @@ export default function Login() {
try { try {
if (!values["password"]) return; if (!values["password"]) return;
const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] }); const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] });
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
// TODO: review
//CustomerStore.instance.connect(token.accessToken, token.refreshToken);
setValidationErrors([]); setValidationErrors([]);
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
} catch (error: any) { } catch (error: any) {
@ -234,6 +267,49 @@ export default function Login() {
{step === LoginStep.PASSWORD_FORGOTTEN && ( {step === LoginStep.PASSWORD_FORGOTTEN && (
<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} /> <PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
)} )}
{isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen}
onClose={() => {
// After 4nk authentication is complete, get the process for the pairing ID
MessageBus.getInstance().initMessageListener();
MessageBus.getInstance().isReady().then(async () => {
try {
// Find the customer
const customer: any = (await CustomerService.getCustomers())
.map((process: any) => process.processData)
.find((customer: any) => customer.contact.email === email);
// Get the pairing ID
const pairingId = await MessageBus.getInstance().getPairingId();
console.log('[Login] Got pairing ID:', pairingId);
// Get all processes
const processes = await MessageBus.getInstance().getProcesses();
console.log('[Login] Got processes:', Object.keys(processes));
const targetProcess = processes[pairingId];
if (targetProcess) {
console.log('[Login] Found target process:', targetProcess);
// Connect the user with the process data
UserStore.instance.connect(customer /*targetProcess*/);
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
} else {
console.error('[Login] No process found for pairing ID:', pairingId);
// Handle the case where no process is found
}
MessageBus.getInstance().destroyMessageListener();
} catch (error) {
console.error('[Login] Error getting process:', error);
MessageBus.getInstance().destroyMessageListener();
}
});
setIsAuthModalOpen(false);
}}
/>}
</div> </div>
{/* <Confirm {/* <Confirm
isOpen={isErrorModalOpen} isOpen={isErrorModalOpen}

View File

@ -6,99 +6,292 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
import HelpBox from "@Front/Components/Elements/HelpBox"; import HelpBox from "@Front/Components/Elements/HelpBox";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import CookieService from "@Front/Services/CookieService/CookieService";
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, useState } from "react"; import React, { useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
// Importer les composants de UI pour les barres de progression
import { LinearProgress, Box, Typography as MuiTypography } from "@mui/material";
import AuthModal from "src/sdk/AuthModal"; import AuthModal from "src/sdk/AuthModal";
import MessageBus from "src/sdk/MessageBus";
import Iframe from "src/sdk/Iframe";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import ImportData, { ProgressInfo } from "src/common/Api/LeCoffreApi/sdk/ImportData";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService";
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
export default function LoginCallBack() { export default function LoginCallBack() {
const router = useRouter(); const router = useRouter();
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); const [idNotUser, setIdNotUser] = useState<any>(null);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => { // États pour les barres de progression
async function getUser() { const [showProgress, setShowProgress] = useState(false);
// TODO: review const [progressInfo, setProgressInfo] = useState<ProgressInfo>({
// HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client globalProgress: 0,
// Replace with http://localhost:3000/authorized-client currentStep: '',
if (window.location.href.startsWith('http://local.lecoffreio.4nkweb:3000/authorized-client')) { stepProgress: 0,
window.location.href = window.location.href.replace('http://local.lecoffreio.4nkweb:3000/authorized-client', 'http://localhost:3000/authorized-client'); description: ''
return; });
}
const code = router.query["code"]; const getOffice = async (idNotUser: any) => {
if (code) { return await new Promise<any>((resolve: (office: any) => void) => {
try { OfficeService.getOffices().then((processes: any[]) => {
const token = await Auth.getInstance().getIdnotJwt(code as string); const officeFound: any = processes.length > 0 ? processes.map((process: any) => process.processData).find((office: any) => office.idNot === idNotUser.office.idNot) : null;
if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path); if (officeFound) {
await UserStore.instance.connect(token.accessToken, token.refreshToken); resolve(officeFound);
const jwt = JwtService.getInstance().decodeJwt(); } else {
if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); const officeData: any = {
if (jwt.rules && !jwt.rules.includes("GET folders")) { idNot: idNotUser.office.idNot,
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); name: idNotUser.office.name,
} crpcen: idNotUser.office.crpcen,
setIsAuthModalOpen(true); address: {
//return router.push(Module.getInstance().get().modules.pages.Folder.props.path); create: {
return; address: idNotUser.office.address.address,
} catch (e: any) { zip_code: idNotUser.office.address.zip_code,
if (e.http_status === 401 && e.message === "Email not found") { city: idNotUser.office.address.city
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=3"); }
} },
if (e.http_status === 409) { office_status: 'ACTIVATED'
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=4"); };
} const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
}
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); OfficeService.createOffice(officeData, validatorId).then((process: any) => {
if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); if (process) {
const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken); const office: any = process.processData;
const jwt = JwtService.getInstance().decodeJwt(); resolve(office);
if (!jwt) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); }
if (!jwt.rules.includes("GET folders")) { });
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); }
} });
if (isTokenRefreshed) { });
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");
}
getUser();
}),
[router];
return ( const getCollaborator = async (idNotUser: any) => {
<DefaultDoubleSidePage title={"Login"} image={backgroundImage}> return await new Promise<any>(async (resolve: (role: any) => void) => {
<div className={classes["root"]}> const processFound: any | null = await CollaboratorService.getCollaboratorBy({ idNot: idNotUser.idNot });
<div className={classes["title-container"]}> if (processFound) {
<Image alt="coffre" src={CoffreIcon} width={56} /> resolve(processFound.processData);
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}> } else {
Connexion à votre espace professionnel const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
</Typography> const office: any = await getOffice(idNotUser);
</div>
<Loader color={"var(--secondary-default-base, #FF4617)"} width={29} /> if (!await ImportData.isDone()) {
<div /> LoaderService.getInstance().hide();
<HelpBox setShowProgress(true);
title="Vous n'arrivez pas à vous connecter ?"
description="Notre équipe de support est là pour vous aider." await ImportData.import(office, validatorId, (info: ProgressInfo) => {
button={{ text: "Contacter l'administrateur", link: "mailto:support@lecoffre.io" }} setProgressInfo(info);
/> });
{isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen} setShowProgress(false);
onClose={() => { LoaderService.getInstance().show();
setIsAuthModalOpen(false); }
router.push(Module.getInstance().get().modules.pages.Folder.props.path);
}} const role: any = (await RoleService.getRoles())
/>} .map((process: any) => process.processData)
</div> .find((role: any) => role.name === idNotUser.role.name);
</DefaultDoubleSidePage>
); const officeRole: any = (await OfficeRoleService.getOfficeRoles())
.map((process: any) => process.processData)
.filter((officeRole: any) => officeRole.office.uid === office.uid)
.find((officeRole: any) => officeRole.name === idNotUser.office_role.name);
const collaboratorData: any = {
idNot: idNotUser.idNot,
contact: idNotUser.contact,
office: {
uid: office.uid
},
role: {
uid: role.uid
},
office_role: {
uid: officeRole.uid
}
};
CollaboratorService.createCollaborator(collaboratorData, validatorId).then((process: any) => {
if (process) {
const collaborator: any = process.processData;
resolve(collaborator);
}
});
}
});
};
useEffect(() => {
async function getUser() {
UserStore.instance.disconnect();
// TODO: review
// HACK: If start with http://local.lecoffreio.4nkweb:3000/authorized-client
// Replace with http://localhost:3000/authorized-client
if (window.location.href.startsWith('http://local.lecoffreio.4nkweb: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 {
// Nettoyer l'URL pour ne garder que la racine
const rootUrl = window.location.origin;
if (window.location.href !== rootUrl) {
window.history.replaceState({}, document.title, rootUrl);
}
const user: any = await Auth.getInstance().getIdNotUser(code as string);
setIdNotUser(user.idNotUser);
setIsAuthModalOpen(true);
/*
const token: any = null;
if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path);
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 && !jwt.rules.includes("GET folders")) {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.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");
}
if (e.http_status === 409) {
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=4");
}
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
}
}
/*
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);
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")) {
return router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
}
if (isTokenRefreshed) {
//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");
}
getUser();
}, [router]);
return (
<DefaultDoubleSidePage title={"Login"} image={backgroundImage}>
<div className={classes["root"]} style={showProgress ? { width: 'calc(75vw - 70px)', maxWidth: '1130px', margin: '80px 0 80px 70px' } : undefined}>
<div className={classes["title-container"]}>
<Image alt="coffre" src={CoffreIcon} width={56} />
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_ACCENT}>
Connexion à votre espace professionnel
</Typography>
</div>
{!showProgress ? (
<Loader color={"var(--secondary-default-base, #FF4617)"} width={29} />
) : (
<Box sx={{ width: '100%', mb: 4, p: 4, px: 6, borderRadius: 2, boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}>
<MuiTypography variant="h6" align="center" sx={{ mb: 2, fontWeight: 600, color: 'var(--primary-default-base, #006BE0)' }}>
Importation des données
</MuiTypography>
{/* Progression globale */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<MuiTypography variant="body2" sx={{ fontWeight: 500, color: 'text.primary' }}>
Progression globale: <span style={{ color: 'var(--secondary-default-base, #FF4617)', fontWeight: 600 }}>{Math.round(progressInfo.globalProgress)}%</span>
</MuiTypography>
<MuiTypography variant="body2" sx={{ fontWeight: 500, color: 'text.secondary' }}>
<span style={{ fontStyle: 'italic' }}>{progressInfo.currentStep}</span>
</MuiTypography>
</Box>
<LinearProgress
variant="determinate"
value={progressInfo.globalProgress}
sx={{
height: 12,
borderRadius: 6,
backgroundColor: 'rgba(255, 70, 23, 0.15)',
'& .MuiLinearProgress-bar': {
backgroundColor: 'var(--secondary-default-base, #FF4617)',
transition: 'transform 0.5s ease'
}
}}
/>
{/* Progression par étape */}
<Box sx={{ mt: 2, mb: 1 }}>
<MuiTypography variant="body2" sx={{ display: 'flex', justifyContent: 'space-between', fontWeight: 500, color: 'text.primary' }}>
<span>Progression de l'étape: <span style={{ color: 'var(--primary-default-base, #006BE0)', fontWeight: 600 }}>{Math.round(progressInfo.stepProgress)}%</span></span>
<span style={{ maxWidth: '60%', textAlign: 'right', color: 'text.secondary' }}>{progressInfo.description || ''}</span>
</MuiTypography>
</Box>
<LinearProgress
variant="determinate"
value={progressInfo.stepProgress}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(0, 107, 224, 0.15)',
'& .MuiLinearProgress-bar': {
backgroundColor: 'var(--primary-default-base, #006BE0)',
transition: 'transform 0.3s ease'
}
}}
/>
</Box>
)}
<div />
<HelpBox
title="Vous n'arrivez pas à vous connecter ?"
description="Notre équipe de support est là pour vous aider."
button={{ text: "Contacter l'administrateur", link: "mailto:support@lecoffre.io" }}
/>
{isAuthModalOpen && <AuthModal
isOpen={isAuthModalOpen}
onClose={() => {
setIsAuthModalOpen(false);
setIsConnected(true);
setTimeout(() => {
LoaderService.getInstance().show();
MessageBus.getInstance().initMessageListener();
MessageBus.getInstance().isReady().then(async () => {
const collaborator: any = await getCollaborator(idNotUser);
UserStore.instance.connect(collaborator);
MessageBus.getInstance().destroyMessageListener();
LoaderService.getInstance().hide();
/*
if (jwt.rules && !jwt.rules.includes("GET folders")) {
router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
}
*/
window.location.href = Module.getInstance().get().modules.pages.Folder.props.path;
});
}, 100);
}}
/>}
{isConnected && <Iframe />}
</div>
</DefaultDoubleSidePage>
);
} }

View File

@ -1,4 +1,3 @@
import Offices from "@Front/Api/LeCoffreApi/SuperAdmin/Offices/Offices";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultOfficeDashboard from "@Front/Components/LayoutTemplates/DefaultOfficeDashboard"; import DefaultOfficeDashboard from "@Front/Components/LayoutTemplates/DefaultOfficeDashboard";
import User, { Office } from "le-coffre-resources/dist/SuperAdmin"; import User, { Office } from "le-coffre-resources/dist/SuperAdmin";
@ -7,6 +6,9 @@ import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
type IProps = {}; type IProps = {};
export default function OfficeInformations(props: IProps) { export default function OfficeInformations(props: IProps) {
const router = useRouter(); const router = useRouter();
@ -19,22 +21,21 @@ export default function OfficeInformations(props: IProps) {
useEffect(() => { useEffect(() => {
async function getOffice() { async function getOffice() {
if (!officeUid) return; if (!officeUid) return;
const office = await Offices.getInstance().getByUid(officeUid as string, { const office: any = await new Promise<any>((resolve: (office: any) => void) => {
q: { OfficeService.getOfficeByUid(officeUid as string).then((process: any) => {
address: true, if (process) {
users: { const office: any = process.processData;
include: { resolve(office);
role: true, }
office_role: true, });
contact: true,
},
},
},
}); });
if (!office) return; if (!office) return;
setOfficeSelected(office); setOfficeSelected(office);
const adminUsers = office.users?.filter((user) => { const users: any[] = (await CollaboratorService.getCollaboratorsBy({ 'office.uid': office.uid }))
.map((process: any) => process.processData);
const adminUsers = users.filter((user) => {
if (user.office_role && user.office_role.name === "admin") { if (user.office_role && user.office_role.name === "admin") {
return true; return true;
} }
@ -46,8 +47,9 @@ export default function OfficeInformations(props: IProps) {
} }
return false; return false;
}); });
setAdminUsers(adminUsers);
const collaboratorUsers = office.users?.filter((user) => { const collaboratorUsers = users.filter((user) => {
if (user.office_role && user.office_role.name === "admin") { if (user.office_role && user.office_role.name === "admin") {
return false; return false;
} }
@ -59,9 +61,7 @@ export default function OfficeInformations(props: IProps) {
} }
return true; return true;
}); });
setCollaboratorUsers(collaboratorUsers);
setAdminUsers(adminUsers!);
setCollaboratorUsers(collaboratorUsers!);
} }
getOffice(); getOffice();

View File

@ -5,11 +5,14 @@ import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import FilePreview from "@Front/Components/DesignSystem/FilePreview"; import FilePreview from "@Front/Components/DesignSystem/FilePreview";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import OfficeRib from "@Front/Api/LeCoffreApi/Notary/OfficeRib/OfficeRib";
import DepositRib from "@Front/Components/DesignSystem/DepositRib"; import DepositRib from "@Front/Components/DesignSystem/DepositRib";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import Loader from "@Front/Components/DesignSystem/Loader"; import Loader from "@Front/Components/DesignSystem/Loader";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import OfficeRibService from "src/common/Api/LeCoffreApi/sdk/OfficeRibService";
import WatermarkService from "@Front/Services/WatermarkService";
export default function Rib() { export default function Rib() {
const [documentList, setDocumentList] = useState<File[]>([]); const [documentList, setDocumentList] = useState<File[]>([]);
const router = useRouter(); const router = useRouter();
@ -24,10 +27,11 @@ export default function Rib() {
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
try { try {
const blob = await OfficeRib.getInstance().getRibStream(); const officeRib: any = (await OfficeRibService.getOfficeRib()).processData;
setFileBlob(blob.blob); const fileBlob: Blob = new Blob([officeRib.file_blob.data], { type: officeRib.file_blob.type });
setFileBlob(fileBlob);
setKey(key); setKey(key);
setFileName(blob.fileName); setFileName(officeRib.file_name);
} catch (error) { } catch (error) {
setFileBlob(undefined); setFileBlob(undefined);
setFileName(""); setFileName("");
@ -54,13 +58,44 @@ export default function Rib() {
async function onRibModalAccepted() { async function onRibModalAccepted() {
// Send documents to the backend for validation // Send documents to the backend for validation
if (documentList.length === 0) return; if (documentList.length === 0) return;
const formData = new FormData(); const file = documentList[0]!;
formData.append("file", documentList[0]!, documentList[0]!.name);
await OfficeRib.getInstance().post(formData); LoaderService.getInstance().show();
// Add watermark to the file before processing
WatermarkService.getInstance().addWatermark(file).then(async (watermarkedFile) => {
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
const date: Date = new Date();
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
onCloseRibModal(); const fileName: string = `aplc_${file.name.split('.')[0]}_${strDate}.${file.name.split('.').pop()}`;
fetchData();
const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
const fileBlob: any = {
type: watermarkedFile.type,
data: uint8Array
};
const fileData: any = {
file_blob: fileBlob,
file_name: fileName
};
const validatorId: string = '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0';
OfficeRibService.createOfficeRib(fileData, validatorId).then(() => {
LoaderService.getInstance().hide();
onCloseRibModal();
setIsLoading(true);
setTimeout(() => fetchData(), 2000);
});
}
};
reader.readAsArrayBuffer(watermarkedFile);
});
} }
function openRibModal(): void { function openRibModal(): void {
@ -73,10 +108,18 @@ export default function Rib() {
} }
async function onDeleteModalAccepted() { async function onDeleteModalAccepted() {
await OfficeRib.getInstance().delete(); LoaderService.getInstance().show();
OfficeRibService.getOfficeRib().then((process: any) => {
if (process) {
OfficeRibService.updateOfficeRib(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
LoaderService.getInstance().hide();
onCloseDeleteModal();
onCloseDeleteModal(); setIsLoading(true);
fetchData(); setTimeout(() => fetchData(), 2000);
});
}
});
} }
function openDeleteModal(): void { function openDeleteModal(): void {
@ -88,7 +131,23 @@ export default function Rib() {
} }
const onDocumentChange = (documentList: File[]) => { const onDocumentChange = (documentList: File[]) => {
setDocumentList(documentList); if (documentList.length === 0) return;
if (fileBlob) {
LoaderService.getInstance().show();
OfficeRibService.getOfficeRib().then((process: any) => {
if (process) {
OfficeRibService.updateOfficeRib(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
LoaderService.getInstance().hide();
onCloseDeleteModal();
setIsLoading(true);
setDocumentList(documentList);
});
}
});
} else {
setDocumentList(documentList);
}
}; };
return ( return (

View File

@ -19,6 +19,7 @@ import { ValidationError } from "class-validator";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService"; import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import UserStore from "@Front/Stores/UserStore";
type IProps = {}; type IProps = {};
export default function RolesCreate(props: IProps) { export default function RolesCreate(props: IProps) {
@ -29,10 +30,8 @@ export default function RolesCreate(props: IProps) {
const router = useRouter(); const router = useRouter();
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 }) => {
const jwt = JwtService.getInstance().decodeJwt(); const user: any = UserStore.instance.getUser();
const officeId: string = user.office.uid;
// TODO: review
const officeId = 'demo_notary_office_id'; //jwt?.office_Id;
const officeRole = OfficeRole.hydrate<OfficeRole>({ const officeRole = OfficeRole.hydrate<OfficeRole>({
name: values["name"], name: values["name"],
@ -64,9 +63,9 @@ export default function RolesCreate(props: IProps) {
title: "Succès !", title: "Succès !",
description: "Rôle créé avec succès" description: "Rôle créé avec succès"
}); });
setTimeout(() => LoaderService.getInstance().hide(), 2000);
const role: any = processCreated.processData; const role: any = processCreated.processData;
router.push(Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path.replace("[uid]", role.uid!)); router.push(Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path.replace("[uid]", role.uid!));
LoaderService.getInstance().hide();
} }
}); });

View File

@ -1,31 +1,25 @@
import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import Button from "@Front/Components/DesignSystem/Button"; import Button from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import DefaultRoleDashboard from "@Front/Components/LayoutTemplates/DefaultRoleDashboard"; import DefaultRoleDashboard from "@Front/Components/LayoutTemplates/DefaultRoleDashboard";
import { OfficeRole, Rule, RulesGroup } from "le-coffre-resources/dist/Admin";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import React from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import RulesGroups from "@Front/Api/LeCoffreApi/Admin/RulesGroups/RulesGroups";
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService"; import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
type RuleGroupsCheckbox = RulesGroup & { import RuleGroupService from "src/common/Api/LeCoffreApi/sdk/RuleGroupService";
checked: boolean;
};
export default function RolesInformations() { export default function RolesInformations() {
const router = useRouter(); const router = useRouter();
let { roleUid } = router.query; let { roleUid } = router.query;
const [roleSelected, setRoleSelected] = useState<OfficeRole | null>(null); const [roleSelected, setRoleSelected] = useState<any | null>(null);
const [rulesGroupsCheckboxes, setRulesGroupsCheckboxes] = useState<RuleGroupsCheckbox[]>([]); const [rulesCheckboxes, setRulesCheckboxes] = useState<any[]>([]);
const [selectAll, setSelectAll] = useState<boolean>(false); const [selectAll, setSelectAll] = useState<boolean>(false);
const [isConfirmModalOpened, setIsConfirmModalOpened] = useState<boolean>(false); const [isConfirmModalOpened, setIsConfirmModalOpened] = useState<boolean>(false);
@ -43,71 +37,28 @@ export default function RolesInformations() {
async function getUser() { async function getUser() {
if (!roleUid) return; if (!roleUid) return;
/*
const role = await OfficeRoles.getInstance().getByUid(roleUid as string, {
q: {
rules: true,
},
});
*/
LoaderService.getInstance().show(); LoaderService.getInstance().show();
const role: any = await new Promise<any>((resolve: (role: any) => void) => {
RoleService.getRoleByUid(roleUid as string).then((process: any) => {
if (process) {
const role: any = process.processData;
resolve(role);
setTimeout(() => LoaderService.getInstance().hide(), 2000);
}
});
})
/* TODO: review const officeRole: any = (await OfficeRoleService.getOfficeRoleByUid(roleUid as string)).processData;
const rulesGroups = await RulesGroups.getInstance().get({ const ruleGroups: any[] = (await RuleGroupService.getRuleGroups())
include: { .map((process: any) => process.processData);
rules: true,
},
});
*/
const rulesGroups: RulesGroup[] = [
{
uid: 'toto',
name: 'toto',
rules: [
{
uid: 'toto',
name: 'toto',
label: 'toto',
namespace: 'toto',
created_at: new Date(),
updated_at: new Date(),
}
],
created_at: new Date(),
updated_at: new Date(),
}
];
if (!role) return; const rulesCheckboxes: any[] = officeRole.rules ? ruleGroups.map((ruleGroup: any) => {
setRoleSelected(role); const isChecked: boolean = officeRole.rules.find((r1: any) => ruleGroup.rules.find((r2: any) => r2.uid === r1.uid)) !== undefined;
return {
...ruleGroup,
checked: isChecked
};
}) : ruleGroups;
// TODO: review setRoleSelected(officeRole);
if (!role.rules) return; setRulesCheckboxes(rulesCheckboxes);
const rulesCheckboxes = rulesGroups
.map((ruleGroup) => {
if (ruleGroup.rules?.every((rule) => role.rules?.find((r: any) => r.uid === rule.uid))) {
return { ...ruleGroup, checked: true };
}
return { ...ruleGroup, checked: false };
})
.sort((ruleA, ruleB) => (ruleA.name! < ruleB.name! ? 1 : -1))
.sort((rule) => (rule.checked ? -1 : 1));
const selectAll = rulesCheckboxes.every((rule) => rule.checked); const selectAll: boolean = rulesCheckboxes.every((ruleCheckbox: any) => ruleCheckbox.checked);
setSelectAll(selectAll); setSelectAll(selectAll);
setRulesGroupsCheckboxes(rulesCheckboxes);
}
LoaderService.getInstance().hide();
}
getUser(); getUser();
}, [roleUid]); }, [roleUid]);
@ -115,45 +66,64 @@ export default function RolesInformations() {
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
setSelectAll(e.target.checked); setSelectAll(e.target.checked);
const checked = e.target.checked; const checked = e.target.checked;
rulesGroupsCheckboxes.forEach((rule) => (rule.checked = checked)); rulesCheckboxes.forEach((rule) => (rule.checked = checked));
setRulesGroupsCheckboxes([...rulesGroupsCheckboxes]); setRulesCheckboxes([...rulesCheckboxes]);
}, },
[rulesGroupsCheckboxes], [rulesCheckboxes],
); );
const modifyRules = useCallback(async () => { const modifyRules = useCallback(async () => {
if (!roleSelected || !roleSelected.uid) return; if (!roleSelected || !roleSelected.uid) return;
const rulesGroupsChecked = rulesGroupsCheckboxes.filter((rule) => rule.checked);
let newRules: Rule[] = []; LoaderService.getInstance().show();
for (let ruleGroup of rulesGroupsChecked) { const ruleGroups: any[] = (await RuleGroupService.getRuleGroups())
if (!ruleGroup.rules) continue; .map((process: any) => process.processData);
newRules = [...newRules, ...ruleGroup.rules];
} const newRules: any[] = rulesCheckboxes
await OfficeRoles.getInstance().put(roleSelected.uid, { .filter((ruleCheckbox: any) => ruleCheckbox.checked)
uid: roleSelected.uid, .map((ruleCheckbox: any) => ruleCheckbox.rules)
rules: newRules, .reduce((acc: any, curr: any) => [...acc, ...curr], [])
.map((ruleCheckbox: any) => ({ uid: ruleCheckbox.uid }));
await new Promise<void>(async (resolve: () => void) => {
OfficeRoleService.getOfficeRoleByUid(roleSelected.uid).then((process: any) => {
if (process) {
OfficeRoleService.updateOfficeRole(process, { rules: newRules }).then(resolve);
}
});
}); });
const roleUpdated = await OfficeRoles.getInstance().getByUid(roleSelected.uid, { const officeRoleUpdated: any = (await OfficeRoleService.getOfficeRoleByUid(roleSelected.uid)).processData;
q: {
rules: true, const rulesCheckboxesUpdated: any[] = officeRoleUpdated.rules ? ruleGroups.map((ruleGroup: any) => {
}, const isChecked: boolean = officeRoleUpdated.rules.find((r1: any) => ruleGroup.rules.find((r2: any) => r2.uid === r1.uid)) !== undefined;
}); return {
setRoleSelected(roleUpdated); ...ruleGroup,
checked: isChecked
};
}) : ruleGroups;
setRoleSelected(officeRoleUpdated);
setRulesCheckboxes(rulesCheckboxesUpdated);
const selectAllUpdated = rulesCheckboxesUpdated.every((ruleCheckbox: any) => ruleCheckbox.checked);
setSelectAll(selectAllUpdated);
LoaderService.getInstance().hide();
closeConfirmModal(); closeConfirmModal();
}, [closeConfirmModal, roleSelected, rulesGroupsCheckboxes]); }, [closeConfirmModal, roleSelected, rulesCheckboxes]);
const handleRuleChange = useCallback( const handleRuleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const ruleUid = e.target.value; const ruleUid = e.target.value;
const rule = rulesGroupsCheckboxes.find((rule) => rule.uid === ruleUid); const rule = rulesCheckboxes.find((rule) => rule.uid === ruleUid);
if (!rule) return; if (!rule) return;
rule.checked = e.target.checked; rule.checked = e.target.checked;
setRulesGroupsCheckboxes([...rulesGroupsCheckboxes]); setRulesCheckboxes([...rulesCheckboxes]);
}, },
[rulesGroupsCheckboxes], [rulesCheckboxes],
); );
return ( return (
@ -163,7 +133,7 @@ export default function RolesInformations() {
<Typography typo={ETypo.TITLE_H1}>Gestion des rôles</Typography> <Typography typo={ETypo.TITLE_H1}>Gestion des rôles</Typography>
</div> </div>
<div className={classes["subtitle"]}> <div className={classes["subtitle"]}>
<Typography typo={ETypo.TITLE_H5}>{roleSelected?.name}</Typography> <Typography typo={ETypo.TITLE_H5}>{roleSelected?.label}</Typography>
</div> </div>
<div className={classes["rights-container"]}> <div className={classes["rights-container"]}>
<div className={classes["rights-header"]}> <div className={classes["rights-header"]}>
@ -181,11 +151,11 @@ export default function RolesInformations() {
</div> </div>
<Form> <Form>
<div className={classes["rights"]}> <div className={classes["rights"]}>
{rulesGroupsCheckboxes.map((ruleGroup) => ( {rulesCheckboxes.map((rule) => (
<div className={classes["right"]} key={ruleGroup.uid}> <div className={classes["right"]} key={rule.uid}>
<CheckBox <CheckBox
option={{ label: ruleGroup.name!, value: ruleGroup.uid }} option={{ label: rule.name, value: rule.uid }}
checked={ruleGroup.checked} checked={rule.checked}
onChange={handleRuleChange} onChange={handleRuleChange}
/> />
</div> </div>

View File

@ -1,11 +1,9 @@
import backgroundImage from "@Assets/images/background_refonte.svg"; import backgroundImage from "@Assets/images/background_refonte.svg";
import LogoIcon from "@Assets/logo_small_blue.svg"; import LogoIcon from "@Assets/logo_small_blue.svg";
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList"; import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList";
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block"; import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import JwtService from "@Front/Services/JwtService/JwtService";
import { OfficeFolder } from "le-coffre-resources/dist/Customer"; import { OfficeFolder } from "le-coffre-resources/dist/Customer";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -14,45 +12,47 @@ import { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import UserStore from "@Front/Stores/UserStore";
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
export default function SelectFolder() { export default function SelectFolder() {
const router = useRouter(); const router = useRouter();
const [folders, setFolders] = useState<OfficeFolder[]>([]); const [folders, setFolders] = useState<OfficeFolder[]>([]);
useEffect(() => { useEffect(() => {
const jwt = JwtService.getInstance().decodeCustomerJwt(); const customer: any = UserStore.instance.getUser();
if (!jwt) return; if (!customer) {
return;
}
LoaderService.getInstance().show();
FolderService.getFolders((processes: any[]) => {
if (processes.length > 0) {
let folders: any[] = processes.map((process: any) => process.processData);
Folders.getInstance() // FilterBy customer.uid
.get({ folders = folders.filter((folder: any) => folder.customers && folder.customers.length > 0 && folder.customers.some((customer: any) => customer.uid === customer.uid));
q: {
where: { // OrderBy created_at desc
customers: { folders = folders.sort((a: any, b: any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
some: {
contact: { setFolders(folders);
email: jwt.email, LoaderService.getInstance().hide();
}, }
}, });
},
},
orderBy: [
{
created_at: "desc",
},
],
include: {
customers: true,
},
},
})
.then((folders) => setFolders(folders));
}, []); }, []);
const handleSelectBlock = useCallback( const handleSelectBlock = useCallback(
(folder: IBlock) => { (folder: IBlock) => {
const customer: any = UserStore.instance.getUser();
if (!customer) {
return;
}
router.push( router.push(
Module.getInstance() Module.getInstance()
.get() .get()
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.id ?? ""), .modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.id ?? "").replace("[profileUid]", customer.uid ?? ""),
); );
}, },
[router], [router],

View File

@ -20,6 +20,8 @@ import classes from "./classes.module.scss";
import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles"; import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
import Loader from "@Front/Components/DesignSystem/Loader"; import Loader from "@Front/Components/DesignSystem/Loader";
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
type IProps = {}; type IProps = {};
export default function UserInformations(props: IProps) { export default function UserInformations(props: IProps) {
const router = useRouter(); const router = useRouter();
@ -43,6 +45,8 @@ export default function UserInformations(props: IProps) {
const getUser = useCallback(async () => { const getUser = useCallback(async () => {
if (!userUid) return; if (!userUid) return;
setIsLoading(true); setIsLoading(true);
/*
const user = await Users.getInstance().getByUid(userUid as string, { const user = await Users.getInstance().getByUid(userUid as string, {
q: { q: {
contact: true, contact: true,
@ -61,7 +65,17 @@ export default function UserInformations(props: IProps) {
votes: true, votes: true,
}, },
}); });
*/
const user: any = await new Promise<any>((resolve: (collaborator: any) => void) => {
CollaboratorService.getCollaboratorByUid(userUid as string).then((process: any) => {
if (process) {
const collaborator: any = process.processData;
resolve(collaborator);
}
});
});
if (!user) return; if (!user) return;
/*
const roles = await OfficeRoles.getInstance().get({ const roles = await OfficeRoles.getInstance().get({
where: { where: {
office: { uid: user.office_membership?.uid }, office: { uid: user.office_membership?.uid },
@ -69,6 +83,7 @@ export default function UserInformations(props: IProps) {
}, },
}); });
if (!roles) return; if (!roles) return;
*/
setIsLoading(false); setIsLoading(false);
setUserSelected(user); setUserSelected(user);
}, [userUid]); }, [userUid]);
@ -286,12 +301,10 @@ export default function UserInformations(props: IProps) {
<div> <div>
<Typography typo={ETypo.TEXT_SM_REGULAR}> <Typography typo={ETypo.TEXT_SM_REGULAR}>
{currentAppointment.choice === EVote.NOMINATE {currentAppointment.choice === EVote.NOMINATE
? `Un ou des collaborateurs souhaitent attribuer le titre de Super Admin à ce collaborateur. Il manque ${ ? `Un ou des collaborateurs souhaitent attribuer le titre de Super Admin à ce collaborateur. Il manque ${3 - currentAppointment.votes?.length!
3 - currentAppointment.votes?.length! } vote(s) pour que le collaborateur se voit attribuer le titre.`
} vote(s) pour que le collaborateur se voit attribuer le titre.` : `Un ou des collaborateurs souhaitent retirer le titre de Super Admin à ce collaborateur. Il manque ${3 - currentAppointment.votes?.length!
: `Un ou des collaborateurs souhaitent retirer le titre de Super Admin à ce collaborateur. Il manque ${ } vote(s) pour que le collaborateur se voit retirer le titre.`}
3 - currentAppointment.votes?.length!
} vote(s) pour que le collaborateur se voit retirer le titre.`}
</Typography> </Typography>
</div> </div>
{userHasVoted() && ( {userHasVoted() && (
@ -312,9 +325,8 @@ export default function UserInformations(props: IProps) {
onClose={closeSuperAdminModal} onClose={closeSuperAdminModal}
onAccept={handleSuperAdminModalAccepted} onAccept={handleSuperAdminModalAccepted}
closeBtn closeBtn
header={`Souhaitez-vous attribuer un vote à ${ header={`Souhaitez-vous attribuer un vote à ${userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name } pour ${superAdminModalType === "add" ? "devenir" : "retirer son rôle de"} Super Administrateur ?`}
} pour ${superAdminModalType === "add" ? "devenir" : "retirer son rôle de"} Super Administrateur ?`}
confirmText={"Attribuer un vote"} confirmText={"Attribuer un vote"}
cancelText={"Annuler"}> cancelText={"Annuler"}>
<div className={classes["modal-content"]}> <div className={classes["modal-content"]}>
@ -331,12 +343,10 @@ export default function UserInformations(props: IProps) {
closeBtn closeBtn
header={ header={
adminModalType === "add" adminModalType === "add"
? `Souhaitez-vous nommer ${ ? `Souhaitez-vous nommer ${userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name } administrateur de son office ?`
} administrateur de son office ?` : `Souhaitez-vous retirer le rôle administrateur de son office à ${userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
: `Souhaitez-vous retirer le rôle administrateur de son office à ${ } ?`
userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name
} ?`
} }
confirmText={adminModalType === "add" ? "Ajouter" : "Retirer"} confirmText={adminModalType === "add" ? "Ajouter" : "Retirer"}
cancelText={"Annuler"}> cancelText={"Annuler"}>

View File

@ -160,6 +160,13 @@
"path": "/folders/select", "path": "/folders/select",
"labelKey": "select_folder" "labelKey": "select_folder"
} }
},
"VerifyDocuments": {
"enabled": true,
"props": {
"path": "/folders/[folderUid]/verify-documents",
"labelKey": "verify_documents"
}
} }
} }
}, },

View File

@ -160,6 +160,13 @@
"path": "/folders/select", "path": "/folders/select",
"labelKey": "select_folder" "labelKey": "select_folder"
} }
},
"VerifyDocuments": {
"enabled": true,
"props": {
"path": "/folders/[folderUid]/verify-documents",
"labelKey": "verify_documents"
}
} }
} }
}, },

View File

@ -160,6 +160,13 @@
"path": "/folders/select", "path": "/folders/select",
"labelKey": "select_folder" "labelKey": "select_folder"
} }
},
"VerifyDocuments": {
"enabled": true,
"props": {
"path": "/folders/[folderUid]/verify-documents",
"labelKey": "verify_documents"
}
} }
} }
}, },

View File

@ -160,6 +160,13 @@
"path": "/folders/select", "path": "/folders/select",
"labelKey": "select_folder" "labelKey": "select_folder"
} }
},
"VerifyDocuments": {
"enabled": true,
"props": {
"path": "/folders/[folderUid]/verify-documents",
"labelKey": "verify_documents"
}
} }
} }
}, },

View File

@ -31,6 +31,8 @@ export class FrontendVariables {
public _4NK_URL!: string; public _4NK_URL!: string;
public API_URL!: string;
private constructor() {} private constructor() {}
public static getInstance(): FrontendVariables { public static getInstance(): FrontendVariables {

View File

@ -29,7 +29,7 @@ export interface ICustomerJwtPayload {
export default class JwtService { export default class JwtService {
private static instance: JwtService; private static instance: JwtService;
private constructor() {} private constructor() { }
public static getInstance() { public static getInstance() {
return (this.instance ??= new this()); return (this.instance ??= new this());
@ -38,25 +38,25 @@ export default class JwtService {
public getUserJwtPayload(): IUserJwtPayload | undefined { public getUserJwtPayload(): IUserJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return; if (!accessToken) return;
return jwt_decode(accessToken); return undefined; //jwt_decode(accessToken);
} }
public getCustomerJwtPayload(): ICustomerJwtPayload | undefined { public getCustomerJwtPayload(): ICustomerJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return; if (!accessToken) return;
return jwt_decode(accessToken); return undefined; //jwt_decode(accessToken);
} }
public decodeJwt(): IUserJwtPayload | undefined { public decodeJwt(): IUserJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return; if (!accessToken) return;
return jwt_decode(accessToken); return undefined; //jwt_decode(accessToken);
} }
public decodeCustomerJwt(): ICustomerJwtPayload | undefined { public decodeCustomerJwt(): ICustomerJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return; if (!accessToken) return;
return jwt_decode(accessToken); return undefined; //jwt_decode(accessToken);
} }
/** /**
@ -74,8 +74,7 @@ export default class JwtService {
const headers = new Headers(); const headers = new Headers();
headers.append("Authorization", `Bearer ${refreshToken}`); headers.append("Authorization", `Bearer ${refreshToken}`);
const response = await fetch( const response = await fetch(
`${ `${variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/idnot/user/auth/refresh-token`, }/idnot/user/auth/refresh-token`,
{ method: "POST", headers: headers }, { method: "POST", headers: headers },
); );
@ -93,8 +92,7 @@ export default class JwtService {
const headers = new Headers(); const headers = new Headers();
headers.append("Authorization", `Bearer ${refreshToken}`); headers.append("Authorization", `Bearer ${refreshToken}`);
const response = await fetch( const response = await fetch(
`${ `${variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/id360/customers/refresh-token`, }/id360/customers/refresh-token`,
{ method: "POST", headers: headers }, { method: "POST", headers: headers },
); );
@ -122,8 +120,7 @@ export default class JwtService {
const headers = new Headers(); const headers = new Headers();
headers.append("Authorization", `Bearer ${refreshToken}`); headers.append("Authorization", `Bearer ${refreshToken}`);
const response = await fetch( const response = await fetch(
`${ `${variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/idnot/user/auth/refresh-token`, }/idnot/user/auth/refresh-token`,
{ method: "POST", headers: headers }, { method: "POST", headers: headers },
); );
@ -141,8 +138,7 @@ export default class JwtService {
const headers = new Headers(); const headers = new Headers();
headers.append("Authorization", `Bearer ${refreshToken}`); headers.append("Authorization", `Bearer ${refreshToken}`);
const response = await fetch( const response = await fetch(
`${ `${variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/id360/customers/refresh-token`, }/id360/customers/refresh-token`,
{ method: "POST", headers: headers }, { method: "POST", headers: headers },
); );

View File

@ -0,0 +1,527 @@
import { saveAs } from 'file-saver';
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
const separator = '\x1f';
export interface CustomerInfo {
firstName: string;
lastName: string;
postalAddress: string;
email: string;
}
export interface NotaryInfo {
name: string;
// Add more notary fields as needed
}
export interface Metadata {
fileName: string,
createdAt: Date,
isDeleted: boolean,
updatedAt: Date,
commitmentId: string,
documentUid: string;
documentType: string;
merkleProof: string;
}
export interface CertificateData {
customer: CustomerInfo;
notary: NotaryInfo;
folderUid: string;
documentHash: string;
metadata: Metadata;
}
export default class PdfService {
private static instance: PdfService;
public static getInstance(): PdfService {
if (!PdfService.instance) {
PdfService.instance = new PdfService();
}
return PdfService.instance;
}
/**
* Generate a certificate PDF for a document
* @param certificateData - Data needed for the certificate
* @returns Promise<Blob> - The generated PDF as a blob
*/
public async generateCertificate(certificateData: CertificateData): Promise<Blob> {
try {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([595, 842]); // A4 size
const { width, height } = page.getSize();
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
const helveticaObliqueFont = await pdfDoc.embedFont(StandardFonts.HelveticaOblique);
let y = height - 40; // Start 40pt from the top
const leftMargin = 20;
const rightSectionX = 320;
const lineSpacing = 16;
// Notary Information Section (Top Left)
page.drawText('Notaire Validateur:', {
x: leftMargin,
y: y,
size: 12,
font: helveticaBoldFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(certificateData.notary.name, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
// Customer Information Section (Top Right)
let yRight = height - 40;
page.drawText('Fournisseur de Document:', {
x: rightSectionX,
y: yRight,
size: 12,
font: helveticaBoldFont,
color: rgb(0, 0, 0)
});
yRight -= lineSpacing;
page.drawText(`${certificateData.customer.firstName} ${certificateData.customer.lastName}`, {
x: rightSectionX,
y: yRight,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
yRight -= lineSpacing;
page.drawText(certificateData.customer.email, {
x: rightSectionX,
y: yRight,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
yRight -= lineSpacing;
page.drawText(certificateData.customer.postalAddress, {
x: rightSectionX,
y: yRight,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
// Centered Title
y -= 4 * lineSpacing; // Add more space between header and title
const titleText = 'Certificat de Validation de Document';
const titleWidth = helveticaBoldFont.widthOfTextAtSize(titleText, 20);
page.drawText(titleText, {
x: (width - titleWidth) / 2,
y: y,
size: 20,
font: helveticaBoldFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
// Add a line separator
page.drawLine({
start: { x: leftMargin, y: y },
end: { x: width - leftMargin, y: y },
thickness: 0.5,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
// Document Information Section
page.drawText('Informations du Document', {
x: leftMargin,
y: y,
size: 12,
font: helveticaBoldFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(`UID du Dossier: ${certificateData.folderUid}`, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(`Type de Document: ${certificateData.metadata.documentType}`, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(`Nom du fichier: ${certificateData.metadata.fileName}`, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(`Créé le: ${certificateData.metadata.createdAt.toLocaleDateString('fr-FR')}`, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
page.drawText(`Dernière modification: ${certificateData.metadata.updatedAt.toLocaleDateString('fr-FR')}`, {
x: leftMargin,
y: y,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0)
});
y -= lineSpacing;
const keywords = [
certificateData.documentHash,
certificateData.metadata.commitmentId,
JSON.stringify(certificateData.metadata.merkleProof)
];
pdfDoc.setKeywords([keywords.join(separator)]);
// Add explanatory text about certificate usage
const explanatoryText = "Ce certificat doit être utilisé avec le document qu'il certifie pour être vérifié sur la page dédiée. Le document correspondant à ce certificat doit être téléchargé depuis LeCoffre et peut être conservé avec le certificat tant qu'il n'est pas modifié.";
const explanatoryLines = this.splitTextToFit(helveticaObliqueFont, explanatoryText, width - (2 * leftMargin), 11);
y -= (explanatoryLines.length * 12) + 40; // Space after verification data
explanatoryLines.forEach((line, index) => {
page.drawText(line, {
x: leftMargin,
y: y - (index * 14), // Slightly more spacing for readability
size: 11,
font: helveticaObliqueFont,
color: rgb(0.3, 0.3, 0.3) // Slightly grayed out to distinguish from main content
});
});
// Footer
const footerText1 = `Page 1 sur 1`;
const footerText2 = `Généré le ${new Date().toLocaleString('fr-FR')}`;
const footerWidth1 = helveticaObliqueFont.widthOfTextAtSize(footerText1, 10);
const footerWidth2 = helveticaObliqueFont.widthOfTextAtSize(footerText2, 10);
page.drawText(footerText1, {
x: (width - footerWidth1) / 2,
y: 35,
size: 10,
font: helveticaObliqueFont,
color: rgb(0, 0, 0)
});
page.drawText(footerText2, {
x: (width - footerWidth2) / 2,
y: 25,
size: 10,
font: helveticaObliqueFont,
color: rgb(0, 0, 0)
});
const pdfBytes = await pdfDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' });
} catch (error) {
console.error('Error generating certificate:', error);
throw new Error('Failed to generate certificate');
}
}
/**
* Split text to fit within a specified width
* @param font - PDF font instance
* @param text - Text to split
* @param maxWidth - Maximum width in points
* @param fontSize - Font size
* @returns Array of text lines
*/
private splitTextToFit(font: any, text: string, maxWidth: number, fontSize: number): string[] {
const lines: string[] = [];
let currentLine = '';
// Split by characters (pdf-lib doesn't have word-level text measurement)
const chars = text.split('');
for (const char of chars) {
const testLine = currentLine + char;
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
if (testWidth <= maxWidth) {
currentLine = testLine;
} else {
if (currentLine) {
lines.push(currentLine);
currentLine = char;
} else {
// If even a single character is too wide, force a break
lines.push(char);
}
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
/**
* Download a certificate PDF
* @param certificateData - Data needed for the certificate
* @param filename - Optional filename for the download
*/
public async downloadCertificate(certificateData: CertificateData, filename?: string): Promise<void> {
try {
const pdfBlob = await this.generateCertificate(certificateData);
const defaultFilename = `certificate_${certificateData.metadata.documentUid}_${new Date().toISOString().split('T')[0]}.pdf`;
saveAs(pdfBlob, filename || defaultFilename);
} catch (error) {
console.error('Error downloading certificate:', error);
throw error;
}
}
/**
* Parse a PDF certificate and extract document information
* @param pdfBlob - The PDF file as a blob
* @returns Promise<{documentHash: string, documentUid: string, commitmentId: string, merkleProof?: string}> - Extracted information
*/
public async parseCertificate(certificateFile: File): Promise<{ documentHash: string, commitmentId: string, merkleProof: any }> {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = async () => {
try {
// Read the metadata and get the validation data from the keywords
const pdfDoc = await PDFDocument.load(await certificateFile.arrayBuffer());
const keywords = pdfDoc.getKeywords()?.split(separator);
if (!keywords) {
throw new Error("No keywords found in certificate");
}
if (keywords.length !== 3) {
throw new Error("Invalid keywords found in certificate");
}
const documentHash = keywords[0];
const commitmentId = keywords[1];
const merkleProof = keywords[2];
if (!documentHash || !commitmentId || !merkleProof) {
throw new Error("Invalid keywords found in certificate");
}
resolve({ documentHash, commitmentId, merkleProof });
} catch (error) {
reject(error);
}
};
fileReader.onerror = () => {
reject(new Error('Failed to read PDF file'));
};
fileReader.readAsArrayBuffer(certificateFile);
});
}
/**
* Generate a combined PDF with multiple certificates
* @param certificates - Array of certificate data
* @param folder - Folder information
* @returns Promise<Blob> - Combined PDF as blob
*/
public async generateCombinedCertificates(certificates: CertificateData[], folder: any): Promise<Blob> {
try {
// Import pdf-lib dynamically to avoid SSR issues
const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib');
// Create a new PDF document
const pdfDoc = await PDFDocument.create();
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
// Add a cover page
const coverPage = pdfDoc.addPage([595.28, 841.89]); // A4 size
const { width, height } = coverPage.getSize();
// Cover page title
coverPage.drawText('Certificats de Validation', {
x: 50,
y: height - 100,
size: 24,
font: helveticaBold,
color: rgb(0, 0, 0),
});
// Folder information
coverPage.drawText(`Dossier: ${folder.folder_number || folder.uid}`, {
x: 50,
y: height - 150,
size: 16,
font: helveticaFont,
color: rgb(0, 0, 0),
});
coverPage.drawText(`Nom: ${folder.name || 'N/A'}`, {
x: 50,
y: height - 180,
size: 16,
font: helveticaFont,
color: rgb(0, 0, 0),
});
coverPage.drawText(`Date de génération: ${new Date().toLocaleDateString('fr-FR')}`, {
x: 50,
y: height - 210,
size: 16,
font: helveticaFont,
color: rgb(0, 0, 0),
});
coverPage.drawText(`Nombre de certificats: ${certificates.length}`, {
x: 50,
y: height - 240,
size: 16,
font: helveticaFont,
color: rgb(0, 0, 0),
});
// Add each certificate as a separate page
for (let i = 0; i < certificates.length; i++) {
const certificate = certificates[i];
if (!certificate) continue;
const page = pdfDoc.addPage([595.28, 841.89]); // A4 size
const { width: pageWidth, height: pageHeight } = page.getSize();
// Certificate title
page.drawText(`Certificat ${i + 1}`, {
x: 50,
y: pageHeight - 50,
size: 20,
font: helveticaBold,
color: rgb(0, 0, 0),
});
// Customer information
page.drawText('Informations Client:', {
x: 50,
y: pageHeight - 100,
size: 16,
font: helveticaBold,
color: rgb(0, 0, 0),
});
page.drawText(`Nom: ${certificate.customer.firstName} ${certificate.customer.lastName}`, {
x: 50,
y: pageHeight - 130,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Adresse: ${certificate.customer.postalAddress}`, {
x: 50,
y: pageHeight - 150,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Email: ${certificate.customer.email}`, {
x: 50,
y: pageHeight - 170,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
// Document information
page.drawText('Informations Document:', {
x: 50,
y: pageHeight - 200,
size: 16,
font: helveticaBold,
color: rgb(0, 0, 0),
});
page.drawText(`Type: ${certificate.metadata.documentType}`, {
x: 50,
y: pageHeight - 230,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Fichier: ${certificate.metadata.fileName}`, {
x: 50,
y: pageHeight - 250,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Hash: ${certificate.documentHash}`, {
x: 50,
y: pageHeight - 270,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Commitment ID: ${certificate.metadata.commitmentId}`, {
x: 50,
y: pageHeight - 290,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Date de création: ${certificate.metadata.createdAt.toLocaleDateString('fr-FR')}`, {
x: 50,
y: pageHeight - 310,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
page.drawText(`Date de mise à jour: ${certificate.metadata.updatedAt.toLocaleDateString('fr-FR')}`, {
x: 50,
y: pageHeight - 330,
size: 12,
font: helveticaFont,
color: rgb(0, 0, 0),
});
// Add page number
page.drawText(`Page ${i + 2}`, {
x: pageWidth - 80,
y: 30,
size: 10,
font: helveticaFont,
color: rgb(0.5, 0.5, 0.5),
});
}
// Save the PDF
const pdfBytes = await pdfDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' });
} catch (error) {
console.error('Error generating combined certificates:', error);
throw error;
}
}
}

View File

@ -0,0 +1,222 @@
export default class WatermarkService {
private static instance: WatermarkService;
private watermarkText: string = 'Certifié par LeCoffre';
public static getInstance(): WatermarkService {
if (!WatermarkService.instance) {
WatermarkService.instance = new WatermarkService();
}
return WatermarkService.instance;
}
/**
* Add a watermark to a file based on its type
* @param file - The original file
* @returns Promise<File> - The file with watermark added
*/
public async addWatermark(file: File): Promise<File> {
const fileType = file.type;
try {
if (fileType.startsWith('image/')) {
return await this.addWatermarkToImage(file);
} else if (fileType === 'application/pdf') {
return await this.addWatermarkToPdf(file);
} else {
// For other file types, return the original file
console.warn(`Watermark not supported for file type: ${fileType}`);
return file;
}
} catch (error) {
console.error('Error adding watermark:', error);
// Return original file if watermarking fails
return file;
}
}
/**
* Add watermark to image files
* @param file - Image file
* @returns Promise<File> - Image with watermark
*/
private async addWatermarkToImage(file: File): Promise<File> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
try {
const img = new Image();
img.onload = () => {
try {
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Could not get canvas context'));
return;
}
// Set canvas size to image size
canvas.width = img.width;
canvas.height = img.height;
// Draw original image
ctx.drawImage(img, 0, 0);
// Add watermark
this.addImageWatermark(ctx, img.width, img.height);
// Convert to blob
canvas.toBlob((blob) => {
if (blob) {
const watermarkedFile = new File([blob], file.name, { type: file.type });
resolve(watermarkedFile);
} else {
reject(new Error('Could not create blob from canvas'));
}
}, file.type);
} catch (error) {
reject(error);
}
};
img.onerror = reject;
img.src = event.target?.result as string;
} catch (error) {
reject(error);
}
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
/**
* Add watermark to PDF files
* @param file - PDF file
* @returns Promise<File> - PDF with watermark
*/
private async addWatermarkToPdf(file: File): Promise<File> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async (event) => {
try {
const arrayBuffer = event.target?.result as ArrayBuffer;
// Import pdf-lib dynamically to avoid SSR issues
const { PDFDocument, rgb } = await import('pdf-lib');
// Load the existing PDF
const pdfDoc = await PDFDocument.load(arrayBuffer);
// Get all pages
const pages = pdfDoc.getPages();
// Add watermark to each page
for (const page of pages) {
this.addPdfWatermark(page, rgb(0.8, 0.2, 0.2)); // Pale red color
}
// Save the modified PDF
const pdfBytes = await pdfDoc.save();
const watermarkedFile = new File([pdfBytes], file.name, { type: file.type });
resolve(watermarkedFile);
} catch (error) {
console.error('Error adding watermark to PDF:', error);
// If PDF watermarking fails, return original file
resolve(file);
}
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
/**
* Add watermark to image using Canvas
* @param ctx - Canvas 2D context
* @param width - Image width
* @param height - Image height
*/
private addImageWatermark(ctx: CanvasRenderingContext2D, width: number, height: number): void {
// Save current state
ctx.save();
// Set watermark properties
ctx.fillStyle = 'rgba(204, 51, 51, 0.7)'; // Semi-transparent pale red (matching PDF color)
ctx.font = '10px Arial';
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
// Position watermark in bottom-right corner with timestamp
const dateTime = new Date().toLocaleString('fr-FR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
const lineHeight = 12; // Space between lines
const x = width - 20; // 20 pixels from right edge (matching PDF margins)
const y = 20; // 20 pixels from bottom (matching PDF margins)
// Add watermark text (second line - top)
ctx.fillText(this.watermarkText, x, y + lineHeight);
// Add date/time (first line - bottom)
ctx.fillText(dateTime, x, y);
// Restore state
ctx.restore();
}
/**
* Add watermark to PDF using pdf-lib
* @param page - PDF page from pdf-lib
* @param color - Color for the watermark
*/
private addPdfWatermark(page: any, color: any): void {
const { width, height } = page.getSize();
// Calculate watermark position (bottom-right corner)
const dateTime = new Date().toLocaleString('fr-FR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
const fontSize = 10;
const lineHeight = 12; // Space between lines
// Calculate text widths (approximate - pdf-lib doesn't have a direct method for this)
// Using a conservative estimate: ~6 points per character for 10pt font
const estimatedTextWidth1 = this.watermarkText.length * 6;
const estimatedTextWidth2 = dateTime.length * 6;
const maxTextWidth = Math.max(estimatedTextWidth1, estimatedTextWidth2);
// Position watermark with proper margins (20 points from edges)
const x = width - maxTextWidth - 20; // 20 points from right edge
const y = 20; // 20 points from bottom
// Add watermark text with transparency (first line)
page.drawText(this.watermarkText, {
x: x,
y: y + lineHeight, // Second line (top)
size: fontSize,
color: color,
opacity: 0.7
});
// Add date/time with transparency (second line)
page.drawText(dateTime, {
x: x,
y: y, // First line (bottom)
size: fontSize,
color: color,
opacity: 0.7
});
}
}

View File

@ -9,27 +9,18 @@ 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 async connect(user: any) {
const decodedPayload = JwtService.getInstance().decodeJwt();
return decodedPayload?.role;
}
public async connect(accessToken: string, refreshToken: string) {
try { try {
//Save tokens in cookies //Save tokens in cookies
CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken); CookieService.getInstance().setCookie("leCoffreAccessToken", JSON.stringify(user));
CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken); this.event.emit("connection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
this.event.emit("connection", this.accessToken);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return false; return false;
@ -39,13 +30,11 @@ export default class UserStore {
public async disconnect() { public async disconnect() {
try { try {
//Remove tokens from cookies
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
User.getInstance().clear(); User.getInstance().clear();
this.event.emit("disconnection", this.accessToken); //Remove tokens from cookies
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
this.event.emit("disconnection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -60,4 +49,8 @@ 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 getUser(): any {
return JSON.parse(CookieService.getInstance().getCookie("leCoffreAccessToken") || "");
}
} }

View File

@ -2,7 +2,6 @@
import CookieService from "@Front/Services/CookieService/CookieService"; 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 User from "src/sdk/User"; import User from "src/sdk/User";
@ -10,23 +9,16 @@ 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();
private constructor() {} private constructor() { }
public isConnected(): boolean { public isConnected(): boolean {
return !!CookieService.getInstance().getCookie("leCoffreAccessToken"); return !!CookieService.getInstance().getCookie("leCoffreAccessToken");
} }
public getRole(): string | undefined { public async connect(user: any) {
const decodedPayload = JwtService.getInstance().decodeJwt();
return decodedPayload?.role;
}
public async connect(accessToken: string, refreshToken: string) {
try { try {
//Save tokens in cookies //Save tokens in cookies
CookieService.getInstance().setCookie("leCoffreAccessToken", accessToken); CookieService.getInstance().setCookie("leCoffreAccessToken", JSON.stringify(user));
CookieService.getInstance().setCookie("leCoffreRefreshToken", refreshToken);
this.event.emit("connection", CookieService.getInstance().getCookie("leCoffreAccessToken")); this.event.emit("connection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -37,12 +29,10 @@ export default class UserStore {
public async disconnect() { public async disconnect() {
try { try {
//Remove tokens from cookies
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
CookieService.getInstance().deleteCookie("leCoffreRefreshToken");
User.getInstance().clear(); User.getInstance().clear();
//Remove tokens from cookies
CookieService.getInstance().deleteCookie("leCoffreAccessToken");
this.event.emit("disconnection", CookieService.getInstance().getCookie("leCoffreAccessToken")); this.event.emit("disconnection", CookieService.getInstance().getCookie("leCoffreAccessToken"));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -59,11 +49,7 @@ export default class UserStore {
return () => this.event.off("connection", callback); return () => this.event.off("connection", callback);
} }
public getAccessToken(): string { public getUser(): any {
return CookieService.getInstance().getCookie("leCoffreAccessToken") || ""; return JSON.parse(CookieService.getInstance().getCookie("leCoffreAccessToken") || "");
}
public getRefreshToken(): string {
return CookieService.getInstance().getCookie("leCoffreRefreshToken") || "";
} }
} }

View File

@ -8,6 +8,7 @@ export async function middleware(request: NextRequest) {
const cookies = request.cookies.get("leCoffreAccessToken"); const cookies = request.cookies.get("leCoffreAccessToken");
if (!cookies) return NextResponse.redirect(new URL("/", request.url)); if (!cookies) return NextResponse.redirect(new URL("/", request.url));
/*
// Decode it // Decode it
const userDecodedToken = jwt_decode(cookies.value) as IUserJwtPayload; const userDecodedToken = jwt_decode(cookies.value) as IUserJwtPayload;
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload; const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
@ -23,6 +24,7 @@ export async function middleware(request: NextRequest) {
if (customerDecodedToken.customerId && customerDecodedToken.exp < now) { if (customerDecodedToken.customerId && customerDecodedToken.exp < now) {
return NextResponse.redirect(new URL("/id360/customer-callback", request.url)); return NextResponse.redirect(new URL("/id360/customer-callback", request.url));
} }
*/
return NextResponse.next(); return NextResponse.next();
} }

Some files were not shown because too many files have changed in this diff Show More