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 BlockchainViewer from './components/ProcessesViewer';
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'
const iframeUrl = 'https://dev3.4nkweb.com'
@ -66,7 +66,7 @@ function App() {
})
});
}
}, [isConnected, userPairingId]);
}, [isConnected, userPairingId, processes]);
// Gestionnaire pour afficher la modale de connexion
const handleLogin = useCallback(() => {
@ -108,21 +108,33 @@ 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((_profileCreated: ProfileCreated) => {
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) => {
@ -197,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 && (

View File

@ -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; }
}

View File

@ -6,7 +6,7 @@ import type { ProfileData } from '../sdk/models/ProfileData';
interface ProfileModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (profileData: ProfileData) => void;
onSubmit: (profileData: ProfileData, validatorId: string | null, ownerId: string | null) => void;
initialData?: Partial<ProfileData>;
}
@ -23,6 +23,10 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
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, type, files, value } = e.target;
@ -44,7 +48,12 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
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 (
@ -176,6 +185,45 @@ function ProfileModal({ isOpen, onClose, onSubmit, initialData = {} }: ProfileMo
</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>

View File

@ -1,7 +1,7 @@
import IframeReference from './IframeReference';
import EventBus from './EventBus';
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 { v4 as uuidv4 } from 'uuid';
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) => {
this.checkToken().then(() => {
const userStore = UserStore.getInstance();
@ -310,16 +310,33 @@ export default class MessageBus {
const correlationId = uuidv4();
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) {
return;
}
unsubscribe();
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);
});
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) {
return;
}
@ -329,9 +346,11 @@ export default class MessageBus {
});
this.sendMessage({
type: 'CREATE_PROFILE',
profileData,
accessToken,
type: 'CREATE_PROCESS',
processData: profileData,
privateFields: profilePrivateData,
roles,
accessToken
});
}).catch(console.error);
});
@ -347,14 +366,13 @@ export default class MessageBus {
this.initMessageListener(correlationId);
const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
console.log(processCreated);
if (responseId !== correlationId) {
return;
}
unsubscribe();
this.destroyMessageListener();
// 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 (!processCreated.processId || typeof processCreated.processId !== 'string') reject('Returned invalid process id');
// 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> {
return {
demiurge: {
members: [ownerId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
validation_rules: [

View File

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