Compare commits

...

6 Commits

Author SHA1 Message Date
Sosthene
e6b9f58cea Update handleProfileSubmit 2025-06-13 20:27:08 +02:00
Sosthene
8636d37d29 Update ProfileModal 2025-06-13 20:26:50 +02:00
Sosthene
a25e1b4ae5 Add error-message css 2025-06-13 20:26:26 +02:00
Sosthene
7807ae315f Make createProfile compatible with CREATE_PROCESS interface 2025-06-13 20:25:27 +02:00
Sosthene
2084b99978 Add demiurge role to FolderRoles 2025-06-13 20:24:17 +02:00
Sosthene
d5088d9e36 [bug] Fix ProfileData 2025-06-13 20:23:57 +02:00
6 changed files with 160 additions and 36 deletions

View File

@ -11,7 +11,7 @@ import UserStore from './sdk/UserStrore';
import Iframe from './sdk/Iframe' import Iframe from './sdk/Iframe'
import BlockchainViewer from './components/ProcessesViewer'; import BlockchainViewer from './components/ProcessesViewer';
import FolderModal from './components/FolderModal'; import FolderModal from './components/FolderModal';
import type { ProfileCreated, ProfileData } from './sdk/models/ProfileData' import { ProfilePrivateFields, setDefaultProfileRoles, type ProfileCreated, type ProfileData } from './sdk/models/ProfileData'
import { FolderPrivateFields, setDefaultFolderRoles, type FolderCreated, type FolderData } from './sdk/models/FolderData' import { FolderPrivateFields, setDefaultFolderRoles, type FolderCreated, type FolderData } from './sdk/models/FolderData'
const iframeUrl = 'https://dev3.4nkweb.com' const iframeUrl = 'https://dev3.4nkweb.com'
@ -66,7 +66,7 @@ function App() {
}) })
}); });
} }
}, [isConnected, userPairingId]); }, [isConnected, userPairingId, processes]);
// Gestionnaire pour afficher la modale de connexion // Gestionnaire pour afficher la modale de connexion
const handleLogin = useCallback(() => { const handleLogin = useCallback(() => {
@ -108,21 +108,33 @@ function App() {
}, []); }, []);
// Gestionnaire pour soumettre les données du profil // Gestionnaire pour soumettre les données du profil
const handleProfileSubmit = useCallback((profileData: ProfileData) => { const handleProfileSubmit = useCallback((profileData: ProfileData, validatorId: string | null, ownerId: string | null) => {
// Ajouter le validator fixe aux données du profil if (userPairingId !== null) {
const completeProfileData = { if (validatorId === null && ownerId === null) {
...profileData, console.error("No validator or owner ID provided");
validator: '884cb36a346a79af8697559f16940141f068bdf1656f88fa0df0e9ecd7311fb8:0' return;
}; }
const messageBus = MessageBus.getInstance(iframeUrl);
MessageBus.getInstance(iframeUrl).createProfile(completeProfileData).then((_profileCreated: ProfileCreated) => { if (validatorId === null) {
MessageBus.getInstance(iframeUrl).getProcesses().then((processes: any) => { validatorId = userPairingId;
setProcesses(processes); } 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 // Gestionnaire pour soumettre les données du dossier
const handleFolderSubmit = useCallback((folderData: FolderData) => { const handleFolderSubmit = useCallback((folderData: FolderData) => {
@ -197,7 +209,7 @@ function App() {
<ProfileModal <ProfileModal
isOpen={showProfileModal} isOpen={showProfileModal}
onClose={handleCloseProfileModal} onClose={handleCloseProfileModal}
onSubmit={handleProfileSubmit} onSubmit={(profileData: ProfileData, validatorId: string | null, ownerId: string | null) => handleProfileSubmit(profileData, validatorId, ownerId)}
/> />
)} )}
{showFolderModal && ( {showFolderModal && (

View File

@ -165,10 +165,30 @@
box-shadow: 0 2px 4px rgba(103, 58, 183, 0.2); 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 */ /* Styles adaptatifs pour les écrans plus petits */
@media (max-width: 768px) { @media (max-width: 768px) {
.form-row { .form-row {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 0.5rem; gap: 0.5rem;
} }
} }
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

View File

@ -6,7 +6,7 @@ import type { ProfileData } from '../sdk/models/ProfileData';
interface ProfileModalProps { interface ProfileModalProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onSubmit: (profileData: ProfileData) => void; onSubmit: (profileData: ProfileData, validatorId: string | null, ownerId: string | null) => void;
initialData?: Partial<ProfileData>; initialData?: Partial<ProfileData>;
} }
@ -23,6 +23,10 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
idDocument: initialData.idDocument || null, idDocument: initialData.idDocument || null,
idCertified: false, 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 handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, type, files, value } = e.target; const { name, type, files, value } = e.target;
@ -44,7 +48,12 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); 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 ( return (
@ -176,6 +185,45 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
</div> </div>
</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"> <div className="form-actions">
<button type="button" className="btn-cancel" onClick={onClose}>Annuler</button> <button type="button" className="btn-cancel" onClick={onClose}>Annuler</button>
<button type="submit" className="btn-submit">Créer le profil</button> <button type="submit" className="btn-submit">Créer le profil</button>

View File

@ -1,7 +1,7 @@
import IframeReference from './IframeReference'; import IframeReference from './IframeReference';
import EventBus from './EventBus'; import EventBus from './EventBus';
import UserStore from './UserStrore'; import UserStore from './UserStrore';
import type { ProfileCreated, ProfileData } from './models/ProfileData'; import { isProfileData, type ProfileCreated, type ProfileData } from './models/ProfileData';
import { isFolderData, type FolderCreated, type FolderData } from './models/FolderData'; import { isFolderData, type FolderCreated, type FolderData } from './models/FolderData';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import type { RoleDefinition } from './models/Roles'; import type { RoleDefinition } from './models/Roles';
@ -301,7 +301,7 @@ export default class MessageBus {
}); });
} }
public createProfile(profileData: ProfileData): Promise<ProfileCreated> { public createProfile(profileData: ProfileData, profilePrivateData: string[], roles: Record<string, RoleDefinition>): Promise<ProfileCreated> {
return new Promise<ProfileCreated>((resolve: (profileCreated: ProfileCreated) => void, reject: (error: string) => void) => { return new Promise<ProfileCreated>((resolve: (profileCreated: ProfileCreated) => void, reject: (error: string) => void) => {
this.checkToken().then(() => { this.checkToken().then(() => {
const userStore = UserStore.getInstance(); const userStore = UserStore.getInstance();
@ -310,16 +310,33 @@ export default class MessageBus {
const correlationId = uuidv4(); const correlationId = uuidv4();
this.initMessageListener(correlationId); this.initMessageListener(correlationId);
const unsubscribe = EventBus.getInstance().on('PROFILE_CREATED', (responseId: string, profileCreated: ProfileCreated) => { const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
if (responseId !== correlationId) { if (responseId !== correlationId) {
return; return;
} }
unsubscribe(); unsubscribe();
this.destroyMessageListener(); this.destroyMessageListener();
// 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); resolve(profileCreated);
}); });
const unsubscribeError = EventBus.getInstance().on('ERROR_PROFILE_CREATED', (responseId: string, error: string) => { const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_CREATED', (responseId: string, error: string) => {
if (responseId !== correlationId) { if (responseId !== correlationId) {
return; return;
} }
@ -329,9 +346,11 @@ export default class MessageBus {
}); });
this.sendMessage({ this.sendMessage({
type: 'CREATE_PROFILE', type: 'CREATE_PROCESS',
profileData, processData: profileData,
accessToken, privateFields: profilePrivateData,
roles,
accessToken
}); });
}).catch(console.error); }).catch(console.error);
}); });
@ -347,14 +366,13 @@ export default class MessageBus {
this.initMessageListener(correlationId); this.initMessageListener(correlationId);
const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => { const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
console.log(processCreated);
if (responseId !== correlationId) { if (responseId !== correlationId) {
return; return;
} }
unsubscribe(); unsubscribe();
this.destroyMessageListener(); this.destroyMessageListener();
// Return value must contain the data commited in the new process // Return value must contain the data commited in the new process
const folderData = processCreated.folderCreated; const folderData = processCreated.processData;
if (!folderData || !isFolderData(folderData)) reject('Returned invalid process data'); if (!folderData || !isFolderData(folderData)) reject('Returned invalid process data');
if (!processCreated.processId || typeof processCreated.processId !== 'string') reject('Returned invalid process id'); if (!processCreated.processId || typeof processCreated.processId !== 'string') reject('Returned invalid process id');
// TODO check that process is of type Process // TODO check that process is of type Process

View File

@ -90,6 +90,11 @@ export interface FolderCreated {
export function setDefaultFolderRoles(ownerId: string, stakeholdersId: string[], customersId: string[]): Record<string, RoleDefinition> { export function setDefaultFolderRoles(ownerId: string, stakeholdersId: string[], customersId: string[]): Record<string, RoleDefinition> {
return { return {
demiurge: {
members: [ownerId],
validation_rules: [],
storages: []
},
owner: { owner: {
members: [ownerId], members: [ownerId],
validation_rules: [ validation_rules: [

View File

@ -1,4 +1,4 @@
import type { FileBlob } from "./Data"; import { isFileBlob, type FileBlob } from "./Data";
import type { RoleDefinition } from "./Roles"; import type { RoleDefinition } from "./Roles";
export interface ProfileData { export interface ProfileData {
@ -18,20 +18,36 @@ export function isProfileData(data: any): data is ProfileData{
if (typeof data !== 'object' || data === null) return false; if (typeof data !== 'object' || data === null) return false;
const requiredStringFields = [ const requiredStringFields = [
'folderNumber',
'name', 'name',
'deedType', 'surname',
'description', 'email',
'archived_description', 'phone',
'status', 'address',
'created_at', 'postalCode',
'updated_at' 'city',
'country',
]; ];
for (const field of requiredStringFields) { for (const field of requiredStringFields) {
if (typeof data[field] !== 'string') return false; 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; return true;
} }
@ -64,6 +80,11 @@ export interface ProfileCreated {
export function setDefaultProfileRoles(ownerId: string[], validatorId: string): Record<string, RoleDefinition> { export function setDefaultProfileRoles(ownerId: string[], validatorId: string): Record<string, RoleDefinition> {
return { return {
demiurge: {
members: [...ownerId, validatorId],
validation_rules: [],
storages: []
},
owner: { owner: {
members: ownerId, members: ownerId,
validation_rules: [ validation_rules: [