Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1869d4509b | ||
![]() |
16383491b4 | ||
![]() |
e86f687f6c | ||
![]() |
68f6b31a6c | ||
![]() |
4740f4a67c | ||
![]() |
ae64b057a6 | ||
![]() |
1dee8b9ef6 | ||
![]() |
334f548cbb | ||
![]() |
1d0d0c034d | ||
![]() |
f404054438 | ||
![]() |
94b6e58cd0 | ||
![]() |
32d76e2328 | ||
![]() |
32b5f697c5 | ||
![]() |
ac68c1d4b1 | ||
![]() |
e6b9f58cea | ||
![]() |
8636d37d29 | ||
![]() |
a25e1b4ae5 | ||
![]() |
7807ae315f | ||
![]() |
2084b99978 | ||
![]() |
d5088d9e36 | ||
![]() |
24a8be727c | ||
![]() |
f9f9739cbd | ||
![]() |
a71dc88407 | ||
![]() |
512d981b1b | ||
![]() |
c6f42e893b | ||
![]() |
02a490d3e3 | ||
![]() |
32f11a56ef | ||
![]() |
086fa86bbe | ||
![]() |
aad1a4bfe2 | ||
8eec5b3455 | |||
09e8530c6a | |||
![]() |
74ae167e3c | ||
![]() |
90b2ed5e6e | ||
![]() |
78c7d009c1 | ||
![]() |
d9851105ab | ||
![]() |
d8105fa3cf |
115
.gitignore
vendored
115
.gitignore
vendored
@ -1,24 +1,95 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
**/.cargo/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# Cache et build
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
**/target/
|
||||
**/.next/
|
||||
**/.turbo/
|
||||
**/coverage/
|
||||
**/.pytest_cache/
|
||||
**/.cache/
|
||||
**/.pnpm-store/
|
||||
**/.venv/
|
||||
**/vendor/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Git
|
||||
**/*.git/
|
||||
**/*.orig*
|
||||
|
||||
# Backup des projets existants
|
||||
**/*backup*
|
||||
**/backups/
|
||||
**/*backups*
|
||||
|
||||
|
||||
**/*wallet*
|
||||
**/*keys*
|
||||
|
||||
**/*node_modules*
|
||||
**/*cursor*
|
||||
**/*pid*
|
||||
**/*next*
|
||||
|
||||
# Dossiers de logs communs
|
||||
log/
|
||||
logs/
|
||||
**/log/
|
||||
**/logs/
|
12
README.md
12
README.md
@ -44,7 +44,17 @@ L'application communique avec la plateforme [4NK] via une iframe et un bus de me
|
||||
L'URL de l'iframe est définie dans `App.tsx` :
|
||||
|
||||
```typescript
|
||||
const iframeUrl = 'https://dev3.4nkweb.com'
|
||||
const iframeUrl = '<PUBLIC_BASE_URL>'
|
||||
```
|
||||
|
||||
Pour modifier l'environnement cible, vous devez changer cette URL.
|
||||
|
||||
## 📋 Fichiers centralisés
|
||||
|
||||
Les fichiers suivants sont centralisés dans le dépôt principal `4NK_env` :
|
||||
- `CODE_OF_CONDUCT.md` - Code de conduite
|
||||
- `CODEOWNERS` - Propriétaires du code
|
||||
- `CONTRIBUTING.md` - Guide de contribution
|
||||
- `LICENSE` - Licence du projet
|
||||
|
||||
Voir : [`4NK_env/CODE_OF_CONDUCT.md`](../../CODE_OF_CONDUCT.md), [`4NK_env/CODEOWNERS`](../../CODEOWNERS), [`4NK_env/CONTRIBUTING.md`](../../CONTRIBUTING.md), [`4NK_env/LICENSE`](../../LICENSE)
|
||||
|
1433
package-lock.json
generated
1433
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
118
src/App.tsx
118
src/App.tsx
@ -11,10 +11,10 @@ import UserStore from './sdk/UserStrore';
|
||||
import Iframe from './sdk/Iframe'
|
||||
import BlockchainViewer from './components/ProcessesViewer';
|
||||
import FolderModal from './components/FolderModal';
|
||||
import type { ProfileData } from './sdk/models/ProfileData'
|
||||
import type { FolderData } from './sdk/models/FolderData'
|
||||
import { ProfilePrivateFields, setDefaultProfileRoles, type ProfileCreated, type ProfileData } from './sdk/models/ProfileData'
|
||||
import { FolderPrivateFields, setDefaultFolderRoles, type FolderCreated, type FolderData } from './sdk/models/FolderData'
|
||||
|
||||
const iframeUrl = 'https://dev3.4nkweb.com'
|
||||
export const iframeUrl = 'https://dev3.4nkweb.com'
|
||||
|
||||
function App() {
|
||||
const [receivedMessages, setReceivedMessages] = useState<{ timestamp: string; data: any }[]>([])
|
||||
@ -23,43 +23,51 @@ function App() {
|
||||
const [showAuthModal, setShowAuthModal] = useState(false)
|
||||
const [showFolderModal, setShowFolderModal] = useState(false)
|
||||
const [processes, setProcesses] = useState<any>(null)
|
||||
const [myProcesses, setMyProcesses] = useState<string[]>([])
|
||||
const [userPairingId, setUserPairingId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setIsConnected(UserStore.getInstance().isConnected());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setUserPairingId(UserStore.getInstance().getUserPairingId());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
|
||||
for (const key of Object.keys(processes)) {
|
||||
try {
|
||||
const process = processes[key];
|
||||
if (Object.keys(process.states?.[0]?.keys).length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
console.log(key);
|
||||
console.log(process);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to retrieve data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
messageBus.getData('467b005278cf516a42a54ba777fcbab29748072b52c01a988a596662e7b7844a:0', 'ada06b5c6e5add8a281b284a31a258355b33a9f0dbc4a5dcfe77dfd4eb904011').then((data: any) => {
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [isConnected, iframeUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && processes !== null) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getMyProcesses().then((res: string[]) => {
|
||||
setMyProcesses(res);
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [isConnected, processes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && userPairingId === null) {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
messageBus.isReady().then(() => {
|
||||
messageBus.getUserPairingId().then((userPairingId: string) => {
|
||||
UserStore.getInstance().pair(userPairingId);
|
||||
setUserPairingId(UserStore.getInstance().getUserPairingId());
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [isConnected, userPairingId, processes]);
|
||||
|
||||
// Gestionnaire pour afficher la modale de connexion
|
||||
const handleLogin = useCallback(() => {
|
||||
// Afficher la modale de connexion
|
||||
@ -100,32 +108,52 @@ function App() {
|
||||
}, []);
|
||||
|
||||
// Gestionnaire pour soumettre les données du profil
|
||||
const handleProfileSubmit = useCallback((profileData: ProfileData) => {
|
||||
// Ajouter le validator fixe aux données du profil
|
||||
const completeProfileData = {
|
||||
...profileData,
|
||||
validator: '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0'
|
||||
};
|
||||
|
||||
MessageBus.getInstance(iframeUrl).createProfile(completeProfileData).then((_profileData: ProfileData) => {
|
||||
MessageBus.getInstance(iframeUrl).getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
const handleProfileSubmit = useCallback((profileData: ProfileData, validatorId: string | null, ownerId: string | null) => {
|
||||
if (userPairingId !== null) {
|
||||
if (validatorId === null && ownerId === null) {
|
||||
console.error("No validator or owner ID provided");
|
||||
return;
|
||||
}
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
if (validatorId === null) {
|
||||
validatorId = userPairingId;
|
||||
} else if (ownerId === null) {
|
||||
ownerId = userPairingId;
|
||||
}
|
||||
const roles = setDefaultProfileRoles([ownerId!], validatorId!);
|
||||
const profilePrivateFields = ProfilePrivateFields;
|
||||
messageBus.createProfile(profileData, profilePrivateFields, roles).then((_profileCreated: ProfileCreated) => {
|
||||
messageBus.notifyProcessUpdate(_profileCreated.processId, _profileCreated.process.states[0].state_id).then(() => {
|
||||
messageBus.validateState(_profileCreated.processId, _profileCreated.process.states[0].state_id).then((_updatedProcess: any) => {
|
||||
messageBus.getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
setShowProfileModal(false);
|
||||
}, []);
|
||||
setShowProfileModal(false);
|
||||
}
|
||||
}, [userPairingId]);
|
||||
|
||||
// Gestionnaire pour soumettre les données du dossier
|
||||
const handleFolderSubmit = useCallback((folderData: FolderData) => {
|
||||
MessageBus.getInstance(iframeUrl).createFolder(folderData).then((_folderData: FolderData) => {
|
||||
MessageBus.getInstance(iframeUrl).getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
if (userPairingId !== null) {
|
||||
const roles = setDefaultFolderRoles(userPairingId, [], []);
|
||||
const folderPrivateFields = FolderPrivateFields;
|
||||
MessageBus.getInstance(iframeUrl).createFolder(folderData, folderPrivateFields, roles).then((_folderCreated: FolderCreated) => {
|
||||
MessageBus.getInstance(iframeUrl).notifyProcessUpdate(_folderCreated.processId, _folderCreated.process.states[0].state_id).then(() => {
|
||||
MessageBus.getInstance(iframeUrl).validateState(_folderCreated.processId, _folderCreated.process.states[0].state_id).then((_updatedProcess: any) => {
|
||||
MessageBus.getInstance(iframeUrl).getProcesses().then((processes: any) => {
|
||||
setProcesses(processes);
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
setShowFolderModal(false);
|
||||
}, []);
|
||||
setShowFolderModal(false);
|
||||
}
|
||||
}, [userPairingId]);
|
||||
|
||||
// Gestionnaire du clic sur le bouton Vider les messages
|
||||
const handleClearMessages = useCallback(() => {
|
||||
@ -161,7 +189,7 @@ function App() {
|
||||
{/* Espace pour contenu supplémentaire à droite */}
|
||||
<div className="content-area">
|
||||
{/* Affichage des blocs de la blockchain */}
|
||||
<BlockchainViewer processes={processes} />
|
||||
<BlockchainViewer processes={processes} myProcesses={myProcesses} onProcessesUpdate={setProcesses}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -181,7 +209,7 @@ function App() {
|
||||
<ProfileModal
|
||||
isOpen={showProfileModal}
|
||||
onClose={handleCloseProfileModal}
|
||||
onSubmit={handleProfileSubmit}
|
||||
onSubmit={(profileData: ProfileData, validatorId: string | null, ownerId: string | null) => handleProfileSubmit(profileData, validatorId, ownerId)}
|
||||
/>
|
||||
)}
|
||||
{showFolderModal && (
|
||||
|
@ -171,4 +171,210 @@
|
||||
word-break: break-all;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.process-actions {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.update-button {
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.update-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.data-fields-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.data-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.data-field:hover {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.data-field-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.data-type-icon,
|
||||
.data-value-icon {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.data-field-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding-left: 2rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.hash-tooltip {
|
||||
cursor: help;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.hash-tooltip:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: var(--text-color);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.download-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.loading-message,
|
||||
.no-access-message {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 6px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.no-access-message {
|
||||
color: #ff6b6b;
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
}
|
||||
|
||||
.field-update-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--text-color-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-left: auto;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.field-update-button:hover {
|
||||
color: var(--primary-color);
|
||||
background-color: rgba(var(--primary-color-rgb), 0.1);
|
||||
}
|
||||
|
||||
.field-update-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.edit-form input[type="text"],
|
||||
.edit-form input[type="number"],
|
||||
.edit-form select,
|
||||
.edit-form textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: var(--text-color);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.edit-form input[type="file"] {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: var(--text-color);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.edit-form textarea {
|
||||
font-family: monospace;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.edit-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.edit-form-actions button {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.edit-form-actions button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.edit-form-actions button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { useState, memo } from 'react';
|
||||
import './ProcessesViewer.css';
|
||||
import { isFileBlob, type FileBlob } from '../sdk/models/Data';
|
||||
import MessageBus from '../sdk/MessageBus';
|
||||
import { iframeUrl } from '../App';
|
||||
|
||||
interface BlockState {
|
||||
commited_in: string;
|
||||
state_id: string;
|
||||
public_data: {
|
||||
memberPublicName?: string | number[];
|
||||
pairedAddresses?: string[] | number[];
|
||||
};
|
||||
pcd_commitment: Record<string, string>;
|
||||
public_data: Record<string, any>;
|
||||
// Autres propriétés disponibles si nécessaires
|
||||
}
|
||||
|
||||
@ -21,10 +22,100 @@ interface Processes {
|
||||
|
||||
interface ProcessesViewerProps {
|
||||
processes: Processes | null;
|
||||
myProcesses: string[];
|
||||
onProcessesUpdate?: (processes: Processes) => void;
|
||||
}
|
||||
|
||||
function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
const compareStates = (
|
||||
currentState: BlockState,
|
||||
index: number,
|
||||
previousState?: BlockState,
|
||||
currentPrivateData?: Record<string, any>,
|
||||
previousPrivateData?: Record<string, any>
|
||||
) => {
|
||||
const result: Record<string, {
|
||||
value: any,
|
||||
status: 'unchanged' | 'modified',
|
||||
hash?: string,
|
||||
isPrivate: boolean,
|
||||
stateId: string
|
||||
}> = {};
|
||||
|
||||
// Ajouter toutes les données publiques de l'état actuel
|
||||
Object.keys(currentState.public_data).forEach(key => {
|
||||
const currentValue = currentState.public_data[key];
|
||||
const previousValue = previousState?.public_data[key];
|
||||
const isModified = index > 0 &&
|
||||
previousValue !== undefined &&
|
||||
JSON.stringify(currentValue) !== JSON.stringify(previousValue);
|
||||
|
||||
result[key] = {
|
||||
value: currentValue,
|
||||
status: isModified ? 'modified' : 'unchanged',
|
||||
hash: currentState.pcd_commitment[key],
|
||||
isPrivate: false,
|
||||
stateId: currentState.state_id
|
||||
};
|
||||
});
|
||||
|
||||
// Gérer les données privées
|
||||
if (index === 0) {
|
||||
// Pour le premier état, on ajoute simplement les données privées actuelles
|
||||
if (currentPrivateData) {
|
||||
Object.entries(currentPrivateData).forEach(([key, value]) => {
|
||||
result[key] = {
|
||||
value,
|
||||
status: 'unchanged',
|
||||
hash: currentState.pcd_commitment[key],
|
||||
isPrivate: true,
|
||||
stateId: currentState.state_id
|
||||
};
|
||||
});
|
||||
}
|
||||
} else if (previousPrivateData) {
|
||||
// Pour les états suivants, on commence par les données privées de l'état précédent
|
||||
Object.entries(previousPrivateData).forEach(([key, value]) => {
|
||||
result[key] = {
|
||||
value,
|
||||
status: 'unchanged',
|
||||
hash: previousState?.pcd_commitment[key],
|
||||
isPrivate: true,
|
||||
stateId: previousState!.state_id
|
||||
};
|
||||
});
|
||||
|
||||
// Puis on met à jour les données privées qui ont changé
|
||||
if (currentPrivateData) {
|
||||
Object.entries(currentPrivateData).forEach(([key, value]) => {
|
||||
result[key] = {
|
||||
value,
|
||||
status: 'modified',
|
||||
hash: currentState.pcd_commitment[key],
|
||||
isPrivate: true,
|
||||
stateId: currentState.state_id
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: ProcessesViewerProps) {
|
||||
const [expandedBlocks, setExpandedBlocks] = useState<string[]>([]);
|
||||
const [isFiltered, setIsFiltered] = useState<boolean>(false);
|
||||
const [privateData, setPrivateData] = useState<Record<string, Record<string, any>>>({});
|
||||
const [editingField, setEditingField] = useState<{
|
||||
processId: string;
|
||||
stateId: string;
|
||||
key: string;
|
||||
value: any;
|
||||
} | null>(null);
|
||||
const [tempValue, setTempValue] = useState<any>(null);
|
||||
|
||||
const handleFilterClick = () => {
|
||||
setIsFiltered(prev => !prev);
|
||||
};
|
||||
|
||||
// Si pas de données, afficher un message
|
||||
if (!processes || Object.keys(processes).length === 0) {
|
||||
@ -44,64 +135,362 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const formatAddress = (address: string | number[] | undefined): string => {
|
||||
if (!address) return "Adresse non disponible";
|
||||
|
||||
if (Array.isArray(address)) {
|
||||
// Si c'est un tableau de nombres, on le convertit en chaîne de caractères
|
||||
try {
|
||||
// Convertir les codes ASCII en caractères
|
||||
const chars = address.map(code => String.fromCharCode(Number(code)));
|
||||
return chars.join('');
|
||||
} catch (e) {
|
||||
return "Adresse encodée (format non supporté)";
|
||||
}
|
||||
} else if (typeof address === 'string') {
|
||||
// Si c'est déjà une chaîne, on la retourne telle quelle
|
||||
return address;
|
||||
const fetchPrivateData = async (processId: string, stateId: string) => {
|
||||
if (!expandedBlocks.includes(processId) || !myProcesses.includes(processId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
await messageBus.isReady();
|
||||
|
||||
return "Format d'adresse inconnu";
|
||||
const data = await messageBus.getData(processId, stateId);
|
||||
|
||||
setPrivateData(prev => ({
|
||||
...prev,
|
||||
[stateId]: data
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error fetching private data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const formatName = (name: string | number[] | undefined): string => {
|
||||
if (!name) return "Nom non disponible";
|
||||
const handleDownload = (name: string | undefined, fileBlob: FileBlob) => {
|
||||
const blob = new Blob([fileBlob.data], { type: fileBlob.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = name || 'download';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
if (Array.isArray(name)) {
|
||||
if (name.length === 1 && name[0] === 96) {
|
||||
return "`"; // Caractère spécial
|
||||
const formatValue = (key: string, value: string | number[] | FileBlob) => {
|
||||
if (isFileBlob(value)) {
|
||||
return (
|
||||
<button
|
||||
className="download-button"
|
||||
onClick={() => handleDownload(key, value)}
|
||||
title="Télécharger le fichier"
|
||||
>
|
||||
📥 Télécharger
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return JSON.stringify(value || '');
|
||||
};
|
||||
|
||||
const getDataIcon = (value: any) => {
|
||||
if (isFileBlob(value)) return '📄';
|
||||
if (typeof value === 'string') return '📝';
|
||||
if (typeof value === 'number') return '🔢';
|
||||
if (Array.isArray(value)) return '📋';
|
||||
if (typeof value === 'boolean') return '✅';
|
||||
return '📦'; // object
|
||||
};
|
||||
|
||||
const handleFieldUpdate = async (processId: string, stateId: string, key: string, value: any) => {
|
||||
try {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
await messageBus.isReady();
|
||||
|
||||
const updateData = {
|
||||
[key]: value
|
||||
};
|
||||
|
||||
// First update the process
|
||||
const updatedProcess = await messageBus.updateProcess(processId, stateId, updateData, [], null);
|
||||
if (!updatedProcess) {
|
||||
throw new Error('No updated process found');
|
||||
}
|
||||
try {
|
||||
const chars = name.map(code => String.fromCharCode(Number(code)));
|
||||
return chars.join('');
|
||||
} catch (e) {
|
||||
return "Nom encodé (format non supporté)";
|
||||
|
||||
const newStateId = updatedProcess.diffs[0]?.state_id;
|
||||
|
||||
if (!newStateId) {
|
||||
throw new Error('No new state id found');
|
||||
}
|
||||
} else if (typeof name === 'string') {
|
||||
return name;
|
||||
|
||||
// Then notify about the update
|
||||
await messageBus.notifyProcessUpdate(processId, newStateId);
|
||||
|
||||
// Finally validate the state
|
||||
await messageBus.validateState(processId, newStateId);
|
||||
|
||||
// Refresh the processes data
|
||||
const updatedProcesses = await messageBus.getProcesses();
|
||||
if (onProcessesUpdate) {
|
||||
onProcessesUpdate(updatedProcesses);
|
||||
}
|
||||
console.log('Process updated successfully');
|
||||
} catch (error) {
|
||||
console.error('Error updating field:', error);
|
||||
// You might want to show an error message to the user here
|
||||
}
|
||||
};
|
||||
|
||||
const renderEditForm = (key: string, value: any, onSave: (newValue: any) => void, onCancel: () => void) => {
|
||||
// Initialize tempValue when editing starts
|
||||
if (tempValue === null) {
|
||||
setTempValue(value);
|
||||
}
|
||||
|
||||
return "Format de nom inconnu";
|
||||
const handleFormClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
if (isFileBlob(value)) {
|
||||
return (
|
||||
<div className="edit-form">
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (event.target?.result) {
|
||||
const arrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
setTempValue({
|
||||
type: file.type,
|
||||
data: uint8Array
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="edit-form-actions">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSave(tempValue);
|
||||
setTempValue(null);
|
||||
}}>Sauvegarder</button>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
setTempValue(null);
|
||||
}}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return (
|
||||
<div className="edit-form" onClick={handleFormClick} onMouseDown={handleFormClick}>
|
||||
<select
|
||||
value={tempValue.toString()}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
setTempValue(e.target.value === 'true');
|
||||
}}
|
||||
>
|
||||
<option value="true">Vrai</option>
|
||||
<option value="false">Faux</option>
|
||||
</select>
|
||||
<div className="edit-form-actions">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSave(tempValue);
|
||||
}}>Sauvegarder</button>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<div className="edit-form" onClick={handleFormClick} onMouseDown={handleFormClick}>
|
||||
<textarea
|
||||
value={JSON.stringify(tempValue, null, 2)}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
const newValue = JSON.parse(e.target.value);
|
||||
if (Array.isArray(newValue)) {
|
||||
setTempValue(newValue);
|
||||
}
|
||||
} catch (error) {
|
||||
// Invalid JSON, ignore
|
||||
}
|
||||
}}
|
||||
onClick={handleFormClick}
|
||||
onMouseDown={handleFormClick}
|
||||
rows={4}
|
||||
/>
|
||||
<div className="edit-form-actions">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSave(tempValue);
|
||||
}}>Sauvegarder</button>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="edit-form" onClick={handleFormClick} onMouseDown={handleFormClick}>
|
||||
<input
|
||||
type={typeof value === 'number' ? 'number' : 'text'}
|
||||
value={tempValue}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
const newValue = typeof value === 'number'
|
||||
? parseFloat(e.target.value)
|
||||
: e.target.value;
|
||||
setTempValue(newValue);
|
||||
}}
|
||||
onClick={handleFormClick}
|
||||
onMouseDown={handleFormClick}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="edit-form-actions">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSave(tempValue);
|
||||
}}>Sauvegarder</button>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}}>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDataField = (
|
||||
key: string,
|
||||
value: any,
|
||||
hash: string | undefined,
|
||||
isPrivate: boolean,
|
||||
processId: string,
|
||||
stateId: string,
|
||||
status: 'unchanged' | 'modified' = 'unchanged',
|
||||
originStateId?: string
|
||||
) => {
|
||||
const isEditing = editingField?.key === key &&
|
||||
editingField?.processId === processId &&
|
||||
editingField?.stateId === stateId;
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
return status === 'modified' ? '#32a852' : 'transparent';
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="data-field"
|
||||
key={key}
|
||||
style={{
|
||||
backgroundColor: getStatusColor(status),
|
||||
transition: 'background-color 0.3s'
|
||||
}}
|
||||
>
|
||||
<div className="data-field-header">
|
||||
<span className="data-type-icon" title={isPrivate ? 'Donnée privée' : 'Donnée publique'}>
|
||||
{isPrivate ? '🔒' : '🌐'}
|
||||
</span>
|
||||
<span className="data-value-icon">{getDataIcon(value)}</span>
|
||||
<span className="data-label">{key}</span>
|
||||
{originStateId && originStateId !== stateId && (
|
||||
<span className="data-origin" title={`Propagé depuis l'état ${originStateId}`}>
|
||||
↺
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
className="field-update-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setEditingField({ processId, stateId, key, value });
|
||||
}}
|
||||
title="Mettre à jour cette valeur"
|
||||
>
|
||||
{isEditing ? '✕' : '🔄'}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="data-field-content"
|
||||
>
|
||||
{isEditing ? (
|
||||
<div>
|
||||
{renderEditForm(
|
||||
key,
|
||||
value,
|
||||
async (newValue) => {
|
||||
await handleFieldUpdate(processId, stateId, key, newValue);
|
||||
setEditingField(null);
|
||||
setTempValue(null);
|
||||
},
|
||||
() => {
|
||||
setEditingField(null);
|
||||
setTempValue(null);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{formatValue(key, value)}
|
||||
{hash && (
|
||||
<div className="hash-tooltip" title={`Hash: ${hash}`}>
|
||||
🔑
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="processes-viewer">
|
||||
<h2>Processus</h2>
|
||||
<p className="block-count">{Object.keys(processes).length} processus disponible(s)</p>
|
||||
<button onClick={handleFilterClick}>
|
||||
{isFiltered ? 'Show All Processes' : 'Filter Processes'}
|
||||
</button>
|
||||
<p className="block-count">
|
||||
{isFiltered
|
||||
? Object.keys(processes).filter(processId => myProcesses.includes(processId)).length
|
||||
: Object.keys(processes).length} processus disponible(s)
|
||||
</p>
|
||||
|
||||
<div className="block-list">
|
||||
{Object.entries(processes).map(([blockId, block]) => {
|
||||
const isExpanded = expandedBlocks.includes(blockId);
|
||||
const stateCount = block.states.length;
|
||||
{Object.entries(processes).map(([processId, process]) => {
|
||||
if (isFiltered && !myProcesses.includes(processId)) {
|
||||
return null;
|
||||
}
|
||||
const isExpanded = expandedBlocks.includes(processId);
|
||||
const stateCount = process.states.length - 1; // We just ignore the last state, which is always empty
|
||||
// Le premier état est le plus récent
|
||||
|
||||
return (
|
||||
<div key={blockId} className="block-item">
|
||||
<div key={processId} className="block-item">
|
||||
<div
|
||||
className={`block-header ${isExpanded ? 'expanded' : ''}`}
|
||||
onClick={() => toggleBlock(blockId)}
|
||||
onClick={() => toggleBlock(processId)}
|
||||
>
|
||||
<div className="block-id">{blockId.substring(0, 8)}...{blockId.substring(blockId.length - 4)}</div>
|
||||
<div className="block-id">{processId.substring(0, 8)}...{processId.substring(processId.length - 4)}</div>
|
||||
<div className="block-state-count">{stateCount} état(s)</div>
|
||||
<div className="block-toggle">{isExpanded ? '▼' : '▶'}</div>
|
||||
</div>
|
||||
@ -109,45 +498,62 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
{isExpanded && (
|
||||
<div className="block-details">
|
||||
<div className="block-complete-id">
|
||||
<strong>ID complet:</strong> {blockId}
|
||||
<strong>Process ID:</strong> {processId}
|
||||
</div>
|
||||
|
||||
{block.states.map((state, index) => (
|
||||
<div key={`${blockId}-state-${index}`} className="state-item">
|
||||
<h4>État {index + 1}</h4>
|
||||
<div className="state-detail">
|
||||
<strong>State ID:</strong> {state.state_id}
|
||||
</div>
|
||||
<div className="state-detail">
|
||||
<strong>Commited dans:</strong> {state.commited_in}
|
||||
</div>
|
||||
{process.states.map((state, index) => {
|
||||
if (index === stateCount) return null;
|
||||
|
||||
// Fetch private data if needed
|
||||
if (myProcesses.includes(processId) && !privateData[state.state_id]) {
|
||||
console.log('Fetching private data for state:', state.state_id);
|
||||
setTimeout(() => {
|
||||
fetchPrivateData(processId, state.state_id);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
<div className="state-public-data">
|
||||
<h5>Données publiques</h5>
|
||||
<div className="public-data-item">
|
||||
<strong>Nom:</strong> {formatName(state.public_data.memberPublicName)}
|
||||
const statePrivateData = privateData[state.state_id] || {};
|
||||
|
||||
// On utilise compareStates avec l'état précédent et les données privées
|
||||
const stateData = compareStates(
|
||||
state,
|
||||
index,
|
||||
index > 0 ? process.states[index - 1] : undefined,
|
||||
statePrivateData,
|
||||
index > 0 ? privateData[process.states[index - 1].state_id] : undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={`${processId}-state-${index}`} className="state-item">
|
||||
<h4>État {index + 1}</h4>
|
||||
<div className="state-detail">
|
||||
<strong>TransactionId:</strong> {state.commited_in}
|
||||
</div>
|
||||
<div className="state-detail">
|
||||
<strong>Empreinte totale de l'état:</strong> {state.state_id}
|
||||
</div>
|
||||
|
||||
<div className="data-fields-container">
|
||||
{/* Toutes les données (publiques et privées) */}
|
||||
{Object.entries(stateData).map(([key, { value, status, hash, isPrivate, stateId }]) =>
|
||||
renderDataField(key, value, hash, isPrivate, processId, stateId, status, state.state_id)
|
||||
)}
|
||||
|
||||
{/* Message de chargement pour les données privées si nécessaire */}
|
||||
{myProcesses.includes(processId) && Object.keys(statePrivateData).length === 0 && (
|
||||
<div className="loading-message">Chargement des données privées...</div>
|
||||
)}
|
||||
|
||||
{/* Message si pas d'accès aux données privées */}
|
||||
{!myProcesses.includes(processId) && (
|
||||
<div className="no-access-message">
|
||||
🔒 Vous n'avez pas accès aux données privées de ce processus
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{state.public_data.pairedAddresses && (
|
||||
<div className="public-data-item">
|
||||
<strong>Adresses associées:</strong>
|
||||
<ul className="address-list">
|
||||
{Array.isArray(state.public_data.pairedAddresses) ?
|
||||
(typeof state.public_data.pairedAddresses[0] === 'string' ? (
|
||||
(state.public_data.pairedAddresses as string[]).map((addr, i) => (
|
||||
<li key={i}>{addr}</li>
|
||||
))
|
||||
) : (
|
||||
<li>{formatAddress(state.public_data.pairedAddresses as number[])}</li>
|
||||
)) : (
|
||||
<li>{String(state.public_data.pairedAddresses || '')}</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -156,7 +562,7 @@ function ProcessesViewer({ processes }: ProcessesViewerProps) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
ProcessesViewer.displayName = 'ProcessesViewer';
|
||||
export default memo(ProcessesViewer);
|
||||
|
@ -165,10 +165,30 @@
|
||||
box-shadow: 0 2px 4px rgba(103, 58, 183, 0.2);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
background-color: #ffebee; /* Light red background */
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
border-left: 4px solid red; /* Left border for emphasis */
|
||||
font-weight: bold;
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.error-message::before {
|
||||
content: "⚠️ "; /* Unicode for warning emoji */
|
||||
}
|
||||
|
||||
/* Styles adaptatifs pour les écrans plus petits */
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import React, { useState, memo } from 'react';
|
||||
import Modal from './modal/Modal';
|
||||
import './ProfileModal.css';
|
||||
import type { ProfileData } from '../sdk/models/ProfileData';
|
||||
import type { FileBlob } from '../sdk/models/Data';
|
||||
|
||||
interface ProfileModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (profileData: ProfileData) => void;
|
||||
onSubmit: (profileData: ProfileData, validatorId: string | null, ownerId: string | null) => void;
|
||||
initialData?: Partial<ProfileData>;
|
||||
}
|
||||
|
||||
@ -20,20 +21,46 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
|
||||
postalCode: initialData.postalCode || '',
|
||||
city: initialData.city || '',
|
||||
country: initialData.country || '',
|
||||
idDocument: initialData.idDocument || '',
|
||||
idDocument: initialData.idDocument || null,
|
||||
idCertified: false,
|
||||
});
|
||||
const [validatorId, setValidatorId] = useState<string | null>(null);
|
||||
const [ownerId, setOwnerId] = useState<string | null>(null);
|
||||
const [isOwnProfile, setIsOwnProfile] = useState<boolean>(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setProfileData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
const { name, type, files, value } = e.target;
|
||||
|
||||
if (type === 'file' && files) {
|
||||
// Assuming you want to handle a single file
|
||||
const file = files[0];
|
||||
file.arrayBuffer().then(arrayBuffer => {
|
||||
const fileBlob: FileBlob = {
|
||||
type: file.type,
|
||||
data: new Uint8Array(arrayBuffer)
|
||||
};
|
||||
setProfileData(prev => ({
|
||||
...prev,
|
||||
[name]: fileBlob
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
setProfileData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSubmit(profileData);
|
||||
if (!validatorId && !ownerId) {
|
||||
setErrorMessage("Please set either a Validator ID or an Owner ID.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
onSubmit(profileData, validatorId, ownerId);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -157,16 +184,53 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
|
||||
<div className="form-field">
|
||||
<label htmlFor="idDocument">Document d'identité <span className="optional">(optionnel)</span></label>
|
||||
<input
|
||||
type="text"
|
||||
type="file"
|
||||
id="idDocument"
|
||||
name="idDocument"
|
||||
value={profileData.idDocument || ''}
|
||||
onChange={handleChange}
|
||||
placeholder="Numéro de document"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section">
|
||||
<h3 className="section-title">Profil</h3>
|
||||
<button type="button" onClick={() => setIsOwnProfile(!isOwnProfile)}>
|
||||
{isOwnProfile ? "Je veux faire valider mon profil" : "Je valide le profil d'un utilisateur"}</button>
|
||||
{isOwnProfile ? (
|
||||
<div className="form-field">
|
||||
<label htmlFor="validatorId">ID du validateur</label>
|
||||
<input
|
||||
type="text"
|
||||
id="validatorId"
|
||||
name="validatorId"
|
||||
value={validatorId || ''}
|
||||
onChange={(e) => {
|
||||
setValidatorId(e.target.value);
|
||||
setOwnerId(null);
|
||||
}}
|
||||
placeholder="ID du validateur"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="form-field">
|
||||
<label htmlFor="ownerId">ID de l'utilisateur</label>
|
||||
<input
|
||||
type="text"
|
||||
id="ownerId"
|
||||
name="ownerId"
|
||||
value={ownerId || ''}
|
||||
onChange={(e) => {
|
||||
setOwnerId(e.target.value);
|
||||
setValidatorId(null);
|
||||
}}
|
||||
placeholder="ID de l'utilisateur"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{errorMessage && <div className="error-message">{errorMessage}</div>}
|
||||
|
||||
<div className="form-actions">
|
||||
<button type="button" className="btn-cancel" onClick={onClose}>Annuler</button>
|
||||
<button type="submit" className="btn-submit">Créer le profil</button>
|
||||
|
@ -1,15 +1,18 @@
|
||||
import IframeReference from './IframeReference';
|
||||
import EventBus from './EventBus';
|
||||
import UserStore from './UserStrore';
|
||||
import type { ProfileData } from './models/ProfileData';
|
||||
import type { FolderData } from './models/FolderData';
|
||||
import { isProfileData, type ProfileCreated, type ProfileData } from './models/ProfileData';
|
||||
import { isFolderData, type FolderCreated, type FolderData } from './models/FolderData';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { RoleDefinition } from './models/Roles';
|
||||
|
||||
export default class MessageBus {
|
||||
private static instance: MessageBus;
|
||||
private readonly origin: string;
|
||||
private messageListener: ((event: MessageEvent) => void) | null = null;
|
||||
private errors: { [key: string]: string } = {};
|
||||
private readyPromise: Promise<void> | null = null;
|
||||
private isReadyFlag = false;
|
||||
|
||||
private constructor(origin: string) {
|
||||
this.origin = origin;
|
||||
@ -23,19 +26,28 @@ export default class MessageBus {
|
||||
}
|
||||
|
||||
public isReady(): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void) => {
|
||||
if (this.isReadyFlag) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.readyPromise) {
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
||||
this.readyPromise = new Promise<void>((resolve) => {
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('IS_READY', (responseId: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
if (responseId !== correlationId) return;
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
this.isReadyFlag = true;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
||||
public requestLink(): Promise<void> {
|
||||
@ -68,6 +80,41 @@ export default class MessageBus {
|
||||
});
|
||||
}
|
||||
|
||||
public getUserPairingId(): Promise<string> {
|
||||
return new Promise<string>((resolve: (userPairingId: string) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('PAIRING_ID', (responseId: string, userPairingId: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(userPairingId);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PAIRING_ID', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'GET_PAIRING_ID',
|
||||
accessToken,
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public validateToken(): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve: (isValid: boolean) => void, reject: (error: string) => void) => {
|
||||
const userStore = UserStore.getInstance();
|
||||
@ -148,10 +195,15 @@ export default class MessageBus {
|
||||
public getProcesses(): Promise<any> {
|
||||
return new Promise<any>((resolve: (processes: any) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
console.log(correlationId);
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('PROCESSES_RETRIEVED', (responseId: string, processes: any) => {
|
||||
console.log(responseId);
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
@ -170,14 +222,15 @@ export default class MessageBus {
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'GET_PROCESSES'
|
||||
type: 'GET_PROCESSES',
|
||||
accessToken,
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public getData(processId: string, stateId: string): Promise<any> {
|
||||
return new Promise<any>((resolve: (data: any) => void, reject: (error: string) => void) => {
|
||||
public getMyProcesses(): Promise<string[]> {
|
||||
return new Promise<string[]>((resolve: (myProcesses: string[]) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
@ -185,7 +238,42 @@ export default class MessageBus {
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('DATA_RETRIEVED', (responseId: string, data: any) => {
|
||||
const unsubscribe = EventBus.getInstance().on('GET_MY_PROCESSES', (responseId: string, myProcesses: string[]) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(myProcesses);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_GET_MY_PROCESSES', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'GET_MY_PROCESSES',
|
||||
accessToken,
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public getData(processId: string, stateId: string): Promise<Record<string, any>> {
|
||||
return new Promise<Record<string, any>>((resolve: (data: Record<string, any>) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('DATA_RETRIEVED', (responseId: string, data: Record<string, any>) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
@ -207,32 +295,31 @@ export default class MessageBus {
|
||||
type: 'RETRIEVE_DATA',
|
||||
processId,
|
||||
stateId,
|
||||
token: accessToken
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public createProfile(profileData: ProfileData): Promise<ProfileData> {
|
||||
return new Promise<ProfileData>((resolve: (profileData: ProfileData) => void, reject: (error: string) => void) => {
|
||||
public getPublicData(encodedData: number[]): Promise<any> {
|
||||
return new Promise<any>((resolve: (data: any) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
const refreshToken = userStore.getRefreshToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('PROFILE_CREATED', (responseId: string, profileData: ProfileData) => {
|
||||
const unsubscribe = EventBus.getInstance().on('PUBLIC_DATA_DECODED', (responseId: string, data: any) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(profileData);
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PROFILE_CREATED', (responseId: string, error: string) => {
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PUBLIC_DATA_DECODED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
@ -242,17 +329,16 @@ export default class MessageBus {
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'CREATE_PROFILE',
|
||||
profileData,
|
||||
accessToken,
|
||||
refreshToken
|
||||
type: 'DECODE_PUBLIC_DATA',
|
||||
encodedData,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public createFolder(folderData: FolderData): Promise<FolderData> {
|
||||
return new Promise<FolderData>((resolve: (folderData: FolderData) => void, reject: (error: string) => void) => {
|
||||
public createProfile(profileData: ProfileData, profilePrivateData: string[], roles: Record<string, RoleDefinition>): Promise<ProfileCreated> {
|
||||
return new Promise<ProfileCreated>((resolve: (profileCreated: ProfileCreated) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
@ -260,16 +346,33 @@ export default class MessageBus {
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('FOLDER_CREATED', (responseId: string, folderData: FolderData) => {
|
||||
const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(folderData);
|
||||
// Return value must contain the data commited in the new process
|
||||
const profileData = processCreated.processData;
|
||||
if (!profileData || !isProfileData(profileData)) {
|
||||
reject('Returned invalid profile data');
|
||||
}
|
||||
if (!processCreated.processId || typeof processCreated.processId !== 'string') {
|
||||
console.error('Returned invalid process id');
|
||||
reject('Returned invalid process id');
|
||||
}
|
||||
// TODO check that process is of type Process
|
||||
|
||||
const profileCreated: ProfileCreated = {
|
||||
processId: processCreated.processId,
|
||||
process: processCreated.process,
|
||||
profileData
|
||||
};
|
||||
|
||||
resolve(profileCreated);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_FOLDER_CREATED', (responseId: string, error: string) => {
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_CREATED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
@ -279,9 +382,177 @@ export default class MessageBus {
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'CREATE_FOLDER',
|
||||
folderData,
|
||||
token: accessToken
|
||||
type: 'CREATE_PROCESS',
|
||||
processData: profileData,
|
||||
privateFields: profilePrivateData,
|
||||
roles,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public createFolder(folderData: FolderData, folderPrivateData: string[], roles: Record<string, RoleDefinition>): Promise<FolderCreated> {
|
||||
return new Promise<FolderCreated>((resolve: (folderData: FolderCreated) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
// Return value must contain the data commited in the new process
|
||||
const folderData = processCreated.processData;
|
||||
if (!folderData || !isFolderData(folderData)) reject('Returned invalid process data');
|
||||
if (!processCreated.processId || typeof processCreated.processId !== 'string') reject('Returned invalid process id');
|
||||
// TODO check that process is of type Process
|
||||
|
||||
const folderCreated: FolderCreated = {
|
||||
processId: processCreated.processId,
|
||||
process: processCreated.process,
|
||||
folderData
|
||||
};
|
||||
|
||||
resolve(folderCreated);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_CREATED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'CREATE_PROCESS',
|
||||
processData: folderData,
|
||||
privateFields: folderPrivateData,
|
||||
roles,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public updateProcess(processId: string, lastStateId: string, newData: Record<string, any>, privateFields: string[], roles: Record<string, RoleDefinition> | null): Promise<any> {
|
||||
return new Promise<any>((resolve: (updatedProcess: any) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('PROCESS_UPDATED', (responseId: string, updatedProcess: any) => {
|
||||
console.log('PROCESS_UPDATED', updatedProcess);
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(updatedProcess);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_UPDATED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'UPDATE_PROCESS',
|
||||
processId,
|
||||
lastStateId,
|
||||
newData,
|
||||
privateFields,
|
||||
roles,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public notifyProcessUpdate(processId: string, stateId: string): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('UPDATE_NOTIFIED', (responseId: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve();
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_UPDATE_NOTIFIED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'NOTIFY_UPDATE',
|
||||
processId,
|
||||
stateId,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
public validateState(processId: string, stateId: string): Promise<any> {
|
||||
return new Promise<any>((resolve: (updatedProcess: any) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
const accessToken = userStore.getAccessToken()!;
|
||||
|
||||
const correlationId = uuidv4();
|
||||
this.initMessageListener(correlationId);
|
||||
|
||||
const unsubscribe = EventBus.getInstance().on('STATE_VALIDATED', (responseId: string, updatedProcess: any) => {
|
||||
console.log(updatedProcess);
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribe();
|
||||
this.destroyMessageListener();
|
||||
resolve(updatedProcess);
|
||||
});
|
||||
|
||||
const unsubscribeError = EventBus.getInstance().on('ERROR_STATE_VALIDATED', (responseId: string, error: string) => {
|
||||
if (responseId !== correlationId) {
|
||||
return;
|
||||
}
|
||||
unsubscribeError();
|
||||
this.destroyMessageListener();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
this.sendMessage({
|
||||
type: 'VALIDATE_STATE',
|
||||
processId,
|
||||
stateId,
|
||||
accessToken
|
||||
});
|
||||
}).catch(console.error);
|
||||
});
|
||||
@ -371,6 +642,17 @@ export default class MessageBus {
|
||||
EventBus.getInstance().emit('TOKEN_RENEWED', correlationId, message.accessToken, message.refreshToken);
|
||||
break;
|
||||
|
||||
case 'GET_PAIRING_ID':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_PAIRING_ID', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('PAIRING_ID', correlationId, message.userPairingId);
|
||||
break;
|
||||
|
||||
case 'PROCESSES_RETRIEVED': // GET_PROCESSES
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
@ -381,25 +663,14 @@ export default class MessageBus {
|
||||
EventBus.getInstance().emit('PROCESSES_RETRIEVED', correlationId, message.processes);
|
||||
break;
|
||||
|
||||
case 'PROFILE_CREATED': // CREATE_PROFILE
|
||||
case 'GET_MY_PROCESSES':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_PROFILE_CREATED', correlationId, error);
|
||||
EventBus.getInstance().emit('ERROR_GET_MY_PROCESSES', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('PROFILE_CREATED', correlationId, message.profileData);
|
||||
break;
|
||||
|
||||
case 'FOLDER_CREATED': // CREATE_FOLDER
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_FOLDER_CREATED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('GET_MY_PROCESSES', correlationId, message.myProcesses);
|
||||
break;
|
||||
|
||||
case 'DATA_RETRIEVED': // RETRIEVE_DATA
|
||||
@ -413,6 +684,62 @@ export default class MessageBus {
|
||||
EventBus.getInstance().emit('DATA_RETRIEVED', correlationId, message.data);
|
||||
break;
|
||||
|
||||
case 'PROCESS_CREATED':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_PROCESS_CREATED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('PROCESS_CREATED', correlationId, message.processCreated);
|
||||
break;
|
||||
|
||||
case 'UPDATE_NOTIFIED':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_UPDATE_NOTIFIED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('UPDATE_NOTIFIED', correlationId);
|
||||
break;
|
||||
|
||||
case 'STATE_VALIDATED':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_STATE_VALIDATED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('STATE_VALIDATED', correlationId, message.validatedProcess);
|
||||
break;
|
||||
|
||||
case 'PROCESS_UPDATED':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_PROCESS_UPDATED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
console.log('PROCESS_UPDATED', message);
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('PROCESS_UPDATED', correlationId, message.updatedProcess);
|
||||
break;
|
||||
|
||||
case 'PUBLIC_DATA_DECODED':
|
||||
if (this.errors[correlationId]) {
|
||||
const error = this.errors[correlationId];
|
||||
delete this.errors[correlationId];
|
||||
EventBus.getInstance().emit('ERROR_PUBLIC_DATA_DECODED', correlationId, error);
|
||||
return;
|
||||
}
|
||||
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
|
||||
EventBus.getInstance().emit('PUBLIC_DATA_DECODED', correlationId, message.decodedData);
|
||||
break;
|
||||
|
||||
case 'ERROR':
|
||||
console.error('Error:', message);
|
||||
this.errors[correlationId] = message.error;
|
||||
|
@ -22,6 +22,7 @@ export default class UserStore {
|
||||
public disconnect(): void {
|
||||
sessionStorage.removeItem('accessToken');
|
||||
sessionStorage.removeItem('refreshToken');
|
||||
sessionStorage.removeItem('userPairingId');
|
||||
}
|
||||
|
||||
public getAccessToken(): string | null {
|
||||
@ -31,4 +32,12 @@ export default class UserStore {
|
||||
public getRefreshToken(): string | null {
|
||||
return sessionStorage.getItem('refreshToken');
|
||||
}
|
||||
|
||||
public pair(userPairingId: string): void {
|
||||
sessionStorage.setItem('userPairingId', userPairingId);
|
||||
}
|
||||
|
||||
public getUserPairingId(): string | null {
|
||||
return sessionStorage.getItem('userPairingId');
|
||||
}
|
||||
}
|
||||
|
15
src/sdk/models/Data.ts
Normal file
15
src/sdk/models/Data.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface FileBlob {
|
||||
type: string,
|
||||
data: Uint8Array
|
||||
};
|
||||
|
||||
export function isFileBlob(data: any): data is FileBlob {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null &&
|
||||
'type' in data &&
|
||||
typeof data.type === 'string' &&
|
||||
'data' in data &&
|
||||
data.data instanceof Uint8Array
|
||||
);
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
import { isFileBlob, type FileBlob } from "./Data";
|
||||
import type { RoleDefinition } from "./Roles";
|
||||
|
||||
export interface FolderData {
|
||||
folderNumber: string;
|
||||
name: string;
|
||||
@ -8,7 +11,127 @@ export interface FolderData {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
customers: string[];
|
||||
documents: string[];
|
||||
documents: FileBlob[];
|
||||
motes: string[];
|
||||
stakeholders: string[];
|
||||
}
|
||||
|
||||
export function isFolderData(data: any): data is FolderData {
|
||||
if (typeof data !== 'object' || data === null) return false;
|
||||
|
||||
const requiredStringFields = [
|
||||
'folderNumber',
|
||||
'name',
|
||||
'deedType',
|
||||
'description',
|
||||
'archived_description',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
|
||||
for (const field of requiredStringFields) {
|
||||
if (typeof data[field] !== 'string') return false;
|
||||
}
|
||||
|
||||
const requiredArrayFields = [
|
||||
'customers',
|
||||
'motes',
|
||||
'stakeholders'
|
||||
];
|
||||
|
||||
for (const field of requiredArrayFields) {
|
||||
if (!Array.isArray(data[field]) || !data[field].every((item: any) => typeof item === 'string')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const requiredFileBlobArrayFields = [
|
||||
'documents',
|
||||
];
|
||||
|
||||
for (const field of requiredFileBlobArrayFields) {
|
||||
if (!Array.isArray(data[field])) return false;
|
||||
if (data[field].length > 0 && !data[field].every(isFileBlob)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const emptyFolderData: FolderData = {
|
||||
folderNumber: '',
|
||||
name: '',
|
||||
deedType: '',
|
||||
description: '',
|
||||
archived_description: '',
|
||||
status: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
customers: [],
|
||||
documents: [],
|
||||
motes: [],
|
||||
stakeholders: []
|
||||
};
|
||||
|
||||
const folderDataFields: string[] = Object.keys(emptyFolderData);
|
||||
|
||||
const FolderPublicFields: string[] = [];
|
||||
|
||||
// All the attributes are private in that case
|
||||
export const FolderPrivateFields = [
|
||||
...folderDataFields.filter(key => !FolderPublicFields.includes(key))
|
||||
];
|
||||
|
||||
export interface FolderCreated {
|
||||
processId: string,
|
||||
process: any, // Process
|
||||
folderData: FolderData,
|
||||
}
|
||||
|
||||
export function setDefaultFolderRoles(ownerId: string, stakeholdersId: string[], customersId: string[]): Record<string, RoleDefinition> {
|
||||
return {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...folderDataFields, 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
stakeholders: {
|
||||
members: stakeholdersId,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['documents', 'motes'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
customers: {
|
||||
members: customersId,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: folderDataFields,
|
||||
min_sig_member: 0.0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { isFileBlob, type FileBlob } from "./Data";
|
||||
import type { RoleDefinition } from "./Roles";
|
||||
|
||||
export interface ProfileData {
|
||||
name: string;
|
||||
surname: string;
|
||||
@ -7,5 +10,113 @@ export interface ProfileData {
|
||||
postalCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
idDocument?: string;
|
||||
idDocument: FileBlob | null;
|
||||
idCertified: boolean;
|
||||
}
|
||||
|
||||
export function isProfileData(data: any): data is ProfileData{
|
||||
if (typeof data !== 'object' || data === null) return false;
|
||||
|
||||
const requiredStringFields = [
|
||||
'name',
|
||||
'surname',
|
||||
'email',
|
||||
'phone',
|
||||
'address',
|
||||
'postalCode',
|
||||
'city',
|
||||
'country',
|
||||
];
|
||||
|
||||
for (const field of requiredStringFields) {
|
||||
if (typeof data[field] !== 'string') return false;
|
||||
}
|
||||
|
||||
const requiredBooleanFields = [
|
||||
'idCertified',
|
||||
];
|
||||
|
||||
for (const field of requiredBooleanFields) {
|
||||
if (typeof data[field] !== 'boolean') return false;
|
||||
}
|
||||
|
||||
const requiredFileFields = [
|
||||
'idDocument',
|
||||
];
|
||||
|
||||
for (const field of requiredFileFields) {
|
||||
if (!isFileBlob(data[field]) && data[field] !== null) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const emptyProfileData: ProfileData = {
|
||||
name: '',
|
||||
surname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
country: '',
|
||||
idDocument: null,
|
||||
idCertified: false,
|
||||
};
|
||||
|
||||
const profileDataFields: string[] = Object.keys(emptyProfileData);
|
||||
|
||||
const ProfilePublicFields: string[] = ['idCertified'];
|
||||
|
||||
export const ProfilePrivateFields = [
|
||||
...profileDataFields.filter(key => !ProfilePublicFields.includes(key))
|
||||
];
|
||||
|
||||
export interface ProfileCreated {
|
||||
processId: string,
|
||||
process: any, // Process
|
||||
profileData: ProfileData,
|
||||
}
|
||||
|
||||
export function setDefaultProfileRoles(ownerId: string[], validatorId: string): Record<string, RoleDefinition> {
|
||||
return {
|
||||
demiurge: {
|
||||
members: [...ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: ownerId,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...ProfilePrivateFields, 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...profileDataFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: ownerId,
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
11
src/sdk/models/Roles.ts
Normal file
11
src/sdk/models/Roles.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface ValidationRule {
|
||||
quorum: number,
|
||||
fields: string[],
|
||||
min_sig_member: number,
|
||||
}
|
||||
|
||||
export interface RoleDefinition {
|
||||
members: string[],
|
||||
validation_rules: ValidationRule[],
|
||||
storages: string[]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user