Compare commits
269 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
277e19ba76 | ||
![]() |
ac05049f5a | ||
![]() |
842250abfd | ||
![]() |
a4ef82b68f | ||
![]() |
0bed1cecc9 | ||
![]() |
041ff5e98f | ||
![]() |
9f8c178920 | ||
![]() |
91ed42e879 | ||
![]() |
573a3e49e5 | ||
![]() |
2a62c2649d | ||
![]() |
c130e2d588 | ||
![]() |
9498001973 | ||
![]() |
db6ee4e5e8 | ||
![]() |
9aa6e85825 | ||
![]() |
c7034fe0d3 | ||
![]() |
e6ba657e53 | ||
![]() |
d6ae777e22 | ||
![]() |
f9f3745d6f | ||
![]() |
09fe6b89ff | ||
![]() |
20877250e2 | ||
![]() |
1e4010e64e | ||
![]() |
0a5aff1b84 | ||
![]() |
b4f9dafdab | ||
![]() |
c4207cfd3a | ||
![]() |
a3793512c2 | ||
![]() |
a87bca5042 | ||
![]() |
bb139480fb | ||
![]() |
b3f1184b3b | ||
![]() |
bf0a23c117 | ||
![]() |
dfdc2971a0 | ||
![]() |
19fcbb6fad | ||
![]() |
30ace4bb7a | ||
![]() |
31d55122a4 | ||
![]() |
eeb9531fd4 | ||
![]() |
1082b76513 | ||
![]() |
8ec4300b92 | ||
![]() |
f286d6864c | ||
![]() |
c2d64fce15 | ||
![]() |
f659362682 | ||
![]() |
8d5e32fb79 | ||
![]() |
87bb57dae0 | ||
![]() |
6e0e4ecbfe | ||
![]() |
6eb4b6e0be | ||
![]() |
2410a40a4b | ||
![]() |
86cb4a228b | ||
![]() |
ed6035ad9c | ||
![]() |
5284d9be04 | ||
![]() |
22bcc727c9 | ||
![]() |
087ea0b2c3 | ||
![]() |
b0ac349a69 | ||
![]() |
e198904e00 | ||
![]() |
d70e02ebd2 | ||
![]() |
48d9c79040 | ||
![]() |
baa4e19432 | ||
![]() |
80c091fde4 | ||
![]() |
7ef06bc6b5 | ||
![]() |
707cb174c1 | ||
![]() |
bc89a790ce | ||
![]() |
e2a55f187b | ||
![]() |
e1f4f8b4ed | ||
![]() |
b507c54c70 | ||
![]() |
80b74edcc6 | ||
![]() |
dc6bb1da30 | ||
![]() |
cba30de720 | ||
![]() |
b9232ef9f3 | ||
![]() |
614104d68e | ||
![]() |
495775f68a | ||
![]() |
792f6eb3cc | ||
![]() |
45cec46cac | ||
![]() |
96f8788c08 | ||
![]() |
9e8ed1ad1d | ||
![]() |
5af06c76f6 | ||
![]() |
387e7ed65c | ||
![]() |
1bd368c948 | ||
![]() |
0cbd1fc714 | ||
![]() |
04fcc2e5b2 | ||
![]() |
6cfeb5cab8 | ||
![]() |
6ad0b5c339 | ||
![]() |
dd22e4baa0 | ||
![]() |
6a986028e9 | ||
![]() |
a8a3d58ed1 | ||
![]() |
52dca1cd3c | ||
![]() |
623dd4f637 | ||
![]() |
829c1ef614 | ||
![]() |
0f0a035967 | ||
![]() |
2c1a33330c | ||
![]() |
e63295767a | ||
![]() |
a761f0841a | ||
![]() |
e754f04c77 | ||
![]() |
8e791c5807 | ||
![]() |
a6e9cbb028 | ||
![]() |
89d76f017e | ||
![]() |
ff53727d6b | ||
![]() |
960cb5c24e | ||
![]() |
18ec096de9 | ||
![]() |
94847d15ad | ||
![]() |
0b905b34eb | ||
![]() |
427a8854df | ||
![]() |
1a76bf0525 | ||
![]() |
3dd2b55417 | ||
![]() |
11fea86b14 | ||
![]() |
f51a5a79aa | ||
![]() |
233c352359 | ||
![]() |
7315c16329 | ||
![]() |
995bb70bbb | ||
![]() |
fe52bb4c68 | ||
![]() |
c435e46bde | ||
![]() |
94f4079518 | ||
![]() |
4833e3c872 | ||
![]() |
ee5c2db9aa | ||
![]() |
f1a5ba9c0d | ||
![]() |
298e31e201 | ||
![]() |
2f97110845 | ||
![]() |
cf84f59353 | ||
![]() |
304556c1f1 | ||
![]() |
a9b3bf9bd1 | ||
![]() |
4c011f9ec6 | ||
![]() |
c2e1da365e | ||
![]() |
612c0cd0ec | ||
![]() |
e73614a779 | ||
![]() |
0fa012bc85 | ||
![]() |
32c74d0a3e | ||
![]() |
7a7312afd4 | ||
![]() |
26c26d4fd8 | ||
![]() |
9ac66c1e16 | ||
![]() |
3aaf543175 | ||
![]() |
ed982620de | ||
![]() |
b174d9491a | ||
![]() |
2361e184f8 | ||
![]() |
24698b0b64 | ||
![]() |
c0fc55e93e | ||
![]() |
22fb7122a5 | ||
![]() |
56878c977c | ||
![]() |
0a3be835a9 | ||
![]() |
2450d674e2 | ||
![]() |
6a7918d081 | ||
![]() |
b6899cbf3f | ||
![]() |
7961165e1f | ||
![]() |
5d79b3df16 | ||
![]() |
b70516bc86 | ||
![]() |
b22eda0399 | ||
![]() |
dfe48d1de9 | ||
![]() |
129e3ea973 | ||
![]() |
b4d8562519 | ||
![]() |
4540ac726a | ||
![]() |
9a3685ec4f | ||
![]() |
02f6a10b9d | ||
![]() |
229a5a9585 | ||
![]() |
87bc0290d7 | ||
![]() |
bb44128e12 | ||
![]() |
a8bdb18cc4 | ||
![]() |
09a2fee475 | ||
![]() |
a6dfd45299 | ||
![]() |
e9f1d3991d | ||
![]() |
ad78491a90 | ||
![]() |
2455e2cca2 | ||
![]() |
222b8dc503 | ||
![]() |
e6df9cbba0 | ||
![]() |
114c20dd26 | ||
![]() |
c1f12b2cf7 | ||
![]() |
8b70278614 | ||
![]() |
5ea18802f6 | ||
![]() |
b889ed498c | ||
![]() |
5b819bbe3f | ||
![]() |
f0a16e4ad9 | ||
![]() |
859a3428bd | ||
![]() |
7672c291b1 | ||
![]() |
6aa4a21d17 | ||
![]() |
5a703126d2 | ||
![]() |
0d1d2d1822 | ||
![]() |
4ee08765e9 | ||
![]() |
6c03da2139 | ||
![]() |
8cc2b1f00a | ||
![]() |
8801c704fa | ||
![]() |
4140b9fe09 | ||
![]() |
db9a77725f | ||
![]() |
068532d8be | ||
![]() |
19fca09d0f | ||
![]() |
0986d60974 | ||
![]() |
a78666b4a9 | ||
![]() |
b6b0522b59 | ||
f87f7f747d | |||
![]() |
489d0bb5c3 | ||
3899cf1a53 | |||
![]() |
8aa82f3a9c | ||
![]() |
6e86e65845 | ||
87976783a3 | |||
9589cf1116 | |||
0f28476aed | |||
bb0a504fc3 | |||
d672e79064 | |||
b0f699f6f0 | |||
d596f4212e | |||
82c9921372 | |||
d4fdf3ac6d | |||
d4f51cec5f | |||
![]() |
0492b3e28d | ||
![]() |
0aa0792ea3 | ||
![]() |
18f0c3e504 | ||
![]() |
d76ba5eda9 | ||
b1c1b5c284 | |||
6b09941e28 | |||
6a8c4063e6 | |||
2500e19e71 | |||
![]() |
97e86308ce | ||
b7f725bcfd | |||
649f930a1b | |||
1a397a8a5d | |||
eeec82437a | |||
21d2d7b94d | |||
c8f6d625f8 | |||
09a2c85119 | |||
ed4b797230 | |||
f48240bea3 | |||
d1af444ed1 | |||
02091bf433 | |||
4d11a9b7ef | |||
6edde3781b | |||
dc54ec1a9b | |||
863533be33 | |||
4b7a08534d | |||
c5bf332242 | |||
![]() |
1c3afac679 | ||
![]() |
63fc45927f | ||
![]() |
5b3f432c3a | ||
![]() |
758a32a4d6 | ||
![]() |
6f6d3e8de5 | ||
![]() |
c87ad8fed5 | ||
![]() |
56fe4fbcd3 | ||
![]() |
d94fd9e017 | ||
![]() |
4f76d43f38 | ||
![]() |
7bfe3bcad2 | ||
![]() |
c178b60d51 | ||
![]() |
a450d80600 | ||
![]() |
095c4efba2 | ||
![]() |
723322cc0a | ||
![]() |
d17f4aa8d9 | ||
![]() |
7a137dbe2f | ||
![]() |
a351c1814c | ||
![]() |
4e8dbcbf17 | ||
![]() |
2e54e77aee | ||
96ed1e50fa | |||
e4c440d6df | |||
d3e13bd801 | |||
ca5a59c51a | |||
![]() |
76650e3068 | ||
![]() |
5667f07b51 | ||
![]() |
19f517a946 | ||
![]() |
01d56c864a | ||
![]() |
e8d7c5777f | ||
c939065562 | |||
![]() |
fd4a9d32b7 | ||
![]() |
eb4e47a73e | ||
![]() |
5620084a35 | ||
![]() |
5b3fcc1eaf | ||
![]() |
cc4da39f74 | ||
![]() |
e98b9ff6d7 | ||
![]() |
393bdae782 | ||
![]() |
82ee58dd25 | ||
![]() |
7fa33cbdbc | ||
![]() |
bd5d50cf97 | ||
6ed6682824 | |||
39c14ff490 | |||
f9abdd31cd | |||
d7e27bbb9a | |||
ccc0a1620c | |||
5ad6465b74 | |||
7435a33fe0 | |||
65f67993ba |
2
.ci-trigger
Normal file
2
.ci-trigger
Normal file
@ -0,0 +1,2 @@
|
||||
# CI Trigger
|
||||
# CI Trigger Sun Sep 21 19:57:49 UTC 2025
|
95
.cursorignore
Normal file
95
.cursorignore
Normal file
@ -0,0 +1,95 @@
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# 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/
|
165
.cursorrules
Normal file
165
.cursorrules
Normal file
@ -0,0 +1,165 @@
|
||||
# Règles globales Cursor pour les projets
|
||||
|
||||
## Principes généraux
|
||||
- Lire impérativement le fichier `.cursorrules` au démarrage de chaque session.
|
||||
- Lire tous les fichiers du dossier `docs/`, le code et les paramètres avant de commencer.
|
||||
- Poser des questions et proposer des améliorations si nécessaire.
|
||||
- Ajouter les leçons apprises à ce fichier `.cursorrules`.
|
||||
- Écrire des documents complets et exhaustifs.
|
||||
- Respecter strictement les règles de lint du Markdown.
|
||||
- Préférer toujours un shell **bash** à PowerShell.
|
||||
- Fermer et relancer le terminal avant chaque utilisation.
|
||||
- Si le terminal est interrompu, analyser la commande précédente (interruption probablement volontaire).
|
||||
- Exécuter automatiquement les étapes de résolution de problème.
|
||||
- Expliquer les commandes complexes avant de les lancer.
|
||||
- Compiler régulièrement et corriger toutes les erreurs avant de passer à l’étape suivante.
|
||||
- Tester, documenter, compiler, aligner tag git, changelog et version avant déploiement et push.
|
||||
- Utiliser `docx2txt` pour lire les fichiers `.docx`.
|
||||
- Ajouter automatiquement les dépendances et rechercher systématiquement les dernières versions.
|
||||
- Faire des commandes simples et claires en plusieurs étapes.
|
||||
- Vérifie toujours tes hypothèses avant de commencer.
|
||||
- N'oublie jamais qu'après la correction d'un problème, il faut corriger toutes les erreurs qui peuvent en découler.
|
||||
|
||||
## Organisation des fichiers et répertoires
|
||||
- Scripts regroupés dans `scripts/`
|
||||
- Configurations regroupées dans `confs/`
|
||||
- Journaux regroupés dans `logs/`
|
||||
- Répertoires obligatoires :
|
||||
- `docs/` : documentation de toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
|
||||
- `tests/` : tests liés à toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
|
||||
- Remplacer les résumés (`RESUME`) par des mises à jour dans `docs/`.
|
||||
|
||||
## Configuration critique des services
|
||||
- Mempool du réseau signet :
|
||||
`https://mempool2.4nkweb.com/fr/docs/api/rest`
|
||||
|
||||
## Développement et sécurité
|
||||
- Ne jamais committer de clés privées ou secrets.
|
||||
- Utiliser des variables d’environnement pour les données sensibles.
|
||||
- Définir correctement les dépendances Docker avec healthchecks.
|
||||
- Utiliser les URLs de service Docker Compose (`http://service_name:port`).
|
||||
- Documenter toutes les modifications importantes dans `docs/`.
|
||||
- Externaliser au maximum les variables d’environnement.
|
||||
- Toujours utiliser une clé SSH pour cloner les dépôts.
|
||||
- Monter en version les dépôts au début du travail.
|
||||
- Pousser les tags docker `ext` via la CI sur `git.4nkweb.com`.
|
||||
- Corriger systématiquement les problèmes, même mineurs, sans contournement.
|
||||
|
||||
## Scripts (règles critiques)
|
||||
- Vérifier l’existence d’un script dans `scripts/` avant toute action.
|
||||
- Utiliser les scripts existants plutôt que des commandes directes.
|
||||
- Ne jamais créer plusieurs versions ou noms de scripts.
|
||||
- Améliorer l’existant au lieu de créer des variantes (`startup-v2.sh`, etc.).
|
||||
|
||||
## Images Docker (règles critiques)
|
||||
- Ajouter systématiquement `apt update && apt upgrade` dans les Dockerfiles.
|
||||
- Installer en arrière-plan dans les images Docker :
|
||||
`curl, git, sed, awk, nc, wget, jq, telnet, tee, wscat, ping, npm (dernière version)`
|
||||
- Appliquer à tous les Dockerfiles et `docker-compose.yml`.
|
||||
- N'utilise pas les version test ou dev ou ext-dev mais toujours les version ext, relance leur compilation si nécessaire
|
||||
|
||||
## Fichiers de configuration (règles critiques)
|
||||
- Vérifier l’écriture effective après chaque modification.
|
||||
- Fichiers concernés : `nginx.conf`, `bitcoin.conf`, `package.json`, `Cargo.toml`.
|
||||
- Utiliser `cat`, `jq` ou vérificateurs de syntaxe.
|
||||
|
||||
## Connexion au réseau Bitcoin signet
|
||||
Commande unique et obligatoire :
|
||||
```bash
|
||||
docker exec bitcoin-signet bitcoin-cli -signet -rpccookiefile=/home/bitcoin/.bitcoin/signet/.cookie getblockchaininfo
|
||||
````
|
||||
|
||||
## Connexion au relay/faucet bootstrap
|
||||
|
||||
* Test via WSS : `wss://dev3.4nkweb.com/ws/`
|
||||
* Envoi Faucet, réponse attendue avec `NewTx` (tx hex et tweak\_data).
|
||||
|
||||
## Debug
|
||||
|
||||
* Automatiser dans le code toute solution validée.
|
||||
* Pérenniser les retours d’expérience dans code et paramètres.
|
||||
* Compléter les tests pour éviter les régressions.
|
||||
|
||||
## Nginx
|
||||
|
||||
* Tous les fichiers dans `conf/ngnix` doivent être mappés avec ceux du serveur.
|
||||
|
||||
## Minage (règles critiques)
|
||||
|
||||
* Toujours valider les adresses utilisées (adresses TSP non reconnues).
|
||||
* Utiliser uniquement des adresses Bitcoin valides (bech32m).
|
||||
* Vérifier que le minage génère des blocs avec transactions, pas uniquement coinbase.
|
||||
* Surveiller les logs du minage pour détecter les erreurs d’adresse.
|
||||
* Vérifier la propagation via le mempool externe.
|
||||
|
||||
## Mempool externe
|
||||
|
||||
* Utiliser `https://mempool2.4nkweb.com` pour vérifier les transactions.
|
||||
* Vérifier la synchronisation entre réseau local et externe.
|
||||
|
||||
## Données et modèles
|
||||
|
||||
* Utiliser les fichiers CSV comme base des modèles de données.
|
||||
* Être attentif aux en-têtes multi-lignes.
|
||||
* Confirmer la structure comprise et demander définition de toutes les colonnes.
|
||||
* Corriger automatiquement incohérences de type.
|
||||
|
||||
## Implémentation et architecture
|
||||
|
||||
* Code splitting avec `React.lazy` et `Suspense`.
|
||||
* Centraliser l’état avec Redux ou Context API.
|
||||
* Créer une couche d’abstraction pour les services de données.
|
||||
* Aller systématiquement au bout d’une implémentation.
|
||||
|
||||
## Préparation open source
|
||||
|
||||
Chaque projet doit être prêt pour un dépôt sur `git.4nkweb.com` :
|
||||
|
||||
* Inclure : `LICENSE` (MIT, Apache 2.0 ou GPL), `CONTRIBUTING.md`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`.
|
||||
* Aligner documentation et tests avec `4NK_node`.
|
||||
|
||||
## Versioning et documentation
|
||||
|
||||
* Mettre à jour documentation et tests systématiquement.
|
||||
* Gérer versioning avec changelog.
|
||||
* Demander validation avant tag.
|
||||
* Documenter les hypothèses testées dans un REX technique.
|
||||
* Tester avant tout commit.
|
||||
* Tester les buildsavant tout tag.
|
||||
|
||||
## Bonnes pratiques de confidentialité et sécurité
|
||||
|
||||
### Docker
|
||||
- Ne jamais stocker de secrets (clés, tokens, mots de passe) dans les Dockerfiles ou docker-compose.yml.
|
||||
- Utiliser des fichiers `.env` sécurisés (non commités avec copie en .env.example) pour toutes les variables sensibles.
|
||||
- Ne pas exécuter de conteneurs avec l’utilisateur root, privilégier un utilisateur dédié.
|
||||
- Limiter les capacités des conteneurs (option `--cap-drop`) pour réduire la surface d’attaque.
|
||||
- Scanner régulièrement les images Docker avec un outil de sécurité (ex : Trivy, Clair).
|
||||
- Mettre à jour en continu les images de base afin d’éliminer les vulnérabilités.
|
||||
- Ne jamais exposer de ports inutiles.
|
||||
- Restreindre les volumes montés au strict nécessaire.
|
||||
- Utiliser des réseaux Docker internes pour la communication inter-containers.
|
||||
- Vérifier et tenir à jour les .dockerignore.
|
||||
|
||||
### Git
|
||||
- Ne jamais committer de secrets, clés ou identifiants (même temporairement).
|
||||
- Configurer des hooks Git (pre-commit) pour détecter automatiquement les secrets et les failles.
|
||||
- Vérifier l’historique (`git log`, `git filter-repo`) pour s’assurer qu’aucune information sensible n’a été poussée.
|
||||
- Signer les commits avec GPG pour garantir l’authenticité.
|
||||
- Utiliser systématiquement SSH pour les connexions à distance.
|
||||
- Restreindre les accès aux dépôts (principes du moindre privilège).
|
||||
- Documenter les changements sensibles dans `CHANGELOG.md`.
|
||||
- Ne jamais pousser directement sur `main` ou `master`, toujours passer par des branches de feature ou PR.
|
||||
- Vérifier et tenir à jour les .gitignore.
|
||||
- Vérifier et tenir à jour les .gitkeep.
|
||||
- Vérifier et tenir à jour les .gitattributes.
|
||||
|
||||
### Cursor
|
||||
- Toujours ouvrir une session en commençant par relire le fichier `.cursorrules`.
|
||||
- Vérifier que Cursor ne propose pas de commit contenant des secrets ou fichiers sensibles.
|
||||
- Ne pas exécuter dans Cursor de commandes non comprises ou copiées sans vérification.
|
||||
- Préférer l’utilisation de scripts audités dans `scripts/` plutôt que des commandes directes dans Cursor.
|
||||
- Fermer et relancer Cursor régulièrement pour éviter des contextes persistants non désirés.
|
||||
- Ne jamais partager le contenu du terminal ou des fichiers sensibles via Cursor en dehors du périmètre du projet.
|
||||
- Vérifier et tenir à jour les .cursorrules.
|
||||
- Vérifier et tenir à jour les .cursorignore.
|
95
.dockerignore
Normal file
95
.dockerignore
Normal file
@ -0,0 +1,95 @@
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
**/.cargo/
|
||||
|
||||
# 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/
|
@ -1,6 +0,0 @@
|
||||
BACK_API_PROTOCOL=
|
||||
BACK_API_HOSTNAME=
|
||||
BACK_API_PORT=
|
||||
BACK_API_ROOT_URL=
|
||||
BACK_API_VERSION=
|
||||
|
126
.gitea/workflows/build-ext.yml
Normal file
126
.gitea/workflows/build-ext.yml
Normal file
@ -0,0 +1,126 @@
|
||||
name: build-and-push-ext
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- ext
|
||||
|
||||
jobs:
|
||||
build_push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare SSH agent (optional)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
eval "$(ssh-agent -s)"
|
||||
if [ -n "${{ secrets.SSH_PRIVATE_KEY || '' }}" ]; then
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' | ssh-add - >/dev/null 2>&1 || true
|
||||
fi
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan git.4nkweb.com >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
echo "SSH agent ready: $SSH_AUTH_SOCK"
|
||||
# Rendre l'agent dispo aux steps suivants
|
||||
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
|
||||
echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Load .env(.example) and export NEXT_PUBLIC_* variables
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -a
|
||||
if [ -f .env ]; then
|
||||
. ./.env
|
||||
elif [ -f .env.example ]; then
|
||||
# Parser .env.example même s'il est formaté en tableau Markdown
|
||||
# On retire la bordure '|' éventuelle et on ne garde que les lignes KEY=VALUE
|
||||
tmpenv=$(mktemp)
|
||||
sed -E 's/^\|\s*//; s/\s*\|\s*$//' .env.example \
|
||||
| awk 'NF>0' \
|
||||
| grep -E '^[A-Z0-9_]+=.*' \
|
||||
> "$tmpenv"
|
||||
# Charger uniquement les variables
|
||||
. "$tmpenv"
|
||||
rm -f "$tmpenv"
|
||||
fi
|
||||
set +a
|
||||
echo "Environment NEXT_PUBLIC_* available (current step):" || true
|
||||
env | grep '^NEXT_PUBLIC_' || true
|
||||
|
||||
# Exporter pour les étapes suivantes via GITHUB_ENV
|
||||
count=0
|
||||
while IFS='=' read -r key value; do
|
||||
echo "$key=$value" >> "$GITHUB_ENV"
|
||||
count=$((count+1))
|
||||
done < <(env | grep '^NEXT_PUBLIC_')
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "Aucune variable NEXT_PUBLIC_* détectée (ni .env/.env.example ni secrets). Abandon." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Compute Docker tag from commit message or fallback
|
||||
id: tag
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
msg=$(git log -1 --pretty=%B)
|
||||
if [[ "$msg" =~ ci:\ docker_tag=([a-zA-Z0-9._:-]+) ]]; then
|
||||
tag="${BASH_REMATCH[1]}"
|
||||
else
|
||||
tag="ext"
|
||||
fi
|
||||
echo "TAG=$tag" | tee -a $GITHUB_OUTPUT
|
||||
|
||||
- name: Docker login (git.4nkweb.com)
|
||||
shell: bash
|
||||
env:
|
||||
REG_USER: ${{ secrets.USER }}
|
||||
REG_TOKEN: ${{ secrets.TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "$REG_TOKEN" | docker login git.4nkweb.com -u "$REG_USER" --password-stdin
|
||||
|
||||
- name: Build image (target ext)
|
||||
shell: bash
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "${SSH_AUTH_SOCK:-}" ]; then
|
||||
buildArgs=()
|
||||
# 1) Ajouter toutes les variables NEXT_PUBLIC_* chargées depuis .env
|
||||
while IFS='=' read -r key _; do
|
||||
[ -n "$key" ] || continue
|
||||
val="${!key:-}"
|
||||
buildArgs+=(--build-arg "$key=$val")
|
||||
done < <(env | grep '^NEXT_PUBLIC_' | cut -d= -f1 | sort)
|
||||
# 2) Fallback/override possibles depuis les secrets CI pour certaines clés critiques
|
||||
[ -n "${{ secrets.NEXT_PUBLIC_4NK_URL || '' }}" ] && buildArgs+=(--build-arg NEXT_PUBLIC_4NK_URL="${{ secrets.NEXT_PUBLIC_4NK_URL }}")
|
||||
[ -n "${{ secrets.NEXT_PUBLIC_4NK_IFRAME_URL || '' }}" ] && buildArgs+=(--build-arg NEXT_PUBLIC_4NK_IFRAME_URL="${{ secrets.NEXT_PUBLIC_4NK_IFRAME_URL }}")
|
||||
[ -n "${{ secrets.NEXT_PUBLIC_IDNOT_CLIENT_ID || '' }}" ] && buildArgs+=(--build-arg NEXT_PUBLIC_IDNOT_CLIENT_ID="${{ secrets.NEXT_PUBLIC_IDNOT_CLIENT_ID }}")
|
||||
|
||||
# 3) Fail si aucune variable NEXT_PUBLIC_* n'est définie
|
||||
if ! env | grep -q '^NEXT_PUBLIC_'; then
|
||||
echo "Aucune variable NEXT_PUBLIC_* détectée (ni .env/.env.example ni secrets). Abandon." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker build --target ext --ssh default "${buildArgs[@]}" \
|
||||
-t git.4nkweb.com/4nk/lecoffre-front:${{ steps.tag.outputs.TAG }} \
|
||||
-f Dockerfile .
|
||||
else
|
||||
echo "SSH_AUTH_SOCK non défini: l'agent SSH n'est pas disponible. Assurez-vous de définir secrets.SSH_PRIVATE_KEY."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Push image
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker push git.4nkweb.com/4nk/lecoffre-front:${{ steps.tag.outputs.TAG }}
|
78
.github/workflows/ppd.yml
vendored
78
.github/workflows/ppd.yml
vendored
@ -1,78 +0,0 @@
|
||||
name: Preprod - Build & Deploy to Scaleway
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [preprod]
|
||||
|
||||
env:
|
||||
PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a
|
||||
NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64
|
||||
CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool
|
||||
|
||||
IMAGE_NAME: front
|
||||
CONTAINER_NAME: front
|
||||
|
||||
jobs:
|
||||
build-and-push-image-lecoffre:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Copy SSH
|
||||
run: cp ~/.ssh/id_rsa id_rsa
|
||||
- name: Login to Scaleway Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: nologin
|
||||
password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}
|
||||
- name: Get Git Commit SHA
|
||||
id: vars
|
||||
run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Build the Docker Image
|
||||
run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
- name: Push the Docker Image to Scaleway Container Registry
|
||||
run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
deploy-to-scaleway-lecoffre:
|
||||
needs: build-and-push-image-lecoffre
|
||||
runs-on: ubuntu-latest
|
||||
environment: preprod
|
||||
steps:
|
||||
- name: Install CLI
|
||||
uses: scaleway/action-scw@v0
|
||||
- name: Get container ID
|
||||
run: |
|
||||
echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV
|
||||
env:
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
||||
- name: Deploy the container based on the new image
|
||||
run: |
|
||||
env_string=""
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == *"="* ]]; then
|
||||
key=$(echo "$line" | cut -d '=' -f 1)
|
||||
value=$(echo "$line" | cut -d '=' -f 2-)
|
||||
if [[ -n "$key" ]]; then
|
||||
env_string+="environment-variables.$key=$value "
|
||||
fi
|
||||
fi
|
||||
done <<< "$ENV_VARS"
|
||||
env_string=$(echo $env_string | sed 's/ $//')
|
||||
scw container container update ${{ env.CONTAINER_ID }} $env_string
|
||||
env:
|
||||
ENV_VARS: ${{ secrets.ENV }}
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
||||
|
79
.github/workflows/prd.yml
vendored
79
.github/workflows/prd.yml
vendored
@ -1,79 +0,0 @@
|
||||
name: Prod - Build & Deploy to Scaleway
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
|
||||
PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a
|
||||
NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834
|
||||
CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam
|
||||
|
||||
IMAGE_NAME: front
|
||||
CONTAINER_NAME: front
|
||||
|
||||
jobs:
|
||||
build-and-push-image-lecoffre:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Copy SSH
|
||||
run: cp ~/.ssh/id_rsa id_rsa
|
||||
- name: Login to Scaleway Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: nologin
|
||||
password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}
|
||||
- name: Get Git Commit SHA
|
||||
id: vars
|
||||
run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Build the Docker Image
|
||||
run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
- name: Push the Docker Image to Scaleway Container Registry
|
||||
run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
deploy-to-scaleway-lecoffre:
|
||||
needs: build-and-push-image-lecoffre
|
||||
runs-on: ubuntu-latest
|
||||
environment: prod
|
||||
steps:
|
||||
- name: Install CLI
|
||||
uses: scaleway/action-scw@v0
|
||||
- name: Get container ID
|
||||
run: |
|
||||
echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV
|
||||
env:
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
||||
- name: Deploy the container based on the new image
|
||||
run: |
|
||||
env_string=""
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == *"="* ]]; then
|
||||
key=$(echo "$line" | cut -d '=' -f 1)
|
||||
value=$(echo "$line" | cut -d '=' -f 2-)
|
||||
if [[ -n "$key" ]]; then
|
||||
env_string+="environment-variables.$key=$value "
|
||||
fi
|
||||
fi
|
||||
done <<< "$ENV_VARS"
|
||||
env_string=$(echo $env_string | sed 's/ $//')
|
||||
scw container container update ${{ env.CONTAINER_ID }} $env_string
|
||||
env:
|
||||
ENV_VARS: ${{ secrets.ENV }}
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
||||
|
74
.github/workflows/stg.yml
vendored
74
.github/workflows/stg.yml
vendored
@ -1,74 +0,0 @@
|
||||
name: Staging - Build & Deploy to Scaleway
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [staging]
|
||||
|
||||
env:
|
||||
PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a
|
||||
NAMESPACE_ID_LECOFFRE: f8137e85-47ad-46a5-9e2e-18af5de829c5
|
||||
CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffrestgbqbfhtv6
|
||||
|
||||
IMAGE_NAME: front
|
||||
CONTAINER_NAME: front
|
||||
|
||||
jobs:
|
||||
build-and-push-image-lecoffre:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Copy SSH
|
||||
run: cp ~/.ssh/id_rsa id_rsa
|
||||
- name: Login to Scaleway Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: nologin
|
||||
password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}
|
||||
- name: Build the Docker Image
|
||||
run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
- name: Push the Docker Image to Scaleway Container Registry
|
||||
run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }}
|
||||
deploy-to-scaleway-lecoffre:
|
||||
needs: build-and-push-image-lecoffre
|
||||
runs-on: ubuntu-latest
|
||||
environment: staging
|
||||
steps:
|
||||
- name: Install CLI
|
||||
uses: scaleway/action-scw@v0
|
||||
- name: Get container ID
|
||||
run: |
|
||||
echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV
|
||||
env:
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
||||
- name: Deploy the container based on the new image
|
||||
run: |
|
||||
env_string=""
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == *"="* ]]; then
|
||||
key=$(echo "$line" | cut -d '=' -f 1)
|
||||
value=$(echo "$line" | cut -d '=' -f 2-)
|
||||
if [[ -n "$key" ]]; then
|
||||
env_string+="environment-variables.$key=$value "
|
||||
fi
|
||||
fi
|
||||
done <<< "$ENV_VARS"
|
||||
env_string=$(echo $env_string | sed 's/ $//')
|
||||
scw container container update ${{ env.CONTAINER_ID }} $env_string
|
||||
env:
|
||||
ENV_VARS: ${{ secrets.ENV }}
|
||||
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }}
|
||||
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }}
|
||||
SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }}
|
||||
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }}
|
119
.gitignore
vendored
119
.gitignore
vendored
@ -1,44 +1,95 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# 4NK Environment - Git Ignore
|
||||
# ============================
|
||||
confs/
|
||||
# Dossiers de sauvegarde des scripts
|
||||
**/backup/
|
||||
**/*backup*
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
dist/
|
||||
**/.cargo/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
# Fichiers temporaires
|
||||
**/*.tmp*
|
||||
**/*.temp*
|
||||
**/*.log*
|
||||
**/*.pid*
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
dist/
|
||||
# Fichiers de configuration locale
|
||||
**/*.env*
|
||||
**/*.conf*
|
||||
**/*.yaml*
|
||||
**/*.yml*
|
||||
**/*.ini*
|
||||
**/*.json*
|
||||
**/*.toml*
|
||||
**/*.lock*
|
||||
|
||||
# production
|
||||
/build
|
||||
# Données et logs
|
||||
**/*.logs*
|
||||
**/*.data
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
# Certificats et clés
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.crt
|
||||
**/*.p12
|
||||
**/*.pfx
|
||||
ssl/
|
||||
certs/
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
# Docker
|
||||
**/*.docker*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
# Cache et build
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
**/target/
|
||||
**/.next/
|
||||
**/.turbo/
|
||||
**/coverage/
|
||||
**/.pytest_cache/
|
||||
**/.cache/
|
||||
**/.pnpm-store/
|
||||
**/.venv/
|
||||
**/vendor/
|
||||
**/*.*.o
|
||||
**/*.so
|
||||
**/*.dylib
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
# IDE et éditeurs
|
||||
**/*.vscode/
|
||||
**/*.idea/
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
node_modules
|
||||
id_rsa
|
||||
# OS
|
||||
**/*.DS_Store
|
||||
**/*Thumbs.db
|
||||
**/*tmp*
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
# 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/
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,7 +1,7 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
@ -32,6 +32,6 @@
|
||||
"rust-client.disableRustup": true,
|
||||
"rust-client.autoStartRls": false,
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
35
CHANGELOG.md
Normal file
35
CHANGELOG.md
Normal file
@ -0,0 +1,35 @@
|
||||
## v0.1.1
|
||||
|
||||
- Ajout d'une image Docker de production "ext" (Next.js `next start`) lisant les URLs d'API via variables d'environnement, évitant toute référence à `localhost`.
|
||||
- Remplacement d'URLs codées en dur dans l'écran de connexion email par l'utilisation de `FrontendVariables` et des `NEXT_PUBLIC_*`.
|
||||
- Documentation `docs/ext.md` ajoutée (build, run, push, variables supportées).
|
||||
|
||||
## v0.1.2
|
||||
|
||||
- LoginCallback (`index.tsx`) ajusté: suppression de la redirection spéciale `dev3.4nkweb.com` au profit d'un flux standard basé sur variables d'environnement.
|
||||
|
||||
## v0.1.3
|
||||
## v0.1.4
|
||||
|
||||
## v0.1.5
|
||||
|
||||
- IdNot: appel backend changé en POST `/api/v1/idnot/auth` avec `{ code }` dans le corps (évite erreurs 502 proxy sur URL longue).
|
||||
- Documentation et tests mis à jour.
|
||||
- Rappel déploiement: builder et pousser l’image tagguée `ext` pour récupération par `lecoffre_node`.
|
||||
|
||||
- Sous-chemin Next activé: `basePath: '/lecoffre'`.
|
||||
- Normalisation SSR du `targetOrigin` de l’iframe sur l’origine de `NEXT_PUBLIC_4NK_URL`.
|
||||
- CI: build-arg optionnel `NEXT_PUBLIC_4NK_URL` depuis les secrets.
|
||||
|
||||
- Optimisations build Docker:
|
||||
- `.dockerignore` pour réduire le contexte.
|
||||
- Next.js `output: 'standalone'` pour une image runtime plus légère.
|
||||
- Caches BuildKit (npm et .next) pour accélérer les builds.
|
||||
- Runtime basé sur `server.js` (standalone) au lieu de `next start`.
|
||||
|
||||
## v0.1.6
|
||||
|
||||
- Mise en place d’un "callback bridge" pour IdNot:
|
||||
- Page bridge côté domaine autorisé (`/authorized-client`) qui POST le `code` vers `/api/v1/idnot/auth` et redirige.
|
||||
- Page front `/lecoffre/authorized-bridge` qui consomme `#token`/`#error`, stocke le cookie et redirige vers le tableau de bord.
|
||||
- Permet le login sans modifier la liste des callbacks autorisés chez IdNot.
|
72
Dockerfile
72
Dockerfile
@ -1,46 +1,60 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:19-alpine AS deps
|
||||
# Dockerfile multi-stage pour la CI - build générique, variables au runtime
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
# Stage de base
|
||||
FROM docker.io/library/debian:bookworm-slim AS base
|
||||
|
||||
COPY package.json ./
|
||||
# Installation des dépendances système
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
RUN apk update && apk add openssh-client git
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
RUN chmod 600 ~/.ssh/id_rsa
|
||||
RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa
|
||||
RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
RUN npm install --frozen-lockfile
|
||||
# Stage de build
|
||||
FROM base AS builder
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:19-alpine AS builder
|
||||
# Copie des fichiers de dépendances
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install --no-audit --no-fund
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
# Copie du code source
|
||||
COPY . .
|
||||
|
||||
COPY --from=deps leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps leCoffre-front/package.json package.json
|
||||
COPY tsconfig.json tsconfig.json
|
||||
COPY next.config.js next.config.js
|
||||
COPY src src
|
||||
# Build sans injection de variables NEXT_PUBLIC_* (évite tout bake-time)
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:19-alpine AS production
|
||||
# Stage de production (stage 'ext' pour la CI)
|
||||
FROM base AS ext
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
# Copie des fichiers buildés depuis le stage builder
|
||||
COPY --from=builder /leCoffre-front/.next ./.next
|
||||
COPY --from=builder /leCoffre-front/public ./public
|
||||
COPY --from=builder /leCoffre-front/package.json ./package.json
|
||||
COPY --from=builder /leCoffre-front/node_modules ./node_modules
|
||||
|
||||
RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
|
||||
# Copie du script de démarrage
|
||||
COPY start-runtime.js ./
|
||||
RUN chmod +x start-runtime.js
|
||||
|
||||
COPY public ./public
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/.next ./.next
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/next.config.js ./next.config.js
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/package.json ./package.json
|
||||
# Configuration runtime
|
||||
EXPOSE 8080
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=8080
|
||||
|
||||
# Utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
EXPOSE 3000
|
||||
# Utiliser le script de démarrage qui injecte les variables au runtime
|
||||
CMD ["node", "start-runtime.js"]
|
||||
|
198
Dockerfile.backup_20250923_233404
Normal file
198
Dockerfile.backup_20250923_233404
Normal file
@ -0,0 +1,198 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
FROM debian:bookworm-slim AS deps
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
|
||||
# Installation des dépendances
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm install --no-audit --no-fund
|
||||
|
||||
# Configuration pour le développement
|
||||
FROM debian:bookworm-slim AS development
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY --from=deps /leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps /leCoffre-front/package.json ./package.json
|
||||
COPY . .
|
||||
|
||||
# Création de l'utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
EXPOSE 3000
|
||||
|
||||
# --- Build de production
|
||||
FROM debian:bookworm-slim AS builder
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY --from=deps /leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps /leCoffre-front/package.json ./package.json
|
||||
COPY . .
|
||||
|
||||
# Arguments/variables d'environnement publics pour le build Next
|
||||
ARG NEXT_PUBLIC_BACK_API_PROTOCOL
|
||||
ARG NEXT_PUBLIC_BACK_API_HOST
|
||||
ARG NEXT_PUBLIC_BACK_API_PORT
|
||||
ARG NEXT_PUBLIC_BACK_API_ROOT_URL
|
||||
ARG NEXT_PUBLIC_BACK_API_VERSION
|
||||
ARG NEXT_PUBLIC_FRONT_APP_HOST
|
||||
ARG NEXT_PUBLIC_FRONT_APP_PORT
|
||||
ARG NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT
|
||||
ARG NEXT_PUBLIC_IDNOT_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IDNOT_BASE_URL
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI
|
||||
ARG NEXT_PUBLIC_DOCAPOSTE_API_URL
|
||||
ARG NEXT_PUBLIC_HOTJAR_SITE_ID
|
||||
ARG NEXT_PUBLIC_HOTJAR_VERSION
|
||||
ARG NEXT_PUBLIC_4NK_URL
|
||||
ARG NEXT_PUBLIC_4NK_IFRAME_URL
|
||||
ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_DEFAULT_VALIDATOR_ID
|
||||
ARG NEXT_PUBLIC_DEFAULT_STORAGE_URLS
|
||||
ARG NEXT_PUBLIC_BACK_BASE
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED
|
||||
|
||||
ENV NEXT_PUBLIC_BACK_API_PROTOCOL=${NEXT_PUBLIC_BACK_API_PROTOCOL} \
|
||||
NEXT_PUBLIC_BACK_API_HOST=${NEXT_PUBLIC_BACK_API_HOST} \
|
||||
NEXT_PUBLIC_BACK_API_PORT=${NEXT_PUBLIC_BACK_API_PORT} \
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL=${NEXT_PUBLIC_BACK_API_ROOT_URL} \
|
||||
NEXT_PUBLIC_BACK_API_VERSION=${NEXT_PUBLIC_BACK_API_VERSION} \
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=${NEXT_PUBLIC_FRONT_APP_HOST} \
|
||||
NEXT_PUBLIC_FRONT_APP_PORT=${NEXT_PUBLIC_FRONT_APP_PORT} \
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=${NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT} \
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=${NEXT_PUBLIC_IDNOT_CLIENT_ID} \
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=${NEXT_PUBLIC_IDNOT_BASE_URL} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI=${NEXT_PUBLIC_IDNOT_REDIRECT_URI} \
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL=${NEXT_PUBLIC_DOCAPOSTE_API_URL} \
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID=${NEXT_PUBLIC_HOTJAR_SITE_ID} \
|
||||
NEXT_PUBLIC_HOTJAR_VERSION=${NEXT_PUBLIC_HOTJAR_VERSION} \
|
||||
NEXT_PUBLIC_4NK_URL=${NEXT_PUBLIC_4NK_URL} \
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL=${NEXT_PUBLIC_4NK_IFRAME_URL} \
|
||||
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} \
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID=${NEXT_PUBLIC_DEFAULT_VALIDATOR_ID} \
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS=${NEXT_PUBLIC_DEFAULT_STORAGE_URLS} \
|
||||
NEXT_PUBLIC_BACK_BASE=${NEXT_PUBLIC_BACK_BASE} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED=${NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED}
|
||||
|
||||
RUN --mount=type=cache,target=/leCoffre-front/.next/cache npm run build
|
||||
|
||||
# --- Image d'exécution "ext"
|
||||
FROM debian:bookworm-slim AS ext
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Re-déclarer les ARG pour l'étape runtime et les exposer en ENV
|
||||
ARG NEXT_PUBLIC_BACK_API_PROTOCOL
|
||||
ARG NEXT_PUBLIC_BACK_API_HOST
|
||||
ARG NEXT_PUBLIC_BACK_API_PORT
|
||||
ARG NEXT_PUBLIC_BACK_API_ROOT_URL
|
||||
ARG NEXT_PUBLIC_BACK_API_VERSION
|
||||
ARG NEXT_PUBLIC_FRONT_APP_HOST
|
||||
ARG NEXT_PUBLIC_FRONT_APP_PORT
|
||||
ARG NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT
|
||||
ARG NEXT_PUBLIC_IDNOT_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IDNOT_BASE_URL
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI
|
||||
ARG NEXT_PUBLIC_DOCAPOSTE_API_URL
|
||||
ARG NEXT_PUBLIC_HOTJAR_SITE_ID
|
||||
ARG NEXT_PUBLIC_HOTJAR_VERSION
|
||||
ARG NEXT_PUBLIC_4NK_URL
|
||||
ARG NEXT_PUBLIC_4NK_IFRAME_URL
|
||||
ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_DEFAULT_VALIDATOR_ID
|
||||
ARG NEXT_PUBLIC_DEFAULT_STORAGE_URLS
|
||||
ARG NEXT_PUBLIC_BACK_BASE
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3000 \
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=${NEXT_PUBLIC_BACK_API_PROTOCOL} \
|
||||
NEXT_PUBLIC_BACK_API_HOST=${NEXT_PUBLIC_BACK_API_HOST} \
|
||||
NEXT_PUBLIC_BACK_API_PORT=${NEXT_PUBLIC_BACK_API_PORT} \
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL=${NEXT_PUBLIC_BACK_API_ROOT_URL} \
|
||||
NEXT_PUBLIC_BACK_API_VERSION=${NEXT_PUBLIC_BACK_API_VERSION} \
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=${NEXT_PUBLIC_FRONT_APP_HOST} \
|
||||
NEXT_PUBLIC_FRONT_APP_PORT=${NEXT_PUBLIC_FRONT_APP_PORT} \
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=${NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT} \
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=${NEXT_PUBLIC_IDNOT_CLIENT_ID} \
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=${NEXT_PUBLIC_IDNOT_BASE_URL} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI=${NEXT_PUBLIC_IDNOT_REDIRECT_URI} \
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL=${NEXT_PUBLIC_DOCAPOSTE_API_URL} \
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID=${NEXT_PUBLIC_HOTJAR_SITE_ID} \
|
||||
NEXT_PUBLIC_HOTJAR_VERSION=${NEXT_PUBLIC_HOTJAR_VERSION} \
|
||||
NEXT_PUBLIC_4NK_URL=${NEXT_PUBLIC_4NK_URL} \
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL=${NEXT_PUBLIC_4NK_IFRAME_URL} \
|
||||
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} \
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID=${NEXT_PUBLIC_DEFAULT_VALIDATOR_ID} \
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS=${NEXT_PUBLIC_DEFAULT_STORAGE_URLS} \
|
||||
NEXT_PUBLIC_BACK_BASE=${NEXT_PUBLIC_BACK_BASE} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED=${NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED}
|
||||
|
||||
# Next.js standalone runtime (output: 'standalone')
|
||||
COPY --from=builder /leCoffre-front/.next/standalone ./
|
||||
COPY --from=builder /leCoffre-front/.next/static ./.next/static
|
||||
COPY --from=builder /leCoffre-front/public ./public
|
||||
|
||||
# Création de l'utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
USER lecoffreuser
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
@ -1,44 +0,0 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:19-alpine AS deps
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
RUN apk update && apk add openssh-client git
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
RUN chmod 600 ~/.ssh/id_rsa
|
||||
RUN eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa
|
||||
RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/known_hosts
|
||||
|
||||
RUN npm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:19-alpine AS builder
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
COPY --from=deps leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps leCoffre-front/package.json package.json
|
||||
COPY tsconfig.json tsconfig.json
|
||||
COPY src src
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:19-alpine AS production
|
||||
|
||||
WORKDIR leCoffre-front
|
||||
|
||||
RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
|
||||
|
||||
COPY public ./public
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/.next ./.next
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/package.json ./package.json
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
EXPOSE 3000
|
198
Dockerfile.original
Normal file
198
Dockerfile.original
Normal file
@ -0,0 +1,198 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
FROM debian:bookworm-slim AS deps
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY package.json ./
|
||||
COPY package-lock.json ./
|
||||
|
||||
# Installation des dépendances
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm install --no-audit --no-fund
|
||||
|
||||
# Configuration pour le développement
|
||||
FROM debian:bookworm-slim AS development
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY --from=deps /leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps /leCoffre-front/package.json ./package.json
|
||||
COPY . .
|
||||
|
||||
# Création de l'utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
EXPOSE 3000
|
||||
|
||||
# --- Build de production
|
||||
FROM debian:bookworm-slim AS builder
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY --from=deps /leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps /leCoffre-front/package.json ./package.json
|
||||
COPY . .
|
||||
|
||||
# Arguments/variables d'environnement publics pour le build Next
|
||||
ARG NEXT_PUBLIC_BACK_API_PROTOCOL
|
||||
ARG NEXT_PUBLIC_BACK_API_HOST
|
||||
ARG NEXT_PUBLIC_BACK_API_PORT
|
||||
ARG NEXT_PUBLIC_BACK_API_ROOT_URL
|
||||
ARG NEXT_PUBLIC_BACK_API_VERSION
|
||||
ARG NEXT_PUBLIC_FRONT_APP_HOST
|
||||
ARG NEXT_PUBLIC_FRONT_APP_PORT
|
||||
ARG NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT
|
||||
ARG NEXT_PUBLIC_IDNOT_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IDNOT_BASE_URL
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI
|
||||
ARG NEXT_PUBLIC_DOCAPOSTE_API_URL
|
||||
ARG NEXT_PUBLIC_HOTJAR_SITE_ID
|
||||
ARG NEXT_PUBLIC_HOTJAR_VERSION
|
||||
ARG NEXT_PUBLIC_4NK_URL
|
||||
ARG NEXT_PUBLIC_4NK_IFRAME_URL
|
||||
ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_DEFAULT_VALIDATOR_ID
|
||||
ARG NEXT_PUBLIC_DEFAULT_STORAGE_URLS
|
||||
ARG NEXT_PUBLIC_BACK_BASE
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED
|
||||
|
||||
ENV NEXT_PUBLIC_BACK_API_PROTOCOL=${NEXT_PUBLIC_BACK_API_PROTOCOL} \
|
||||
NEXT_PUBLIC_BACK_API_HOST=${NEXT_PUBLIC_BACK_API_HOST} \
|
||||
NEXT_PUBLIC_BACK_API_PORT=${NEXT_PUBLIC_BACK_API_PORT} \
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL=${NEXT_PUBLIC_BACK_API_ROOT_URL} \
|
||||
NEXT_PUBLIC_BACK_API_VERSION=${NEXT_PUBLIC_BACK_API_VERSION} \
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=${NEXT_PUBLIC_FRONT_APP_HOST} \
|
||||
NEXT_PUBLIC_FRONT_APP_PORT=${NEXT_PUBLIC_FRONT_APP_PORT} \
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=${NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT} \
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=${NEXT_PUBLIC_IDNOT_CLIENT_ID} \
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=${NEXT_PUBLIC_IDNOT_BASE_URL} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI=${NEXT_PUBLIC_IDNOT_REDIRECT_URI} \
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL=${NEXT_PUBLIC_DOCAPOSTE_API_URL} \
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID=${NEXT_PUBLIC_HOTJAR_SITE_ID} \
|
||||
NEXT_PUBLIC_HOTJAR_VERSION=${NEXT_PUBLIC_HOTJAR_VERSION} \
|
||||
NEXT_PUBLIC_4NK_URL=${NEXT_PUBLIC_4NK_URL} \
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL=${NEXT_PUBLIC_4NK_IFRAME_URL} \
|
||||
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} \
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID=${NEXT_PUBLIC_DEFAULT_VALIDATOR_ID} \
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS=${NEXT_PUBLIC_DEFAULT_STORAGE_URLS} \
|
||||
NEXT_PUBLIC_BACK_BASE=${NEXT_PUBLIC_BACK_BASE} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED=${NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED}
|
||||
|
||||
RUN --mount=type=cache,target=/leCoffre-front/.next/cache npm run build
|
||||
|
||||
# --- Image d'exécution "ext"
|
||||
FROM debian:bookworm-slim AS ext
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Installation des dépendances de base
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Re-déclarer les ARG pour l'étape runtime et les exposer en ENV
|
||||
ARG NEXT_PUBLIC_BACK_API_PROTOCOL
|
||||
ARG NEXT_PUBLIC_BACK_API_HOST
|
||||
ARG NEXT_PUBLIC_BACK_API_PORT
|
||||
ARG NEXT_PUBLIC_BACK_API_ROOT_URL
|
||||
ARG NEXT_PUBLIC_BACK_API_VERSION
|
||||
ARG NEXT_PUBLIC_FRONT_APP_HOST
|
||||
ARG NEXT_PUBLIC_FRONT_APP_PORT
|
||||
ARG NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT
|
||||
ARG NEXT_PUBLIC_IDNOT_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_IDNOT_BASE_URL
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI
|
||||
ARG NEXT_PUBLIC_DOCAPOSTE_API_URL
|
||||
ARG NEXT_PUBLIC_HOTJAR_SITE_ID
|
||||
ARG NEXT_PUBLIC_HOTJAR_VERSION
|
||||
ARG NEXT_PUBLIC_4NK_URL
|
||||
ARG NEXT_PUBLIC_4NK_IFRAME_URL
|
||||
ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_DEFAULT_VALIDATOR_ID
|
||||
ARG NEXT_PUBLIC_DEFAULT_STORAGE_URLS
|
||||
ARG NEXT_PUBLIC_BACK_BASE
|
||||
ARG NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3000 \
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=${NEXT_PUBLIC_BACK_API_PROTOCOL} \
|
||||
NEXT_PUBLIC_BACK_API_HOST=${NEXT_PUBLIC_BACK_API_HOST} \
|
||||
NEXT_PUBLIC_BACK_API_PORT=${NEXT_PUBLIC_BACK_API_PORT} \
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL=${NEXT_PUBLIC_BACK_API_ROOT_URL} \
|
||||
NEXT_PUBLIC_BACK_API_VERSION=${NEXT_PUBLIC_BACK_API_VERSION} \
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=${NEXT_PUBLIC_FRONT_APP_HOST} \
|
||||
NEXT_PUBLIC_FRONT_APP_PORT=${NEXT_PUBLIC_FRONT_APP_PORT} \
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=${NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT} \
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=${NEXT_PUBLIC_IDNOT_CLIENT_ID} \
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=${NEXT_PUBLIC_IDNOT_BASE_URL} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI=${NEXT_PUBLIC_IDNOT_REDIRECT_URI} \
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL=${NEXT_PUBLIC_DOCAPOSTE_API_URL} \
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID=${NEXT_PUBLIC_HOTJAR_SITE_ID} \
|
||||
NEXT_PUBLIC_HOTJAR_VERSION=${NEXT_PUBLIC_HOTJAR_VERSION} \
|
||||
NEXT_PUBLIC_4NK_URL=${NEXT_PUBLIC_4NK_URL} \
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL=${NEXT_PUBLIC_4NK_IFRAME_URL} \
|
||||
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} \
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID=${NEXT_PUBLIC_DEFAULT_VALIDATOR_ID} \
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS=${NEXT_PUBLIC_DEFAULT_STORAGE_URLS} \
|
||||
NEXT_PUBLIC_BACK_BASE=${NEXT_PUBLIC_BACK_BASE} \
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED=${NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED}
|
||||
|
||||
# Next.js standalone runtime (output: 'standalone')
|
||||
COPY --from=builder /leCoffre-front/.next/standalone ./
|
||||
COPY --from=builder /leCoffre-front/.next/static ./.next/static
|
||||
COPY --from=builder /leCoffre-front/public ./public
|
||||
|
||||
# Création de l'utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
USER lecoffreuser
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
41
Dockerfile.runtime
Normal file
41
Dockerfile.runtime
Normal file
@ -0,0 +1,41 @@
|
||||
# Dockerfile optimisé pour la CI - variables injectées au runtime
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
# Installation des dépendances système
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Copie des fichiers de dépendances
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install --no-audit --no-fund
|
||||
|
||||
# Copie du code source
|
||||
COPY . .
|
||||
|
||||
# Build avec des variables d'environnement par défaut (surchargées au runtime)
|
||||
# Build runtime: pas d'injection NEXT_PUBLIC_* au build (évite tout bake-time)
|
||||
RUN npm run build
|
||||
|
||||
# Configuration runtime
|
||||
EXPOSE 8080
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=8080
|
||||
|
||||
# Utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
CMD ["node", "server.js"]
|
47
Dockerfile.simple
Normal file
47
Dockerfile.simple
Normal file
@ -0,0 +1,47 @@
|
||||
# Dockerfile optimisé pour la CI - build générique, variables au runtime
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
# Installation des dépendances système
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -y --fix-missing \
|
||||
ca-certificates curl jq git \
|
||||
net-tools iputils-ping dnsutils \
|
||||
netcat-openbsd telnet procps && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Installation de Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /leCoffre-front
|
||||
|
||||
# Copie des fichiers de dépendances
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install --no-audit --no-fund
|
||||
|
||||
# Copie du code source
|
||||
COPY . .
|
||||
|
||||
# Build avec des variables génériques (surchargées au runtime)
|
||||
# Pas d'ENV NEXT_PUBLIC_* au build pour éviter le bake-time
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Configuration runtime
|
||||
EXPOSE 8080
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=8080
|
||||
|
||||
# Copie du script de démarrage et permissions
|
||||
COPY start-runtime.js ./
|
||||
RUN chmod +x start-runtime.js
|
||||
|
||||
# Utilisateur non-root
|
||||
RUN useradd -m -u 1000 lecoffreuser && \
|
||||
mkdir -p /leCoffre-front && chown -R lecoffreuser:lecoffreuser /leCoffre-front
|
||||
|
||||
USER lecoffreuser
|
||||
|
||||
# Utiliser le script de démarrage qui injecte les variables au runtime
|
||||
CMD ["node", "start-runtime.js"]
|
13
README.md
13
README.md
@ -36,3 +36,16 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
### Documentation centralisée
|
||||
- Les documents de ce projet sont centralisés dans: `/home/debian/4NK_env/docs/lecoffre-front/`
|
||||
|
||||
## 📋 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)
|
||||
|
7
lecoffre-front.code-workspace
Normal file
7
lecoffre-front.code-workspace
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
1
middleware.ts
Normal file
1
middleware.ts
Normal file
@ -0,0 +1 @@
|
||||
// Disabled: trailing slash handled by Nginx
|
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
@ -1,11 +1,29 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
...(isProd
|
||||
? {
|
||||
output: 'export',
|
||||
basePath: '/lecoffre',
|
||||
assetPrefix: '/lecoffre',
|
||||
trailingSlash: true,
|
||||
images: { unoptimized: true },
|
||||
}
|
||||
: {}),
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
// Will be available on both server and client
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
NEXT_PUBLIC_BACK_API_HOST: process.env.NEXT_PUBLIC_BACK_API_HOST,
|
||||
NEXT_PUBLIC_BACK_API_PORT: process.env.NEXT_PUBLIC_BACK_API_PORT,
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: process.env.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
NEXT_PUBLIC_BACK_API_VERSION: process.env.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: process.env.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
@ -13,14 +31,21 @@ const nextConfig = {
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: process.env.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: process.env.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED,
|
||||
NEXT_PUBLIC_BACK_BASE: process.env.NEXT_PUBLIC_BACK_BASE,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
|
||||
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
|
||||
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL: process.env.NEXT_PUBLIC_4NK_IFRAME_URL,
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID: process.env.NEXT_PUBLIC_DEFAULT_VALIDATOR_ID,
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS: process.env.NEXT_PUBLIC_DEFAULT_STORAGE_URLS,
|
||||
},
|
||||
|
||||
serverRuntimeConfig: {
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
NEXT_PUBLIC_BACK_API_HOST: process.env.NEXT_PUBLIC_BACK_API_HOST,
|
||||
NEXT_PUBLIC_BACK_API_PORT: process.env.NEXT_PUBLIC_BACK_API_PORT,
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: process.env.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
NEXT_PUBLIC_BACK_API_VERSION: process.env.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: process.env.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
@ -28,14 +53,21 @@ const nextConfig = {
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: process.env.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: process.env.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED,
|
||||
NEXT_PUBLIC_BACK_BASE: process.env.NEXT_PUBLIC_BACK_BASE,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
|
||||
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
|
||||
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL: process.env.NEXT_PUBLIC_4NK_IFRAME_URL,
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID: process.env.NEXT_PUBLIC_DEFAULT_VALIDATOR_ID,
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS: process.env.NEXT_PUBLIC_DEFAULT_STORAGE_URLS,
|
||||
},
|
||||
|
||||
env: {
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
NEXT_PUBLIC_BACK_API_HOST: process.env.NEXT_PUBLIC_BACK_API_HOST,
|
||||
NEXT_PUBLIC_BACK_API_PORT: process.env.NEXT_PUBLIC_BACK_API_PORT,
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: process.env.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
NEXT_PUBLIC_BACK_API_VERSION: process.env.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: process.env.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
@ -43,9 +75,15 @@ const nextConfig = {
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: process.env.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: process.env.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI,
|
||||
NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED: process.env.NEXT_PUBLIC_IDNOT_REDIRECT_URI_FIXED,
|
||||
NEXT_PUBLIC_BACK_BASE: process.env.NEXT_PUBLIC_BACK_BASE,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
NEXT_PUBLIC_HOTJAR_SITE_ID: process.env.NEXT_PUBLIC_HOTJAR_SITE_ID,
|
||||
NEXT_PUBLIC_HOTJAR_VERSION: process.env.NEXT_PUBLIC_HOTJAR_VERSION,
|
||||
NEXT_PUBLIC_4NK_URL: process.env.NEXT_PUBLIC_4NK_URL,
|
||||
NEXT_PUBLIC_4NK_IFRAME_URL: process.env.NEXT_PUBLIC_4NK_IFRAME_URL,
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
|
||||
NEXT_PUBLIC_DEFAULT_VALIDATOR_ID: process.env.NEXT_PUBLIC_DEFAULT_VALIDATOR_ID,
|
||||
NEXT_PUBLIC_DEFAULT_STORAGE_URLS: process.env.NEXT_PUBLIC_DEFAULT_STORAGE_URLS,
|
||||
},
|
||||
|
||||
// webpack: config => {
|
||||
|
4077
package-lock.json
generated
4077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "lecoffre-front",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build": "NEXT_TELEMETRY_DISABLED=1 next build --no-lint",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write src"
|
||||
@ -30,13 +30,14 @@
|
||||
"heroicons": "^2.1.5",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.167",
|
||||
"le-coffre-resources": "git+https://git.4nkweb.com/4nk/lecoffre-ressources.git#v2.167",
|
||||
"next": "^14.2.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"prettier": "^2.8.7",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-hotjar": "^6.3.1",
|
||||
|
||||
"react-select": "^5.7.2",
|
||||
"react-toastify": "^9.1.3",
|
||||
"sass": "^1.59.2",
|
||||
|
63
src/common/Api/LeCoffreApi/sdk/AbstractService.ts
Normal file
63
src/common/Api/LeCoffreApi/sdk/AbstractService.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import MessageBus from 'src/sdk/MessageBus';
|
||||
|
||||
export default abstract class AbstractService {
|
||||
|
||||
protected static readonly messageBus: MessageBus = MessageBus.getInstance();
|
||||
|
||||
private static readonly CACHE_TTL = 60 * 60 * 1000; // 60 minutes cache TTL
|
||||
|
||||
protected constructor() { }
|
||||
|
||||
protected static setItem(key: string, processId: string, process: any): void {
|
||||
const cache: Record<string, {process: any, timestamp: number}> =
|
||||
JSON.parse(sessionStorage.getItem(key) || '{}');
|
||||
|
||||
cache[processId] = {
|
||||
process: process, // we overwrite existing process
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
sessionStorage.setItem(key, JSON.stringify(cache));
|
||||
}
|
||||
|
||||
protected static getItem(key: string, processId: string): any | null {
|
||||
const cache: Record<string, {process: any, timestamp: number}> =
|
||||
JSON.parse(sessionStorage.getItem(key) || '{}');
|
||||
|
||||
const item = cache[processId];
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now: number = Date.now();
|
||||
if ((now - item.timestamp) < this.CACHE_TTL) {
|
||||
return { process: item.process, timestamp: item.timestamp };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static getItems(key: string): Record<string, any> {
|
||||
const cache: Record<string, {process: any, timestamp: number}> =
|
||||
JSON.parse(sessionStorage.getItem(key) || '{}');
|
||||
const now: number = Date.now();
|
||||
|
||||
const validItems: Record<string, any> = {};
|
||||
for (const [processId, item] of Object.entries(cache)) {
|
||||
if (now - item.timestamp < this.CACHE_TTL) {
|
||||
validItems[processId] = item.process;
|
||||
}
|
||||
}
|
||||
|
||||
return validItems;
|
||||
}
|
||||
|
||||
protected static removeItem(key: string, processId: string): void {
|
||||
const cache: Record<string, {process: any, timestamp: number}> =
|
||||
JSON.parse(sessionStorage.getItem(key) || '{}');
|
||||
|
||||
delete cache[processId];
|
||||
|
||||
sessionStorage.setItem(key, JSON.stringify(cache));
|
||||
}
|
||||
}
|
191
src/common/Api/LeCoffreApi/sdk/CollaboratorService.ts
Normal file
191
src/common/Api/LeCoffreApi/sdk/CollaboratorService.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class CollaboratorService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static async createCollaborator(collaboratorData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'collaborator',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...collaboratorData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
const allFields: string[] = [...privateFields, 'roles'];
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
|
||||
await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
|
||||
return { processId: processCreated.processId, processData: finalProcessData };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static getCollaborators(callback: (processes: Record<string, any>) => void): void {
|
||||
const items: Record<string, any> = this.getItems('_collaborators_');
|
||||
if (Object.keys(items).length > 0) {
|
||||
setTimeout(() => callback(items), 0);
|
||||
}
|
||||
|
||||
this.messageBus.getProcessesDecoded((processId: string, data: any) =>
|
||||
data['utype'] &&
|
||||
data['utype'] === 'collaborator' &&
|
||||
data['isDeleted'] &&
|
||||
data['isDeleted'] === 'false' &&
|
||||
!Object.keys(items).includes(processId)
|
||||
).then(async (processesData: Record<string, any>) => {
|
||||
if (Object.keys(processesData).length === 0) {
|
||||
// If no new processes and no cached items, return empty array
|
||||
if (Object.keys(items).length === 0) {
|
||||
callback([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Single callback with all completed items
|
||||
callback(items);
|
||||
}).catch(error => {
|
||||
console.error('Failed to fetch collaborators:', error);
|
||||
// Return cached data if available, otherwise empty array
|
||||
callback(items);
|
||||
});
|
||||
}
|
||||
|
||||
public static getCollaboratorsBy(whereClause: { [path: string]: any }): Promise<Record<string, any>> {
|
||||
return new Promise<Record<string, any>>((resolve: (collaborators: Record<string, any>) => void) => {
|
||||
this.getCollaborators((processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
resolve({});
|
||||
} else {
|
||||
const filteredEntries = Object.entries(processes).filter(([processId, process]) => {
|
||||
for (const path in whereClause) {
|
||||
const paths: string[] = path.split('.');
|
||||
|
||||
let value: any = process;
|
||||
value['processId'] = processId;
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const currentPath = paths[i];
|
||||
if (!currentPath || value === undefined || value === null) {
|
||||
break;
|
||||
}
|
||||
value = value[currentPath];
|
||||
}
|
||||
|
||||
if (value !== whereClause[path]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Convert filtered entries back to a Record
|
||||
const filteredProcesses: Record<string, any> = {};
|
||||
filteredEntries.forEach(([processId, process]) => {
|
||||
filteredProcesses[processId] = process;
|
||||
});
|
||||
|
||||
resolve(filteredProcesses);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static getCollaboratorBy(whereClause: { [path: string]: any }): Promise<any | null> {
|
||||
return new Promise<any | null>((resolve: (collaborator: any | null) => void) => {
|
||||
this.getCollaborators((processes: Record<string, any>) => {
|
||||
const processArray = Object.values(processes);
|
||||
if (processArray.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
resolve(processArray.find((process: any) => {
|
||||
const collaborator: any = process.processData;
|
||||
|
||||
for (const path in whereClause) {
|
||||
const paths: string[] = path.split('.');
|
||||
|
||||
let value: any = collaborator;
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const currentPath = paths[i];
|
||||
if (!currentPath || value === undefined || value === null) {
|
||||
break;
|
||||
}
|
||||
value = value[currentPath];
|
||||
}
|
||||
|
||||
if (value !== whereClause[path]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static async updateCollaborator(processId: string, newData: any): Promise<void> {
|
||||
try {
|
||||
const processUpdated = await this.messageBus.updateProcess(
|
||||
processId,
|
||||
{ updated_at: new Date().toISOString(), ...newData },
|
||||
[],
|
||||
null
|
||||
);
|
||||
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
await this.messageBus.notifyUpdate(processId, newStateId);
|
||||
await this.messageBus.validateState(processId, newStateId);
|
||||
|
||||
const processData = await this.messageBus.getProcessData(processId);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_collaborators_', processId, processData);
|
||||
} catch (error) {
|
||||
console.error('Failed to update collaborator:', error);
|
||||
throw error; // Re-throw to allow caller to handle
|
||||
}
|
||||
}
|
||||
}
|
111
src/common/Api/LeCoffreApi/sdk/CustomerService.ts
Normal file
111
src/common/Api/LeCoffreApi/sdk/CustomerService.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class CustomerService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static async createCustomer(customerData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'customer',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...customerData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
const allFields: string[] = [...privateFields, 'roles'];
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1,
|
||||
fields: allFields,
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
|
||||
await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
|
||||
|
||||
return { processId: processCreated.processId, processData: finalProcessData[processCreated.processId] };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static getCustomers(callback: (processes: Record<string, any>) => void): void {
|
||||
// Check if we have valid cache
|
||||
const items: Record<string, any> = this.getItems('_customers_');
|
||||
if (Object.keys(items).length > 0) {
|
||||
setTimeout(() => callback(items), 0);
|
||||
}
|
||||
|
||||
this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
|
||||
return values['utype']
|
||||
&& values['utype'] === 'customer'
|
||||
&& values['isDeleted']
|
||||
&& values['isDeleted'] === 'false';
|
||||
}).then(async (processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
callback(items);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedItems: Record<string, any> = { ...items };
|
||||
|
||||
for (const [processId, process] of Object.entries(processes)) {
|
||||
// Update cache
|
||||
this.setItem('_customers_', processId, process);
|
||||
|
||||
updatedItems[processId] = process;
|
||||
}
|
||||
|
||||
callback(updatedItems);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateCustomer(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const customerUid: string = process.processData.uid;
|
||||
this.removeItem('_customers_', customerUid);
|
||||
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
77
src/common/Api/LeCoffreApi/sdk/DatabaseService.ts
Normal file
77
src/common/Api/LeCoffreApi/sdk/DatabaseService.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import getConfig from 'next/config';
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
export default class DatabaseService {
|
||||
|
||||
// Empêcher l'instanciation de cette classe utilitaire
|
||||
private constructor() { }
|
||||
|
||||
/**
|
||||
* Récupère les données d'une table avec pagination
|
||||
* @param tableName Nom de la table à consulter
|
||||
* @param page Numéro de page (commence à 1)
|
||||
* @param limit Nombre d'éléments par page
|
||||
* @returns Données de la table avec pagination
|
||||
*/
|
||||
public static async getTableData(tableName: string, page: number = 1, limit: number = 10): Promise<any> {
|
||||
// Vérification des paramètres
|
||||
if (!tableName) {
|
||||
throw new Error('Le nom de la table est requis');
|
||||
}
|
||||
|
||||
// Validation du nom de la table (par sécurité)
|
||||
const tableNameRegex = /^[a-zA-Z0-9_]+$/;
|
||||
if (!tableNameRegex.test(tableName)) {
|
||||
throw new Error('Nom de table invalide');
|
||||
}
|
||||
|
||||
try {
|
||||
// Construction de l'URL avec paramètres de pagination
|
||||
const baseUrl = DatabaseService.buildBaseUrl();
|
||||
const url = `${baseUrl}/db/${tableName}?page=${page}&limit=${limit}`;
|
||||
|
||||
// Appel à l'API REST
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || 'Erreur lors de la récupération des données');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de l\'accès à la base de données:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit l'URL de base en utilisant les variables d'environnement
|
||||
* @returns URL de base de l'API
|
||||
*/
|
||||
private static buildBaseUrl(): string {
|
||||
// Vérification des variables d'environnement requises
|
||||
const requiredVars = [
|
||||
'NEXT_PUBLIC_BACK_API_PROTOCOL',
|
||||
'NEXT_PUBLIC_BACK_API_HOST',
|
||||
'NEXT_PUBLIC_BACK_API_PORT',
|
||||
'NEXT_PUBLIC_BACK_API_ROOT_URL',
|
||||
'NEXT_PUBLIC_BACK_API_VERSION'
|
||||
];
|
||||
|
||||
for (const varName of requiredVars) {
|
||||
if (!publicRuntimeConfig[varName]) {
|
||||
throw new Error(`${varName} is not defined in environment variables`);
|
||||
}
|
||||
}
|
||||
|
||||
// Construction de l'URL de base
|
||||
return `${publicRuntimeConfig.NEXT_PUBLIC_BACK_API_PROTOCOL}${publicRuntimeConfig.NEXT_PUBLIC_BACK_API_HOST}:${publicRuntimeConfig.NEXT_PUBLIC_BACK_API_PORT}${publicRuntimeConfig.NEXT_PUBLIC_BACK_API_ROOT_URL}${publicRuntimeConfig.NEXT_PUBLIC_BACK_API_VERSION}`;
|
||||
}
|
||||
}
|
123
src/common/Api/LeCoffreApi/sdk/DeedTypeService.ts
Normal file
123
src/common/Api/LeCoffreApi/sdk/DeedTypeService.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class DeedTypeService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static async createDeedType(deedTypeData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'deedType',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
document_types: [],
|
||||
...deedTypeData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
const allFields: string[] = [...privateFields, 'roles'];
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
|
||||
await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
|
||||
|
||||
return { processId: processCreated.processId, processData: finalProcessData };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static getDeedTypes(callback: (processes: Record<string, any>) => void): void {
|
||||
// Check if we have valid cache
|
||||
const items: Record<string, any> = this.getItems('_deed_types_');
|
||||
if (Object.keys(items).length > 0) {
|
||||
setTimeout(() => callback(items), 0);
|
||||
}
|
||||
|
||||
this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
|
||||
return values['utype']
|
||||
&& values['utype'] === 'deedType'
|
||||
&& values['isDeleted']
|
||||
&& values['isDeleted'] === 'false';
|
||||
}).then(async (processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
callback(items);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[DeedTypeService/getDeedTypes] processes', processes);
|
||||
|
||||
const updatedItems: Record<string, any> = { ...items };
|
||||
|
||||
for (const [processId, process] of Object.entries(processes)) {
|
||||
// Update cache
|
||||
this.setItem('_deed_types_', processId, process);
|
||||
|
||||
updatedItems[processId] = process;
|
||||
}
|
||||
|
||||
callback(updatedItems);
|
||||
});
|
||||
}
|
||||
|
||||
public static async updateDeedType(processId: string, newData: any): Promise<void> {
|
||||
try {
|
||||
const processUpdated = await this.messageBus.updateProcess(
|
||||
processId,
|
||||
{ updated_at: new Date().toISOString(), ...newData },
|
||||
[],
|
||||
null
|
||||
);
|
||||
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
await this.messageBus.notifyUpdate(processId, newStateId);
|
||||
await this.messageBus.validateState(processId, newStateId);
|
||||
|
||||
const processData = await this.messageBus.getProcessData(processId);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_deed_types_', processId, processData);
|
||||
} catch (error) {
|
||||
console.error('Failed to update deed type:', error);
|
||||
throw error; // Re-throw to allow caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
}
|
152
src/common/Api/LeCoffreApi/sdk/DocumentService.ts
Normal file
152
src/common/Api/LeCoffreApi/sdk/DocumentService.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
export default class DocumentService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createDocument(documentData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'document',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...documentData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [...[ownerId], validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...privateFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getDocumentByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getDocuments(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_documents_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'document' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (const process of processes) {
|
||||
// Update cache
|
||||
this.setItem('_documents_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getDocumentByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_documents_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'document' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
const process: any = processes[0];
|
||||
|
||||
// Update cache
|
||||
this.setItem('_documents_', process);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateDocument(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const documentUid: string = process.processData.uid;
|
||||
this.removeItem('_documents_', documentUid);
|
||||
|
||||
this.getDocumentByUid(documentUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
117
src/common/Api/LeCoffreApi/sdk/DocumentTypeService.ts
Normal file
117
src/common/Api/LeCoffreApi/sdk/DocumentTypeService.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class DocumentTypeService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createDocumentType(documentTypeData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'documentType',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...documentTypeData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
const allFields: string[] = [...privateFields, 'roles'];
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.messageBus.getProcessData(processCreated.processId).then((processData: any) => {
|
||||
resolve({ processId: processCreated.processId, processData: processData });
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getDocumentTypes(callback: (processes: Record<string, any>) => void): void {
|
||||
// Check if we have valid cache
|
||||
const items: Record<string, any> = this.getItems('_document_types_');
|
||||
if (Object.keys(items).length > 0) {
|
||||
setTimeout(() => callback(items), 0);
|
||||
}
|
||||
|
||||
this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
|
||||
return values['utype'] === 'documentType'
|
||||
&& values['isDeleted'] === 'false';
|
||||
}).then(async (processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
callback(items);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedItems: Record<string, any> = { ...items };
|
||||
|
||||
for (const [processId, process] of Object.entries(processes)) {
|
||||
// Update cache
|
||||
this.setItem('_document_types_', processId, process);
|
||||
|
||||
updatedItems[processId] = process;
|
||||
}
|
||||
|
||||
callback(updatedItems);
|
||||
});
|
||||
}
|
||||
|
||||
public static async updateDocumentType(processId: string, newData: any): Promise<void> {
|
||||
try {
|
||||
const processUpdated = await this.messageBus.updateProcess(
|
||||
processId,
|
||||
{ updated_at: new Date().toISOString(), ...newData },
|
||||
[],
|
||||
null
|
||||
);
|
||||
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
await this.messageBus.notifyUpdate(processId, newStateId);
|
||||
await this.messageBus.validateState(processId, newStateId);
|
||||
|
||||
const processData = await this.messageBus.getProcessData(processId);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_document_types_', processId, processData);
|
||||
} catch (error) {
|
||||
console.error('Failed to update deed type:', error);
|
||||
throw error; // Re-throw to allow caller to handle
|
||||
}
|
||||
}
|
||||
}
|
98
src/common/Api/LeCoffreApi/sdk/FileService.ts
Normal file
98
src/common/Api/LeCoffreApi/sdk/FileService.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import MessageBus from 'src/sdk/MessageBus';
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import { FileData } from '../../../../front/Api/Entities/types';
|
||||
|
||||
export default class FileService {
|
||||
|
||||
private static readonly messageBus: MessageBus = MessageBus.getInstance();
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static createFile(fileData: FileData, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'file',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...fileData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [...[ownerId], validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...privateFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
resolve(processCreated);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getFileByUid(uid: string): Promise<any> {
|
||||
return this.messageBus.getFileByUid(uid);
|
||||
}
|
||||
|
||||
public static updateFile(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
180
src/common/Api/LeCoffreApi/sdk/FolderService.ts
Normal file
180
src/common/Api/LeCoffreApi/sdk/FolderService.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
import { DEFAULT_STORAGE_URLS, DEFAULT_VALIDATOR_ID } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class FolderService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static async createFolder(folderData: any, customersId: string[]): Promise<{ processId: string, processData: any }> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'folder',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...folderData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
const allFields: string[] = [...privateFields, 'roles'];
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owners: {
|
||||
members: [ownerId, DEFAULT_VALIDATOR_ID],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
guests: {
|
||||
members: [],
|
||||
validation_rules: [{
|
||||
quorum: 0.01,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.01,
|
||||
}],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
customers: {
|
||||
members: customersId,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: allFields,
|
||||
min_sig_member: 0.0,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, DEFAULT_VALIDATOR_ID],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
|
||||
await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
|
||||
const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
|
||||
|
||||
return { processId: processCreated.processId, processData: finalProcessData };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static getFolders(callback: (processes: Record<string, any>) => void): void {
|
||||
// Check if we have valid cache
|
||||
const items: Record<string, any> = this.getItems('_folders_');
|
||||
if (Object.keys(items).length > 0) {
|
||||
setTimeout(() => callback(items), 0);
|
||||
}
|
||||
|
||||
this.messageBus.getProcessesDecoded((_processId: string, values: any) => {
|
||||
return values['utype']
|
||||
&& values['utype'] === 'folder'
|
||||
&& values['isDeleted']
|
||||
&& values['isDeleted'] === 'false';
|
||||
}).then(async (processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
callback(items);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[FolderService/getFolders] processes', processes);
|
||||
|
||||
const updatedItems: Record<string, any> = { ...items };
|
||||
|
||||
for (const [processId, process] of Object.entries(processes)) {
|
||||
// Update cache
|
||||
this.setItem('_folders_', processId, process);
|
||||
|
||||
updatedItems[processId] = process;
|
||||
}
|
||||
|
||||
callback(updatedItems);
|
||||
});
|
||||
}
|
||||
|
||||
public static getFoldersBy(whereClause: { [path: string]: any }): Promise<Record<string, any>> {
|
||||
return new Promise<Record<string, any>>((resolve: (folders: Record<string, any>) => void) => {
|
||||
this.getFolders((processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length === 0) {
|
||||
resolve({});
|
||||
} else {
|
||||
const filteredEntries = Object.entries(processes).filter(([processId, process]) => {
|
||||
for (const path in whereClause) {
|
||||
const paths: string[] = path.split('.');
|
||||
|
||||
let value: any = process;
|
||||
value['processId'] = processId;
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const currentPath = paths[i];
|
||||
if (!currentPath || value === undefined || value === null) {
|
||||
break;
|
||||
}
|
||||
value = value[currentPath];
|
||||
}
|
||||
|
||||
if (value !== whereClause[path]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Convert filtered entries back to a Record
|
||||
const filteredProcesses: Record<string, any> = {};
|
||||
filteredEntries.forEach(([processId, process]) => {
|
||||
filteredProcesses[processId] = process;
|
||||
});
|
||||
|
||||
resolve(filteredProcesses);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static async updateFolder(processId: string, newData: any): Promise<void> {
|
||||
try {
|
||||
const processUpdated = await this.messageBus.updateProcess(
|
||||
processId,
|
||||
{ updated_at: new Date().toISOString(), ...newData },
|
||||
[],
|
||||
null
|
||||
);
|
||||
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
await this.messageBus.notifyUpdate(processId, newStateId);
|
||||
await this.messageBus.validateState(processId, newStateId);
|
||||
|
||||
const processData = await this.messageBus.getProcessData(processId);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_folders_', processId, processData);
|
||||
} catch (error) {
|
||||
console.error('Failed to update folder:', error);
|
||||
throw error; // Re-throw to allow caller to handle
|
||||
}
|
||||
}
|
||||
}
|
509
src/common/Api/LeCoffreApi/sdk/ImportData.ts
Normal file
509
src/common/Api/LeCoffreApi/sdk/ImportData.ts
Normal file
@ -0,0 +1,509 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
import MessageBus from 'src/sdk/MessageBus';
|
||||
|
||||
import DatabaseService from './DatabaseService';
|
||||
import RuleService from './RuleService';
|
||||
import RuleGroupService from './RuleGroupService';
|
||||
import RoleService from './RoleService';
|
||||
import OfficeRoleService from './OfficeRoleService';
|
||||
import { DEFAULT_VALIDATOR_ID } from '@Front/Config/AppConstants';
|
||||
|
||||
const mandatoryRoles = ['Notaire', 'Collaborateur'];
|
||||
|
||||
/**
|
||||
* Type pour le callback de progression
|
||||
*/
|
||||
export interface ProgressInfo {
|
||||
/** Progression globale (0-100) */
|
||||
globalProgress: number;
|
||||
/** Nom de l'étape en cours */
|
||||
currentStep: string;
|
||||
/** Progression de l'étape en cours (0-100) */
|
||||
stepProgress: number;
|
||||
/** Description optionnelle de l'action en cours */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export default class ImportData {
|
||||
|
||||
protected static readonly messageBus: MessageBus = MessageBus.getInstance();
|
||||
|
||||
public static async import(office: any, validatorId: string, onProgress?: (info: ProgressInfo) => void): Promise<void> {
|
||||
// Définir les étapes d'importation dynamiquement
|
||||
const importSteps = [
|
||||
{
|
||||
name: 'Règles',
|
||||
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
|
||||
await this.importRules(progressCallback)
|
||||
},
|
||||
{
|
||||
name: 'Groupes de règles',
|
||||
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
|
||||
await this.importRuleGroups(progressCallback)
|
||||
},
|
||||
{
|
||||
name: 'Rôles',
|
||||
function: async (progressCallback?: (subProgress: number, description?: string) => void) =>
|
||||
await this.importRoles(progressCallback)
|
||||
},
|
||||
{
|
||||
name: 'Rôles d\'office',
|
||||
function: async (progressCallback?: (subProgress: number, description?: string) => void, prevResults?: any[]) =>
|
||||
await this.importOfficeRoles(office, prevResults![1], progressCallback)
|
||||
}
|
||||
];
|
||||
|
||||
// Calculer la part de progression pour chaque étape
|
||||
const totalSteps = importSteps.length;
|
||||
const stepWeight = 100 / totalSteps;
|
||||
|
||||
// Appel du callback avec 0% au début
|
||||
onProgress?.({
|
||||
globalProgress: 0,
|
||||
currentStep: 'Initialisation',
|
||||
stepProgress: 0,
|
||||
description: 'Début de l\'importation des données'
|
||||
});
|
||||
|
||||
// Exécuter chaque étape d'importation séquentiellement
|
||||
const results: any[] = [];
|
||||
|
||||
for (let i = 0; i < importSteps.length; i++) {
|
||||
const step = importSteps[i];
|
||||
if (!step) continue; // S'assurer que l'étape existe
|
||||
|
||||
const startProgress = i * stepWeight;
|
||||
|
||||
// Créer un callback de progression pour cette étape
|
||||
const stepProgressCallback = (subProgress: number, description?: string) => {
|
||||
onProgress?.({
|
||||
globalProgress: startProgress + (subProgress * stepWeight / 100),
|
||||
currentStep: step.name,
|
||||
stepProgress: subProgress,
|
||||
description
|
||||
});
|
||||
};
|
||||
|
||||
// Exécuter l'étape et stocker le résultat si nécessaire
|
||||
const result = await step.function(stepProgressCallback, results);
|
||||
if (result !== undefined) {
|
||||
results.push(result);
|
||||
} else {
|
||||
// Push empty array to maintain consistent indexing
|
||||
results.push([]);
|
||||
}
|
||||
console.log(`Step ${i} (${step.name}): ${result?.length || 0} items`);
|
||||
}
|
||||
|
||||
if (!await this.isDone()) {
|
||||
await this.done(validatorId);
|
||||
}
|
||||
}
|
||||
|
||||
public static async isDone(): Promise<boolean> {
|
||||
return await this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'importData' &&
|
||||
publicValues['isDeleted'] && publicValues['isDeleted'] === 'false'
|
||||
).then(async (processes: any[]) => processes.length > 0);
|
||||
}
|
||||
|
||||
private static async done(validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'importData',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...privateFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
private static async importRules(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
|
||||
console.log('Importing rules');
|
||||
const rules: any[] = [];
|
||||
|
||||
const INIT_PROGRESS = 0;
|
||||
const FETCH_PROGRESS = 30;
|
||||
const CREATE_END_PROGRESS = 90;
|
||||
const FINAL_PROGRESS = 100;
|
||||
onProgress?.(INIT_PROGRESS, 'Initialisation');
|
||||
|
||||
let page = 1;
|
||||
let limit = 10;
|
||||
let totalPages = 1;
|
||||
|
||||
onProgress?.(FETCH_PROGRESS, 'Récupération des règles existantes');
|
||||
let result = await DatabaseService.getTableData('rules', page, limit);
|
||||
if (result && result.success && result.pagination) {
|
||||
totalPages = result.pagination.totalPages || 1;
|
||||
}
|
||||
|
||||
const FETCH_PAGE_PROGRESS_START = FETCH_PROGRESS;
|
||||
const FETCH_PAGE_PROGRESS_END = 60;
|
||||
const CREATE_START_PROGRESS = 60;
|
||||
|
||||
while (result && result.success) {
|
||||
const fetchPageProgress = FETCH_PAGE_PROGRESS_START + ((page / totalPages) * (FETCH_PAGE_PROGRESS_END - FETCH_PAGE_PROGRESS_START));
|
||||
|
||||
onProgress?.(fetchPageProgress, `Page ${page}/${totalPages} : Récupération des règles`);
|
||||
const existingRules: any[] = (await RuleService.getRules()).map((process: any) => process.processData);
|
||||
const filteredRules: any[] = result.data.filter((rule: any) => !existingRules.some((existingRule: any) => existingRule.uid === rule.uid));
|
||||
|
||||
if (filteredRules.length === 0) {
|
||||
console.debug('All rules already imported');
|
||||
}
|
||||
|
||||
const totalFilteredRules = filteredRules.length;
|
||||
for (let i = 0; i < totalFilteredRules; i++) {
|
||||
console.log(`Adding rule ${filteredRules[i].name}`);
|
||||
rules.push((await RuleService.createRule(filteredRules[i], DEFAULT_VALIDATOR_ID)).processData);
|
||||
|
||||
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
|
||||
const ruleProgressIncrement = progressRange / (totalFilteredRules * totalPages);
|
||||
const progress = CREATE_START_PROGRESS + ((page - 1) * totalFilteredRules + i + 1) * ruleProgressIncrement;
|
||||
onProgress?.(progress, `Page ${page}/${totalPages} : Création de la règle ${i + 1}/${totalFilteredRules} - ${filteredRules[i].label}`);
|
||||
}
|
||||
|
||||
if (!result.pagination.hasNextPage) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
|
||||
result = await DatabaseService.getTableData('rules', page, limit);
|
||||
}
|
||||
|
||||
onProgress?.(FINAL_PROGRESS, 'Importation des règles terminée');
|
||||
return rules;
|
||||
}
|
||||
|
||||
private static async importRuleGroups(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
|
||||
console.log('Importing rule groups');
|
||||
const ruleGroups: any[] = [];
|
||||
|
||||
const INIT_PROGRESS = 0;
|
||||
const FETCH_PROGRESS = 30;
|
||||
const CREATE_END_PROGRESS = 90;
|
||||
const FINAL_PROGRESS = 100;
|
||||
onProgress?.(INIT_PROGRESS, 'Initialisation');
|
||||
|
||||
let page = 1;
|
||||
let limit = 10;
|
||||
let totalPages = 1;
|
||||
|
||||
onProgress?.(FETCH_PROGRESS, 'Récupération des groupes de règles existants');
|
||||
let result = await DatabaseService.getTableData('rules_groups', page, limit);
|
||||
if (result && result.success && result.pagination) {
|
||||
totalPages = result.pagination.totalPages || 1;
|
||||
}
|
||||
|
||||
const FETCH_PAGE_PROGRESS_START = FETCH_PROGRESS;
|
||||
const FETCH_PAGE_PROGRESS_END = 60;
|
||||
const CREATE_START_PROGRESS = 60;
|
||||
|
||||
while (result && result.success) {
|
||||
const fetchPageProgress = FETCH_PAGE_PROGRESS_START + ((page / totalPages) * (FETCH_PAGE_PROGRESS_END - FETCH_PAGE_PROGRESS_START));
|
||||
|
||||
onProgress?.(fetchPageProgress, `Page ${page}/${totalPages} : Récupération du groupe de règles`);
|
||||
const existingRuleGroups: any[] = (await RuleGroupService.getRuleGroups()).map((process: any) => process.processData);
|
||||
const filteredRuleGroups: any[] = result.data.filter((rule: any) => !existingRuleGroups.some((existingRule: any) => existingRule.uid === rule.uid));
|
||||
|
||||
const totalFilteredRuleGroups = filteredRuleGroups.length;
|
||||
for (let i = 0; i < totalFilteredRuleGroups; i++) {
|
||||
console.log(`Adding rule group ${filteredRuleGroups[i].name}`);
|
||||
ruleGroups.push((await RuleGroupService.createRuleGroup(filteredRuleGroups[i], DEFAULT_VALIDATOR_ID)).processData);
|
||||
|
||||
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
|
||||
const ruleProgressIncrement = progressRange / (totalFilteredRuleGroups * totalPages);
|
||||
const progress = CREATE_START_PROGRESS + ((page - 1) * totalFilteredRuleGroups + i + 1) * ruleProgressIncrement;
|
||||
onProgress?.(progress, `Page ${page}/${totalPages} : Création du groupe de règles ${i + 1}/${totalFilteredRuleGroups} - ${filteredRuleGroups[i].label}`);
|
||||
}
|
||||
|
||||
if (!result.pagination.hasNextPage) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
|
||||
result = await DatabaseService.getTableData('rules_groups', page, limit);
|
||||
}
|
||||
|
||||
onProgress?.(FINAL_PROGRESS, 'Importation des groupes de règles terminée');
|
||||
return ruleGroups;
|
||||
}
|
||||
|
||||
private static async importRoles(onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
|
||||
console.log('Importing roles');
|
||||
// Constantes de progression - pourraient être paramétrées
|
||||
const INIT_PROGRESS = 0;
|
||||
const FETCH_PROGRESS = 30;
|
||||
const CREATE_START_PROGRESS = FETCH_PROGRESS;
|
||||
const CREATE_END_PROGRESS = 90;
|
||||
const FINAL_PROGRESS = 100;
|
||||
|
||||
onProgress?.(INIT_PROGRESS, 'Initialisation');
|
||||
return await new Promise<any[]>((resolve: (roles: any[]) => void) => {
|
||||
const defaultRoles: any[] = [
|
||||
{
|
||||
name: 'super-admin',
|
||||
label: 'Super administrateur'
|
||||
},
|
||||
{
|
||||
name: 'admin',
|
||||
label: 'Administrateur'
|
||||
},
|
||||
{
|
||||
name: 'notary',
|
||||
label: 'Notaire'
|
||||
},
|
||||
{
|
||||
name: 'default',
|
||||
label: 'Utilisateur'
|
||||
}
|
||||
];
|
||||
RoleService.getRoles().then(async (processes: any[]) => {
|
||||
onProgress?.(FETCH_PROGRESS, 'Récupération des rôles existants');
|
||||
const roles: any[] = processes.map((process: any) => process.processData);
|
||||
if (roles.length === 0) {
|
||||
const defaultRolesNames: string[] = defaultRoles.map((role: any) => role.name);
|
||||
const totalRoles = defaultRoles.length;
|
||||
for (let i = 0; i < totalRoles; i++) {
|
||||
if (!defaultRolesNames.includes(roles[i].name)) {
|
||||
roles.push((await RoleService.createRole(defaultRoles[i], DEFAULT_VALIDATOR_ID)).processData);
|
||||
} else {
|
||||
console.log(`Role ${defaultRoles[i].name} already exists`);
|
||||
}
|
||||
|
||||
// Progression dynamique pendant la création des rôles
|
||||
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
|
||||
const progress = CREATE_START_PROGRESS + ((i + 1) / totalRoles) * progressRange;
|
||||
onProgress?.(progress, `Création du rôle ${i + 1}/${totalRoles} : ${defaultRoles[i].label}`);
|
||||
}
|
||||
}
|
||||
onProgress?.(FINAL_PROGRESS, 'Importation des rôles terminée');
|
||||
resolve(roles);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async importDefaultRoles(officeUid: string, ruleGroups: any[], onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
|
||||
console.log('Importing default roles');
|
||||
let officeRoles: any[] = [];
|
||||
|
||||
const CREATE_START_PROGRESS = 60;
|
||||
const CREATE_END_PROGRESS = 90;
|
||||
|
||||
onProgress?.(CREATE_START_PROGRESS, 'Création des rôles par défaut');
|
||||
|
||||
// Prepare the collaborator rules from rule groups
|
||||
const collaboratorRules: any[] = ruleGroups
|
||||
.map((ruleGroup: any) => ruleGroup.rules || [])
|
||||
.reduce((acc: any, curr: any) => [...acc, ...curr], [])
|
||||
.map((rule: any) => ({ uid: rule.uid }));
|
||||
|
||||
console.log(`Found ${collaboratorRules.length} collaborator rules from ${ruleGroups.length} rule groups`);
|
||||
|
||||
// Get fresh list of existing roles (including ones we just created)
|
||||
const updatedExistingRoles = await OfficeRoleService.getOfficeRoles();
|
||||
const existingRoles = updatedExistingRoles
|
||||
.map((role: any) => role.processData)
|
||||
.filter((roleData: any) => roleData.office?.uid === officeUid);
|
||||
|
||||
const existingRoleNames = existingRoles.map((role: any) => role.name);
|
||||
|
||||
const missingMandatoryRoles = mandatoryRoles.filter(roleName =>
|
||||
!existingRoleNames.includes(roleName)
|
||||
);
|
||||
|
||||
console.log(`Found ${existingRoleNames.length} existing roles, ${missingMandatoryRoles.length} mandatory roles missing`);
|
||||
|
||||
if (missingMandatoryRoles.length === 0) {
|
||||
onProgress?.(CREATE_END_PROGRESS, 'Tous les rôles obligatoires existent déjà');
|
||||
return officeRoles;
|
||||
}
|
||||
|
||||
for (let i = 0; i < missingMandatoryRoles.length; i++) {
|
||||
const roleName = missingMandatoryRoles[i];
|
||||
const fallbackRole = {
|
||||
name: roleName,
|
||||
office: { uid: officeUid },
|
||||
// Only Notaire gets rules, Collaborateur gets none
|
||||
...(roleName === 'Notaire' && { rules: collaboratorRules })
|
||||
};
|
||||
|
||||
console.log(`Creating missing mandatory role: ${roleName}`);
|
||||
|
||||
officeRoles.push((await OfficeRoleService.createOfficeRole(fallbackRole, DEFAULT_VALIDATOR_ID)).processData);
|
||||
|
||||
const progressRange = CREATE_END_PROGRESS - CREATE_START_PROGRESS;
|
||||
const progress = CREATE_START_PROGRESS + ((i + 1) / missingMandatoryRoles.length) * progressRange;
|
||||
onProgress?.(progress, `Création rôle obligatoire ${i + 1}/${missingMandatoryRoles.length} - ${roleName}`);
|
||||
}
|
||||
|
||||
return officeRoles;
|
||||
}
|
||||
|
||||
private static async importOfficeRoles(office: any, ruleGroups: any[], onProgress?: (progress: number, description?: string) => void): Promise<any[]> {
|
||||
console.log('Importing office roles');
|
||||
let officeRoles: any[] = [];
|
||||
const officeUid = office.processData?.uid;
|
||||
if (!officeUid) {
|
||||
console.error('Office UID is not set');
|
||||
return officeRoles;
|
||||
}
|
||||
console.log(`Processing ${ruleGroups.length} rule groups for office ${officeUid}`);
|
||||
|
||||
const INIT_PROGRESS = 0;
|
||||
const FETCH_PROGRESS = 30;
|
||||
const CREATE_END_PROGRESS = 90;
|
||||
const FINAL_PROGRESS = 100;
|
||||
onProgress?.(INIT_PROGRESS, 'Initialisation');
|
||||
|
||||
let page = 1;
|
||||
let limit = 10;
|
||||
let totalPages = 1;
|
||||
|
||||
onProgress?.(FETCH_PROGRESS, 'Récupération des rôles d\'office existants');
|
||||
let result = await DatabaseService.getTableData('office_roles', page, limit);
|
||||
if (result && result.success && result.pagination) {
|
||||
totalPages = result.pagination.totalPages || 1;
|
||||
}
|
||||
|
||||
const FETCH_PAGE_PROGRESS_START = FETCH_PROGRESS;
|
||||
const FETCH_PAGE_PROGRESS_END = 60;
|
||||
const CREATE_START_PROGRESS = 60;
|
||||
|
||||
// Collect all office roles for this office from all pages
|
||||
const allOfficeRolesForThisOffice: any[] = [];
|
||||
while (result && result.success) {
|
||||
const fetchPageProgress = FETCH_PAGE_PROGRESS_START + ((page / totalPages) * (FETCH_PAGE_PROGRESS_END - FETCH_PAGE_PROGRESS_START));
|
||||
onProgress?.(fetchPageProgress, `Page ${page}/${totalPages} : Récupération des rôles d'office`);
|
||||
|
||||
// Collect office roles for this office from current page
|
||||
const officeRolesFromPage = result.data.filter((officeRole: any) =>
|
||||
officeRole.office?.uid === officeUid
|
||||
);
|
||||
allOfficeRolesForThisOffice.push(...officeRolesFromPage);
|
||||
|
||||
if (!result.pagination.hasNextPage) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
result = await DatabaseService.getTableData('office_roles', page, limit);
|
||||
}
|
||||
|
||||
console.log(`Found ${allOfficeRolesForThisOffice.length} office roles in database for this office`);
|
||||
|
||||
if (allOfficeRolesForThisOffice.length === 0) {
|
||||
console.log('No office roles found in database, creating defaults');
|
||||
return await this.importDefaultRoles(officeUid, ruleGroups, onProgress);
|
||||
}
|
||||
|
||||
// Get all existing office role processes (to avoid duplicates)
|
||||
const existingOfficeRoles: any[] = await OfficeRoleService.getOfficeRoles();
|
||||
const existingOfficeRoleUids = existingOfficeRoles.map((existingRole: any) =>
|
||||
existingRole.processData.uid
|
||||
);
|
||||
|
||||
console.log(`Found ${existingOfficeRoles.length} existing office role processes`);
|
||||
|
||||
// Import all office roles found in database (if not already imported)
|
||||
const dbRolesToImport = allOfficeRolesForThisOffice.filter((dbRole: any) =>
|
||||
!existingOfficeRoleUids.includes(dbRole.uid)
|
||||
);
|
||||
|
||||
console.log(`Importing ${dbRolesToImport.length} new office roles from database`);
|
||||
|
||||
for (let i = 0; i < dbRolesToImport.length; i++) {
|
||||
const roleData = dbRolesToImport[i];
|
||||
|
||||
// Ensure office UID is set correctly
|
||||
if (!roleData.office) {
|
||||
roleData.office = { uid: officeUid };
|
||||
} else if (!roleData.office.uid) {
|
||||
roleData.office.uid = officeUid;
|
||||
}
|
||||
|
||||
console.log(`Importing office role: ${roleData.name}`);
|
||||
|
||||
officeRoles.push((await OfficeRoleService.createOfficeRole(roleData, DEFAULT_VALIDATOR_ID)).processData);
|
||||
|
||||
const progressRange = (CREATE_END_PROGRESS - CREATE_START_PROGRESS) * 0.7; // 70% for db imports
|
||||
const progress = CREATE_START_PROGRESS + ((i + 1) / dbRolesToImport.length) * progressRange;
|
||||
onProgress?.(progress, `Import base de données ${i + 1}/${dbRolesToImport.length} - ${roleData.name}`);
|
||||
}
|
||||
|
||||
// Now check for mandatory roles and create any missing ones
|
||||
const mandatoryRolesProgress = CREATE_START_PROGRESS + (CREATE_END_PROGRESS - CREATE_START_PROGRESS) * 0.7;
|
||||
onProgress?.(mandatoryRolesProgress, 'Vérification des rôles obligatoires');
|
||||
|
||||
const defaultRolesCreated = await this.importDefaultRoles(officeUid, ruleGroups, (subProgress, description) => {
|
||||
// Map sub-progress to remaining 20% of total progress
|
||||
const mappedProgress = mandatoryRolesProgress + (subProgress - 60) * 0.3 / 30; // Scale 60-90 to remaining 30%
|
||||
onProgress?.(mappedProgress, description);
|
||||
});
|
||||
|
||||
officeRoles.push(...defaultRolesCreated);
|
||||
|
||||
onProgress?.(FINAL_PROGRESS, 'Importation des rôles d\'office terminée');
|
||||
return officeRoles;
|
||||
}
|
||||
}
|
45
src/common/Api/LeCoffreApi/sdk/Loader/LoaderService.ts
Normal file
45
src/common/Api/LeCoffreApi/sdk/Loader/LoaderService.ts
Normal file
@ -0,0 +1,45 @@
|
||||
class LoaderService {
|
||||
private static instance: LoaderService;
|
||||
private _isVisible: boolean = false;
|
||||
private _callbacks: Array<(isVisible: boolean) => void> = [];
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static getInstance(): LoaderService {
|
||||
if (!LoaderService.instance) {
|
||||
LoaderService.instance = new LoaderService();
|
||||
}
|
||||
return LoaderService.instance;
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this._isVisible = true;
|
||||
this._notifySubscribers();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this._isVisible = false;
|
||||
this._notifySubscribers();
|
||||
}
|
||||
|
||||
public get isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
public subscribe(callback: (isVisible: boolean) => void): () => void {
|
||||
this._callbacks.push(callback);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
this._callbacks = this._callbacks.filter(cb => cb !== callback);
|
||||
};
|
||||
}
|
||||
|
||||
private _notifySubscribers(): void {
|
||||
this._callbacks.forEach(callback => {
|
||||
callback(this._isVisible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LoaderService;
|
38
src/common/Api/LeCoffreApi/sdk/Loader/classes.module.scss
Normal file
38
src/common/Api/LeCoffreApi/sdk/Loader/classes.module.scss
Normal file
@ -0,0 +1,38 @@
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 1rem;
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
37
src/common/Api/LeCoffreApi/sdk/Loader/index.tsx
Normal file
37
src/common/Api/LeCoffreApi/sdk/Loader/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
import LoaderService from "./LoaderService";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
// Composant fusionné qui gère à la fois l'abonnement au service et l'affichage
|
||||
const Loader: React.FC = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// S'abonner aux changements d'état du loader
|
||||
const unsubscribe = LoaderService.getInstance().subscribe((visible) => {
|
||||
setIsVisible(visible);
|
||||
});
|
||||
|
||||
// Nettoyage de l'abonnement
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Ne rien afficher si le loader n'est pas visible
|
||||
if (!isVisible) return null;
|
||||
|
||||
// Affichage du loader avec overlay
|
||||
return (
|
||||
<div className={classes["loader-container"]}>
|
||||
<div className={classes["loader"]}>
|
||||
<ArrowPathIcon className={classes["spinner"]} width={40} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
109
src/common/Api/LeCoffreApi/sdk/NoteService.ts
Normal file
109
src/common/Api/LeCoffreApi/sdk/NoteService.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import MessageBus from 'src/sdk/MessageBus';
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
export default class NoteService {
|
||||
|
||||
private static readonly messageBus: MessageBus = MessageBus.getInstance();
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static createNote(noteData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'note',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...noteData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [...[ownerId], validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...privateFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
resolve(processCreated);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getNotes(): Promise<any[]> {
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['utype'] && publicValues['utype'] === 'note' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false');
|
||||
}
|
||||
|
||||
public static getNoteByUid(uid: string): Promise<any> {
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) => publicValues['uid'] && publicValues['uid'] === uid && publicValues['utype'] && publicValues['utype'] === 'note' && publicValues['isDeleted'] && publicValues['isDeleted'] === 'false').then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
const process: any = processes[0];
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateNote(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
98
src/common/Api/LeCoffreApi/sdk/OfficeRibService.ts
Normal file
98
src/common/Api/LeCoffreApi/sdk/OfficeRibService.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import MessageBus from 'src/sdk/MessageBus';
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import { FileData } from '../../../../front/Api/Entities/types';
|
||||
|
||||
export default class OfficeRibService {
|
||||
|
||||
private static readonly messageBus: MessageBus = MessageBus.getInstance();
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static createOfficeRib(fileData: FileData, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'officeRib',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...fileData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [...[ownerId], validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.5,
|
||||
fields: ['idCertified', 'roles'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: [...privateFields],
|
||||
min_sig_member: 0,
|
||||
},
|
||||
],
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
resolve(processCreated);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getOfficeRib(): Promise<any> {
|
||||
return this.messageBus.getFileByUtype('officeRib');
|
||||
}
|
||||
|
||||
public static updateOfficeRib(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
150
src/common/Api/LeCoffreApi/sdk/OfficeRoleService.ts
Normal file
150
src/common/Api/LeCoffreApi/sdk/OfficeRoleService.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
import OfficeService from './OfficeService';
|
||||
import RuleService from './RuleService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class OfficeRoleService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createOfficeRole(roleData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'officeRole',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...roleData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getOfficeRoleByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getOfficeRoles(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_office_roles_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'officeRole' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (let process of processes) {
|
||||
// Update cache
|
||||
this.setItem('_office_roles_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getOfficeRoleByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_office_roles_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'officeRole' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length !== 1) {
|
||||
resolve(null);
|
||||
} else {
|
||||
// Update cache
|
||||
this.setItem('_office_roles_', processes[0]);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateOfficeRole(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const roleUid: string = process.processData.uid;
|
||||
this.removeItem('_office_roles_', roleUid);
|
||||
|
||||
this.getOfficeRoleByUid(roleUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
189
src/common/Api/LeCoffreApi/sdk/OfficeService.ts
Normal file
189
src/common/Api/LeCoffreApi/sdk/OfficeService.ts
Normal file
@ -0,0 +1,189 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
export default class OfficeService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createOffice(officeData: any, owners: string[], validatorId: string, storageUrls: string[]): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
// Create a set for all owners to avoid duplicates
|
||||
const ownersSet: Set<string> = new Set([...owners, ownerId]);
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'office',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...officeData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: Array.from(ownersSet),
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: Array.from(ownersSet),
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01, // effectively any owner can make any change
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 0.01, // need to sign with at least one device
|
||||
},
|
||||
],
|
||||
storages: storageUrls
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1, // validator can do anything alone
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: storageUrls
|
||||
},
|
||||
apophis: {
|
||||
members: Array.from(ownersSet), // any owner can terminate the office
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getOfficeByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getOffices(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_offices_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'office' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (const process of processes) {
|
||||
// Update cache
|
||||
this.setItem('_offices_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getOfficeByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_offices_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'office' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
const process: any = processes[0];
|
||||
|
||||
// Update cache
|
||||
this.setItem('_offices_', process);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateDocument(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const officeUid: string = process.processData.uid;
|
||||
this.removeItem('_offices_', officeUid);
|
||||
|
||||
this.getOfficeByUid(officeUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
public static addCollaborators(process: any, existingRoles: any, collaborators: any[]): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
const newRoles: any = existingRoles;
|
||||
const owners: string[] = newRoles['owner'].members;
|
||||
if (!owners) {
|
||||
console.error('[addCollaborators] owner role not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const previousOwnersLength: number = owners.length;
|
||||
for (const collaborator of collaborators) {
|
||||
if (owners.includes(collaborator)) {
|
||||
console.debug('[addCollaborators] collaborator already in owner role');
|
||||
continue;
|
||||
} else {
|
||||
owners.push(collaborator);
|
||||
}
|
||||
}
|
||||
|
||||
if (previousOwnersLength === owners.length) {
|
||||
console.error('[addCollaborators] no new collaborators added');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('newRoles : ', newRoles);
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString() }, [], newRoles).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const officeUid: string = process.processData.uid;
|
||||
this.removeItem('_offices_', officeUid);
|
||||
|
||||
this.getOfficeByUid(officeUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
148
src/common/Api/LeCoffreApi/sdk/RoleService.ts
Normal file
148
src/common/Api/LeCoffreApi/sdk/RoleService.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class RoleService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createRole(roleData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'role',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...roleData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getRoleByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getRoles(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_roles_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'role' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (const process of processes) {
|
||||
// Update cache
|
||||
this.setItem('_roles_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getRoleByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_roles_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'role' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
const process: any = processes[0];
|
||||
|
||||
// Update cache
|
||||
this.setItem('_roles_', process);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateRole(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const roleUid: string = process.processData.uid;
|
||||
this.removeItem('_roles_', roleUid);
|
||||
|
||||
this.getRoleByUid(roleUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
167
src/common/Api/LeCoffreApi/sdk/RuleGroupService.ts
Normal file
167
src/common/Api/LeCoffreApi/sdk/RuleGroupService.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
|
||||
import RuleService from './RuleService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class RuleGroupService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createRuleGroup(ruleGroupData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'ruleGroup',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...ruleGroupData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getRuleGroupByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getRuleGroups(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_rule_groups_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'ruleGroup' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (let process of processes) {
|
||||
process = await this.completeRuleGroup(process);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_rule_groups_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getRuleGroupByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_rule_groups_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'ruleGroup' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then(async (processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
let process: any = processes[0];
|
||||
process = await this.completeRuleGroup(process);
|
||||
|
||||
// Update cache
|
||||
this.setItem('_rule_groups_', process);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateRuleGroup(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const ruleUid: string = process.processData.uid;
|
||||
this.removeItem('_rule_groups_', ruleUid);
|
||||
|
||||
this.getRuleGroupByUid(ruleUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
private static async completeRuleGroup(process: any): Promise<any> {
|
||||
if (process.processData.rules && process.processData.rules.length > 0) {
|
||||
process.processData.rules = await new Promise<any[]>(async (resolve: (rules: any[]) => void) => {
|
||||
const rules: any[] = [];
|
||||
for (const rule of process.processData.rules) {
|
||||
rules.push((await RuleService.getRuleByUid(rule.uid)).processData);
|
||||
}
|
||||
resolve(rules);
|
||||
});
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
}
|
148
src/common/Api/LeCoffreApi/sdk/RuleService.ts
Normal file
148
src/common/Api/LeCoffreApi/sdk/RuleService.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import User from 'src/sdk/User';
|
||||
|
||||
import AbstractService from './AbstractService';
|
||||
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
|
||||
|
||||
export default class RuleService extends AbstractService {
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static createRule(ruleData: any, validatorId: string): Promise<any> {
|
||||
const ownerId: string = User.getInstance().getPairingId()!;
|
||||
|
||||
const processData: any = {
|
||||
uid: uuidv4(),
|
||||
utype: 'rule',
|
||||
isDeleted: 'false',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
...ruleData
|
||||
};
|
||||
|
||||
const privateFields: string[] = Object.keys(processData);
|
||||
privateFields.splice(privateFields.indexOf('uid'), 1);
|
||||
privateFields.splice(privateFields.indexOf('utype'), 1);
|
||||
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
|
||||
|
||||
const roles: any = {
|
||||
demiurge: {
|
||||
members: [ownerId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
},
|
||||
owner: {
|
||||
members: [ownerId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: [...privateFields, 'roles', 'uid', 'utype', 'isDeleted'],
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
validator: {
|
||||
members: [validatorId],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1,
|
||||
fields: [...privateFields, 'roles', 'isDeleted', 'uid', 'utype'],
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: [...DEFAULT_STORAGE_URLS]
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId, validatorId],
|
||||
validation_rules: [],
|
||||
storages: []
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
|
||||
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
|
||||
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
|
||||
this.getRuleByUid(processCreated.processData.uid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static getRules(): Promise<any[]> {
|
||||
// Check if we have valid cache
|
||||
const items: any[] = this.getItems('_rules_');
|
||||
|
||||
return this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'rule' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false' &&
|
||||
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
return items;
|
||||
} else {
|
||||
for (const process of processes) {
|
||||
// Update cache
|
||||
this.setItem('_rules_', process);
|
||||
|
||||
items.push(process);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getRuleByUid(uid: string): Promise<any> {
|
||||
// Check if we have valid cache
|
||||
const item: any = this.getItem('_rules_', uid);
|
||||
if (item) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
|
||||
this.messageBus.getProcessesDecoded((publicValues: any) =>
|
||||
publicValues['uid'] &&
|
||||
publicValues['uid'] === uid &&
|
||||
publicValues['utype'] &&
|
||||
publicValues['utype'] === 'rule' &&
|
||||
publicValues['isDeleted'] &&
|
||||
publicValues['isDeleted'] === 'false'
|
||||
).then((processes: any[]) => {
|
||||
if (processes.length === 0) {
|
||||
resolve(null);
|
||||
} else {
|
||||
const process: any = processes[0];
|
||||
|
||||
// Update cache
|
||||
this.setItem('_rules_', process);
|
||||
|
||||
resolve(process);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
public static updateRule(process: any, newData: any): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string) => void) => {
|
||||
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
|
||||
const newStateId: string = processUpdated.diffs[0]?.state_id;
|
||||
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
|
||||
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
|
||||
const ruleUid: string = process.processData.uid;
|
||||
this.removeItem('_rules_', ruleUid);
|
||||
|
||||
this.getRuleByUid(ruleUid).then(resolve).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
}
|
@ -115,4 +115,31 @@ export default class Auth extends BaseApiService {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async clientAuth(body: IClientAuthParams): Promise<IClientAuthReturn> {
|
||||
// Construct the full URL for the client-auth endpoint
|
||||
// This endpoint is at /api/v1/client-auth, not part of the customer auth namespace
|
||||
const url = new URL(this.baseURl.concat("/client-auth"));
|
||||
try {
|
||||
// Create custom headers for this specific endpoint
|
||||
const headers = new Headers();
|
||||
headers.set("Content-Type", "application/json");
|
||||
headers.set("x-session-id", body.sessionId);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ pairingId: body.pairingId })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,105 @@ export default class Auth extends BaseApiService {
|
||||
|
||||
public async loginWithIdNot() {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const url = new URL(
|
||||
`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${
|
||||
variables.FRONT_APP_HOST
|
||||
}/authorized-client&scope=openid,profile&response_type=code`,
|
||||
);
|
||||
|
||||
// Determine next URL for the final redirect after backend callback processing
|
||||
const nextUrl = (typeof window !== 'undefined')
|
||||
? `${window.location.origin}/authorized-client`
|
||||
: `${variables.FRONT_APP_HOST}/authorized-client`;
|
||||
|
||||
// Use same-origin API (proxied by dev4) to avoid CORS
|
||||
const apiOrigin = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}${variables.BACK_API_PORT ? `:${variables.BACK_API_PORT}` : ''}`;
|
||||
const stateEndpoint = new URL(`${variables.BACK_API_ROOT_URL || '/api'}/v1/idnot/state`, apiOrigin);
|
||||
|
||||
try {
|
||||
// 1) Ask backend for a signed state that embeds next_url
|
||||
const resp = await fetch(stateEndpoint.toString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Session-ID': `front_${Math.random().toString(36).slice(2)}`,
|
||||
},
|
||||
body: JSON.stringify({ next_url: nextUrl })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.error('[IDNOT] Failed to create state', resp.status);
|
||||
throw new Error(`Failed to create state: ${resp.status}`);
|
||||
}
|
||||
const { state } = await resp.json();
|
||||
if (!state) {
|
||||
throw new Error('Empty state from backend');
|
||||
}
|
||||
|
||||
// 2) Build the IdNot authorization URL with fixed redirect_uri and the signed state
|
||||
const fixedRedirect = variables.IDNOT_REDIRECT_URI_FIXED || 'https://lecoffreio.4nkweb.com/authorized-client';
|
||||
const authorizeBase = `${variables.IDNOT_BASE_URL}${variables.IDNOT_AUTHORIZE_ENDPOINT}`;
|
||||
const scopeParam = encodeURIComponent('openid profile');
|
||||
const authorizeUrl = `${authorizeBase}?client_id=${encodeURIComponent(variables.IDNOT_CLIENT_ID)}&redirect_uri=${encodeURIComponent(fixedRedirect)}&scope=${scopeParam}&response_type=code&state=${encodeURIComponent(state)}`;
|
||||
|
||||
console.log('[IDNOT] authorizeUrl', authorizeUrl);
|
||||
console.log('[IDNOT] state', state);
|
||||
|
||||
// Prefer navigating here if running in browser; also return the URL for callers that want to handle navigation
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.assign(authorizeUrl);
|
||||
}
|
||||
return authorizeUrl;
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async idNotAuth(autorizationCode: string | string[]): Promise<{ idNotUser: any; authToken: string }> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/idnot/auth`);
|
||||
const code = Array.isArray(autorizationCode) ? autorizationCode[0] : autorizationCode;
|
||||
try {
|
||||
return await this.postRequest<{ idNotUser: any; authToken: string }>(url, { code });
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getIdNotUser(): Promise<{ success: boolean; data: any }> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/idnot/user`);
|
||||
try {
|
||||
return await this.getRequest(url);
|
||||
}
|
||||
catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getIdNotOfficeForUser(userId: string): Promise<any[]> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/idnot/user/rattachements`);
|
||||
url.searchParams.set('idNot', userId);
|
||||
try {
|
||||
return await this.getRequest(url);
|
||||
}
|
||||
catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getIdNotUserForOffice(officeId: string): Promise<any[]> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/idnot/office/rattachements`);
|
||||
url.searchParams.set('idNot', officeId);
|
||||
try {
|
||||
return await this.getRequest(url);
|
||||
} catch (err) {
|
||||
@ -40,12 +134,27 @@ export default class Auth extends BaseApiService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getIdnotJwt(autorizationCode: string | string[]): Promise<{ accessToken: string; refreshToken: string }> {
|
||||
public async getUserProcessByIdNot(pairingId: string): Promise<{ success: boolean; data: { processId: string, processData: { [key: string]: any } } }> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
|
||||
const url = new URL(`${baseBackUrl}/api/v1/idnot/user/${autorizationCode}`);
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/process/user`);
|
||||
url.searchParams.set('pairingId', pairingId);
|
||||
try {
|
||||
return await this.postRequest<{ accessToken: string; refreshToken: string }>(url);
|
||||
return await this.getRequest(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getOfficeProcessByIdNot(): Promise<{ success: boolean; data: { processId: string, processData: { [key: string]: any } } }> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = this.getBaseUrl();
|
||||
|
||||
const url = new URL(`${baseBackUrl}/process/office`);
|
||||
try {
|
||||
return await this.getRequest(url);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { FrontendVariables } from "@Front/Config/VariablesFront";
|
||||
import CookieService from "@Front/Services/CookieService/CookieService";
|
||||
import jwt_decode from "jwt-decode";
|
||||
import JwtService, { ICustomerJwtPayload, IUserJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
|
||||
export enum ContentType {
|
||||
JSON = "application/json",
|
||||
@ -18,11 +16,15 @@ export default abstract class BaseApiService {
|
||||
protected readonly variables = FrontendVariables.getInstance();
|
||||
|
||||
protected constructor() {
|
||||
BaseApiService.baseUrl ??=
|
||||
this.variables.BACK_API_PROTOCOL +
|
||||
this.variables.BACK_API_HOST +
|
||||
this.variables.BACK_API_ROOT_URL +
|
||||
this.variables.BACK_API_VERSION;
|
||||
if (!BaseApiService.baseUrl) {
|
||||
const protocol = (this.variables.BACK_API_PROTOCOL || '').replace(/:\/\/$/, '');
|
||||
const host = (this.variables.BACK_API_HOST || '').replace(/\/$/, '');
|
||||
const port = this.variables.BACK_API_PORT ? `:${this.variables.BACK_API_PORT.replace(/^:/, '')}` : '';
|
||||
const root = `/${(this.variables.BACK_API_ROOT_URL || '')}`.replace(/\/+/g, '/').replace(/\/$/, '');
|
||||
const version = `/${(this.variables.BACK_API_VERSION || '')}`.replace(/\/+/g, '/');
|
||||
|
||||
BaseApiService.baseUrl = `${protocol}://${host}${port}${root}${version}`;
|
||||
}
|
||||
}
|
||||
|
||||
protected getBaseUrl(): string {
|
||||
@ -37,6 +39,10 @@ export default abstract class BaseApiService {
|
||||
if (contentType === ContentType.JSON || contentType === ContentType.PDF) {
|
||||
headers.set("Content-Type", contentType);
|
||||
}
|
||||
// Toujours annoncer qu'on attend du JSON côté front pour les appels API
|
||||
headers.set("Accept", "application/json");
|
||||
// Propager un identifiant de requête pour corrélation côté proxy/backend
|
||||
headers.set("X-Session-ID", `front_${Math.random().toString(36).slice(2)}`);
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
}
|
||||
@ -45,8 +51,7 @@ export default abstract class BaseApiService {
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
protected async getRequest<T>(url: URL, token?: string, contentType?: ContentType, ref?: IRef, fileName?: string) {
|
||||
await this.checkJwtToken();
|
||||
protected async getRequest<T>(url: URL, contentType?: ContentType, ref?: IRef, fileName?: string) {
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "GET",
|
||||
@ -55,8 +60,7 @@ export default abstract class BaseApiService {
|
||||
return this.sendRequest<T>(request, ref, fileName);
|
||||
}
|
||||
|
||||
protected async postRequest<T>(url: URL, body: { [key: string]: unknown } = {}, token?: string) {
|
||||
await this.checkJwtToken();
|
||||
protected async postRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
return this.sendRequest<T>(
|
||||
async () =>
|
||||
await fetch(url, {
|
||||
@ -68,7 +72,6 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
|
||||
protected async postRequestFormData<T>(url: URL, body: FormData) {
|
||||
await this.checkJwtToken();
|
||||
return this.sendRequest<T>(
|
||||
async () =>
|
||||
await fetch(url, {
|
||||
@ -80,7 +83,6 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
|
||||
protected async putRequest<T>(url: URL, body: { [key: string]: unknown } = {}, token?: string) {
|
||||
await this.checkJwtToken();
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
@ -92,7 +94,6 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
|
||||
protected async patchRequest<T>(url: URL, body: { [key: string]: unknown } = {}) {
|
||||
await this.checkJwtToken();
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PATCH",
|
||||
@ -104,7 +105,6 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
|
||||
protected async deleteRequest<T>(url: URL, body: { [key: string]: unknown } = {}, token?: string) {
|
||||
await this.checkJwtToken();
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "DELETE",
|
||||
@ -116,7 +116,6 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
|
||||
protected async putFormDataRequest<T>(url: URL, body: FormData, token?: string) {
|
||||
await this.checkJwtToken();
|
||||
const request = async () =>
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
@ -129,45 +128,16 @@ export default abstract class BaseApiService {
|
||||
|
||||
private async sendRequest<T>(request: () => Promise<Response>, ref?: IRef, fileName?: string): Promise<T> {
|
||||
const response = await request();
|
||||
try {
|
||||
// Log minimal côté client pour corrélation (status + URL)
|
||||
// Note: les en-têtes ici sont ceux de la réponse serveur
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('[HTTP]', response.status, response.url);
|
||||
} catch {}
|
||||
|
||||
return this.processResponse<T>(response, request, ref, fileName);
|
||||
}
|
||||
|
||||
private async checkJwtToken() {
|
||||
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
|
||||
if (!accessToken) return;
|
||||
|
||||
const userDecodedToken = jwt_decode(accessToken) as IUserJwtPayload;
|
||||
const customerDecodedToken = jwt_decode(accessToken) as ICustomerJwtPayload;
|
||||
|
||||
if (!userDecodedToken && !customerDecodedToken) return;
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (userDecodedToken.userId && userDecodedToken.exp < now) {
|
||||
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken");
|
||||
if (!refreshToken) {
|
||||
return;
|
||||
}
|
||||
const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload;
|
||||
if (decodedRefreshToken.exp < now) {
|
||||
return;
|
||||
}
|
||||
await JwtService.getInstance().refreshToken(refreshToken);
|
||||
}
|
||||
if (customerDecodedToken.customerId && customerDecodedToken.exp < now) {
|
||||
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken");
|
||||
if (!refreshToken) {
|
||||
return;
|
||||
}
|
||||
const decodedRefreshToken = jwt_decode(refreshToken) as IUserJwtPayload | ICustomerJwtPayload;
|
||||
if (decodedRefreshToken.exp < now) {
|
||||
return;
|
||||
}
|
||||
await JwtService.getInstance().refreshToken(refreshToken);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
protected async processResponse<T>(response: Response, request: () => Promise<Response>, ref?: IRef, fileName?: string): Promise<T> {
|
||||
let responseContent: T;
|
||||
ref && (ref["response"] = response);
|
||||
|
2
src/front/Api/Entities/index.ts
Normal file
2
src/front/Api/Entities/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './rule';
|
21
src/front/Api/Entities/types.ts
Normal file
21
src/front/Api/Entities/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* FileBlob interface representing a file's binary data and metadata
|
||||
* Used for file transmission and storage in the application
|
||||
*/
|
||||
export interface FileBlob {
|
||||
/** MIME type of the file (e.g., "application/pdf", "image/jpeg") */
|
||||
type: string;
|
||||
/** Binary data of the file as Uint8Array */
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* FileData interface representing a complete file object with blob and metadata
|
||||
* Used when creating or updating files in the system
|
||||
*/
|
||||
export interface FileData {
|
||||
/** The file blob containing type and binary data */
|
||||
file_blob: FileBlob;
|
||||
/** The name of the file */
|
||||
file_name: string;
|
||||
}
|
@ -2,6 +2,7 @@ import { Contact, Customer } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
import BaseNotary from "../BaseNotary";
|
||||
import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
|
||||
import { FrontendVariables } from "@Front/Config/VariablesFront";
|
||||
|
||||
// TODO Type get query params -> Where + inclue + orderby
|
||||
export interface IGetCustomersparams {
|
||||
@ -88,10 +89,15 @@ export default class Customers extends BaseNotary {
|
||||
}
|
||||
}
|
||||
|
||||
public async sendReminder(uid: string, documentsUid: string[]): Promise<void> {
|
||||
const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`));
|
||||
public async sendReminder(office: any, customer: any): Promise<void> {
|
||||
const variables = FrontendVariables.getInstance();
|
||||
const baseBackUrl = `${variables.BACK_API_PROTOCOL}://${variables.BACK_API_HOST}:${variables.BACK_API_PORT}${variables.BACK_API_ROOT_URL}${variables.BACK_API_VERSION}`;
|
||||
|
||||
const url = new URL(`${baseBackUrl}/api/send_reminder`);
|
||||
|
||||
//const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`));
|
||||
try {
|
||||
await this.postRequest<void>(url, { documentsUid });
|
||||
await this.postRequest<void>(url, { office, customer });
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
|
@ -14,10 +14,15 @@ import classNames from "classnames";
|
||||
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "../Button";
|
||||
import Confirm from "../OldModal/Confirm";
|
||||
import Documents from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
|
||||
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
||||
import Alert from "../OldModal/Alert";
|
||||
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
type IProps = {
|
||||
onChange?: (files: File[]) => void;
|
||||
open: boolean;
|
||||
@ -196,7 +201,7 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidMount(): void {}
|
||||
public override componentDidMount(): void { }
|
||||
|
||||
private onCloseAlertUpload() {
|
||||
this.setState({ showFailedUploaded: null });
|
||||
@ -206,35 +211,91 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
});
|
||||
const filesArray = this.state.currentFiles;
|
||||
|
||||
const filesArray = this.state.currentFiles;
|
||||
if (!filesArray) return;
|
||||
let documentCreated: Document = {} as Document;
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
let documentCreated: any;
|
||||
try {
|
||||
documentCreated = await Documents.getInstance().post({
|
||||
folder: {
|
||||
uid: this.props.folder_uid,
|
||||
},
|
||||
depositor: {
|
||||
uid: this.props.customer_uid,
|
||||
},
|
||||
documentCreated = await new Promise<any>((resolve: (document: any) => void) => {
|
||||
const documentTypeData: any = {
|
||||
folder: {
|
||||
uid: this.props.folder_uid,
|
||||
},
|
||||
depositor: {
|
||||
uid: this.props.customer_uid,
|
||||
}
|
||||
};
|
||||
|
||||
DocumentService.createDocument(documentTypeData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
if (processCreated) {
|
||||
const document: any = processCreated.processData;
|
||||
resolve(document);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({ showFailedDocument: "Le dossier est vérifié aucune modification n'est acceptée", isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const customer: any = await new Promise<void>((resolve: (customer: any) => void) => {
|
||||
CustomerService.getCustomerByUid(this.props.customer_uid).then((process: any) => {
|
||||
if (process) {
|
||||
const customer: any = process.processData;
|
||||
resolve(customer);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (let i = 0; i < filesArray.length; i++) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", filesArray[i]!.file, filesArray[i]!.fileName);
|
||||
const query = JSON.stringify({ document: { uid: documentCreated.uid } });
|
||||
formData.append("q", query);
|
||||
try {
|
||||
await Files.getInstance().post(formData);
|
||||
} catch (e) {
|
||||
this.setState({ showFailedUploaded: "Le fichier ne correspond pas aux critères demandés", isLoading: false });
|
||||
return;
|
||||
}
|
||||
const file = filesArray[i]!.file;
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (event.target?.result) {
|
||||
const date: Date = new Date();
|
||||
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
|
||||
|
||||
const fileName: string = `${file.name.split('.')[0]}_${strDate}.${file.name.split('.').pop()}`;
|
||||
|
||||
const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
const fileBlob: any = {
|
||||
type: file.type,
|
||||
data: uint8Array
|
||||
};
|
||||
|
||||
const fileData: any = {
|
||||
file_blob: fileBlob,
|
||||
file_name: fileName
|
||||
};
|
||||
|
||||
FileService.createFile(fileData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
const fileUid: string = processCreated.processData.uid;
|
||||
|
||||
DocumentService.getDocumentByUid(documentCreated.uid).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
|
||||
let files: any[] = document.files;
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
files.push({ uid: fileUid });
|
||||
|
||||
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
|
||||
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -213,9 +213,9 @@ export default function DragAndDrop(props: IProps) {
|
||||
</div>
|
||||
{documentFiles.length > 0 && (
|
||||
<div className={classes["documents"]}>
|
||||
{documentFiles.map((documentFile) => (
|
||||
{documentFiles.map((documentFile, index) => (
|
||||
<DocumentFileElement
|
||||
key={documentFile.id}
|
||||
key={documentFile.uid || `${documentFile.id}-${index}`}
|
||||
isLoading={documentFile.isLoading}
|
||||
file={documentFile.file}
|
||||
onRemove={() => handleRemove(documentFile)}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
||||
import Notifications from "@Front/Api/LeCoffreApi/Notary/Notifications/Notifications";
|
||||
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
|
||||
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
||||
import Module from "@Front/Config/Module";
|
||||
import Toasts from "@Front/Stores/Toasts";
|
||||
@ -17,6 +15,7 @@ export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
|
||||
const getAnchoringStatus = useCallback(async () => {
|
||||
/* TODO: review
|
||||
const anchors = await OfficeFolderAnchors.getInstance().get({
|
||||
where: {
|
||||
status: {
|
||||
@ -27,7 +26,10 @@ export default function Navigation() {
|
||||
folder: true,
|
||||
},
|
||||
});
|
||||
*/
|
||||
// const anchors = [] as any[];
|
||||
|
||||
/* TODO: review
|
||||
try {
|
||||
for (const anchor of anchors) {
|
||||
await OfficeFolderAnchors.getInstance().getByUid(anchor.folder?.uid as string);
|
||||
@ -35,10 +37,13 @@ export default function Navigation() {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
*/
|
||||
}, []);
|
||||
|
||||
const getNotifications = useCallback(async () => {
|
||||
//await getAnchoringStatus();
|
||||
|
||||
/* TODO: review
|
||||
const notifications = await Notifications.getInstance().get({
|
||||
where: {
|
||||
read: false,
|
||||
@ -50,6 +55,9 @@ export default function Navigation() {
|
||||
notification: { created_at: "desc" },
|
||||
},
|
||||
});
|
||||
*/
|
||||
const notifications = [] as any[];
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
Toasts.getInstance().open({
|
||||
title: notification.notification.message,
|
||||
|
@ -8,6 +8,7 @@ import MenuItem from "@Front/Components/DesignSystem/Menu/MenuItem";
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
isCustomer?: boolean;
|
||||
};
|
||||
type IState = {};
|
||||
|
||||
@ -19,29 +20,31 @@ export default class ProfileModal extends React.Component<IProps, IState> {
|
||||
<>
|
||||
<div className={classes["background"]} onClick={this.props.closeModal} />
|
||||
<div className={classes["root"]}>
|
||||
<MenuItem
|
||||
{!this.props.isCustomer && <MenuItem
|
||||
item={{
|
||||
text: "Mon compte",
|
||||
link: Module.getInstance().get().modules.pages.MyAccount.props.path,
|
||||
}}
|
||||
/>
|
||||
/>}
|
||||
|
||||
<MenuItem
|
||||
{!this.props.isCustomer && <MenuItem
|
||||
item={{
|
||||
text: "Guide de Prise en Main",
|
||||
link: "https://ressources.lecoffre.io/",
|
||||
target: "_blank",
|
||||
}}
|
||||
/>
|
||||
/>}
|
||||
|
||||
<MenuItem
|
||||
|
||||
{!this.props.isCustomer && <MenuItem
|
||||
item={{
|
||||
text: "CGU",
|
||||
link: "/CGU_LeCoffre_io.pdf",
|
||||
hasSeparator: true,
|
||||
}}
|
||||
/>
|
||||
<LogOutButton />
|
||||
/>}
|
||||
|
||||
<LogOutButton isCustomer={this.props.isCustomer} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ import ProfileModal from "./ProfileModal";
|
||||
|
||||
const headerBreakpoint = 1023;
|
||||
|
||||
export default function Profile() {
|
||||
export default function Profile(props: { isCustomer?: boolean }) {
|
||||
const { isOpen, toggle, close } = useOpenable();
|
||||
|
||||
useEffect(() => {
|
||||
@ -27,7 +27,7 @@ export default function Profile() {
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<IconButton icon={<UserIcon />} onClick={toggle} />
|
||||
<ProfileModal isOpen={isOpen} closeModal={close} />
|
||||
<ProfileModal isOpen={isOpen} closeModal={close} isCustomer={props.isCustomer} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
import LogoIcon from "@Assets/logo_standard_neutral.svg";
|
||||
import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
|
||||
import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions";
|
||||
// import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
|
||||
// import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { InformationCircleIcon, LifebuoyIcon } from "@heroicons/react/24/outline";
|
||||
// import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { LifebuoyIcon } from "@heroicons/react/24/outline";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
|
||||
import IconButton from "../IconButton";
|
||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||
import BurgerMenu from "./BurgerMenu";
|
||||
import classes from "./classes.module.scss";
|
||||
import LogoCielNatureIcon from "./logo-ciel-notaires.jpeg";
|
||||
@ -21,20 +20,22 @@ import Profile from "./Profile";
|
||||
|
||||
type IProps = {
|
||||
isUserConnected: boolean;
|
||||
isCustomer?: boolean;
|
||||
};
|
||||
|
||||
const headerHeight = 75;
|
||||
|
||||
export default function Header(props: IProps) {
|
||||
const { isUserConnected } = props;
|
||||
const { isUserConnected, isCustomer } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { pathname } = router;
|
||||
const isOnCustomerLoginPage = Module.getInstance().get().modules.pages.CustomersLogin.props.path === pathname;
|
||||
|
||||
const [cancelAt, setCancelAt] = useState<Date | null>(null);
|
||||
// const [cancelAt, setCancelAt] = useState<Date | null>(null);
|
||||
|
||||
const loadSubscription = useCallback(async () => {
|
||||
/* TODO: review
|
||||
const jwt = JwtService.getInstance().decodeJwt();
|
||||
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
|
||||
if (subscription[0]) {
|
||||
@ -43,6 +44,7 @@ export default function Header(props: IProps) {
|
||||
setCancelAt(new Date(stripeSubscription.cancel_at! * 1000));
|
||||
}
|
||||
}
|
||||
*/
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -61,7 +63,7 @@ export default function Header(props: IProps) {
|
||||
<Image src={LogoIcon} alt="logo" className={classes["logo"]} />
|
||||
</Link>
|
||||
</div>
|
||||
{isUserConnected && (
|
||||
{isUserConnected && !isCustomer && (
|
||||
<>
|
||||
<div className={classes["desktop"]}>
|
||||
<Navigation />
|
||||
@ -82,9 +84,14 @@ export default function Header(props: IProps) {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isCustomer && (
|
||||
<div className={classes["desktop"]}>
|
||||
<Profile isCustomer={isCustomer} />
|
||||
</div>
|
||||
)}
|
||||
{isOnCustomerLoginPage && <Image width={70} height={70} alt="ciel-nature" src={LogoCielNatureIcon}></Image>}
|
||||
</div>
|
||||
{cancelAt && (
|
||||
{/* {cancelAt && (
|
||||
<div className={classes["subscription-line"]}>
|
||||
<InformationCircleIcon height="24" />
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}>
|
||||
@ -92,7 +99,7 @@ export default function Header(props: IProps) {
|
||||
{cancelAt.toLocaleDateString()}.
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -6,14 +6,18 @@ import React, { useCallback } from "react";
|
||||
|
||||
import MenuItem from "../Menu/MenuItem";
|
||||
|
||||
export default function LogOut() {
|
||||
export default function LogOut(props: { isCustomer?: boolean }) {
|
||||
const router = useRouter();
|
||||
const variables = FrontendVariables.getInstance();
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
UserStore.instance
|
||||
.disconnect()
|
||||
.then(() => router.push(`https://qual-connexion.idnot.fr/user/auth/logout?sourceURL=${variables.FRONT_APP_HOST}`));
|
||||
if (!props.isCustomer) {
|
||||
UserStore.instance
|
||||
.disconnect()
|
||||
.then(() => router.push(`https://qual-connexion.idnot.fr/user/auth/logout?sourceURL=${variables.FRONT_APP_HOST}`));
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
}, [router, variables.FRONT_APP_HOST]);
|
||||
|
||||
return <MenuItem item={{ text: "Déconnexion", icon: <PowerIcon />, onClick: disconnect }} />;
|
||||
|
@ -4,6 +4,8 @@ import { IAppRule } from "@Front/Api/Entities/rule";
|
||||
import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
export enum RulesMode {
|
||||
OPTIONAL = "optional",
|
||||
NECESSARY = "necessary",
|
||||
@ -20,18 +22,23 @@ export default function Rules(props: IProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const [isShowing, setIsShowing] = React.useState(false);
|
||||
const [hasJwt, setHasJwt] = React.useState(false);
|
||||
// const [hasJwt, setHasJwt] = React.useState(false);
|
||||
|
||||
const getShowValue = useCallback(() => {
|
||||
//const user: any = UserStore.instance.getUser();
|
||||
if (props.mode === RulesMode.NECESSARY) {
|
||||
//return user.isAdmin && user.isAdmin === 'true';
|
||||
return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
|
||||
}
|
||||
//const ruleNames: string[] = props.rules.map((rule: any) => rule.name);
|
||||
//return user.role.rules.map((rule: any) => rule.name).filter((ruleName: string) => ruleNames.includes(ruleName)).length > 0;
|
||||
return props.rules.length === 0 || !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
|
||||
}, [props.mode, props.rules]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!JwtService.getInstance().decodeJwt()) return;
|
||||
setHasJwt(true);
|
||||
// TODO: review
|
||||
//if (!JwtService.getInstance().decodeJwt()) return;
|
||||
// setHasJwt(true);
|
||||
setIsShowing(getShowValue());
|
||||
}, [getShowValue, isShowing]);
|
||||
|
||||
@ -40,7 +47,8 @@ export default function Rules(props: IProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasJwt || !isShowing) return null;
|
||||
// TODO: review
|
||||
//if (!hasJwt || !isShowing) return null;
|
||||
|
||||
return props.children;
|
||||
}
|
||||
|
@ -57,6 +57,13 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
|
||||
|
||||
useEffect(() => {
|
||||
tabs.current = propsTabs;
|
||||
|
||||
if (tabs.current && tabs.current.length > 0 && tabs.current[0]) {
|
||||
setSelectedTab(tabs.current[0].value);
|
||||
onSelect(tabs.current[0].value);
|
||||
|
||||
setTimeout(() => calculateVisibleElements(), 100);
|
||||
}
|
||||
}, [propsTabs]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -2,11 +2,12 @@ import React, { useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/Admin/Users/Users";
|
||||
|
||||
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
@ -15,6 +16,7 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { collaboratorUid } = router.query;
|
||||
useEffect(() => {
|
||||
/* TODO: review
|
||||
const jwt = JwtService.getInstance().decodeJwt();
|
||||
if (!jwt) return;
|
||||
const query: IGetUsersparams = {
|
||||
@ -28,10 +30,24 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Users.getInstance()
|
||||
.get(query)
|
||||
.then((users) => setCollaborators(users));
|
||||
*/
|
||||
|
||||
const user: any = UserStore.instance.getUser();
|
||||
const officeId: string = user.office.uid;
|
||||
|
||||
CollaboratorService.getCollaborators((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let collaborators: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy office.uid
|
||||
collaborators = collaborators.filter((collaborator: any) => collaborator.office.uid === officeId);
|
||||
|
||||
setCollaborators(collaborators);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
@ -47,10 +63,10 @@ export default function DefaultCollaboratorDashboard(props: IProps) {
|
||||
blocks={
|
||||
collaborators
|
||||
? collaborators.map((collaborator) => ({
|
||||
id: collaborator.uid!,
|
||||
primaryText: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
|
||||
isActive: collaborator.uid === collaboratorUid,
|
||||
}))
|
||||
id: collaborator.uid!,
|
||||
primaryText: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
|
||||
isActive: collaborator.uid === collaboratorUid,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
/>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
@ -8,17 +7,22 @@ import React, { useEffect, useState } from "react";
|
||||
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
|
||||
type IProps = IPropsDashboardWithList & {};
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
|
||||
type IProps = IPropsDashboardWithList & {
|
||||
isReady: boolean;
|
||||
};
|
||||
|
||||
export default function DefaultCustomerDashboard(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { folderUid } = router.query;
|
||||
const { folderUid, profileUid } = router.query;
|
||||
const [folders, setFolders] = useState<OfficeFolder[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
if (!jwt) return;
|
||||
//const jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
//if (!jwt) return;
|
||||
|
||||
/*
|
||||
Folders.getInstance()
|
||||
.get({
|
||||
q: {
|
||||
@ -42,7 +46,21 @@ export default function DefaultCustomerDashboard(props: IProps) {
|
||||
},
|
||||
})
|
||||
.then((folders) => setFolders(folders));
|
||||
}, []);
|
||||
*/
|
||||
|
||||
if (props.isReady) {
|
||||
FolderService.getFolders((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let folders: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// Filter By customer.uid
|
||||
folders = folders.filter((folder: any) => folder.customers.some((customer: any) => customer.uid === profileUid));
|
||||
|
||||
setFolders(folders);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [props.isReady]);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
const folder = folders.find((folder) => folder.uid === block.id);
|
||||
@ -50,10 +68,12 @@ export default function DefaultCustomerDashboard(props: IProps) {
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.uid ?? ""),
|
||||
.modules.pages.ClientDashboard.props.path
|
||||
.replace("[folderUid]", folder.uid ?? "")
|
||||
.replace("[profileUid]", profileUid as string ?? ""),
|
||||
);
|
||||
};
|
||||
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false}/>;
|
||||
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false} isCustomer={true} />;
|
||||
|
||||
function getBlocks(folders: OfficeFolder[]): IBlock[] {
|
||||
return folders.map((folder) => {
|
||||
|
@ -15,6 +15,7 @@ export type IPropsDashboardWithList = {
|
||||
mobileBackText?: string;
|
||||
headerConnected?: boolean;
|
||||
noPadding?: boolean;
|
||||
isCustomer?: boolean;
|
||||
};
|
||||
|
||||
type IProps = IPropsDashboardWithList & ISearchBlockListProps;
|
||||
@ -29,11 +30,12 @@ export default function DefaultDashboardWithList(props: IProps) {
|
||||
headerConnected = true,
|
||||
bottomButton,
|
||||
noPadding = false,
|
||||
isCustomer = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Header isUserConnected={headerConnected} />
|
||||
<Header isUserConnected={headerConnected} isCustomer={isCustomer} />
|
||||
<div className={classes["content"]}>
|
||||
<SearchBlockList blocks={blocks} onSelectedBlock={onSelectedBlock} bottomButton={bottomButton} />
|
||||
<div className={classes["right-side"]} data-no-padding={noPadding}>
|
||||
|
@ -5,31 +5,40 @@ import Module from "@Front/Config/Module";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import { DeedType } from "le-coffre-resources/dist/Notary";
|
||||
import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
|
||||
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
export default function DefaultDeedTypeDashboard(props: IProps) {
|
||||
const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
|
||||
const router = useRouter();
|
||||
const { deedTypeUid } = router.query;
|
||||
useEffect(() => {
|
||||
const query: IGetDeedTypesParams = {
|
||||
where: {
|
||||
archived_at: null,
|
||||
},
|
||||
orderBy: {
|
||||
name: "asc",
|
||||
},
|
||||
};
|
||||
|
||||
DeedTypes.getInstance()
|
||||
.get(query)
|
||||
.then((deedTypes) => setDeedTypes(deedTypes));
|
||||
const { deedTypeUid } = router.query;
|
||||
const [deedTypes, setDeedTypes] = React.useState<DeedType[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
DeedTypeService.getDeedTypes((processes: Record<string, any>) => {
|
||||
const deedTypes = Object.entries(processes).map(([processId, processData]) => ({
|
||||
...processData,
|
||||
processId: processId
|
||||
}));
|
||||
if (deedTypes.length > 0) {
|
||||
deedTypes.filter((deedType: any) => !deedType.archived_at);
|
||||
// OrderBy name asc
|
||||
deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
|
||||
setDeedTypes(deedTypes);
|
||||
} else {
|
||||
console.log('[DefaultDeedTypeDashboard] No deed types found');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
router.push(Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", block.id));
|
||||
// Remove ':0' suffix from processId for URL navigation
|
||||
const urlId = idAsUrl(block.id as string);
|
||||
router.push(Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", urlId));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -38,16 +47,19 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
|
||||
onSelectedBlock={onSelectedBlock}
|
||||
blocks={
|
||||
deedTypes
|
||||
? deedTypes.map((deedTypes) => ({
|
||||
id: deedTypes.uid!,
|
||||
primaryText: deedTypes.name,
|
||||
isActive: deedTypes.uid === deedTypeUid,
|
||||
}))
|
||||
? deedTypes.map((deedType: any) => {
|
||||
const urlId = idAsUrl(deedType.processId);
|
||||
return {
|
||||
id: deedType.processId, // Keep full processId for internal use
|
||||
primaryText: deedType.name,
|
||||
isActive: urlId === deedTypeUid, // Compare without ':0' suffix
|
||||
};
|
||||
})
|
||||
: []
|
||||
}
|
||||
bottomButton={{
|
||||
link: Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
|
||||
text: "Créer une liste de pièces",
|
||||
text: "Créer une liste de pièces", // TODO I think this is misleading, should be "Créer un type d'acte"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import { DocumentType } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
export default function DefaultDocumentTypeDashboard(props: IProps) {
|
||||
@ -15,18 +16,22 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { documentTypeUid } = router.query;
|
||||
useEffect(() => {
|
||||
const jwt = JwtService.getInstance().decodeJwt();
|
||||
if (!jwt) return;
|
||||
DocumentTypes.getInstance()
|
||||
.get({
|
||||
where: {
|
||||
office_uid: jwt.office_Id,
|
||||
},
|
||||
orderBy: {
|
||||
name: "asc",
|
||||
},
|
||||
})
|
||||
.then((documentTypes) => setDocumentTypes(documentTypes));
|
||||
const user: any = UserStore.instance.getUser();
|
||||
const officeId: string = user.office.uid;
|
||||
|
||||
DocumentTypeService.getDocumentTypes().then((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let documents: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy office.uid
|
||||
documents = documents.filter((document: any) => document.office.uid === officeId);
|
||||
|
||||
// OrderBy name asc
|
||||
documents = documents.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
|
||||
setDocumentTypes(documents);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
@ -42,10 +47,10 @@ export default function DefaultDocumentTypeDashboard(props: IProps) {
|
||||
blocks={
|
||||
documentTypes
|
||||
? documentTypes.map((documentType) => ({
|
||||
id: documentType.uid!,
|
||||
primaryText: documentType.name,
|
||||
isActive: documentType.uid === documentTypeUid,
|
||||
}))
|
||||
id: documentType.uid!,
|
||||
primaryText: documentType.name,
|
||||
isActive: documentType.uid === documentTypeUid,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
bottomButton={{
|
||||
|
@ -1,4 +1,3 @@
|
||||
import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
@ -9,6 +8,9 @@ import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList
|
||||
import { useRouter } from "next/router";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
type IProps = IPropsDashboardWithList & {
|
||||
isArchived?: boolean;
|
||||
};
|
||||
@ -24,11 +26,11 @@ export default function DefaultNotaryDashboard(props: IProps) {
|
||||
: Module.getInstance().get().modules.pages.Folder.pages.FolderInformation.props.path;
|
||||
|
||||
const getBlocks = useCallback(
|
||||
(folders: OfficeFolder[]): IBlock[] => {
|
||||
(folders: any[]): IBlock[] => {
|
||||
const pendingFolders = folders
|
||||
.filter((folder) => {
|
||||
const pendingDocuments = (folder.documents ?? []).filter(
|
||||
(document) => document.document_status === EDocumentStatus.DEPOSITED,
|
||||
(document: any) => document.document_status === EDocumentStatus.DEPOSITED,
|
||||
);
|
||||
return pendingDocuments.length >= 1;
|
||||
})
|
||||
@ -39,7 +41,7 @@ export default function DefaultNotaryDashboard(props: IProps) {
|
||||
const otherFolders = folders
|
||||
.filter((folder) => {
|
||||
const pendingDocuments = (folder.documents ?? []).filter(
|
||||
(document) => document.document_status === EDocumentStatus.DEPOSITED,
|
||||
(document: any) => document.document_status === EDocumentStatus.DEPOSITED,
|
||||
);
|
||||
return pendingDocuments.length === 0;
|
||||
})
|
||||
@ -47,15 +49,18 @@ export default function DefaultNotaryDashboard(props: IProps) {
|
||||
return folder1.created_at! > folder2.created_at! ? -1 : 1;
|
||||
});
|
||||
|
||||
return [...pendingFolders, ...otherFolders].map((folder) => {
|
||||
return {
|
||||
id: folder.uid!,
|
||||
const blocks = [...pendingFolders, ...otherFolders].map((folder) => {
|
||||
const res = {
|
||||
id: idAsUrl(folder.processId),
|
||||
primaryText: folder.name,
|
||||
secondaryText: folder.folder_number,
|
||||
isActive: folderUid === folder.uid,
|
||||
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
|
||||
isActive: folderUid === idAsUrl(folder.processId),
|
||||
showAlert: folder.documents?.some((document: any) => document.document_status === EDocumentStatus.DEPOSITED),
|
||||
};
|
||||
return res;
|
||||
});
|
||||
|
||||
return blocks;
|
||||
},
|
||||
[folderUid],
|
||||
);
|
||||
@ -76,6 +81,8 @@ export default function DefaultNotaryDashboard(props: IProps) {
|
||||
useEffect(() => {
|
||||
let targetedStatus: EFolderStatus = EFolderStatus["LIVE" as keyof typeof EFolderStatus];
|
||||
if (isArchived) targetedStatus = EFolderStatus.ARCHIVED;
|
||||
|
||||
/* TODO: review
|
||||
const query: IGetFoldersParams = {
|
||||
q: {
|
||||
where: { status: targetedStatus },
|
||||
@ -110,6 +117,30 @@ export default function DefaultNotaryDashboard(props: IProps) {
|
||||
Folders.getInstance()
|
||||
.get(query)
|
||||
.then((folders) => setFolders(folders));
|
||||
*/
|
||||
|
||||
FolderService.getFolders((processes: Record<string, any>) => {
|
||||
if (Object.keys(processes).length > 0) {
|
||||
let folders: any[] = Object.entries(processes).map(([processId, process]) => {
|
||||
const res = {
|
||||
...process,
|
||||
processId: processId
|
||||
};
|
||||
return res;
|
||||
});
|
||||
|
||||
|
||||
// FilterBy status
|
||||
folders = folders.filter((folder: any) => {
|
||||
const matches = folder.status === targetedStatus;
|
||||
return matches;
|
||||
});
|
||||
|
||||
setFolders(folders);
|
||||
} else {
|
||||
console.debug('[DefaultNotaryDashboard] No processes found');
|
||||
}
|
||||
});
|
||||
}, [isArchived]);
|
||||
|
||||
return (
|
||||
|
@ -1,22 +1,26 @@
|
||||
import { Office } from "le-coffre-resources/dist/SuperAdmin";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import Offices from "@Front/Api/LeCoffreApi/SuperAdmin/Offices/Offices";
|
||||
|
||||
import OfficeService from "src/common/Api/LeCoffreApi/sdk/OfficeService";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
export default function DefaultOfficeDashboard(props: IProps) {
|
||||
const [offices, setOffices] = React.useState<Office[] | null>(null);
|
||||
const [offices, setOffices] = React.useState<any[] | null>(null);
|
||||
const router = useRouter();
|
||||
const { officeUid } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
Offices.getInstance()
|
||||
.get()
|
||||
.then((offices) => setOffices(offices));
|
||||
OfficeService.getOffices().then((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
const offices: any[] = processes.map((process: any) => process.processData);
|
||||
setOffices(offices);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
@ -30,11 +34,11 @@ export default function DefaultOfficeDashboard(props: IProps) {
|
||||
blocks={
|
||||
offices
|
||||
? offices.map((office) => ({
|
||||
id: office.uid!,
|
||||
primaryText: office.name,
|
||||
isActive: office.uid === officeUid,
|
||||
secondaryText: office.crpcen,
|
||||
}))
|
||||
id: office.uid!,
|
||||
primaryText: office.name,
|
||||
isActive: office.uid === officeUid,
|
||||
secondaryText: office.crpcen,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
/>
|
||||
|
@ -4,23 +4,43 @@ import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import { OfficeRole } from "le-coffre-resources/dist/Notary";
|
||||
import OfficeRoles, { IGetRolesParams } from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
|
||||
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
export default function DefaultRoleDashboard(props: IProps) {
|
||||
const [roles, setRoles] = React.useState<OfficeRole[] | null>(null);
|
||||
const [roles, setRoles] = React.useState<any[] | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { roleUid } = router.query;
|
||||
useEffect(() => {
|
||||
const query: IGetRolesParams = {
|
||||
include: { rules: true },
|
||||
};
|
||||
|
||||
OfficeRoles.getInstance()
|
||||
.get(query)
|
||||
.then((roles) => setRoles(roles));
|
||||
useEffect(() => {
|
||||
const user: any = UserStore.instance.getUser();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const office: any = user.office;
|
||||
if (!office) {
|
||||
return;
|
||||
}
|
||||
|
||||
OfficeRoleService.getOfficeRoles().then(async (processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let officeRoles: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy office.uid
|
||||
officeRoles = officeRoles.filter((officeRole: any) => officeRole.office.uid === office.uid);
|
||||
|
||||
// OrderBy name
|
||||
officeRoles = officeRoles.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
|
||||
setRoles(officeRoles);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
@ -34,10 +54,10 @@ export default function DefaultRoleDashboard(props: IProps) {
|
||||
blocks={
|
||||
roles
|
||||
? roles.map((role) => ({
|
||||
id: role.uid!,
|
||||
primaryText: role.name,
|
||||
isActive: role.uid === roleUid,
|
||||
}))
|
||||
id: role.uid!,
|
||||
primaryText: role.name,
|
||||
isActive: role.uid === roleUid,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
bottomButton={{
|
||||
|
@ -1,26 +1,34 @@
|
||||
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users";
|
||||
import User from "le-coffre-resources/dist/SuperAdmin";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
|
||||
|
||||
type IProps = IPropsDashboardWithList;
|
||||
|
||||
export default function DefaultUserDashboard(props: IProps) {
|
||||
const [users, setUsers] = React.useState<User[] | null>(null);
|
||||
const [users, setUsers] = React.useState<any[] | null>(null);
|
||||
const router = useRouter();
|
||||
const { userUid } = router.query;
|
||||
useEffect(() => {
|
||||
const query: IGetUsersparams = {
|
||||
include: { contact: true, office_membership: true },
|
||||
};
|
||||
const user: any = UserStore.instance.getUser();
|
||||
if (!user) return;
|
||||
const officeId: string = user.office.uid;
|
||||
|
||||
Users.getInstance()
|
||||
.get(query)
|
||||
.then((users) => setUsers(users));
|
||||
CollaboratorService.getCollaborators((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let collaborators: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy office.uid
|
||||
collaborators = collaborators.filter((collaborator: any) => collaborator.office.uid === officeId);
|
||||
|
||||
setUsers(collaborators);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
@ -34,11 +42,11 @@ export default function DefaultUserDashboard(props: IProps) {
|
||||
blocks={
|
||||
users
|
||||
? users.map((user) => ({
|
||||
id: user.uid!,
|
||||
primaryText: user.contact?.first_name + " " + user.contact?.last_name,
|
||||
isActive: user.uid === userUid,
|
||||
secondaryText: user.office_membership?.crpcen + " - " + user.office_membership?.name,
|
||||
}))
|
||||
id: user.uid!,
|
||||
primaryText: user.contact?.first_name + " " + user.contact?.last_name,
|
||||
isActive: user.uid === userUid,
|
||||
secondaryText: user.office?.crpcen + " - " + user.office?.name,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
/>
|
||||
|
@ -6,7 +6,8 @@ import { OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Not
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
|
||||
|
||||
import OfficeRibService from "src/common/Api/LeCoffreApi/sdk/OfficeRibService";
|
||||
|
||||
type IProps = {
|
||||
folder: OfficeFolderNotary;
|
||||
@ -19,9 +20,7 @@ export default function ContactBox(props: IProps) {
|
||||
const [ribUrl, setRibUrl] = useState<string | null>(null);
|
||||
|
||||
const notaryContact = useMemo(
|
||||
() =>
|
||||
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ??
|
||||
folder?.stakeholders![0]!.contact,
|
||||
() => folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Notaire")?.contact ?? folder?.stakeholders![0]!.contact,
|
||||
[folder],
|
||||
);
|
||||
|
||||
@ -37,9 +36,13 @@ export default function ContactBox(props: IProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!folder?.office?.uid) return;
|
||||
OfficeRib.getInstance()
|
||||
.getRibStream(folder.office.uid)
|
||||
.then((blob) => setRibUrl(URL.createObjectURL(blob)));
|
||||
OfficeRibService.getOfficeRib().then((process: any) => {
|
||||
if (process) {
|
||||
const officeRib: any = process.processData;
|
||||
const fileBlob: Blob = new Blob([officeRib.file_blob.data], { type: officeRib.file_blob.type });
|
||||
setRibUrl(URL.createObjectURL(fileBlob));
|
||||
}
|
||||
});
|
||||
}, [folder]);
|
||||
|
||||
const downloadRib = useCallback(async () => {
|
||||
|
@ -1,52 +1,178 @@
|
||||
import DragAndDrop, { IDocumentFileWithUid } from "@Front/Components/DesignSystem/DragAndDrop";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { Document } from "le-coffre-resources/dist/Customer";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
|
||||
import { FileBlob, FileData } from "@Front/Api/Entities/types";
|
||||
import WatermarkService from "@Front/Services/WatermarkService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
type IProps = {
|
||||
document: Document;
|
||||
document: any;
|
||||
customer: any;
|
||||
onChange: () => void;
|
||||
};
|
||||
|
||||
export default function DepositDocumentComponent(props: IProps) {
|
||||
const { document, onChange } = props;
|
||||
const { document, customer, onChange } = props;
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [refused_reason, setRefusedReason] = useState<string | null>(null);
|
||||
|
||||
const defaultFiles: IDocumentFileWithUid[] = useMemo(() => {
|
||||
const filesNotArchived = document.files?.filter((file) => !file.archived_at) ?? [];
|
||||
return filesNotArchived.map((file) => ({
|
||||
const filesNotArchived = document.files?.filter((file: any) => !file.archived_at) ?? [];
|
||||
return filesNotArchived.map((file: any) => ({
|
||||
id: file.uid!,
|
||||
file: new File([""], file.file_name!, { type: file.mimetype }),
|
||||
file: new File([""], file.file_name!, { type: file.file_blob.type }),
|
||||
uid: file.uid!,
|
||||
}));
|
||||
}, [document.files]);
|
||||
|
||||
const addFile = useCallback(
|
||||
(file: File) => {
|
||||
const formData = new FormData();
|
||||
const safeFileName = file.name.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
formData.append("file", file, safeFileName);
|
||||
const query = JSON.stringify({ document: { uid: document.uid } });
|
||||
formData.append("q", query);
|
||||
return Files.getInstance()
|
||||
.post(formData)
|
||||
.then(onChange)
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
|
||||
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||
async (file: File) => {
|
||||
try {
|
||||
// Add watermark to the file before processing
|
||||
const watermarkedFile = await WatermarkService.getInstance().addWatermark(file);
|
||||
|
||||
return new Promise<void>(
|
||||
(resolve: () => void) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (event.target?.result) {
|
||||
const date: Date = new Date();
|
||||
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
|
||||
|
||||
const fileName: string = `${document.document_type.name}-${customer.contact.last_name}_${strDate}.${file.name.split('.').pop()}`;
|
||||
|
||||
const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
const fileBlob: FileBlob = {
|
||||
type: watermarkedFile.type,
|
||||
data: uint8Array
|
||||
};
|
||||
|
||||
const fileData: FileData = {
|
||||
file_blob: fileBlob,
|
||||
file_name: fileName
|
||||
};
|
||||
|
||||
FileService.createFile(fileData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
const fileUid: string = processCreated.processData.uid;
|
||||
|
||||
DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
|
||||
let files: any[] = document.files;
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
files.push({ uid: fileUid });
|
||||
|
||||
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
|
||||
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(watermarkedFile);
|
||||
})
|
||||
.then(onChange)
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
|
||||
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||
} catch (error) {
|
||||
console.error('Error processing file with watermark:', error);
|
||||
// If watermarking fails, proceed with original file
|
||||
return new Promise<void>(
|
||||
(resolve: () => void) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (event.target?.result) {
|
||||
const date: Date = new Date();
|
||||
const strDate: string = `${date.getDate().toString().padStart(2, '0')}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getFullYear()}`;
|
||||
|
||||
const fileName: string = `${document.document_type.name}-${customer.contact.last_name}_${strDate}.${file.name.split('.').pop()}`;
|
||||
|
||||
const arrayBuffer: ArrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array: Uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
const fileBlob: FileBlob = {
|
||||
type: file.type,
|
||||
data: uint8Array
|
||||
};
|
||||
|
||||
const fileData: FileData = {
|
||||
file_blob: fileBlob,
|
||||
file_name: fileName
|
||||
};
|
||||
|
||||
FileService.createFile(fileData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
const fileUid: string = processCreated.processData.uid;
|
||||
|
||||
DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
|
||||
let files: any[] = document.files;
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
files.push({ uid: fileUid });
|
||||
|
||||
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.DEPOSITED }).then(() => {
|
||||
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
})
|
||||
.then(onChange)
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier uploadé avec succès!" }))
|
||||
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||
}
|
||||
},
|
||||
[document.uid, onChange],
|
||||
);
|
||||
|
||||
const deleteFile = useCallback(
|
||||
(filedUid: string) => {
|
||||
return Files.getInstance()
|
||||
.delete(filedUid)
|
||||
(fileUid: string) => {
|
||||
return new Promise<void>(
|
||||
(resolve: () => void) => {
|
||||
FileService.getFileByUid(fileUid).then((res: any) => {
|
||||
if (res) {
|
||||
FileService.updateFile(res.processId, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
|
||||
DocumentService.getDocumentByUid(document.uid!).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
|
||||
let files: any[] = document.files;
|
||||
if (!files) {
|
||||
files = [];
|
||||
}
|
||||
files = files.filter((file: any) => file.uid !== fileUid);
|
||||
|
||||
DocumentService.updateDocument(process, { files: files, document_status: EDocumentStatus.ASKED }).then(() => {
|
||||
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(onChange)
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Fichier supprimé avec succès!" }))
|
||||
.catch((error) => ToasterService.getInstance().error({ title: "Erreur !", description: error.message }));
|
||||
@ -55,7 +181,8 @@ export default function DepositDocumentComponent(props: IProps) {
|
||||
);
|
||||
|
||||
const onOpenModal = useCallback(async () => {
|
||||
const refused_reason = document.document_history?.find((history) => history.document_status === "REFUSED")?.refused_reason;
|
||||
if (document.document_status !== "REFUSED") return;
|
||||
const refused_reason = document.refused_reason;
|
||||
if (!refused_reason) return;
|
||||
setRefusedReason(refused_reason);
|
||||
setIsModalOpen(true);
|
||||
|
@ -1,5 +1,3 @@
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/DocumentsNotary";
|
||||
import FilesNotary from "@Front/Api/LeCoffreApi/Customer/FilesNotary/Files";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||
import Table from "@Front/Components/DesignSystem/Table";
|
||||
@ -8,7 +6,7 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
|
||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
// import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
import { ArrowDownTrayIcon, EyeIcon } from "@heroicons/react/24/outline";
|
||||
import { saveAs } from "file-saver";
|
||||
import JSZip from "jszip";
|
||||
@ -16,9 +14,14 @@ import { useRouter } from "next/router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Link from "next/link";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
import Customer from "le-coffre-resources/dist/Customer";
|
||||
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||
import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
const header: readonly IHead[] = [
|
||||
{
|
||||
@ -42,11 +45,31 @@ export default function ReceivedDocuments() {
|
||||
const [customer, setCustomer] = useState<Customer | null>(null);
|
||||
|
||||
const fetchFolderAndCustomer = useCallback(async () => {
|
||||
let jwt: ICustomerJwtPayload | undefined;
|
||||
if (typeof document !== "undefined") {
|
||||
jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
}
|
||||
// let jwt: ICustomerJwtPayload | undefined;
|
||||
// if (typeof document !== "undefined") {
|
||||
// jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
// }
|
||||
|
||||
// TODO: review
|
||||
LoaderService.getInstance().show();
|
||||
const folder: any = await new Promise<any>((resolve: (folder: any) => void) => {
|
||||
FolderService.getFolderByUid(folderUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
resolve(folder);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//const customer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
|
||||
const customer = folder?.customers?.[0];
|
||||
if (!customer) throw new Error("Customer not found");
|
||||
|
||||
setCustomer(customer);
|
||||
|
||||
return { folder, customer };
|
||||
|
||||
/*
|
||||
const folder = await Folders.getInstance().getByUid(folderUid as string, {
|
||||
q: {
|
||||
office: true,
|
||||
@ -76,32 +99,72 @@ export default function ReceivedDocuments() {
|
||||
setCustomer(customer);
|
||||
|
||||
return { folder, customer };
|
||||
*/
|
||||
}, [folderUid]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFolderAndCustomer();
|
||||
}, [folderUid]); // Ne dépend que de folderUid
|
||||
|
||||
// Effet séparé pour charger les documents lorsque customer change
|
||||
useEffect(() => {
|
||||
const customerUid = customer?.uid;
|
||||
if (!folderUid || !customerUid) return;
|
||||
DocumentsNotary.getInstance()
|
||||
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
|
||||
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
|
||||
}, [folderUid, customer, fetchFolderAndCustomer]);
|
||||
|
||||
const onDownload = useCallback((doc: DocumentNotary) => {
|
||||
DocumentService.getDocuments().then(async (processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let documents: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy folder.uid & customer.uid
|
||||
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.customer /*&& document.customer.uid === customerUid*/);
|
||||
|
||||
for (const document of documents) {
|
||||
if (document.files && document.files.length > 0) {
|
||||
const files: any[] = [];
|
||||
for (const file of document.files) {
|
||||
files.push((await FileService.getFileByUid(file.uid)).processData);
|
||||
}
|
||||
document.files = files;
|
||||
}
|
||||
}
|
||||
|
||||
setDocumentsNotary(documents);
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
});
|
||||
}, [folderUid, customer]);
|
||||
|
||||
const onDownload = useCallback(async (doc: any) => {
|
||||
const file = doc.files?.[0];
|
||||
if (!file || !file?.uid || !doc.uid) return;
|
||||
if (!file) return;
|
||||
|
||||
return FilesNotary.getInstance()
|
||||
.download(file.uid, doc.uid)
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.file_name ?? "file";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch((e) => console.warn(e));
|
||||
if (doc.document_status !== EDocumentNotaryStatus.DOWNLOADED) {
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
LoaderService.getInstance().show();
|
||||
DocumentService.getDocumentByUid(doc.uid).then((process: any) => {
|
||||
if (process) {
|
||||
DocumentService.updateDocument(process, { document_status: EDocumentNotaryStatus.DOWNLOADED }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve: () => void) => {
|
||||
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.file_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
resolve();
|
||||
}).catch((e) => console.warn(e));
|
||||
}, []);
|
||||
|
||||
const onDownloadAll = useCallback(async () => {
|
||||
@ -110,16 +173,28 @@ export default function ReceivedDocuments() {
|
||||
const zip = new JSZip();
|
||||
const folder = zip.folder("documents") || zip;
|
||||
|
||||
const downloadPromises = documentsNotary.map(async (doc) => {
|
||||
documentsNotary.map(async (doc: any) => {
|
||||
const file = doc.files?.[0];
|
||||
if (file && file.uid && doc.uid) {
|
||||
const blob = await FilesNotary.getInstance().download(file.uid, doc.uid);
|
||||
if (file) {
|
||||
if (doc.document_status !== EDocumentNotaryStatus.DOWNLOADED) {
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
LoaderService.getInstance().show();
|
||||
DocumentService.getDocumentByUid(doc.uid).then((process: any) => {
|
||||
if (process) {
|
||||
DocumentService.updateDocument(process, { document_status: EDocumentNotaryStatus.DOWNLOADED }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
|
||||
folder.file(file.file_name ?? "file", blob);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(downloadPromises);
|
||||
|
||||
zip.generateAsync({ type: "blob" })
|
||||
.then((blob: any) => {
|
||||
saveAs(blob, "documents.zip");
|
||||
@ -160,12 +235,9 @@ function buildRows(
|
||||
folderUid: string | string[],
|
||||
onDownloadFileNotary: (doc: DocumentNotary) => void,
|
||||
): IRowProps[] {
|
||||
console.log(documentsNotary);
|
||||
console.log(folderUid);
|
||||
|
||||
return documentsNotary.map((documentNotary) => ({
|
||||
key: documentNotary.uid ?? "",
|
||||
name: formatName(documentNotary.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_",
|
||||
name: documentNotary.files?.[0]?.file_name?.split(".")?.[0] || "_",
|
||||
sentAt: new Date(documentNotary.created_at!).toLocaleDateString(),
|
||||
// actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
|
||||
actions: {
|
||||
@ -185,7 +257,3 @@ function buildRows(
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function formatName(text: string): string {
|
||||
return text.replace(/[^a-zA-Z0-9 ]/g, "");
|
||||
}
|
||||
|
@ -3,16 +3,20 @@ import RightArrowIcon from "@Assets/Icons/right-arrow.svg";
|
||||
import Button from "@Front/Components/DesignSystem/Button";
|
||||
import FilePreview from "@Front/Components/DesignSystem/FilePreview";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { DocumentNotary, File } from "le-coffre-resources/dist/Notary";
|
||||
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||
import Image from "next/image";
|
||||
import { NextRouter, useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import { FileBlob } from "@Front/Api/Entities/types";
|
||||
|
||||
import BasePage from "../../Base";
|
||||
import classes from "./classes.module.scss";
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/DocumentsNotary";
|
||||
|
||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||
import FilesNotary from "@Front/Api/LeCoffreApi/Customer/FilesNotary/Files";
|
||||
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {};
|
||||
type IPropsClass = {
|
||||
@ -25,8 +29,8 @@ type IState = {
|
||||
isValidateModalVisible: boolean;
|
||||
refuseText: string;
|
||||
selectedFileIndex: number;
|
||||
selectedFile: File | null;
|
||||
documentNotary: DocumentNotary | null;
|
||||
selectedFile: { uid: string; file_name: string; file_blob: FileBlob } | null;
|
||||
documentNotary: any | null;
|
||||
fileBlob: Blob | null;
|
||||
isLoading: boolean;
|
||||
};
|
||||
@ -71,10 +75,10 @@ class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
|
||||
</div>
|
||||
)}
|
||||
<div className={classes["file-container"]}>
|
||||
{this.state.selectedFile.mimetype === "application/pdf" ||
|
||||
this.state.selectedFile.mimetype === "image/jpeg" ||
|
||||
this.state.selectedFile.mimetype === "image/png" ||
|
||||
this.state.selectedFile.mimetype === "image/jpg" ? (
|
||||
{this.state.selectedFile.file_blob.type === "application/pdf" ||
|
||||
this.state.selectedFile.file_blob.type === "image/jpeg" ||
|
||||
this.state.selectedFile.file_blob.type === "image/png" ||
|
||||
this.state.selectedFile.file_blob.type === "image/jpg" ? (
|
||||
<FilePreview
|
||||
href={this.state.fileBlob ? URL.createObjectURL(this.state.fileBlob) : ""}
|
||||
fileName={this.state.selectedFile.file_name}
|
||||
@ -123,16 +127,31 @@ class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
|
||||
|
||||
override async componentDidMount() {
|
||||
try {
|
||||
const documentNotary = await DocumentsNotary.getInstance().getByUid(this.props.documentUid, {
|
||||
files: true,
|
||||
folder: true,
|
||||
depositor: true,
|
||||
LoaderService.getInstance().show();
|
||||
const documentNotary: any = await new Promise<any>((resolve: (document: any) => void) => {
|
||||
DocumentService.getDocumentByUid(this.props.documentUid).then(async (process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
|
||||
if (document.files && document.files.length > 0) {
|
||||
const files: any[] = [];
|
||||
for (const file of document.files) {
|
||||
files.push((await FileService.getFileByUid(file.uid)).processData);
|
||||
}
|
||||
document.files = files;
|
||||
}
|
||||
|
||||
resolve(document);
|
||||
}
|
||||
});
|
||||
});
|
||||
LoaderService.getInstance().hide();
|
||||
|
||||
this.setState(
|
||||
{
|
||||
documentNotary,
|
||||
selectedFileIndex: 0,
|
||||
selectedFile: documentNotary.files![0]!,
|
||||
selectedFile: documentNotary.files![0] as any,
|
||||
isLoading: false,
|
||||
},
|
||||
() => {
|
||||
@ -149,8 +168,7 @@ class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
|
||||
|
||||
private async getFilePreview(): Promise<void> {
|
||||
try {
|
||||
const fileBlob: Blob = await FilesNotary.getInstance().download(this.state.selectedFile?.uid as string, this.props.documentUid);
|
||||
|
||||
const fileBlob: Blob = new Blob([this.state.selectedFile!.file_blob.data], { type: this.state.selectedFile!.file_blob.type });
|
||||
this.setState({
|
||||
fileBlob,
|
||||
});
|
||||
|
@ -1,16 +1,14 @@
|
||||
"use client";
|
||||
import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
|
||||
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
|
||||
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { DocumentNotary, type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
|
||||
import { DocumentNotary, OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import { useRouter } from "next/router";
|
||||
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
|
||||
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
||||
import DefaultCustomerDashboard from "@Front/Components/LayoutTemplates/DefaultCustomerDashboard";
|
||||
@ -21,28 +19,63 @@ import Module from "@Front/Config/Module";
|
||||
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
|
||||
import NotificationBox from "@Front/Components/DesignSystem/NotificationBox";
|
||||
import ContactBox from "./ContactBox";
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/DocumentsNotary";
|
||||
import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
|
||||
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
|
||||
|
||||
import AuthModal from "src/sdk/AuthModal";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {};
|
||||
|
||||
export default function ClientDashboard(props: IProps) {
|
||||
const router = useRouter();
|
||||
let { folderUid } = router.query;
|
||||
let { folderUid, profileUid } = router.query;
|
||||
const [documents, setDocuments] = useState<Document[] | null>(null);
|
||||
|
||||
const [customer, setCustomer] = useState<Customer | null>(null);
|
||||
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
|
||||
|
||||
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
|
||||
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
|
||||
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(true);
|
||||
|
||||
const fetchFolderAndCustomer = useCallback(async () => {
|
||||
let jwt: ICustomerJwtPayload | undefined;
|
||||
if (typeof document !== "undefined") {
|
||||
jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
}
|
||||
|
||||
// TODO: review
|
||||
LoaderService.getInstance().show();
|
||||
const { folder, customer } = await new Promise<any>((resolve) => {
|
||||
FolderService.getFolderByUid(folderUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
|
||||
const customers: any[] = folder.customers;
|
||||
const customer: any = customers.find((customer: any) => customer.uid === profileUid as string);
|
||||
if (customer) {
|
||||
resolve({ folder: folder, customer });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setCustomer(customer);
|
||||
setFolder(folder);
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
|
||||
return { folder, customer };
|
||||
|
||||
/*
|
||||
const folder = await Folders.getInstance().getByUid(folderUid as string, {
|
||||
q: {
|
||||
office: true,
|
||||
@ -73,46 +106,77 @@ export default function ClientDashboard(props: IProps) {
|
||||
setCustomer(customer);
|
||||
|
||||
return { folder, customer };
|
||||
*/
|
||||
}, [folderUid]);
|
||||
|
||||
const fetchDocuments = useCallback(
|
||||
(customerUid: string | undefined) => {
|
||||
const query: IGetDocumentsparams = {
|
||||
where: { depositor: { uid: customerUid }, folder_uid: folderUid as string },
|
||||
include: {
|
||||
files: true,
|
||||
document_history: true,
|
||||
document_type: true,
|
||||
depositor: true,
|
||||
folder: {
|
||||
include: {
|
||||
customers: {
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
async (customerUid: string | undefined) => {
|
||||
setDocuments([]);
|
||||
LoaderService.getInstance().show();
|
||||
return new Promise<void>((resolve: () => void) => {
|
||||
DocumentService.getDocuments().then(async (processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let documents: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
return Documents.getInstance()
|
||||
.get(query)
|
||||
.then((documents) => setDocuments(documents));
|
||||
// FilterBy folder.uid & depositor.uid
|
||||
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.depositor && document.depositor.uid === customerUid);
|
||||
|
||||
for (const document of documents) {
|
||||
if (document.document_type) {
|
||||
document.document_type = (await DocumentTypeService.getDocumentTypeByUid(document.document_type.uid)).processData;
|
||||
}
|
||||
|
||||
if (document.files && document.files.length > 0) {
|
||||
const files: any[] = [];
|
||||
for (const file of document.files) {
|
||||
files.push((await FileService.getFileByUid(file.uid)).processData);
|
||||
}
|
||||
document.files = files;
|
||||
}
|
||||
}
|
||||
|
||||
setDocuments(documents);
|
||||
}
|
||||
LoaderService.getInstance().hide();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
[folderUid],
|
||||
);
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
|
||||
}, [fetchDocuments, fetchFolderAndCustomer]);
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentsNotary([]);
|
||||
const customerUid = customer?.uid;
|
||||
if (!folderUid || !customerUid) return;
|
||||
DocumentsNotary.getInstance()
|
||||
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
|
||||
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
|
||||
LoaderService.getInstance().show();
|
||||
DocumentService.getDocuments().then(async (processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
let documents: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
// FilterBy folder.uid & customer.uid
|
||||
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.customer && document.customer.uid === customerUid);
|
||||
|
||||
for (const document of documents) {
|
||||
if (document.files && document.files.length > 0) {
|
||||
const files: any[] = [];
|
||||
for (const file of document.files) {
|
||||
files.push((await FileService.getFileByUid(file.uid)).processData);
|
||||
}
|
||||
document.files = files;
|
||||
}
|
||||
}
|
||||
|
||||
setDocumentsNotary(documents);
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
});
|
||||
}, [folderUid, customer?.uid]);
|
||||
|
||||
const documentsNotaryNotRead = useMemo(
|
||||
@ -147,8 +211,8 @@ export default function ClientDashboard(props: IProps) {
|
||||
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument, folder]);
|
||||
|
||||
return (
|
||||
<DefaultCustomerDashboard>
|
||||
<div className={classes["root"]}>
|
||||
<DefaultCustomerDashboard isReady={isReady}>
|
||||
{isReady && (<div className={classes["root"]}>
|
||||
<div className={classes["top"]}>
|
||||
<div className={classes["folder-info-container"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||
@ -227,6 +291,7 @@ export default function ClientDashboard(props: IProps) {
|
||||
<DepositDocumentComponent
|
||||
key={document.uid}
|
||||
document={document}
|
||||
customer={customer}
|
||||
onChange={() => fetchDocuments(customer?.uid)}
|
||||
/>
|
||||
))}
|
||||
@ -244,7 +309,16 @@ export default function ClientDashboard(props: IProps) {
|
||||
Ajouter d'autres documents
|
||||
</Button>
|
||||
{isAddDocumentModalVisible && renderBox()}
|
||||
</div>
|
||||
</div>)}
|
||||
|
||||
{isAuthModalOpen && <AuthModal
|
||||
isOpen={isAuthModalOpen}
|
||||
onClose={() => {
|
||||
setIsReady(true);
|
||||
setIsAuthModalOpen(false);
|
||||
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
|
||||
}}
|
||||
/>}
|
||||
</DefaultCustomerDashboard>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
|
||||
import OfficeRoles from "@Front/Api/LeCoffreApi/Admin/OfficeRoles/OfficeRoles";
|
||||
import Roles from "@Front/Api/LeCoffreApi/Admin/Roles/Roles";
|
||||
import Users from "@Front/Api/LeCoffreApi/Admin/Users/Users";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
||||
import Switch from "@Front/Components/DesignSystem/Switch";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultCollaboratorDashboard from "@Front/Components/LayoutTemplates/DefaultCollaboratorDashboard";
|
||||
import Module from "@Front/Config/Module";
|
||||
import User, { OfficeRole } from "le-coffre-resources/dist/Admin";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@ -18,12 +14,17 @@ import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/Dr
|
||||
import { getLabel } from "@Front/Components/DesignSystem/Dropdown";
|
||||
import SelectField from "@Front/Components/DesignSystem/Form/SelectField";
|
||||
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
|
||||
import OfficeRoleService from "src/common/Api/LeCoffreApi/sdk/OfficeRoleService";
|
||||
import RoleService from "src/common/Api/LeCoffreApi/sdk/RoleService";
|
||||
|
||||
type IProps = {};
|
||||
export default function CollaboratorInformations(props: IProps) {
|
||||
const router = useRouter();
|
||||
let { collaboratorUid } = router.query;
|
||||
|
||||
const [userSelected, setUserSelected] = useState<User | null>(null);
|
||||
const [userSelected, setUserSelected] = useState<any | null>(null);
|
||||
const [availableRoles, setAvailableRoles] = useState<IOption[]>([]);
|
||||
|
||||
const [roleModalOpened, setRoleModalOpened] = useState<boolean>(false);
|
||||
@ -35,7 +36,7 @@ export default function CollaboratorInformations(props: IProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!userSelected) return;
|
||||
setIsAdminChecked(userSelected.role?.name === "admin");
|
||||
setIsAdminChecked(userSelected.role.name === "admin");
|
||||
}, [userSelected]);
|
||||
|
||||
const handleRoleChange = useCallback((option: IOption) => {
|
||||
@ -46,60 +47,54 @@ export default function CollaboratorInformations(props: IProps) {
|
||||
const closeRoleModal = useCallback(() => {
|
||||
setRoleModalOpened(false);
|
||||
setSelectedOption({
|
||||
id: (userSelected?.office_role ? userSelected?.office_role?.uid : userSelected?.role?.uid) ?? "",
|
||||
label: userSelected?.office_role ? userSelected?.office_role?.name : "Utilisateur restreint",
|
||||
id: userSelected?.role?.uid ?? "",
|
||||
label: userSelected?.role?.name ?? "Utilisateur restreint"
|
||||
});
|
||||
}, [userSelected?.office_role, userSelected?.role?.uid]);
|
||||
}, [userSelected?.role]);
|
||||
|
||||
const changeRole = useCallback(async () => {
|
||||
await Users.getInstance().put(
|
||||
userSelected?.uid as string,
|
||||
User.hydrate<User>({
|
||||
uid: userSelected?.uid as string,
|
||||
office_role: OfficeRole.hydrate<OfficeRole>({
|
||||
uid: selectedOption?.id as string,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
setRoleModalOpened(false);
|
||||
LoaderService.getInstance().show();
|
||||
CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
CollaboratorService.updateCollaborator(process, { office_role: { uid: selectedOption?.id as string } }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
setRoleModalOpened(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [selectedOption, userSelected]);
|
||||
|
||||
const changeAdmin = useCallback(async () => {
|
||||
try {
|
||||
if (adminRoleType === "add") {
|
||||
const adminRole = await Roles.getInstance().getOne({
|
||||
where: {
|
||||
name: "admin",
|
||||
},
|
||||
});
|
||||
LoaderService.getInstance().show();
|
||||
CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
|
||||
if (process) {
|
||||
const role: any = (await RoleService.getRoles())
|
||||
.map((process: any) => process.processData)
|
||||
.filter((role: any) => role.name === "admin")[0];
|
||||
|
||||
if (!adminRole) return;
|
||||
await Users.getInstance().put(
|
||||
userSelected?.uid as string,
|
||||
User.hydrate<User>({
|
||||
uid: userSelected?.uid as string,
|
||||
office_role: undefined,
|
||||
role: adminRole,
|
||||
}),
|
||||
);
|
||||
CollaboratorService.updateCollaborator(process, { role: { uid: role.uid } }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
setAdminModalOpened(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const defaultRole = await Roles.getInstance().getOne({
|
||||
where: {
|
||||
name: "default",
|
||||
},
|
||||
});
|
||||
LoaderService.getInstance().show();
|
||||
CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
|
||||
if (process) {
|
||||
const role: any = (await RoleService.getRoles())
|
||||
.map((process: any) => process.processData)
|
||||
.filter((role: any) => role.name === "default")[0];
|
||||
|
||||
if (!defaultRole) return;
|
||||
await Users.getInstance().put(
|
||||
userSelected?.uid as string,
|
||||
User.hydrate<User>({
|
||||
uid: userSelected?.uid as string,
|
||||
office_role: undefined,
|
||||
role: defaultRole,
|
||||
}),
|
||||
);
|
||||
CollaboratorService.updateCollaborator(process, { role: { uid: role.uid } }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
setAdminModalOpened(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
setAdminModalOpened(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -113,37 +108,29 @@ export default function CollaboratorInformations(props: IProps) {
|
||||
}, []);
|
||||
|
||||
const closeAdminModal = useCallback(() => {
|
||||
setIsAdminChecked(userSelected?.role?.name === "admin" && !userSelected.office_role);
|
||||
setIsAdminChecked(userSelected?.role.name === "admin");
|
||||
setAdminModalOpened(false);
|
||||
}, [userSelected]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
if (!collaboratorUid) return;
|
||||
const user = await Users.getInstance().getByUid(collaboratorUid as string, {
|
||||
q: {
|
||||
contact: true,
|
||||
office_role: true,
|
||||
role: true,
|
||||
seats: {
|
||||
include: {
|
||||
subscription: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!user) return;
|
||||
LoaderService.getInstance().show();
|
||||
CollaboratorService.getCollaboratorByUid(collaboratorUid as string).then(async (process: any) => {
|
||||
if (process) {
|
||||
const collaborator: any = process.processData;
|
||||
|
||||
const roles = await OfficeRoles.getInstance().get();
|
||||
if (!roles) return;
|
||||
setAvailableRoles(roles.map((role) => ({ id: role.uid ?? "", label: role.name })));
|
||||
setUserSelected(user);
|
||||
setSelectedOption({
|
||||
id: (user?.office_role ? user?.office_role?.uid : user?.role?.uid) ?? "",
|
||||
label: user?.office_role ? user?.office_role?.name : "Utilisateur restreint",
|
||||
const officeRoles: any[] = (await OfficeRoleService.getOfficeRoles())
|
||||
.map((process: any) => process.processData);
|
||||
|
||||
setUserSelected(collaborator);
|
||||
setAvailableRoles(officeRoles.map((officeRole: any) => ({ id: officeRole.uid, label: officeRole.name })));
|
||||
setSelectedOption({ id: collaborator.office_role.uid, label: collaborator.office_role.name });
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, [collaboratorUid]);
|
||||
|
||||
@ -154,7 +141,7 @@ export default function CollaboratorInformations(props: IProps) {
|
||||
<Typography typo={ETypo.TITLE_H1}>
|
||||
{userSelected?.contact?.first_name + " " + userSelected?.contact?.last_name}
|
||||
</Typography>
|
||||
{userSelected && userSelected.seats?.some((seat) => new Date(seat.subscription!.end_date) >= new Date()) && (
|
||||
{userSelected && userSelected.seats?.some((seat: any) => new Date(seat.subscription!.end_date) >= new Date()) && (
|
||||
<div className={classes["subscription-active"]}>
|
||||
<div className={classes["subscription-active-dot"]} />
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_SUCCESS_600}>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import DeedTypes from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
@ -6,15 +5,22 @@ import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultDeedTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDeedTypeDashboard";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { DeedType, Office } from "le-coffre-resources/dist/Admin";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import { validateOrReject, ValidationError } from "class-validator";
|
||||
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
import Auth from "@Front/Api/Auth/IdNot";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
type IProps = {};
|
||||
export default function DeedTypesCreate(props: IProps) {
|
||||
const [hasChanged, setHasChanged] = useState<boolean>(false);
|
||||
@ -25,38 +31,66 @@ export default function DeedTypesCreate(props: IProps) {
|
||||
const onSubmitHandler = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
const jwt = JwtService.getInstance().decodeJwt();
|
||||
const deedType = DeedType.hydrate<DeedType>({
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
office: Office.hydrate<Office>({
|
||||
uid: jwt?.office_Id,
|
||||
}),
|
||||
});
|
||||
try {
|
||||
await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true });
|
||||
} catch (validationErrors: Array<ValidationError> | any) {
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
const user: any = UserStore.instance.getUser();
|
||||
if (!user) {
|
||||
console.error("DeedTypesCreate: User not found - user is null or undefined");
|
||||
return;
|
||||
}
|
||||
const res = await Auth.getInstance().getOfficeProcessByIdNot();
|
||||
if (!res.success) {
|
||||
console.error("DeedTypesCreate: Office not found - office is null or undefined");
|
||||
return;
|
||||
}
|
||||
const officeId: string | undefined = res.data.processId;
|
||||
const officeIdNot: string | undefined = res.data.processData['idNot'];
|
||||
if (!officeId || !officeIdNot) {
|
||||
console.error("DeedTypesCreate: officeId or officeIdNot is undefined - office.processData.idNot is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
const deedTypeCreated = await DeedTypes.getInstance().post(
|
||||
DeedType.hydrate<DeedType>({
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
office: Office.hydrate<Office>({
|
||||
uid: jwt?.office_Id,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
// TODO: We should update the type definition to be able to use validation again
|
||||
// const deedType = DeedType.hydrate<DeedType>({
|
||||
// name: values["name"],
|
||||
// description: values["description"],
|
||||
// office: Office.hydrate<Office>({
|
||||
// idNot: officeId,
|
||||
// }),
|
||||
// });
|
||||
// try {
|
||||
// await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true });
|
||||
// } catch (validationErrors: Array<ValidationError> | any) {
|
||||
// console.log("validationErrors", validationErrors);
|
||||
// setValidationError(Array.isArray(validationErrors) ? validationErrors : []);
|
||||
// return;
|
||||
// }
|
||||
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeCreated.uid!),
|
||||
);
|
||||
const deedTypeData: any = {
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
office: {
|
||||
uid: officeId,
|
||||
idNot: officeIdNot,
|
||||
}
|
||||
};
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
DeedTypeService.createDeedType(deedTypeData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
ToasterService.getInstance().success({
|
||||
title: "Succès !",
|
||||
description: "Type d'acte créé avec succès"
|
||||
});
|
||||
const deedTypeUid = processCreated.processId;
|
||||
// Remove ':0' suffix from processId for URL navigation
|
||||
const urlId = idAsUrl(deedTypeUid);
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", urlId),
|
||||
);
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
} catch (validationErrors: Array<ValidationError> | any) {
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
setValidationError(Array.isArray(validationErrors) ? validationErrors : []);
|
||||
return;
|
||||
}
|
||||
},
|
||||
@ -93,12 +127,12 @@ export default function DeedTypesCreate(props: IProps) {
|
||||
<TextField
|
||||
name="name"
|
||||
placeholder="Nom de l'acte"
|
||||
validationError={validationError.find((error) => error.property === "name")}
|
||||
validationError={Array.isArray(validationError) ? validationError.find((error) => error.property === "name") : undefined}
|
||||
/>
|
||||
<TextAreaField
|
||||
name="description"
|
||||
placeholder="Description"
|
||||
validationError={validationError.find((error) => error.property === "description")}
|
||||
validationError={Array.isArray(validationError) ? validationError.find((error) => error.property === "description") : undefined}
|
||||
/>
|
||||
<div className={classes["buttons-container"]}>
|
||||
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={onCancel}>
|
||||
|
@ -14,6 +14,11 @@ import { ValidationError } from "class-validator";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
export default function DeedTypesEdit() {
|
||||
const router = useRouter();
|
||||
let { deedTypeUid } = router.query;
|
||||
@ -27,12 +32,15 @@ export default function DeedTypesEdit() {
|
||||
setHasChanged(false);
|
||||
async function getDeedType() {
|
||||
if (!deedTypeUid) return;
|
||||
const deedType = await DeedTypes.getInstance().getByUid(deedTypeUid as string, {
|
||||
q: {
|
||||
document_types: true,
|
||||
},
|
||||
LoaderService.getInstance().show();
|
||||
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls
|
||||
const processId = idAsProcessId(deedTypeUid as string);
|
||||
MessageBus.getInstance().getProcessData(processId).then((processData: any) => {
|
||||
if (processData) {
|
||||
setDeedTypeSelected(processData);
|
||||
}
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
setDeedTypeSelected(deedType);
|
||||
}
|
||||
|
||||
getDeedType();
|
||||
@ -42,42 +50,52 @@ export default function DeedTypesEdit() {
|
||||
setIsConfirmModalVisible(false);
|
||||
}, []);
|
||||
|
||||
const onSubmitHandler = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string | undefined }) => {
|
||||
const deedType = DeedType.hydrate<DeedType>({
|
||||
uid: deedTypeUid as string,
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
});
|
||||
try {
|
||||
await deedType.validateOrReject?.({ groups: ["updateDeedType"], forbidUnknownValues: true });
|
||||
} catch (validationErrors: Array<ValidationError> | any) {
|
||||
if (!Array.isArray(validationErrors)) return;
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await DeedTypes.getInstance().put(
|
||||
deedTypeUid as string,
|
||||
DeedType.hydrate<DeedType>({
|
||||
uid: deedTypeUid as string,
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
}),
|
||||
);
|
||||
const onSubmitHandler = async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string | undefined }) => {
|
||||
// const deedType = DeedType.hydrate<DeedType>({
|
||||
// name: values["name"],
|
||||
// description: values["description"],
|
||||
// });
|
||||
|
||||
// try {
|
||||
// await deedType.validateOrReject?.({ groups: ["updateDeedType"], forbidUnknownValues: true });
|
||||
// } catch (validationErrors: Array<ValidationError> | any) {
|
||||
// if (!Array.isArray(validationErrors)) return;
|
||||
// setValidationError(validationErrors as ValidationError[]);
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
LoaderService.getInstance().show();
|
||||
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls
|
||||
const processId = idAsProcessId(deedTypeUid as string);
|
||||
|
||||
const process = await MessageBus.getInstance().getProcessData(processId);
|
||||
if (process) {
|
||||
console.log('process', process);
|
||||
// New data
|
||||
const newData: any = {
|
||||
name: values["name"],
|
||||
description: values["description"]
|
||||
};
|
||||
|
||||
// Merge process data with new data & update process
|
||||
process[processId].name = newData.name;
|
||||
process[processId].description = newData.description;
|
||||
await DeedTypeService.updateDeedType(processId, newData);
|
||||
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string),
|
||||
);
|
||||
} catch (validationErrors) {
|
||||
if (!Array.isArray(validationErrors)) return;
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[deedTypeUid, router],
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error updating deed type:', error);
|
||||
// Handle error appropriately
|
||||
} finally {
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
};
|
||||
|
||||
const onFieldChange = useCallback((name: string, field: any) => {
|
||||
setHasChanged(true);
|
||||
|
@ -1,7 +1,5 @@
|
||||
import ChevronIcon from "@Assets/Icons/chevron.svg";
|
||||
import PenICon from "@Assets/Icons/pen.svg";
|
||||
import DeedTypes from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
@ -20,6 +18,12 @@ import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
import { idAsProcessId } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
type IProps = {};
|
||||
export default function DeedTypesInformations(props: IProps) {
|
||||
const router = useRouter();
|
||||
@ -48,42 +52,75 @@ export default function DeedTypesInformations(props: IProps) {
|
||||
setIsSaveModalOpened(false);
|
||||
}, []);
|
||||
|
||||
const deleteDeedType = useCallback(async () => {
|
||||
await DeedTypes.getInstance().put(
|
||||
deedTypeUid as string,
|
||||
DeedType.hydrate<DeedType>({
|
||||
uid: deedTypeUid as string,
|
||||
archived_at: new Date(),
|
||||
}),
|
||||
);
|
||||
router.push(Module.getInstance().get().modules.pages.DeedTypes.props.path);
|
||||
}, [deedTypeUid, router]);
|
||||
const deleteDeedType = async () => {
|
||||
LoaderService.getInstance().show();
|
||||
try {
|
||||
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls
|
||||
const processId = idAsProcessId(deedTypeUid as string);
|
||||
const process = await MessageBus.getInstance().getProcessData(processId);
|
||||
|
||||
if (process && process[processId]) {
|
||||
// New data
|
||||
const newData: any = {
|
||||
isDeleted: 'true',
|
||||
archived_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Merge process data with new data & update process
|
||||
process[processId].isDeleted = newData.isDeleted;
|
||||
process[processId].archived_at = newData.archived_at;
|
||||
await DeedTypeService.updateDeedType(processId, newData);
|
||||
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DeedTypes.props.path
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting deed type:', error);
|
||||
} finally {
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getDeedType() {
|
||||
if (!deedTypeUid) return;
|
||||
const deedType = await DeedTypes.getInstance().getByUid(deedTypeUid as string, {
|
||||
q: {
|
||||
document_types: true,
|
||||
},
|
||||
});
|
||||
setDeedTypeSelected(deedType);
|
||||
|
||||
if (!deedType.document_types) return;
|
||||
const documentsOptions: IOption[] = deedType.document_types
|
||||
?.map((documentType) => {
|
||||
return {
|
||||
label: documentType.name,
|
||||
id: documentType.uid ?? "",
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
setSelectedDocuments(documentsOptions);
|
||||
setSelectedDocuments([]);
|
||||
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls
|
||||
const processId = idAsProcessId(deedTypeUid as string);
|
||||
MessageBus.getInstance().getProcessData(processId).then((process: any) => {
|
||||
if (process) {
|
||||
console.log('[DeedTypesInformations] process', process);
|
||||
const deedType: any = process;
|
||||
setDeedTypeSelected(deedType);
|
||||
|
||||
if (!deedType.document_types) return;
|
||||
const documentsOptions: IOption[] = deedType.document_types
|
||||
?.map((documentType: any) => {
|
||||
return {
|
||||
label: documentType.name,
|
||||
id: documentType.uid ?? "",
|
||||
};
|
||||
})
|
||||
.sort((a: any, b: any) => a.label.localeCompare(b.label));
|
||||
setSelectedDocuments(documentsOptions);
|
||||
} else {
|
||||
console.warn('[DeedTypesInformations] process not found:', processId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getDocuments() {
|
||||
const documents = await DocumentTypes.getInstance().get({});
|
||||
setAvailableDocuments(documents);
|
||||
function getDocuments() {
|
||||
setAvailableDocuments([]);
|
||||
DocumentTypeService.getDocumentTypes((processes: Record<string, any>) => {
|
||||
const documents = Object.values(processes);
|
||||
if (documents.length) {
|
||||
setAvailableDocuments(documents);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDocuments();
|
||||
@ -98,11 +135,35 @@ export default function DeedTypesInformations(props: IProps) {
|
||||
);
|
||||
|
||||
const saveDocumentTypes = useCallback(async () => {
|
||||
await DeedTypes.getInstance().put(deedTypeUid as string, {
|
||||
uid: deedTypeUid as string,
|
||||
document_types: selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })),
|
||||
});
|
||||
closeSaveModal();
|
||||
LoaderService.getInstance().show();
|
||||
// deedTypeUid comes from URL without ':0' suffix, add it back for API calls
|
||||
const processId = idAsProcessId(deedTypeUid as string);
|
||||
console.log('[DeedTypesInformations] processId', processId);
|
||||
const deedType = (await MessageBus.getInstance().getProcessData(processId))[processId];
|
||||
if (deedType) {
|
||||
console.log('[DeedTypesInformations] deedType', deedType);
|
||||
|
||||
let document_types: any[] = deedType['document_types'];
|
||||
if (!document_types) {
|
||||
document_types = [];
|
||||
}
|
||||
selectedDocuments.map((selectedDocument: any) => selectedDocument.id as string)
|
||||
.forEach((uid: any) => document_types.push(availableDocuments.find((document: any) => document.uid === uid)));
|
||||
|
||||
// New data
|
||||
const newData: any = {
|
||||
document_types: document_types
|
||||
};
|
||||
|
||||
console.log('[DeedTypesInformations] newData', newData);
|
||||
|
||||
await DeedTypeService.updateDeedType(processId, newData);
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
closeSaveModal();
|
||||
} else {
|
||||
console.warn('[DeedTypesInformations] process not found:', processId);
|
||||
}
|
||||
}, [closeSaveModal, deedTypeUid, selectedDocuments]);
|
||||
|
||||
const onDocumentChangeHandler = useCallback((options: IOption[] | null) => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { validateOrReject, ValidationError } from "class-validator";
|
||||
@ -14,35 +14,85 @@ import { useCallback, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
type IProps = {};
|
||||
export default function DocumentTypesCreate(props: IProps) {
|
||||
const [validationError, setValidationError] = useState<ValidationError[]>([]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
router.push(Module.getInstance().get().modules.pages.DocumentTypes.props.path);
|
||||
}, [router]);
|
||||
|
||||
const onSubmitHandler = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
const jwt = JwtService.getInstance().decodeJwt();
|
||||
if (!jwt) return;
|
||||
const office = Office.hydrate<Office>({
|
||||
uid: jwt.office_Id,
|
||||
});
|
||||
const documentToCreate = DocumentType.hydrate<DocumentType>({
|
||||
...values,
|
||||
office: office,
|
||||
});
|
||||
await validateOrReject(documentToCreate, { groups: ["createDocumentType"] });
|
||||
const documentTypeCreated = await DocumentTypes.getInstance().post(documentToCreate);
|
||||
const user: any = UserStore.instance.getUser();
|
||||
if (!user) {
|
||||
console.error("DocumentTypesCreate: User not found - user is null or undefined");
|
||||
return;
|
||||
}
|
||||
const office = UserStore.instance.getOffice();
|
||||
if (!office) {
|
||||
console.error("DocumentTypesCreate: office not found - office is undefined or null");
|
||||
return;
|
||||
}
|
||||
const officeId = office.processId;
|
||||
const officeIdNot = office.processData.idNot;
|
||||
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeCreated.uid!),
|
||||
);
|
||||
// const documentFormModel = DocumentType.hydrate<DocumentType>({
|
||||
// ...values,
|
||||
// office: Office.hydrate<Office>({
|
||||
// uid: officeId,
|
||||
// })
|
||||
// });
|
||||
// await validateOrReject(documentFormModel, { groups: ["createDocumentType"] });
|
||||
|
||||
const documentTypeData: any = {
|
||||
...values,
|
||||
office: {
|
||||
uid: officeId,
|
||||
idNot: officeIdNot,
|
||||
}
|
||||
};
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
try {
|
||||
const processCreated = await DocumentTypeService.createDocumentType(documentTypeData, DEFAULT_VALIDATOR_ID);
|
||||
ToasterService.getInstance().success({
|
||||
title: "Succès !",
|
||||
description: "Type de document créé avec succès"
|
||||
});
|
||||
const documentTypeUid = idAsUrl(processCreated.processId);
|
||||
if (!documentTypeUid) {
|
||||
console.error("DocumentTypesCreate: documentTypeUid is undefined - processCreated.processId is missing");
|
||||
return;
|
||||
}
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeUid),
|
||||
);
|
||||
} catch (apiError) {
|
||||
ToasterService.getInstance().error({
|
||||
title: "Erreur !",
|
||||
description: "Une erreur est survenue lors de la création du type de document"
|
||||
});
|
||||
console.error("Document type creation error:", apiError);
|
||||
} finally {
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Array) {
|
||||
setValidationError(e);
|
||||
}
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
},
|
||||
[router],
|
||||
@ -71,7 +121,7 @@ export default function DocumentTypesCreate(props: IProps) {
|
||||
validationError={validationError.find((error) => error.property === "public_description")}
|
||||
/>
|
||||
<div className={classes["buttons-container"]}>
|
||||
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
|
||||
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={handleCancel}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit">Créer le document</Button>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
@ -13,6 +12,9 @@ import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
export default function DocumentTypesEdit() {
|
||||
const router = useRouter();
|
||||
let { documentTypeUid } = router.query;
|
||||
@ -23,10 +25,17 @@ export default function DocumentTypesEdit() {
|
||||
useEffect(() => {
|
||||
async function getDocumentType() {
|
||||
if (!documentTypeUid) return;
|
||||
const documentType = await DocumentTypes.getInstance().getByUid(documentTypeUid as string, {
|
||||
_count: true,
|
||||
LoaderService.getInstance().show();
|
||||
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const documentType: any = {
|
||||
...process.processData,
|
||||
processId: process.processId
|
||||
};
|
||||
setDocumentTypeSelected(documentType);
|
||||
}
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
setDocumentTypeSelected(documentType);
|
||||
}
|
||||
|
||||
getDocumentType();
|
||||
@ -46,16 +55,22 @@ export default function DocumentTypesEdit() {
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
|
||||
const documentTypeUpdated = await DocumentTypes.getInstance().put(documentTypeUid as string, documentToUpdate);
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace(
|
||||
"[uid]",
|
||||
documentTypeUpdated.uid ?? "",
|
||||
),
|
||||
);
|
||||
LoaderService.getInstance().show();
|
||||
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
DocumentTypeService.updateDocumentType(process, values).then(() => {
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace(
|
||||
"[uid]",
|
||||
documentTypeUid as string ?? "",
|
||||
)
|
||||
);
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (validationErrors: Array<ValidationError> | any) {
|
||||
if (!Array.isArray(validationErrors)) return;
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import ChevronIcon from "@Assets/Icons/chevron.svg";
|
||||
import PenICon from "@Assets/Icons/pen.svg";
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
|
||||
import Module from "@Front/Config/Module";
|
||||
@ -13,6 +12,8 @@ import { useEffect, useState } from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
|
||||
export default function DocumentTypesInformations() {
|
||||
const router = useRouter();
|
||||
let { documentTypeUid } = router.query;
|
||||
@ -21,12 +22,24 @@ export default function DocumentTypesInformations() {
|
||||
|
||||
useEffect(() => {
|
||||
async function getDocument() {
|
||||
if (!documentTypeUid) return;
|
||||
const document = await DocumentTypes.getInstance().getByUid(documentTypeUid as string, {
|
||||
_count: true,
|
||||
if (!documentTypeUid) {
|
||||
console.log('DocumentTypesInformations: documentTypeUid is not available yet');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = {
|
||||
...process.processData,
|
||||
processId: process.processId
|
||||
};
|
||||
setDocumentSelected(document);
|
||||
} else {
|
||||
console.log('DocumentTypesInformations: No process found for processId:', documentTypeUid);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('DocumentTypesInformations: Error fetching document:', error);
|
||||
});
|
||||
if (!document) return;
|
||||
setDocumentSelected(document);
|
||||
}
|
||||
|
||||
getDocument();
|
||||
@ -66,13 +79,15 @@ export default function DocumentTypesInformations() {
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes["right"]}>
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", documentSelected?.uid ?? "")}
|
||||
className={classes["edit-icon-container"]}>
|
||||
<Image src={PenICon} alt="edit informations" />
|
||||
</Link>
|
||||
{(documentSelected as any)?.processId && (
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", (documentSelected as any).processId)}
|
||||
className={classes["edit-icon-container"]}>
|
||||
<Image src={PenICon} alt="edit informations" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||
@ -9,9 +7,10 @@ import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import { ValidationError } from "class-validator";
|
||||
import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
|
||||
import { Contact, Customer, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { Contact, Customer } from "le-coffre-resources/dist/Notary";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||
@ -19,6 +18,11 @@ import classes from "./classes.module.scss";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "../../../../Config/AppConstants";
|
||||
|
||||
enum ESelectedOption {
|
||||
EXISTING_CUSTOMER = "existing_customer",
|
||||
NEW_CUSTOMER = "new_customer",
|
||||
@ -63,34 +67,64 @@ export default function AddClientToFolder(props: IProps) {
|
||||
values["cell_phone_number"] = "+33" + values["cell_phone_number"].substring(1);
|
||||
}
|
||||
}
|
||||
const contactToCreate = Contact.hydrate<Customer>(values);
|
||||
await contactToCreate.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
|
||||
const contactFormModel = Contact.hydrate<Customer>(values);
|
||||
await contactFormModel.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
|
||||
} catch (validationErrors) {
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const customer: Customer = await Customers.getInstance().post({
|
||||
contact: values,
|
||||
// TODO: review
|
||||
const customerData: any = {
|
||||
contact: values
|
||||
};
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
CustomerService.createCustomer(customerData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
FolderService.getFolderByUid(folderUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const customers: any[] = [];
|
||||
for (const customerUid of process.processData.customers.map((customer: any) => customer.uid)) {
|
||||
customers.push({ uid: customerUid });
|
||||
}
|
||||
customers.push({ uid: processCreated.processData.uid });
|
||||
|
||||
FolderService.updateFolder(process, { customers: customers }).then(() => {
|
||||
ToasterService.getInstance().success({
|
||||
title: "Succès !",
|
||||
description: "Client ajouté avec succès au dossier"
|
||||
});
|
||||
router.push(`/folders/${folderUid}`);
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!customer.uid) return;
|
||||
customersToLink?.push({ uid: customer.uid } as Partial<Customer>);
|
||||
} catch (backError) {
|
||||
if (!Array.isArray(backError)) return;
|
||||
setValidationError(backError as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoaderService.getInstance().show();
|
||||
FolderService.getFolderByUid(folderUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const customers: any[] = [];
|
||||
for (const customerUid of customersToLink.map((customer: any) => customer.uid)) {
|
||||
customers.push({ uid: customerUid });
|
||||
}
|
||||
|
||||
if (customersToLink) {
|
||||
const body = OfficeFolder.hydrate<OfficeFolder>({
|
||||
customers: customersToLink.map((customer) => {
|
||||
return Customer.hydrate<Customer>(customer);
|
||||
}),
|
||||
FolderService.updateFolder(process, { customers: customers }).then(() => {
|
||||
ToasterService.getInstance().success({
|
||||
title: "Succès !",
|
||||
description: selectedCustomers.length > 1 ? "Clients associés avec succès au dossier" : "Client associé avec succès au dossier"
|
||||
});
|
||||
router.push(`/folders/${folderUid}`);
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
await Folders.getInstance().put(folderUid as string, body);
|
||||
router.push(`/folders/${folderUid}`);
|
||||
}
|
||||
},
|
||||
[existingCustomers, folderUid, router, selectedCustomers, selectedOption],
|
||||
@ -98,23 +132,22 @@ export default function AddClientToFolder(props: IProps) {
|
||||
|
||||
const getFolderPreSelectedCustomers = useCallback(
|
||||
async (folderUid: string): Promise<IOption[] | undefined> => {
|
||||
const query = {
|
||||
q: {
|
||||
customers: {
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let preExistingCustomers: IOption[] = [];
|
||||
try {
|
||||
const folder = await Folders.getInstance().getByUid(folderUid, query);
|
||||
preExistingCustomers = folder.customers!.map((customer) => {
|
||||
return {
|
||||
label: customer.contact?.first_name + " " + customer.contact?.last_name,
|
||||
id: customer.uid ?? "",
|
||||
};
|
||||
preExistingCustomers = await new Promise(resolve => {
|
||||
FolderService.getFolderByUid(folderUid as string).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
const preExistingCustomers: IOption[] = folder.customers
|
||||
.map((customer: any) => {
|
||||
return {
|
||||
label: customer.contact?.first_name + " " + customer.contact?.last_name,
|
||||
id: customer.uid ?? "",
|
||||
};
|
||||
});
|
||||
resolve(preExistingCustomers);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
router.push(Module.getInstance().get().modules.pages["404"].props.path);
|
||||
@ -126,25 +159,30 @@ export default function AddClientToFolder(props: IProps) {
|
||||
);
|
||||
|
||||
const loadCustomers = useCallback(async () => {
|
||||
const query = {};
|
||||
const availableCustomers = await Customers.getInstance().get(query);
|
||||
let preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string);
|
||||
const existingCustomers = preExistingCustomers ?? [];
|
||||
LoaderService.getInstance().show();
|
||||
CustomerService.getCustomers().then(async (processes: any[]) => {
|
||||
const availableCustomers: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
existingCustomers.forEach((customer) => {
|
||||
const index = availableCustomers.findIndex((availableCustomer) => availableCustomer.uid === customer.id);
|
||||
if (index !== -1) availableCustomers.splice(index, 1);
|
||||
const preExistingCustomers: IOption[] | undefined = await getFolderPreSelectedCustomers(folderUid as string);
|
||||
|
||||
const existingCustomers = preExistingCustomers ?? [];
|
||||
existingCustomers.forEach((customer) => {
|
||||
const index = availableCustomers.findIndex((availableCustomer) => availableCustomer.uid === customer.id);
|
||||
if (index !== -1) availableCustomers.splice(index, 1);
|
||||
});
|
||||
|
||||
let selectedOption = ESelectedOption.EXISTING_CUSTOMER;
|
||||
if (availableCustomers.length === 0) {
|
||||
selectedOption = ESelectedOption.NEW_CUSTOMER;
|
||||
}
|
||||
|
||||
setAvailableCustomers(availableCustomers);
|
||||
setExistingCustomers(existingCustomers);
|
||||
setIsLoaded(true);
|
||||
setSelectedOption(selectedOption);
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
|
||||
let selectedOption = ESelectedOption.EXISTING_CUSTOMER;
|
||||
if (availableCustomers.length === 0) {
|
||||
selectedOption = ESelectedOption.NEW_CUSTOMER;
|
||||
}
|
||||
|
||||
setAvailableCustomers(availableCustomers);
|
||||
setExistingCustomers(existingCustomers);
|
||||
setIsLoaded(true);
|
||||
setSelectedOption(selectedOption);
|
||||
}, [folderUid, getFolderPreSelectedCustomers]);
|
||||
|
||||
const getSelectedOptions = useCallback((): IOption[] => {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds";
|
||||
import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
|
||||
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||
import AutocompleteMultiSelectField from "@Front/Components/DesignSystem/Form/AutocompleteMultiSelectField";
|
||||
import { IOption as IFormOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||
@ -7,11 +5,16 @@ import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||
import { DocumentType, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import DocumentTypeService from "src/common/Api/LeCoffreApi/sdk/DocumentTypeService";
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
type IProps = {
|
||||
isCreateDocumentModalVisible: boolean;
|
||||
closeModal: () => void;
|
||||
@ -29,20 +32,25 @@ export default function ParameterDocuments(props: IProps) {
|
||||
const [formattedOptions, setFormattedOptions] = useState<IOption[]>([]);
|
||||
|
||||
const getAvailableDocuments = useCallback(async () => {
|
||||
const documents = await DocumentTypes.getInstance().get({});
|
||||
DocumentTypeService.getDocumentTypes().then((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
const documents: any[] = processes.map((process: any) => process.processData);
|
||||
|
||||
const formattedOptions: IOption[] = documents
|
||||
.filter((document) => {
|
||||
return !props.folder.deed?.document_types?.some((documentType) => documentType.uid === document.uid);
|
||||
})
|
||||
.map((document) => {
|
||||
return {
|
||||
label: document.name,
|
||||
id: document.uid ?? "",
|
||||
};
|
||||
});
|
||||
formattedOptions.sort((a, b) => (a.label > b.label ? 1 : -1));
|
||||
setFormattedOptions(formattedOptions);
|
||||
const formattedOptions: IOption[] = documents
|
||||
.filter((document) => {
|
||||
return !props.folder.deed?.document_types?.some((documentType) => documentType.uid === document.uid);
|
||||
})
|
||||
.map((document) => {
|
||||
return {
|
||||
label: document.name,
|
||||
id: document.uid ?? "",
|
||||
};
|
||||
});
|
||||
|
||||
formattedOptions.sort((a, b) => (a.label > b.label ? 1 : -1));
|
||||
setFormattedOptions(formattedOptions);
|
||||
}
|
||||
});
|
||||
}, [props.folder.deed?.document_types]);
|
||||
|
||||
const onVisibleDescriptionChange = (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||
@ -68,18 +76,44 @@ export default function ParameterDocuments(props: IProps) {
|
||||
const addDocument = useCallback(async () => {
|
||||
if (addOrEditDocument === "add") {
|
||||
try {
|
||||
const documentType = await DocumentTypes.getInstance().post({
|
||||
LoaderService.getInstance().show();
|
||||
|
||||
const documentTypeData: any = {
|
||||
name: documentName,
|
||||
private_description: visibleDescription,
|
||||
office: {
|
||||
uid: props.folder.office!.uid!,
|
||||
},
|
||||
public_description: visibleDescription,
|
||||
};
|
||||
|
||||
const documentType: any = await new Promise<any>((resolve: (documentType: any) => void) => {
|
||||
DocumentTypeService.createDocumentType(documentTypeData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
|
||||
const documentType: any = processCreated.processData;
|
||||
resolve(documentType);
|
||||
});
|
||||
});
|
||||
|
||||
const oldDocumentsType = props.folder.deed?.document_types!;
|
||||
await Deeds.getInstance().put(props.folder.deed?.uid!, {
|
||||
document_types: [...oldDocumentsType, documentType],
|
||||
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then(async (process: any) => {
|
||||
if (process) {
|
||||
// New data
|
||||
const newData: any = {
|
||||
document_types: [
|
||||
...oldDocumentsType.map((document: any) => ({ uid: document.uid })),
|
||||
{ uid: documentType.uid }
|
||||
]
|
||||
};
|
||||
|
||||
// Merge process data with new data & update process
|
||||
process.processData.document_types = newData.document_types;
|
||||
await DeedTypeService.updateDeedType(process, newData);
|
||||
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create a new document type in the format expected by the parent component
|
||||
@ -92,29 +126,52 @@ export default function ParameterDocuments(props: IProps) {
|
||||
if (props.onDocumentsUpdated) {
|
||||
props.onDocumentsUpdated([newDocumentType]);
|
||||
}
|
||||
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
handleClose();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
LoaderService.getInstance().show();
|
||||
|
||||
const oldDocumentsType = props.folder.deed?.document_types!;
|
||||
await Deeds.getInstance().put(props.folder.deed?.uid!, {
|
||||
document_types: [
|
||||
...oldDocumentsType,
|
||||
...selectedDocuments.map((document) => DocumentType.hydrate<DocumentType>({ uid: document.id as string })),
|
||||
],
|
||||
await new Promise<void>((resolve: () => void) => {
|
||||
DeedTypeService.getDeedTypeByUid(props.folder.deed?.deed_type?.uid!).then(async (process: any) => {
|
||||
if (process) {
|
||||
// New data
|
||||
const newData: any = {
|
||||
document_types: [
|
||||
...oldDocumentsType.map((document: any) => ({ uid: document.uid })),
|
||||
...selectedDocuments.map((document: any) => ({ uid: document.id as string }))
|
||||
]
|
||||
};
|
||||
|
||||
// Merge process data with new data & update process
|
||||
process.processData.document_types = newData.document_types;
|
||||
await DeedTypeService.updateDeedType(process, newData);
|
||||
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Get the full document details for the selected documents
|
||||
const documentsById = await Promise.all(
|
||||
selectedDocuments.map(async (doc) => {
|
||||
const fullDoc = await DocumentTypes.getInstance().getByUid(doc.id as string);
|
||||
const documentType: any = await new Promise<any>((resolve: (documentType: any) => void) => {
|
||||
DocumentTypeService.getDocumentTypeByUid(doc.id as string).then((process: any) => {
|
||||
if (process) {
|
||||
const documentType: any = process.processData;
|
||||
resolve(documentType);
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
label: fullDoc.name!,
|
||||
value: fullDoc.uid!,
|
||||
description: fullDoc.private_description!,
|
||||
label: documentType.name!,
|
||||
value: documentType.uid!,
|
||||
description: documentType.private_description!,
|
||||
} as IFormOption;
|
||||
})
|
||||
);
|
||||
@ -122,7 +179,8 @@ export default function ParameterDocuments(props: IProps) {
|
||||
if (props.onDocumentsUpdated) {
|
||||
props.onDocumentsUpdated(documentsById);
|
||||
}
|
||||
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
handleClose();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
@ -8,6 +6,7 @@ import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useCallback, useEffect, useState, useRef } from "react";
|
||||
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||
@ -16,6 +15,11 @@ import ParameterDocuments from "./ParameterDocuments";
|
||||
import { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
|
||||
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
export default function AskDocuments() {
|
||||
const router = useRouter();
|
||||
let { folderUid, customerUid } = router.query;
|
||||
@ -55,26 +59,38 @@ export default function AskDocuments() {
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
// TODO: review
|
||||
LoaderService.getInstance().show();
|
||||
const documentAsked: [] = values["document_types"] as [];
|
||||
for (let i = 0; i < documentAsked.length; i++) {
|
||||
await Documents.getInstance().post({
|
||||
const documentTypeUid = documentAsked[i];
|
||||
if (!documentTypeUid) continue;
|
||||
|
||||
const documentData: any = {
|
||||
folder: {
|
||||
uid: folderUid,
|
||||
uid: folderUid as string,
|
||||
},
|
||||
depositor: {
|
||||
uid: customerUid,
|
||||
uid: customerUid as string,
|
||||
},
|
||||
document_type: {
|
||||
uid: documentAsked[i],
|
||||
uid: documentTypeUid
|
||||
},
|
||||
});
|
||||
document_status: EDocumentStatus.ASKED,
|
||||
file_uid: null,
|
||||
};
|
||||
|
||||
await DocumentService.createDocument(documentData, DEFAULT_VALIDATOR_ID);
|
||||
}
|
||||
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
|
||||
);
|
||||
FolderService.refreshFolderByUid(folderUid as string).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@ -93,7 +109,7 @@ export default function AskDocuments() {
|
||||
|
||||
// If those UIDs are already asked, filter them to not show them in the list and only
|
||||
// show the documents that are not asked yet
|
||||
const documentTypes = folder.deed!.document_types!.filter((documentType) => {
|
||||
const documentTypes = folder.deed?.document_types?.filter((documentType) => {
|
||||
if (userDocumentTypesUids.includes(documentType!.uid!)) return false;
|
||||
return true;
|
||||
});
|
||||
@ -119,25 +135,15 @@ export default function AskDocuments() {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const folder = await Folders.getInstance().getByUid(folderUid as string, {
|
||||
q: {
|
||||
deed: {
|
||||
include: {
|
||||
document_types: true,
|
||||
},
|
||||
},
|
||||
office: true,
|
||||
documents: {
|
||||
include: {
|
||||
depositor: true,
|
||||
document_type: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
LoaderService.getInstance().show();
|
||||
FolderService.getFolderByUid(folderUid as string).then(async (process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
setFolder(folder);
|
||||
setDocumentTypes(await getAvailableDocuments(folder));
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
});
|
||||
if (!folder) return;
|
||||
setFolder(folder);
|
||||
setDocumentTypes(await getAvailableDocuments(folder));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -13,9 +13,12 @@ import BasePage from "../../Base";
|
||||
import classes from "./classes.module.scss";
|
||||
import Customer from "le-coffre-resources/dist/Customer";
|
||||
import Note from "le-coffre-resources/dist/Customer/Note";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
import Notes from "@Front/Api/LeCoffreApi/Customer/Notes/Notes";
|
||||
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
|
||||
import NoteService from "src/common/Api/LeCoffreApi/sdk/NoteService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import { DEFAULT_VALIDATOR_ID } from "@Front/Config/AppConstants";
|
||||
|
||||
type IProps = {};
|
||||
|
||||
@ -76,9 +79,29 @@ class CreateCustomerNoteClass extends BasePage<IPropsClass, IState> {
|
||||
}
|
||||
|
||||
public override async componentDidMount() {
|
||||
/* TODO: review
|
||||
// const note = await Notes.getInstance().getByUid(this.props.noteUid, query);
|
||||
const folder = await Folders.getInstance().getByUid(this.props.folderUid, { note: true });
|
||||
const customer = await Customers.getInstance().getByUid(this.props.customerUid);
|
||||
*/
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
|
||||
const folder: any = await FolderService.getFolderByUid(this.props.folderUid).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
return folder;
|
||||
}
|
||||
});
|
||||
|
||||
const customer: any = await CustomerService.getCustomerByUid(this.props.customerUid).then((process: any) => {
|
||||
if (process) {
|
||||
const customer: any = process.processData;
|
||||
return customer;
|
||||
}
|
||||
});
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
|
||||
//get the note of the folder that has customer_uid = this.props.customer.uid
|
||||
// const folderNote = folder.notes?.find((note) => note.customer?.uid === this.props.customerUid);
|
||||
@ -91,14 +114,21 @@ class CreateCustomerNoteClass extends BasePage<IPropsClass, IState> {
|
||||
if (!this.state.folder || !this.state.customer) {
|
||||
throw new Error("Folder or customer not found");
|
||||
}
|
||||
const note = {
|
||||
content: values["content"],
|
||||
folder: this.state.folder,
|
||||
customer: this.state.customer,
|
||||
};
|
||||
|
||||
await Notes.getInstance().post(note);
|
||||
this.props.router.push(this.backwardPath);
|
||||
// TODO: review
|
||||
const noteData: any = {
|
||||
content: values["content"],
|
||||
folder: {
|
||||
uid: this.state.folder.uid
|
||||
},
|
||||
customer: {
|
||||
uid: this.state.customer.uid
|
||||
}
|
||||
};
|
||||
LoaderService.getInstance().show();
|
||||
NoteService.createNote(noteData, DEFAULT_VALIDATOR_ID).then(() => {
|
||||
this.props.router.push(this.backwardPath);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||
import DeedTypes from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
|
||||
import Button from "@Front/Components/DesignSystem/Button";
|
||||
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
@ -11,18 +8,25 @@ import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { ValidationError } from "class-validator/types/validation/ValidationError";
|
||||
import { Deed, Office, OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
||||
import { DeedType } from "le-coffre-resources/dist/Notary";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DeedTypeService from "src/common/Api/LeCoffreApi/sdk/DeedTypeService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import CollaboratorService from "src/common/Api/LeCoffreApi/sdk/CollaboratorService";
|
||||
|
||||
export default function CreateFolder(): JSX.Element {
|
||||
/**
|
||||
* State
|
||||
@ -48,9 +52,10 @@ export default function CreateFolder(): JSX.Element {
|
||||
[key: string]: any;
|
||||
},
|
||||
) => {
|
||||
const officeId = JwtService.getInstance().decodeJwt()?.office_Id;
|
||||
const user: any = UserStore.instance.getUser();
|
||||
const officeId: string = user.office.uid;
|
||||
|
||||
const officeFolderForm = OfficeFolder.hydrate<OfficeFolder>({
|
||||
const officeFolderModel = OfficeFolder.hydrate<OfficeFolder>({
|
||||
folder_number: values["folder_number"],
|
||||
name: values["name"],
|
||||
description: values["description"],
|
||||
@ -67,16 +72,42 @@ export default function CreateFolder(): JSX.Element {
|
||||
});
|
||||
|
||||
try {
|
||||
await officeFolderForm.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true });
|
||||
await officeFolderModel.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: true });
|
||||
} catch (validationErrors) {
|
||||
setValidationError(validationErrors as ValidationError[]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newOfficeFolder = await Folders.getInstance().post(officeFolderForm);
|
||||
if (!newOfficeFolder) return;
|
||||
router.push(`/folders/${newOfficeFolder.uid}`);
|
||||
const folderData: any = {
|
||||
folder_number: values["folder_number"],
|
||||
name: values["name"],
|
||||
deed: {
|
||||
deed_type: {
|
||||
uid: values["deed"],
|
||||
}
|
||||
},
|
||||
description: values["description"],
|
||||
office: {
|
||||
uid: officeId
|
||||
},
|
||||
customers: [],
|
||||
documents: [],
|
||||
notes: [],
|
||||
stakeholders: (folderAccessType === "whole_office" ? availableCollaborators : selectedCollaborators).map((collaborator: any) => ({ uid: collaborator.uid })),
|
||||
status: EFolderStatus.LIVE
|
||||
};
|
||||
|
||||
LoaderService.getInstance().show();
|
||||
FolderService.createFolder(folderData, [], []).then((processCreated: any) => {
|
||||
ToasterService.getInstance().success({
|
||||
title: "Succès !",
|
||||
description: "Dossier créé avec succès"
|
||||
});
|
||||
const folderUid: string = processCreated.processData.uid;
|
||||
router.push(`/folders/${folderUid}`);
|
||||
LoaderService.getInstance().hide();
|
||||
});
|
||||
} catch (backError) {
|
||||
if (!Array.isArray(backError)) return;
|
||||
setValidationError(backError as ValidationError[]);
|
||||
@ -100,22 +131,25 @@ export default function CreateFolder(): JSX.Element {
|
||||
* UseEffect
|
||||
*/
|
||||
useEffect(() => {
|
||||
DeedTypes.getInstance()
|
||||
.get({ where: { archived_at: null } })
|
||||
.then((deedTypes) => setAvailableDeedTypes(deedTypes));
|
||||
// no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller
|
||||
Users.getInstance()
|
||||
.get({
|
||||
include: { contact: true },
|
||||
})
|
||||
.then((users) => {
|
||||
setAvailableCollaborators(users);
|
||||
// set default selected collaborators to the connected user
|
||||
const currentUser = users.find((user) => user.uid === JwtService.getInstance().decodeJwt()?.userId);
|
||||
DeedTypeService.getDeedTypes((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
const deedTypes: any[] = processes.map((process: any) => process.processData);
|
||||
setAvailableDeedTypes(deedTypes);
|
||||
}
|
||||
});
|
||||
|
||||
CollaboratorService.getCollaborators((processes: any[]) => {
|
||||
if (processes.length > 0) {
|
||||
const collaborators: any[] = processes.map((process: any) => process.processData);
|
||||
setAvailableCollaborators(collaborators);
|
||||
|
||||
const user: any = UserStore.instance.getUser();
|
||||
const currentUser: any = collaborators.find((collaborator: any) => collaborator.uid === user.uid);
|
||||
if (currentUser) {
|
||||
setSelectedCollaborators([currentUser]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,171 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2xl, 40px);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl, 32px);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 16px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2xl, 40px);
|
||||
|
||||
.drag-drop-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl, 32px);
|
||||
|
||||
@media (min-width: $screen-m) {
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-lg, 24px);
|
||||
}
|
||||
|
||||
.drag-drop-box {
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
|
||||
// Force the DragAndDrop component to take full width and height
|
||||
> div {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 200px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
|
||||
// Override the fit-content width from DragAndDrop
|
||||
&.root {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-height: 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.file-info {
|
||||
padding: var(--spacing-sm, 8px) var(--spacing-md, 16px);
|
||||
background-color: var(--color-success-50, #f0f9ff);
|
||||
border: 1px solid var(--color-success-200, #bae6fd);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
margin-top: var(--spacing-sm, 8px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
text-align: center;
|
||||
padding: var(--spacing-md, 16px);
|
||||
background-color: var(--color-warning-50, #fffbeb);
|
||||
border: 1px solid var(--color-warning-200, #fde68a);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
}
|
||||
|
||||
.verification-result {
|
||||
text-align: center;
|
||||
padding: var(--spacing-lg, 24px);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
border: 2px solid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 16px);
|
||||
|
||||
&.success {
|
||||
background-color: var(--color-success-50, #f0fdf4);
|
||||
border-color: var(--color-success-200, #bbf7d0);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: var(--color-error-50, #fef2f2);
|
||||
border-color: var(--color-error-200, #fecaca);
|
||||
}
|
||||
|
||||
.verification-details {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: var(--spacing-md, 16px);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
font-family: monospace;
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.merkle-proof-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md, 16px);
|
||||
padding: var(--spacing-lg, 24px);
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
|
||||
.qr-code {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 2px solid var(--color-neutral-200, #e5e7eb);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
padding: var(--spacing-sm, 8px);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.qr-description {
|
||||
text-align: center;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 2px dashed var(--color-neutral-300, #d1d5db);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background-color: var(--color-neutral-50, #f9fafb);
|
||||
}
|
||||
|
||||
.qr-error {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 2px solid var(--color-error-200, #fecaca);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background-color: var(--color-error-50, #fef2f2);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg, 24px);
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 16px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Module from "@Front/Config/Module";
|
||||
import PdfService from "@Front/Services/PdfService";
|
||||
import { FileBlob } from "@Front/Api/Entities/types";
|
||||
import { ShieldCheckIcon } from "@heroicons/react/24/outline";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState } from "react";
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
|
||||
type IProps = {
|
||||
folderUid: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a File object to FileBlob
|
||||
* @param file - The File object to convert
|
||||
* @returns Promise<FileBlob> - The converted FileBlob
|
||||
*/
|
||||
const convertFileToFileBlob = async (file: File): Promise<FileBlob> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const arrayBuffer = reader.result as ArrayBuffer;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
resolve({
|
||||
type: file.type,
|
||||
data: uint8Array
|
||||
});
|
||||
};
|
||||
reader.onerror = () => reject(new Error('Failed to read file'));
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default function DocumentVerification(props: IProps) {
|
||||
const { folderUid } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const [documentToVerify, setDocumentToVerify] = useState<File | null>(null);
|
||||
const [validationCertificate, setValidationCertificate] = useState<File | null>(null);
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
const [verificationResult, setVerificationResult] = useState<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
details?: string;
|
||||
merkleProof?: string;
|
||||
} | null>(null);
|
||||
|
||||
const handleDocumentToVerifyChange = (files: File[]) => {
|
||||
if (files.length > 0 && files[0]) {
|
||||
setDocumentToVerify(files[0]);
|
||||
} else {
|
||||
setDocumentToVerify(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValidationCertificateChange = (files: File[]) => {
|
||||
if (files.length > 0 && files[0]) {
|
||||
setValidationCertificate(files[0]);
|
||||
} else {
|
||||
setValidationCertificate(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyDocuments = async () => {
|
||||
if (!documentToVerify || !validationCertificate) {
|
||||
console.error("Both documents are required for verification");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsVerifying(true);
|
||||
setVerificationResult(null);
|
||||
|
||||
const messageBus = MessageBus.getInstance();
|
||||
|
||||
try {
|
||||
// Here the things we need to verify:
|
||||
// - we can produce the same hash from the document provided than what is in the validation certificate
|
||||
// - the merkle proof is valid with that hash
|
||||
// - the root of the merkle tree is a state id from a commited state in the process
|
||||
// - that process is a file process linked to the right folder
|
||||
// Step 1: Parse the validation certificate
|
||||
const validationData = await PdfService.getInstance().parseCertificate(validationCertificate);
|
||||
|
||||
// Step 2: Convert File to FileBlob and hash the document using MessageBus
|
||||
const fileBlob = await convertFileToFileBlob(documentToVerify);
|
||||
|
||||
await messageBus.isReady();
|
||||
const documentHash = await messageBus.hashDocument(fileBlob, validationData.commitmentId);
|
||||
|
||||
// Step 3: Compare hashes
|
||||
const hashesMatch = documentHash.toLowerCase() === validationData.documentHash.toLowerCase();
|
||||
|
||||
if (!hashesMatch) {
|
||||
throw new Error('Hash du document invalide, le document fourni n\'est pas celui qui a été certifié');
|
||||
}
|
||||
|
||||
// Step 4: Verify the merkle proof
|
||||
const merkleProof = validationData.merkleProof;
|
||||
const merkleProofValid = await messageBus.verifyMerkleProof(merkleProof, documentHash);
|
||||
|
||||
if (!merkleProofValid) {
|
||||
throw new Error('Preuve de Merkle invalide, le document n\'a pas été certifié là où le certificat le prétend');
|
||||
}
|
||||
|
||||
// Step 5: Verify that this file process depends on the right folder process
|
||||
// First pin all the validated documents related to the folder
|
||||
const documentProcesses = await DocumentService.getDocuments();
|
||||
|
||||
const documents = documentProcesses.filter((process: any) =>
|
||||
process.processData.document_status === "VALIDATED" &&
|
||||
process.processData.folder.uid === folderUid
|
||||
);
|
||||
|
||||
if (!documents || documents.length === 0) {
|
||||
throw new Error(`Aucune demande de document trouvé pour le dossier ${folderUid}`);
|
||||
}
|
||||
|
||||
// Step 6: verify that the merkle proof match the last commited state for the file process
|
||||
const stateId = JSON.parse(validationData.merkleProof)['root'];
|
||||
|
||||
let stateIdExists = false;
|
||||
for (const doc of documents) {
|
||||
const processData = doc.processData;
|
||||
|
||||
for (const file of processData.files) {
|
||||
const fileUid = file.uid;
|
||||
const fileProcess = await FileService.getFileByUid(fileUid);
|
||||
const lastUpdatedStateId = fileProcess.lastUpdatedFileState.state_id;
|
||||
|
||||
stateIdExists = lastUpdatedStateId === stateId; // we assume that last state is the validated document, that seems reasonable
|
||||
if (stateIdExists) break;
|
||||
}
|
||||
|
||||
if (stateIdExists) break;
|
||||
}
|
||||
|
||||
if (!stateIdExists) {
|
||||
throw new Error('La preuve fournie ne correspond à aucun document demandé pour ce dossier.');
|
||||
}
|
||||
|
||||
setVerificationResult({
|
||||
success: true,
|
||||
message: "✅ Vérification réussie ! Le document est authentique et intègre.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Verification failed:", error);
|
||||
setVerificationResult({
|
||||
success: false,
|
||||
message: `❌ Erreur lors de la vérification: ${error}`,
|
||||
});
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackToFolder = () => {
|
||||
const folderPath = Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid);
|
||||
router.push(folderPath);
|
||||
};
|
||||
|
||||
const bothDocumentsPresent = documentToVerify && validationCertificate;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<div className={classes["header"]}>
|
||||
<Typography typo={ETypo.TITLE_H2} color={ETypoColor.TEXT_PRIMARY}>
|
||||
Vérification de Documents
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||
Vérifiez l'intégrité et l'authenticité de vos documents
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className={classes["content"]}>
|
||||
<div className={classes["drag-drop-container"]}>
|
||||
<div className={classes["drag-drop-box"]}>
|
||||
<DragAndDrop
|
||||
title="Document à valider"
|
||||
description="Glissez-déposez ou cliquez pour sélectionner le document que vous souhaitez vérifier"
|
||||
onChange={handleDocumentToVerifyChange}
|
||||
/>
|
||||
{documentToVerify && (
|
||||
<div className={classes["file-info"]}>
|
||||
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_SUCCESS_500}>
|
||||
✓ {documentToVerify.name}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classes["drag-drop-box"]}>
|
||||
<DragAndDrop
|
||||
title="Certificat de validation"
|
||||
description="Glissez-déposez ou cliquez pour sélectionner le certificat de validation correspondant"
|
||||
onChange={handleValidationCertificateChange}
|
||||
/>
|
||||
{validationCertificate && (
|
||||
<div className={classes["file-info"]}>
|
||||
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_SUCCESS_500}>
|
||||
✓ {validationCertificate.name}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!bothDocumentsPresent && (
|
||||
<div className={classes["warning"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_WARNING_500}>
|
||||
⚠️ Veuillez sélectionner les deux documents pour procéder à la vérification
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{verificationResult && (
|
||||
<div className={`${classes["verification-result"]} ${classes[verificationResult.success ? "success" : "error"]}`}>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={verificationResult.success ? ETypoColor.COLOR_SUCCESS_500 : ETypoColor.COLOR_ERROR_500}>
|
||||
{verificationResult.message}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={classes["actions"]}>
|
||||
<Button
|
||||
variant={EButtonVariant.SECONDARY}
|
||||
styletype={EButtonstyletype.TEXT}
|
||||
size={EButtonSize.LG}
|
||||
onClick={handleBackToFolder}
|
||||
disabled={isVerifying}
|
||||
>
|
||||
Retour au dossier
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.CONTAINED}
|
||||
size={EButtonSize.LG}
|
||||
onClick={handleVerifyDocuments}
|
||||
rightIcon={<ShieldCheckIcon />}
|
||||
disabled={!bothDocumentsPresent || isVerifying}
|
||||
isLoading={isVerifying}
|
||||
>
|
||||
{isVerifying ? "Vérification en cours..." : "Vérifier les documents"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -12,7 +12,9 @@ import { useCallback } from "react";
|
||||
import { AnchorStatus } from "../..";
|
||||
import classes from "./classes.module.scss";
|
||||
import DeleteCustomerModal from "./DeleteCustomerModal";
|
||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {
|
||||
customer: Customer;
|
||||
@ -29,17 +31,27 @@ export default function ClientBox(props: IProps) {
|
||||
const { isOpen: isErrorModalOpen, open: openErrorModal, close: closeErrorModal } = useOpenable();
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (customerUid: string) => {
|
||||
console.log(customer);
|
||||
const documents = await Documents.getInstance().get({ where: { depositor_uid: customerUid, folder_uid: folderUid } });
|
||||
console.log(documents);
|
||||
(customerUid: string) => {
|
||||
LoaderService.getInstance().show();
|
||||
DocumentService.getDocuments().then((processes: Record<string, any>) => {
|
||||
const processArray = Object.values(processes);
|
||||
if (processArray.length > 0) {
|
||||
let documents: any[] = processArray.map((process: any) => process.processData);
|
||||
|
||||
if (documents && documents.length > 0) {
|
||||
closeDeleteModal();
|
||||
openErrorModal();
|
||||
return;
|
||||
}
|
||||
props.onDelete(customerUid);
|
||||
// FilterBy folder.uid & depositor.uid
|
||||
documents = documents.filter((document: any) => document.folder.uid === folderUid && document.depositor && document.depositor.uid === customerUid);
|
||||
|
||||
if (documents && documents.length > 0) {
|
||||
closeDeleteModal();
|
||||
openErrorModal();
|
||||
return;
|
||||
}
|
||||
|
||||
props.onDelete(customerUid);
|
||||
} else {
|
||||
props.onDelete(customerUid);
|
||||
}
|
||||
});
|
||||
},
|
||||
[closeDeleteModal, customer.documents, openErrorModal, props, customer, folderUid],
|
||||
);
|
||||
|
@ -1,9 +1,12 @@
|
||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {
|
||||
documentUid: string;
|
||||
isOpen: boolean;
|
||||
@ -15,13 +18,25 @@ export default function DeleteAskedDocumentModal(props: IProps) {
|
||||
const { isOpen, onClose, documentUid, onDeleteSuccess } = props;
|
||||
|
||||
const onDelete = useCallback(
|
||||
() =>
|
||||
Documents.getInstance()
|
||||
.delete(documentUid)
|
||||
() => {
|
||||
LoaderService.getInstance().show();
|
||||
new Promise<void>(
|
||||
(resolve: () => void) => {
|
||||
DocumentService.getDocumentByUid(documentUid).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
DocumentService.updateDocument(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
|
||||
FolderService.refreshFolderByUid(document.folder.uid).then(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => onDeleteSuccess(documentUid))
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
|
||||
.then(() => LoaderService.getInstance().hide())
|
||||
.then(onClose)
|
||||
.catch((error) => console.warn(error)),
|
||||
.catch((error) => console.warn(error));
|
||||
},
|
||||
[documentUid, onClose, onDeleteSuccess],
|
||||
);
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {
|
||||
documentUid: string;
|
||||
isOpen: boolean;
|
||||
@ -15,13 +18,20 @@ export default function DeleteSentDocumentModal(props: IProps) {
|
||||
const { isOpen, onClose, documentUid, onDeleteSuccess } = props;
|
||||
|
||||
const onDelete = useCallback(
|
||||
() =>
|
||||
DocumentsNotary.getInstance()
|
||||
.delete(documentUid)
|
||||
.then(() => onDeleteSuccess(documentUid))
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
|
||||
.then(onClose)
|
||||
.catch((error) => console.warn(error)),
|
||||
() => {
|
||||
LoaderService.getInstance().show();
|
||||
DocumentService.getDocumentByUid(documentUid).then((process: any) => {
|
||||
if (process) {
|
||||
const document: any = process.processData;
|
||||
DocumentService.updateDocument(process, { isDeleted: 'true', archived_at: new Date().toISOString() })
|
||||
.then(() => onDeleteSuccess(documentUid))
|
||||
.then(() => FolderService.refreshFolderByUid(document.folder.uid))
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
|
||||
.then(() => LoaderService.getInstance().hide())
|
||||
.then(onClose);
|
||||
}
|
||||
});
|
||||
},
|
||||
[documentUid, onClose, onDeleteSuccess],
|
||||
);
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import { File } from "le-coffre-resources/dist/Customer";
|
||||
import React from "react";
|
||||
import { FileBlob } from "@Front/Api/Entities/types";
|
||||
|
||||
type IProps = {
|
||||
file: File;
|
||||
file: {
|
||||
uid: string;
|
||||
file_blob: FileBlob;
|
||||
};
|
||||
url: string;
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
@ -14,7 +17,7 @@ export default function FilePreviewModal(props: IProps) {
|
||||
|
||||
return (
|
||||
<Modal key={file.uid} isOpen={isOpen} onClose={onClose} fullscreen>
|
||||
<object data={url} type={file.mimetype} width="100%" height="800px" />
|
||||
<object data={url} type={file.file_blob.type} width="100%" height="800px" />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
|
||||
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
|
||||
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
|
||||
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||
import Table from "@Front/Components/DesignSystem/Table";
|
||||
@ -10,9 +6,8 @@ import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Module from "@Front/Config/Module";
|
||||
import useOpenable from "@Front/Hooks/useOpenable";
|
||||
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { ArrowDownTrayIcon, EyeIcon, TrashIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import { Document } from "le-coffre-resources/dist/Customer";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import DocumentNotary, { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
|
||||
import Link from "next/link";
|
||||
@ -23,6 +18,16 @@ import classes from "./classes.module.scss";
|
||||
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
|
||||
import DeleteSentDocumentModal from "./DeleteSentDocumentModal";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
|
||||
|
||||
import PdfService, { CertificateData, Metadata } from "@Front/Services/PdfService";
|
||||
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {
|
||||
customerUid: string;
|
||||
folderUid: string;
|
||||
@ -42,7 +47,7 @@ const tradDocumentsNotaryStatus: Record<EDocumentNotaryStatus, string> = {
|
||||
|
||||
export default function DocumentTables(props: IProps) {
|
||||
const { folderUid, customerUid } = props;
|
||||
const [documents, setDocuments] = useState<Document[]>([]);
|
||||
const [documents, setDocuments] = useState<any[]>([]);
|
||||
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
|
||||
const [focusedDocumentUid, setFocusedDocumentUid] = useState<string | null>(null);
|
||||
|
||||
@ -52,23 +57,44 @@ export default function DocumentTables(props: IProps) {
|
||||
const deleteSentDocumentModal = useOpenable();
|
||||
|
||||
const fetchDocuments = useCallback(
|
||||
() =>
|
||||
Documents.getInstance()
|
||||
.get({
|
||||
where: { folder: { uid: folderUid }, depositor: { uid: customerUid } },
|
||||
include: { files: true, document_type: true },
|
||||
})
|
||||
.then(setDocuments)
|
||||
.catch(console.warn),
|
||||
() => {
|
||||
setDocuments([]);
|
||||
FolderService.getFolderByUid(folderUid).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
const customer: any = folder.customers.find((customer: any) => customer.uid === customerUid);
|
||||
if (customer && customer.documents) {
|
||||
const documents: any[] = customer.documents.filter((document: any) => document.depositor);
|
||||
setDocuments(documents);
|
||||
} else {
|
||||
setDocuments([]);
|
||||
}
|
||||
} else {
|
||||
setDocuments([]);
|
||||
}
|
||||
});
|
||||
},
|
||||
[customerUid, folderUid],
|
||||
);
|
||||
|
||||
const fetchDocumentsNotary = useCallback(
|
||||
() =>
|
||||
DocumentsNotary.getInstance()
|
||||
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
|
||||
.then(setDocumentsNotary)
|
||||
.catch(console.warn),
|
||||
() => {
|
||||
setDocumentsNotary([]);
|
||||
FolderService.getFolderByUid(folderUid).then((process: any) => {
|
||||
if (process) {
|
||||
const folder: any = process.processData;
|
||||
const customer: any = folder.customers.find((customer: any) => customer.uid === customerUid);
|
||||
if (customer && customer.documents) {
|
||||
const documents: any[] = customer.documents.filter((document: any) => document.customer);
|
||||
setDocumentsNotary(documents);
|
||||
} else {
|
||||
setDocumentsNotary([]);
|
||||
}
|
||||
} else {
|
||||
setDocumentsNotary([]);
|
||||
}
|
||||
});
|
||||
},
|
||||
[customerUid, folderUid],
|
||||
);
|
||||
|
||||
@ -95,40 +121,115 @@ export default function DocumentTables(props: IProps) {
|
||||
[deleteSentDocumentModal],
|
||||
);
|
||||
|
||||
const onDownload = useCallback((doc: Document) => {
|
||||
const onDownload = useCallback((doc: any) => {
|
||||
const file = doc.files?.[0];
|
||||
if (!file || !file?.uid) return;
|
||||
if (!file) return;
|
||||
|
||||
return Files.getInstance()
|
||||
.download(file.uid)
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.file_name ?? "file";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch((e) => console.warn(e));
|
||||
return new Promise<void>(async (resolve: () => void) => {
|
||||
if (!file.file_blob) {
|
||||
LoaderService.getInstance().show();
|
||||
file.file_blob = (await FileService.getFileByUid(file.uid)).processData.file_blob;
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
|
||||
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.file_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
resolve();
|
||||
}).catch((e) => console.warn(e));
|
||||
}, []);
|
||||
|
||||
const onDownloadFileNotary = useCallback((doc: DocumentNotary) => {
|
||||
const onDownloadFileNotary = useCallback((doc: any) => {
|
||||
const file = doc.files?.[0];
|
||||
if (!file || !file?.uid) return;
|
||||
if (!file) return;
|
||||
|
||||
return FilesNotary.getInstance()
|
||||
.download(file.uid)
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.file_name ?? "file";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch((e) => console.warn(e));
|
||||
return new Promise<void>(async (resolve: () => void) => {
|
||||
if (!file.file_blob) {
|
||||
LoaderService.getInstance().show();
|
||||
file.file_blob = (await FileService.getFileByUid(file.uid)).processData.file_blob;
|
||||
LoaderService.getInstance().hide();
|
||||
}
|
||||
const blob = new Blob([file.file_blob.data], { type: file.file_blob.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.file_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
resolve();
|
||||
}).catch((e) => console.warn(e));
|
||||
}, []);
|
||||
|
||||
const onDownloadCertificate = useCallback(async (doc: any) => {
|
||||
try {
|
||||
const certificateData: CertificateData = {
|
||||
customer: {
|
||||
firstName: doc.depositor.first_name || doc.depositor.firstName || "N/A",
|
||||
lastName: doc.depositor.last_name || doc.depositor.lastName || "N/A",
|
||||
postalAddress: doc.depositor.postal_address || doc.depositor.address || doc.depositor.postalAddress || "N/A",
|
||||
email: doc.depositor.email || "N/A"
|
||||
},
|
||||
notary: {
|
||||
name: "N/A"
|
||||
},
|
||||
folderUid: folderUid,
|
||||
documentHash: "N/A",
|
||||
metadata: {
|
||||
fileName: "N/A",
|
||||
isDeleted: false,
|
||||
updatedAt: new Date(),
|
||||
commitmentId: "N/A",
|
||||
createdAt: new Date(),
|
||||
documentUid: "N/A",
|
||||
documentType: "N/A",
|
||||
merkleProof: "N/A"
|
||||
}
|
||||
};
|
||||
|
||||
// Get the specific document that was clicked
|
||||
const documentProcess = await DocumentService.getDocumentByUid(doc.uid);
|
||||
if (!documentProcess) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
// Process only the files for this specific document
|
||||
for (const file of documentProcess.processData.files) {
|
||||
const fileProcess = await FileService.getFileByUid(file.uid);
|
||||
|
||||
const hash = fileProcess.lastUpdatedFileState.pcd_commitment.file_blob;
|
||||
certificateData.documentHash = hash;
|
||||
|
||||
const proof = await MessageBus.getInstance().generateMerkleProof(fileProcess.lastUpdatedFileState, 'file_blob');
|
||||
|
||||
const metadata: Metadata = {
|
||||
fileName: fileProcess.processData.file_name,
|
||||
isDeleted: false,
|
||||
updatedAt: new Date(fileProcess.processData.updated_at),
|
||||
commitmentId: fileProcess.lastUpdatedFileState.commited_in,
|
||||
createdAt: new Date(fileProcess.processData.created_at),
|
||||
documentUid: doc.uid,
|
||||
documentType: doc.document_type.name,
|
||||
merkleProof: proof
|
||||
};
|
||||
certificateData.metadata = metadata;
|
||||
}
|
||||
|
||||
await PdfService.getInstance().downloadCertificate(certificateData);
|
||||
} catch (error) {
|
||||
console.error('Error downloading certificate:', error);
|
||||
}
|
||||
}, [folderUid, customerUid]);
|
||||
|
||||
const askedDocuments: IRowProps[] = useMemo(
|
||||
() =>
|
||||
documents
|
||||
@ -136,21 +237,25 @@ export default function DocumentTables(props: IProps) {
|
||||
if (document.document_status !== EDocumentStatus.ASKED) return null;
|
||||
return {
|
||||
key: document.uid,
|
||||
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
|
||||
document_status: {
|
||||
sx: { width: 107 },
|
||||
content: (
|
||||
<Tag
|
||||
color={ETagColor.INFO}
|
||||
variant={ETagVariant.SEMI_BOLD}
|
||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||
label={tradDocumentStatus[document.document_status as EDocumentStatus].toUpperCase()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
date: {
|
||||
sx: { width: 107 },
|
||||
sx: { width: 120 },
|
||||
content: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_",
|
||||
},
|
||||
file: {
|
||||
sx: { width: 120 },
|
||||
content: "_",
|
||||
},
|
||||
actions: {
|
||||
sx: { width: 76 },
|
||||
content: (
|
||||
@ -172,21 +277,25 @@ export default function DocumentTables(props: IProps) {
|
||||
if (document.document_status !== EDocumentStatus.DEPOSITED) return null;
|
||||
return {
|
||||
key: document.uid,
|
||||
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
|
||||
document_status: {
|
||||
sx: { width: 107 },
|
||||
content: (
|
||||
<Tag
|
||||
color={ETagColor.WARNING}
|
||||
variant={ETagVariant.SEMI_BOLD}
|
||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||
label={tradDocumentStatus[document.document_status as EDocumentStatus].toUpperCase()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
date: {
|
||||
sx: { width: 107 },
|
||||
sx: { width: 120 },
|
||||
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||
},
|
||||
file: {
|
||||
sx: { width: 120 },
|
||||
content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||
},
|
||||
actions: {
|
||||
sx: { width: 76 },
|
||||
content: (
|
||||
@ -208,27 +317,31 @@ export default function DocumentTables(props: IProps) {
|
||||
);
|
||||
|
||||
const validatedDocuments: IRowProps[] = useMemo(
|
||||
() =>
|
||||
documents
|
||||
() => {
|
||||
const validated = documents
|
||||
.map((document) => {
|
||||
if (document.document_status !== EDocumentStatus.VALIDATED) return null;
|
||||
return {
|
||||
key: document.uid,
|
||||
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
|
||||
document_status: {
|
||||
sx: { width: 107 },
|
||||
content: (
|
||||
<Tag
|
||||
color={ETagColor.SUCCESS}
|
||||
variant={ETagVariant.SEMI_BOLD}
|
||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||
label={tradDocumentStatus[document.document_status as EDocumentStatus].toUpperCase()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
date: {
|
||||
sx: { width: 107 },
|
||||
sx: { width: 120 },
|
||||
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||
},
|
||||
file: {
|
||||
sx: { width: 120 },
|
||||
content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||
},
|
||||
actions: {
|
||||
sx: { width: 76 },
|
||||
content: (
|
||||
@ -241,13 +354,17 @@ export default function DocumentTables(props: IProps) {
|
||||
<IconButton icon={<EyeIcon />} />
|
||||
</Link>
|
||||
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
|
||||
<IconButton onClick={() => onDownloadCertificate(document)} icon={<DocumentTextIcon />} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
})
|
||||
.filter((document) => document !== null) as IRowProps[],
|
||||
[documents, folderUid, onDownload],
|
||||
.filter((document) => document !== null) as IRowProps[];
|
||||
|
||||
return validated;
|
||||
},
|
||||
[documents, folderUid, onDownload, onDownloadCertificate],
|
||||
);
|
||||
|
||||
const refusedDocuments: IRowProps[] = useMemo(
|
||||
@ -257,21 +374,25 @@ export default function DocumentTables(props: IProps) {
|
||||
if (document.document_status !== EDocumentStatus.REFUSED) return null;
|
||||
return {
|
||||
key: document.uid,
|
||||
document_type: { sx: { width: 400 }, content: document.document_type?.name ?? "_" },
|
||||
document_type: { sx: { width: 300 }, content: document.document_type?.name ?? "Autre document" },
|
||||
document_status: {
|
||||
sx: { width: 107 },
|
||||
content: (
|
||||
<Tag
|
||||
color={ETagColor.ERROR}
|
||||
variant={ETagVariant.SEMI_BOLD}
|
||||
label={tradDocumentStatus[document.document_status].toUpperCase()}
|
||||
label={tradDocumentStatus[document.document_status as EDocumentStatus].toUpperCase()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
date: {
|
||||
sx: { width: 107 },
|
||||
sx: { width: 120 },
|
||||
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||
},
|
||||
file: {
|
||||
sx: { width: 120 },
|
||||
content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||
},
|
||||
actions: { sx: { width: 76 }, content: "" },
|
||||
};
|
||||
})
|
||||
@ -286,17 +407,21 @@ export default function DocumentTables(props: IProps) {
|
||||
return {
|
||||
key: document.uid,
|
||||
document_type: {
|
||||
sx: { width: 400 },
|
||||
content: formatName(document.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_",
|
||||
sx: { width: 300 },
|
||||
content: "Autre document",
|
||||
},
|
||||
document_status: {
|
||||
sx: { width: 107 },
|
||||
content: getTagForSentDocument(document.document_status as EDocumentNotaryStatus),
|
||||
},
|
||||
date: {
|
||||
sx: { width: 107 },
|
||||
sx: { width: 120 },
|
||||
content: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||
},
|
||||
file: {
|
||||
sx: { width: 120 },
|
||||
content: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||
},
|
||||
actions: {
|
||||
sx: { width: 76 },
|
||||
content: (
|
||||
@ -313,10 +438,11 @@ export default function DocumentTables(props: IProps) {
|
||||
);
|
||||
|
||||
const progress = useMemo(() => {
|
||||
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length + refusedDocuments.length;
|
||||
// Exclude refused documents from total - only count documents that are still in progress
|
||||
const total = askedDocuments.length + toValidateDocuments.length + validatedDocuments.length;
|
||||
if (total === 0) return 0;
|
||||
return (validatedDocuments.length / total) * 100;
|
||||
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
|
||||
}, [askedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
|
||||
|
||||
if (documents.length === 0 && documentsNotary.length === 0) return <NoDocument />;
|
||||
|
||||
@ -383,6 +509,10 @@ function getHeader(dateColumnTitle: string, isMobile: boolean): IHead[] {
|
||||
key: "date",
|
||||
title: dateColumnTitle,
|
||||
},
|
||||
{
|
||||
key: "file",
|
||||
title: "Fichier",
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
title: "Action",
|
||||
@ -390,10 +520,6 @@ function getHeader(dateColumnTitle: string, isMobile: boolean): IHead[] {
|
||||
];
|
||||
}
|
||||
|
||||
function formatName(text: string): string {
|
||||
return text.replace(/[^a-zA-Z0-9 ]/g, "");
|
||||
}
|
||||
|
||||
function getTagForSentDocument(status: EDocumentNotaryStatus) {
|
||||
if (status === EDocumentNotaryStatus.SENT) {
|
||||
return (
|
||||
|
@ -11,6 +11,11 @@ import React, { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
import CustomerService from "src/common/Api/LeCoffreApi/sdk/CustomerService";
|
||||
import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import UserStore from "@Front/Stores/UserStore";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
@ -23,9 +28,24 @@ export default function ReminderModal(props: IProps) {
|
||||
const [selectedOptions, setSelectedOptions] = useState<IOption[]>([]);
|
||||
const [isAllSelected, setIsAllSelected] = useState(false);
|
||||
|
||||
const onRemind = useCallback(() => {
|
||||
const onRemind = useCallback(async () => {
|
||||
LoaderService.getInstance().show();
|
||||
|
||||
const documentUids: string[] = selectedOptions.map((option) => option.value) as string[];
|
||||
|
||||
const user: any = UserStore.instance.getUser();
|
||||
const _office: any = user.office;
|
||||
|
||||
const _documents: any[] = [];
|
||||
for (const documentUid of documentUids) {
|
||||
_documents.push((await DocumentService.getDocumentByUid(documentUid)).processData);
|
||||
}
|
||||
const _customer = (await CustomerService.getCustomerByUid(customer.uid!)).processData;
|
||||
|
||||
LoaderService.getInstance().hide();
|
||||
|
||||
Customers.getInstance()
|
||||
.sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[])
|
||||
.sendReminder(_office, _customer)
|
||||
.then(onRemindSuccess)
|
||||
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "La relance a été envoyée avec succès." }))
|
||||
.then(onClose);
|
||||
|
@ -41,7 +41,9 @@ export default function EmailReminder(props: IProps) {
|
||||
}, [customer.uid, folderUid]);
|
||||
|
||||
useEffect(() => {
|
||||
/* TODO: review
|
||||
fetchReminders();
|
||||
*/
|
||||
}, [fetchReminders]);
|
||||
|
||||
const remindersLength = useMemo(() => {
|
||||
|
@ -1,11 +1,9 @@
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Tabs from "@Front/Components/Elements/Tabs";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
|
||||
import Customer from "le-coffre-resources/dist/Customer";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
@ -14,10 +12,14 @@ import classes from "./classes.module.scss";
|
||||
import ClientBox from "./ClientBox";
|
||||
import DocumentTables from "./DocumentTables";
|
||||
import EmailReminder from "./EmailReminder";
|
||||
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
|
||||
type IProps = {
|
||||
folder: OfficeFolder;
|
||||
folder: any;
|
||||
anchorStatus: AnchorStatus;
|
||||
};
|
||||
|
||||
@ -27,18 +29,19 @@ export default function ClientView(props: IProps) {
|
||||
const { folder, anchorStatus } = props;
|
||||
|
||||
const customers: ICustomer[] = useMemo(
|
||||
() =>
|
||||
folder?.customers
|
||||
?.map((customer) => ({
|
||||
id: customer.uid ?? "",
|
||||
() => {
|
||||
return folder?.FolderData?.customers
|
||||
?.map((customer: any) => ({
|
||||
id: customer.uid ?? '',
|
||||
...customer,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
.sort((a: any, b: any) => {
|
||||
return a.documents &&
|
||||
a.documents.filter((document) => document.document_status === EDocumentStatus.DEPOSITED).length > 0
|
||||
a.documents.filter((document: any) => document.document_status === EDocumentStatus.DEPOSITED).length > 0
|
||||
? -1
|
||||
: 1;
|
||||
}) ?? [],
|
||||
}) ?? []
|
||||
},
|
||||
[folder],
|
||||
);
|
||||
|
||||
@ -58,27 +61,23 @@ export default function ClientView(props: IProps) {
|
||||
);
|
||||
|
||||
const handleClientDelete = useCallback(
|
||||
async (customerUid: string) => {
|
||||
if (!folder.uid) return;
|
||||
const documentsNotary = await DocumentsNotary.getInstance().get({
|
||||
where: { customer: { uid: customerUid }, folder: { uid: folder.uid } },
|
||||
(customerUid: string) => {
|
||||
if (!folder.processId) return;
|
||||
LoaderService.getInstance().show();
|
||||
MessageBus.getInstance().getProcessData(folder.processId).then((process: { [key: string]: any }) => {
|
||||
if (process) {
|
||||
const processId = folder.processId;
|
||||
const folderData = process[processId];
|
||||
|
||||
// FilterBy customerUid
|
||||
const customers = folderData.customers.filter((uid: string) => uid !== customerUid);
|
||||
|
||||
FolderService.updateFolder(processId, { customers: customers }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(documentsNotary);
|
||||
|
||||
if (documentsNotary.length > 0) {
|
||||
documentsNotary.forEach(async (doc) => {
|
||||
await DocumentsNotary.getInstance().delete(doc.uid!);
|
||||
});
|
||||
}
|
||||
|
||||
Folders.getInstance().put(
|
||||
folder.uid,
|
||||
OfficeFolder.hydrate<OfficeFolder>({
|
||||
...folder,
|
||||
customers: folder.customers?.filter((customer) => customer.uid !== customerUid),
|
||||
}),
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
[folder],
|
||||
);
|
||||
@ -92,7 +91,7 @@ export default function ClientView(props: IProps) {
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folder.uid ?? "")}>
|
||||
.modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", idAsUrl(folder.processId) ?? "")}>
|
||||
<Button
|
||||
size={EButtonSize.MD}
|
||||
rightIcon={<UserPlusIcon />}
|
||||
@ -108,17 +107,17 @@ export default function ClientView(props: IProps) {
|
||||
<ClientBox
|
||||
customer={customer}
|
||||
anchorStatus={anchorStatus}
|
||||
folderUid={folder.uid}
|
||||
folderUid={folder.processId}
|
||||
onDelete={handleClientDelete}
|
||||
customerNote={folder.notes!.find((value) => value.customer?.uid === customer.uid) ?? null}
|
||||
customerNote={folder.FolderData?.notes?.find((value: any) => value.customer?.uid === customer.uid) ?? null}
|
||||
/>
|
||||
<div className={classes["button-container"]}>
|
||||
{anchorStatus === AnchorStatus.NOT_ANCHORED && (
|
||||
<Link
|
||||
href={Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.uid ?? "")
|
||||
.replace("[customerUid]", customer.uid ?? "")}>
|
||||
.modules.pages.Folder.pages.AskDocument.props.path.replace("[folderUid]", folder.processId ?? "")
|
||||
.replace("[customerUid]", customer?.uid ?? "")}>
|
||||
<Button rightIcon={<DocumentIcon />} variant={EButtonVariant.PRIMARY} fullwidth>
|
||||
Demander un document
|
||||
</Button>
|
||||
@ -128,7 +127,7 @@ export default function ClientView(props: IProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{customer.uid && folder.uid && <DocumentTables customerUid={customer.uid} folderUid={folder.uid} />}
|
||||
{customer?.uid && folder.processId && <DocumentTables customerUid={customer.uid} folderUid={folder.processId} />}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -5,11 +5,12 @@ import { IItem } from "@Front/Components/DesignSystem/Menu/MenuItem";
|
||||
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PaperAirplaneIcon, PencilSquareIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||
import { ArchiveBoxIcon, EllipsisHorizontalIcon, PaperAirplaneIcon, PencilSquareIcon, ShieldCheckIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||
import classNames from "classnames";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { AnchorStatus } from "..";
|
||||
import classes from "./classes.module.scss";
|
||||
@ -24,6 +25,7 @@ type IProps = {
|
||||
|
||||
export default function InformationSection(props: IProps) {
|
||||
const { folder, progress, onArchive, anchorStatus, isArchived } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const menuItemsDekstop = useMemo(() => {
|
||||
let elements: IItem[] = [];
|
||||
@ -43,6 +45,17 @@ export default function InformationSection(props: IProps) {
|
||||
link: Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
|
||||
hasSeparator: true,
|
||||
};
|
||||
const verifyDocumentElement = {
|
||||
icon: <ShieldCheckIcon />,
|
||||
text: "Vérifier le document",
|
||||
onClick: () => {
|
||||
const verifyPath = Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.VerifyDocuments.props.path.replace("[folderUid]", folder?.uid ?? "");
|
||||
router.push(verifyPath);
|
||||
},
|
||||
hasSeparator: false,
|
||||
};
|
||||
|
||||
@ -52,8 +65,11 @@ export default function InformationSection(props: IProps) {
|
||||
elements.push(modifyInformationsElement);
|
||||
}
|
||||
|
||||
// Add verify document option
|
||||
elements.push(verifyDocumentElement);
|
||||
|
||||
return elements;
|
||||
}, [anchorStatus, folder?.uid]);
|
||||
}, [anchorStatus, folder?.uid, router]);
|
||||
|
||||
const menuItemsMobile = useMemo(() => {
|
||||
let elements: IItem[] = [];
|
||||
@ -75,6 +91,17 @@ export default function InformationSection(props: IProps) {
|
||||
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", folder?.uid ?? ""),
|
||||
hasSeparator: true,
|
||||
};
|
||||
const verifyDocumentElement = {
|
||||
icon: <ShieldCheckIcon />,
|
||||
text: "Vérifier le document",
|
||||
onClick: () => {
|
||||
const verifyPath = Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.Folder.pages.VerifyDocuments.props.path.replace("[folderUid]", folder?.uid ?? "");
|
||||
router.push(verifyPath);
|
||||
},
|
||||
hasSeparator: true,
|
||||
};
|
||||
|
||||
// If the folder is not anchored, we can modify the collaborators and the informations
|
||||
if (anchorStatus === AnchorStatus.NOT_ANCHORED) {
|
||||
@ -82,6 +109,9 @@ export default function InformationSection(props: IProps) {
|
||||
elements.push(modifyInformationsElement);
|
||||
}
|
||||
|
||||
// Add verify document option
|
||||
elements.push(verifyDocumentElement);
|
||||
|
||||
elements.push({
|
||||
icon: <PaperAirplaneIcon />,
|
||||
text: "Envoyer des documents",
|
||||
@ -101,7 +131,7 @@ export default function InformationSection(props: IProps) {
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [anchorStatus, folder?.uid, isArchived, onArchive]);
|
||||
}, [anchorStatus, folder?.uid, isArchived, onArchive, router]);
|
||||
return (
|
||||
<section className={classes["root"]}>
|
||||
<div className={classes["info-box1"]}>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { idAsUrl } from "@Front/Utils/ProcessIdUtils";
|
||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
import React, { useMemo } from "react";
|
||||
@ -14,7 +15,7 @@ export default function AddClientSection(props: IProps) {
|
||||
|
||||
const addClientPath = useMemo(() => {
|
||||
if (!folderUid) return "";
|
||||
return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folderUid);
|
||||
return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", idAsUrl(folderUid));
|
||||
}, [folderUid]);
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,3 @@
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Module from "@Front/Config/Module";
|
||||
@ -6,6 +5,9 @@ import { OfficeFolder } from "le-coffre-resources/dist/Notary";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
|
||||
import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
|
||||
|
||||
type IProps = {
|
||||
isOpen: boolean;
|
||||
onClose?: () => void;
|
||||
@ -21,8 +23,18 @@ export default function DeleteFolderModal(props: IProps) {
|
||||
if ((folder?.customers?.length ?? 0) > 0 || (folder?.documents?.length ?? 0) > 0)
|
||||
return console.warn("Cannot delete folder with customers or documents");
|
||||
|
||||
return Folders.getInstance()
|
||||
.delete(folder.uid)
|
||||
return new Promise<void>(
|
||||
(resolve: () => void) => {
|
||||
LoaderService.getInstance().show();
|
||||
FolderService.getFolderByUid(folder.uid!).then((process: any) => {
|
||||
if (process) {
|
||||
FolderService.updateFolder(process, { isDeleted: 'true', archived_at: new Date().toISOString() }).then(() => {
|
||||
LoaderService.getInstance().hide();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => router.push(Module.getInstance().get().modules.pages.Folder.props.path))
|
||||
.then(onClose);
|
||||
}, [folder, router, onClose]);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user