NeuroSooth est une application Vite + React pensée comme une boîte à outils pour les personnes neuro-atypiques. Les exercices somatiques sont présentés sous forme de cartes triables par situation émotionnelle (crise, stress, sommeil, etc.). Chaque carte ouvre un parcours détaillé étape par étape et les membres de la communauté peuvent ajouter leurs propres pratiques, remercier celles qui les ont aidés ou — côté pros — injecter des fiches partenaires validées.
- Onboarding personnalisé – Collecte du prénom et des neuroprofils (
NeuroType) pour adapter les recommandations viagetRecommendedExercises. - Filtrage par situations – Barre de filtres (
Situation) pour ne voir que les exercices pertinents dans le tableau de bord. - Cartes interactives – Aperçu visuel avec tags, durée, compteur de remerciements et états de modération; clic = vue détaillée avec avertissements et instructions chronologiques.
- Dire merci – Bouton « Dire Merci » (
incrementThanks) avec mise à jour optimiste; la mutation est poussée danssyncServicepuis synchronisée avec l’API quand le réseau revient. - Contribution communautaire –
AddExerciseFormpermet de soumettre une technique (image, étapes, situations) qui reste en statut « pending » jusqu’à modération. - Backoffice Partenaires –
PartnerPortaloffre l’inscription/connexion locale de comptes, la publication immédiate de fiches partenaires et l’import CSV/JSON (mappage automatique des colonnes usuelles). - Panel de modération – Vue dédiée pour approuver/refuser les contributions (locales ou issues de l’API), ajouter des notes et suivre les décisions récentes.
- Synchronisation hors-ligne – Cache persistant Dexie (IndexedDB) via
services/storage/offlineDb, service de synchronisation (syncService) et service worker Workbox (src/sw.ts) avec Background Sync pour rejouer les mutations. - Accès administrateur – Le tiroir supérieur droit regroupe l’état réseau, la session partenaire en cours, la saisie des jetons JWT (
services/tokenStore) et l’accès rapide aux vues Partenaires/Modération/Contribution.
- Framework : React 19 + Vite 6 + TypeScript 5.8.
- UI : composants maison (ex.
components/Button.tsx) et classes utilitaires (Tailwind-like). - Données :
INITIAL_EXERCISESdansconstants.ts, types partagés danstypes.ts,services/dataService.tscentralise lecture/écriture et scoring,services/syncService.tsorchestre cache + mutations offline, etservices/apiClient.tspilote les appels REST authentifiés. - Stockage offline :
services/storage/offlineDb.tsencapsule Dexie + attachments (images encodées) avec migration depuis l’ancienlocalStorage. - Tests :
tests/syncService.test.tsvérifie l’hydratation offline, la relecture de file et la résolution de conflits vianode:test. - Backend : Express + PostgreSQL (
server/) avec routesexercisesetmoderation, validation Zod (utils/validation), JWT (auth.ts) et migrations SQL (server/migrations).
.
├── components/
│ └── Button.tsx
├── constants.ts # Exercices préchargés
├── index.tsx # Export de l'app (point d'entrée alternatif)
├── features/
│ ├── admin/ # Panel de modération et outils admin
│ ├── dashboard/ # Tableau de bord principal
│ └── partners/ # Portail partenaires
├── services/
│ ├── apiClient.ts # Client HTTP + gestion des tokens
│ ├── dataService.ts # Accès données + algorithme de recommandation
│ ├── syncService.ts # Cache offline, file de mutations, background sync
│ ├── languageService.ts # Gestion préférences langues + chargement lazy
│ ├── translationService.ts # Traduction dynamique via Google Translate API
│ ├── contentResolver.ts # Résolution des IDs de chaînes traduites
│ ├── tokenStore.ts # Stockage sécurisé des jetons JWT
│ └── storage/
│ ├── offlineDb.ts # Adaptateur IndexedDB + migration localStorage
│ └── dexieShim.ts # Implémentation légère compatible Dexie
├── src/
│ ├── App.tsx # Composant React principal (dashboard, formulaires, panels)
│ ├── main.tsx # Point d'entrée React (rendu, service worker)
│ ├── i18n.ts # Configuration i18next (lazy loading)
│ ├── i18nContext.tsx # Contexte React pour i18n (compatible React 19)
│ ├── index.css # Styles Tailwind + styles personnalisés
│ └── sw.ts # Service worker Workbox + BackgroundSync
├── public/
│ ├── offline.html # Page de fallback hors-ligne
│ └── locales/
│ ├── fr/
│ │ ├── common.json # Labels UI, boutons, filtres
│ │ ├── onboarding.json # Écran d'accueil
│ │ ├── exercise.json # Interface exercices
│ │ ├── partner.json # Portail partenaires
│ │ └── moderation.json # Panel de modération
│ ├── en/, de/, es/, nl/ # Mêmes 5 namespaces
├── tests/
│ └── syncService.test.ts # Tests node:test des scénarios offline/online
└── server/
├── src/ # API Express (routes, auth, db, validation)
│ ├── index.ts # Serveur Express principal
│ ├── auth.ts # Middleware d'authentification JWT
│ ├── db.ts # Connexion PostgreSQL
│ ├── routes/ # Routes API (exercises, moderation, strings)
│ ├── services/ # Services métier (batchTranslation, etc.)
│ └── utils/ # Utilitaires (validation Zod, etc.)
├── scripts/ # Scripts d'initialisation et seed
└── migrations/ # Migrations SQL PostgreSQL
- Node.js (version LTS conseillée)
- npm 10+
- Installer les dépendances :
npm install
- Démarrer le serveur de développement Vite :
npm run dev
- Ouvrir http://localhost:3000, compléter l'onboarding et explorer les filtres/suggestions.
Pour préparer une version de production, exécutez npm run build puis npm run preview afin de vérifier le bundle statique.
Le dépôt inclut une suite node:test focalisée sur syncService (hydratation offline, replay des mutations, résolution de conflits).
npm testLa commande compile les tests TypeScript (tsconfig.test.json + scripts/fix-test-imports.mjs) avant d’exécuter node --test dist-test.
L'application supporte 5 langues avec chargement lazy (optimisé pour réduire le transfert initial de ~80%) :
- Français (par défaut)
- English
- Deutsch (Allemand)
- Español (Espagnol)
- Nederlands (Néerlandais)
- Bibliothèque :
i18next+i18next-http-backend+ contexte React personnalisé (src/i18nContext.tsx) compatible React 19 - Lazy loading : Seule la langue sélectionnée est chargée (5 fichiers JSON au lieu de 25)
- Namespaces : 5 namespaces (common, onboarding, exercise, partner, moderation) pour organisation modulaire
- Stockage prioritaire :
localStorage.neurobox_user_languageavec timestamp - Cache service worker :
CacheFirstavec précaching pour les fichiers/locales/*(30 jours, max 10 entrées par langue) - Traduction dynamique :
services/translationService.tspour contenu généré (exercices) via Google Cloud Translation API
- À l'onboarding : 5 boutons de sélection en haut de l'écran permettent de choisir la langue avant même de commencer
- Post-onboarding : Le sélecteur dans le tiroir administrateur permet de basculer à tout moment
- Détection automatique : Si aucune préférence n'est stockée, l'app détecte la langue du navigateur puis bascule sur le français en fallback
public/locales/
├── fr/
│ ├── common.json # Labels UI, boutons, filtres, menu admin (103 lignes)
│ ├── onboarding.json # Écran d'accueil (8 lignes)
│ ├── exercise.json # Interface exercices et détails
│ ├── partner.json # Portail partenaires et import CSV
│ └── moderation.json # Panel de modération
├── en/, de/, es/, nl/ # Mêmes 5 namespaces
Tout le contenu statique (boutons, filtres, états de modération, menus) est traduit. Pour ajouter une clé, éditez les 5 fichiers du namespace approprié puis utilisez t('namespace:cle') dans les composants React via useTranslation(['common', 'exercise']).
Les exercices (titres, descriptions, étapes) sont maintenant entièrement traduits automatiquement :
- Obtenir une clé API Google Cloud Translation
- L'ajouter dans
.env:VITE_GOOGLE_TRANSLATE_API_KEY=votre_cle_ici GOOGLE_TRANSLATE_API_KEY=votre_cle_ici # Pour le backend
Backend :
server/src/services/translationService.ts- Service de traduction serveur avec Google Translate APIserver/src/services/batchTranslationService.ts- Traduction batch orchestrée- Table
exercise_strings- Textes sources en français - Table
exercise_translations- Traductions par langue - API
/admin/batch-translations- Endpoint pour lancer des traductions batch - API
/strings/translations/{lang}- Récupération des traductions par langue
Frontend :
services/exerciseTranslationService.ts- Récupère et applique les traductionshooks/useExerciseTranslation.ts- Hook React qui traduit automatiquement les exercices selon la langue active- Cache en mémoire des traductions pour performance optimale
-
Seed initial : Peupler la base avec les chaînes sources
docker exec neurobox_app npx tsx server/scripts/seedExerciseStrings.ts -
Lancer une traduction batch : Via le panel admin → "Orchestration des traductions"
- Sélectionner les langues cibles (EN, DE, ES, NL)
- Choisir le périmètre ("exercise" pour tous les exercices)
- Suivre la progression en temps réel
-
Basculer de langue : Les exercices se traduisent automatiquement
- Le hook
useExerciseTranslationdétecte le changement de langue - Récupère les traductions depuis l'API
- Applique les traductions à tous les exercices affichés
- Le hook
Coût estimé : 3-6$/mois pour 1000 utilisateurs avec cache agressif (traductions stockées en base).
Sans clé API, le système affiche un message d'erreur lors des traductions batch et le contenu reste en français.
Un script de validation est fourni dans test-lazy-loading.js :
// Dans la console navigateur, coller le contenu du fichier puis :
testLazyLoading.inspectCache() // Voir les langues en cache
testLazyLoading.simulateSwitch('de') // Tester le switch allemand
testLazyLoading.clearStorage() // Réinitialiser les préférencesVoir LAZY_LOADING_TEST.md pour le guide complet et LANGUAGE_OPTIMIZATION_COMPLETE.md pour les détails d'implémentation.
services/storage/offlineDbutilise un shim IndexedDB léger (dexieShim.ts, compatible Dexie) pour stocker exercices, profil utilisateur, pièces jointes, traductions et filePendingMutationRecord.syncServicegarde le cache en mémoire, notifie l'UI (subscribe/subscribeStatus) et met en file les mutations (createExercise,incrementThanks,moderateExercise).- Les mutations sont rejouées quand
navigator.onLineredevienttrueou lorsqu'unbackground syncest déclenché parsrc/sw.ts. services/dataServiceexpose des helpers (enregistrement utilisateur, scoring personnalisé via neurotypes, modération locale) et délègue la persistance àsyncService.services/languageServicegère les préférences linguistiques avec chargement lazy pour optimiser les performances.services/contentResolverrésout les IDs de chaînes traduites pour le contenu des exercices depuis la base de données.
Une API REST Express vit dans server/ afin de partager les exercices, remercier une pratique et suivre les décisions de modération.
- Copiez
.env.examplevers.envet renseignez au minimumDATABASE_URL,JWT_SECRET,PORTetVITE_API_BASE_URL(utilisé par le client React). - Lancez les migrations PostgreSQL :
npm run server:migrate
- Créer un compte administrateur : après avoir configuré
DATABASE_URL, exécutez le script de seed pour insérer un compte admin par défaut puis changez immédiatement son mot de passe.Vérifiez que l’utilisateur est bien créé avec le rôle admin (utile pour gérer les droitsnpx tsx server/scripts/seedAdmin.ts
moderator) avant de passer en production. - Démarrez l’API en mode développement (TypeScript + watch) :
npm run server:dev
- Pour un build JS :
npm run server:build
npm run server:start
- En production, vous pouvez utiliser le Dockerfile racine :
```bash
docker build -t neurosooth-api .
docker run --env-file .env -p 4000:4000 neurosooth-api
La politique de sécurité du contenu (Content Security Policy) est gérée côté serveur pour renforcer la sécurité de l'application. Elle est configurée dans le fichier server/src/index.ts via le middleware helmet.
La configuration se trouve dans la fonction buildContentSecurityPolicy. Pour autoriser une nouvelle source pour un type de contenu (par exemple, une image), ajoutez le domaine à la directive correspondante.
Exemple : Autoriser une nouvelle source d'images
Pour autoriser les images provenant de https://example.com, modifiez la directive imgSrc comme suit :
// in server/src/index.ts
const directives = {
// ... autres directives
imgSrc: ["'self'", "data:", "blob:", "https://placehold.co", "https://example.com"],
// ... autres directives
};Après avoir modifié la politique, vous devrez redéployer l'application pour que les changements prennent effet.
Un guide détaillé est disponible dans deploy/README.md. Il couvre :
- La génération d’un fichier
.env.serverà partir dedeploy/env.server.example - La construction/pousse de l’image (
docker build -t neurobox:latest .) - L’utilisation du manifeste
docker-compose.neurobox.ymlpour exposer l’API sur un port dédié (4400) et un réseau isolé (neurobox_net) afin de ne pas perturber les autres services Docker du VPS - La configuration nginx (
deploy/nginx.conf.example) qui sert les fichiersdist/et proxifie/api/vers l’API
Suivez ce guide pour installer Docker/compose sur l’hôte, exécuter les migrations (docker compose -f docker-compose.neurobox.yml run --rm neurobox-api npm run server:migrate), démarrer le service (... up -d) puis activer HTTPS avec certbot.
| Méthode | Endpoint | Description |
|---|---|---|
| GET | /api/exercises |
Retourne toutes les pratiques (avec serverId, timestamps, métadonnées de modération et thanksCount). |
| POST | /api/exercises |
Crée un exercice communautaire ou partenaire (jeton partner optionnel). |
| POST | /api/exercises/:id/thanks |
Incrémente le compteur thanksCount. |
| PATCH | /api/exercises/:id/moderation |
Approuve/rejette une contribution et peut la marquer comme supprimée (shouldDelete). |
| GET | /api/moderation/queue |
File des contributions en attente + dernières décisions (JWT moderator requis). |
Les actions sensibles sont protégées via des Bearer tokens signés avec JWT_SECRET. Deux rôles sont supportés :
partner: publier du contenu directement approuvé et accéder au backoffice.moderator: charger la file/api/moderation/queueet appliquer des décisions.
Un utilitaire simplifie la génération locale de tokens :
npm run server:token partner mon-equipe
Le contenu des exercices repose sur un système de traduction persistant qui stocke les chaînes dans PostgreSQL et les met en cache dans IndexedDB. Objectifs : réduire drastiquement les coûts (traduire une fois, servir à tous), garder la compatibilité ascendante et résoudre automatiquement les IDs de chaînes côté frontend via contentResolver.
- String IDs :
exercise.resp_478.title,exercise.resp_478.description,exercise.resp_478.step_1, etc. - Tables PostgreSQL :
exercise_strings(chaînes sources) etexercise_translations(traductions par langue). - Caches IndexedDB :
exerciseStringsetexerciseStringTranslationspour un usage offline.
- Créer les tables :
npm run server:migrate
- Peupler les chaînes initiales (extraites de
INITIAL_EXERCISES) :# Assurez-vous que DATABASE_URL est défini npx tsx scripts/seedExerciseStrings.ts
- Vérifier l’installation (optionnel) :
npx tsx scripts/testTranslationSystem.ts
Les routes suivantes exposent les chaînes et traductions (préfixe /api). Les rôles se basent sur les Bearer tokens générés via npm run server:token <role> <subject>.
| Méthode | Endpoint | Description | Rôle requis |
|---|---|---|---|
| GET | /strings |
Lister toutes les chaînes (filtrage possible via ?context=exercise) |
Public |
| GET | /strings/:id |
Récupérer une chaîne | Public |
| POST | /strings |
Créer une nouvelle chaîne | Partner ou Moderator |
| DELETE | /strings/:id |
Supprimer une chaîne | Moderator |
| GET | /strings/:id/translations |
Voir les traductions d’une chaîne | Public |
| GET | /strings/translations/:lang |
Lister toutes les traductions d’une langue | Public |
| POST | /strings/:id/translations |
Ajouter ou mettre à jour une traduction | Partner ou Moderator |
| POST | /strings/bulk |
Importer en masse chaînes + traductions | Moderator |
- Phase 1 : garder les champs legacy (
title,description,steps) et ajouter les IDs facultatifs (titleStringId, etc.) ; le résolveur retombe sur les champs legacy si l’ID est absent. - Phase 2 : exécuter le seed, renseigner les IDs dans
constants.ts; les nouvelles fiches utilisent directement les IDs. - Phase 3 : généraliser les IDs de chaînes puis retirer les champs legacy (changement majeur).
Référence unique : toute la documentation du système de traduction est désormais centralisée dans cette section du README.
npm run server:token moderator alice
Collez ensuite ces jetons dans la section « Jetons API » du tiroir administrateur. Les valeurs sont stockées dans localStorage via services/tokenStore et injectées automatiquement dans apiClient. Les jetons moderator/admin débloquent le panel de modération, tandis que les jetons partner permettent la publication immédiate des fiches partenaires.
services/syncServicegère la file des mutations (création, remerciements, modération) puis réconcilie les exercices renvoyés par l’API.- Les exercices marqués
deletedAtcôté serveur sont supprimés du cache et n’apparaissent plus dans les recommandations. - Le panel de modération interroge périodiquement
/api/moderation/queue; en cas d’erreur réseau ou de jeton manquant il bascule automatiquement sur les données locales.
- Depuis le tableau de bord principal, cliquez sur Ajouter une technique pour ouvrir
AddExerciseForm. - Renseignez au minimum le titre, la description et une situation cible.
- Indiquez chaque étape dans l’ordre; des URL d’images/GIF optionnelles peuvent améliorer la carte.
- Validez : la contribution est stockée localement, passe en statut « pending » et attend la validation du panel de modération.
- Le bouton « Dire Merci » (vue détail) appelle
incrementThanks, incrémente le compteur et déclenche un rerender des cartes afin que les techniques les plus utiles montent naturellement dans les recommandations. - Ces remerciements sont mis en file dans
syncServiceafin d’être persistés côté serveur dès que possible.
- Accédez-y via le tiroir administrateur (Espace Partenaires).
- Les organisations créent un compte local (stocké dans
localStorage) ou se connectent à un compte existant. - Deux workflows sont proposés :
- Création manuelle : formulaire complet (tags, étapes dynamiques, profils ciblés) publié instantanément et marqué « Partenaire ».
- Import CSV/JSON : parsing tolérant (
mapRowToDraft) avec auto-détection des colonnes (title,description,situations,steps,tags,warning,imageUrl). Les listes acceptent|,;ou,.
- Accessible via le tiroir administrateur (Modération). Sans session partenaire, l’utilisateur est redirigé vers l’espace partenaires pour s’authentifier.
- Affiche les contributions
isCommunitySubmitteden attente et un historique des décisions approuvées/refusées (8 derniers items). - Les modérateurs peuvent saisir une note, appliquer Valider ou Refuser (
moderateExercise). Les décisions mettent à jourmoderationStatus,moderationNotes,moderatedAt,moderatedByet peuvent supprimer l’entrée (shouldDelete). - Si un jeton
moderatorvalide est stocké, la file serveur est chargée toutes les 45 s; sinon l’interface reste fonctionnelle avec les données locales.
vite-plugin-pwainjecte automatiquement le service workersrc/sw.ts(Workbox) et le manifeste (public/manifest.webmanifest).src/sw.tsimplémente une stratégie de cache complète pour une utilisation 100% hors-ligne :- App shell :
StaleWhileRevalidatepour HTML/JS/CSS avec mise en cache immédiate lors de l'installation. - API :
NetworkFirstavec fallback sur cache (timeout 10s) +BackgroundSyncPluginpour rejouer les mutations. - Locales :
StaleWhileRevalidatepour fichiers/locales/*(7 jours, max 10 entrées) permettant le changement de langue hors-ligne. - Images :
CacheFirstavec expiration 30 jours (max 100 entrées). - CDN externes :
StaleWhileRevalidatepour fonts.googleapis.com, aistudiocdn.com, cdn.tailwindcss.com, etc. (max 60 entrées, 30 jours). - Fallback offline :
setCatchHandlerredirige les requêtes échouées vers le cache ou une erreur appropriée.
- App shell :
services/syncServicedéclenche la synchronisation après chaque mutation pour garantir une reprise automatique dès le retour réseau.
- Android / Chrome
- Construire l'app de production :
npm run build. - Démarrer le serveur preview :
npm run preview. - Sur votre PC, identifier votre adresse IP locale (
ipconfigsur Windows,ifconfigsur macOS/Linux). - Sur votre appareil Android connecté au même réseau Wi-Fi, ouvrir Chrome et accéder à
http://VOTRE_IP:4173. - Chrome affichera un bandeau « Ajouter à l'écran d'accueil » ou accéder via ⋮ > Installer l'application.
- Après installation, l'app fonctionne entièrement hors-ligne : le shell, les exercices et les images sont mis en cache automatiquement.
- Les actions (remerciements, contributions) sont mises en file et synchronisées dès le retour réseau.
- Construire l'app de production :
- iOS / Safari
- Même procédure pour accéder à l'URL via Safari.
- Appuyer sur Partager > Sur l'écran d'accueil.
- Lancer l'app en mode standalone.
- Activer le mode avion pour vérifier que l'application reste pleinement fonctionnelle hors-ligne.
- Vérifier/adapter
capacitor.config.ts(App IDcom.neurosooth.app,webDir: dist). - Générer le build web :
npm run build
- Initialiser les plateformes :
npx cap add ios npx cap add android
- Copier le build dans les shells natifs après chaque release :
npx cap sync
- Ouvrir les projets correspondants (
npx cap open ios/android), configurer les certificats stores (App Store / Play Store), puis soumettre les binaires. Les assets PWA (manifest + icônes) sont réutilisés automatiquement.