Import initial de la documentation : statut, roadmap, plans V1/V2, specs verticales (creche, sport), audits securite, plan de test, analyse concurrentielle et maquettes de design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 KiB
Architecture web SaaS MyInfoMate
Décision d'architecture pour les deux canaux web de la solution MyInfoMate : le kiosk web et l'app visiteur web.
⚠️ Statut révisé le 2026-08-07 — les deux canaux ne sont plus au même stade.
- Visiteur web → V1.
visitapp-webest écrit,npm run buildpasse, les 13 types de section sont rendus et l'assistant IA y a été ajouté le 2026-08-06. La section « État d'avancement global » en bas de ce fichier date d'avant et sous-estime ce qui est fait (elle annonce Map/Quiz/Game/GuidedPath en stub et l'assistant hors scope — c'est faux). Il ne reste que le Dockerfile et l'entréeapp.myinfomate.bedans le compose. C'est bloquant : le plan Essentiel vendu en self-service est web-only.- Kiosk web → 📦 V2. Pas commencé, aucun client demandeur, et
tablet-appdont il dérive ne compile plus. Le reste de ce document reste la référence quand le chantier sera repris.
Contexte
Aujourd'hui MyInfoMate dispose de :
tablet-app(Flutter) — kiosk tablette fixemymuseum-visitapp(Flutter) — app native visiteur installée sur device persomanager-app(Flutter) — back-office, génère le client API consommé par les appsmanager-service(C# / ASP.NET Core) — backend API REST
Deux canaux web supplémentaires sont à ajouter :
- Kiosk web — version web de tablet-app pour clients ne voulant pas d'install native
- Visiteur web —
app.myinfomate.be/[nom-client]ouvert via QR code depuis le smartphone du visiteur
Décision
Kiosk web → Flutter Web (extension de tablet-app)
URL cible : kiosk.myinfomate.be/[deviceId]
- Extension du code existant
tablet-app(build web déjà testé surdemo.myinfomate.be) - Routing : si
deviceIddans l'URL → on charge la conf du device. Si pas dedeviceId→ écran de saisie pincode → création d'un device côté backend → tagweb(petite icône dans la liste devices côté manager-app) - Composants UI partagés via un package Dart commun (voir plus bas)
Pourquoi Flutter Web ici : kiosk = écran fixe, connexion stable, plein écran, on charge une fois et on reste. Le bundle Flutter Web (~4-5 MB) n'est pas un problème dans ce contexte. Bénéfice énorme : 100 % de réutilisation du code tablet-app existant.
Analyse de compatibilité web (tablet-app)
Le dossier web/ est déjà présent et configuré. Le code contient 52 occurrences de
kIsWeb — la multiplateforme était anticipée dès le départ.
Blocants à traiter :
| Problème | Priorité | Solution |
|---|---|---|
sqflite pas supporté en web (persistance deviceId/config) |
Haute | Remplacer par hive (support web natif) |
flutter_pdfview mobile-only |
Moyenne | Fallback HTML ou PDF.js en web |
| Mapbox pas supporté en web | Basse | flutter_map déjà dans les deps, ou Google Maps uniquement |
| MQTT désactivé en web | Non prioritaire | Rechargement de page suffit en contexte web |
Effort estimé : 2-3 jours pour une version kiosk web fonctionnelle.
Visiteur web → Next.js (nouvelle stack séparée)
URL cible : app.myinfomate.be/[slug]
- App Next.js dédiée, pas de Flutter Web
- Consomme le même
manager-serviceque les autres apps - Routing dynamique sur le slug client (génération automatique à la création du compte SaaS)
- Référence visuelle :
home_3.0.dart+ConfigurationPagedemymuseum-visitapp
Pourquoi pas Flutter Web ici : ce canal est ouvert via QR code depuis le smartphone perso du visiteur, parfois sur 3G/4G dégradée dans un musée. Flutter Web doit charger 4-5 MB de canvas JS avant d'afficher quoi que ce soit — c'est exactement le scénario où ça plombe l'expérience. Pour un produit SaaS vitrine, le time-to-interactive sur mobile est non négociable.
Pourquoi Next.js plutôt qu'Angular / SvelteKit :
- Angular : faisable mais bundle plus lourd, SSR via Angular Universal moins mature que Next.js. Philosophie calibrée pour back-offices riches (DI, modules, RxJS), pas pour une web app visiteur ultra-légère. On rame contre le framework.
- SvelteKit : plus léger encore mais nouvelle stack complète à apprendre, écosystème plus petit.
- Next.js : meilleur compromis. React + TypeScript, déjà utilisé pour les
landings (
myinfomate-landing,unov-landing), écosystème énorme, SSR/SSG natif, parfait pour le routingapp.myinfomate.be/[slug].
Theming multi-client (équivalent des flavors Flutter)
En Flutter, les couleurs/logos sont injectés au build-time via --dart-define=FLAVOR=client.
En Next.js, c'est runtime — chaque client a son slug et sa config dans le backend.
Avantage SaaS : zéro rebuild par client. Un nouveau client créé dans manager-app dispose instantanément de son URL avec ses couleurs et son logo.
Hiérarchie des couleurs
Les couleurs existent à deux niveaux dans le modèle de données :
ApplicationInstanceDTO.primaryColor / secondaryColor→ couleurs de l'instanceConfigurationDTO.primaryColor / secondaryColor→ couleurs d'une configuration spécifique
Règle d'application :
- Home (liste des configurations) → couleurs de l'instance
- À l'intérieur d'une configuration → couleurs de la configuration si définies, sinon fallback sur l'instance
const primaryColor = configuration.primaryColor ?? instance.primaryColor
const secondaryColor = configuration.secondaryColor ?? instance.secondaryColor
Les deux niveaux sont configurables dans manager-app via ColorPickerInputContainer
(écran Applications pour l'instance, écran Configuration pour chaque config).
Design system CSS
Deux couleurs configurables, le reste dérivé automatiquement :
Configurables (viennent du backend) :
--color-primary → boutons, header, gradient, états actifs
--color-secondary → accent, fin du gradient
Dérivées en JS au chargement :
--color-primary-light → primary à 12% opacité (fonds hover, badges)
--color-on-primary → blanc ou noir selon contraste WCAG automatique
Fixes (identiques pour tous les clients) :
--color-background → #FFFFFF
--color-surface → #F8F8F8 (cards, sections)
--color-text → #1A1A1A
--color-text-muted → #6B7280
--color-border → #E5E7EB
--color-on-primary est calculé automatiquement (lib color2k ou chroma-js)
pour garantir la lisibilité si un client choisit une couleur primaire claire.
// layout.tsx — injection dans <html style={...}>
const luminance = getLuminance(primaryColor)
const onPrimary = luminance > 0.4 ? '#1A1A1A' : '#FFFFFF'
Features — état d'avancement
| Feature | Statut | Notes |
|---|---|---|
| Grille de configurations (home) | ✅ Fait | ConfigurationGrid.tsx |
| Page configuration (liste sections) | ✅ Fait | SectionList.tsx |
| AppBar gradient + sélecteur de langue | ✅ Fait | AppBar.tsx, langue persistée en localStorage |
| Theming CSS variables (couleurs instance/config) | ✅ Fait | theme.ts, fallback instance → config |
| VisitorContext (langue + langues dispo) | ✅ Fait | VisitorContext.tsx |
| Section Article (HTML + carousel + audio flottant) | ✅ Fait | ArticleSection.tsx |
| Section Agenda (filtre par mois + popup détail) | ✅ Fait | AgendaSection.tsx |
| Section Menu (grille + recherche) | ✅ Fait | MenuSection.tsx |
| Section Slider (carousel + dots indicator) | ✅ Fait | SliderSection.tsx |
| Section Video (YouTube / Vimeo / direct) | ✅ Fait | VideoSection.tsx |
| Section PDF (iframe natif + sélecteur) | ✅ Fait | PdfSection.tsx |
| Section Weather (OpenWeatherMap, onglets par jour) | ✅ Fait | WeatherSection.tsx |
| Section Web (iframe) | ✅ Fait | WebSection.tsx |
| Section Map (Leaflet ou Google Maps) | 🔲 À faire | Nécessite Opus — complexe |
| Section Quiz | 🔲 À faire | |
| Section Game (puzzle) | 🔲 À faire | |
| Section GuidedPath | 🔲 À faire | |
| QR Scanner | 🔲 À faire | html5-qrcode ou jsQR |
| Chat assistant | ⏸ Hors scope | Feature premium — mobile uniquement pour l'instant |
Dockerfile + docker-compose app.myinfomate.be |
🔲 À faire | Base : Dockerfile de myinfomate-landing |
Structure des données (depuis manager-app)
Tout le contenu est configuré dans manager-app et exposé via manager-service.
Champs clés à respecter côté rendu web :
ConfigurationDTO (niveau racine) :
primaryColor/secondaryColor→ format#HEX, injectés en CSS variablesimageSource→ image de couvertureloaderImageUrl→ image de chargementlanguages→ liste des langues actives (ex:["FR", "EN", "NL"])sections→ ordonnées parorder, filtrées parisActive
SectionDTO (commun à tous les types) :
title/description→List<TranslationDTO>multilingueimageSource→ icône/image de la sectionisActive→ masquer si falseorder→ ordre dans le menu de navigationtype→ dispatcher vers le bon composant
TranslationDTO :
{ "language": "FR", "value": "Mon titre" }
Tous les champs texte sont multilingues. La langue sélectionnée par le visiteur détermine quelle valeur afficher.
Lien web ↔ instance : webSlug et publicApiKey ajoutés sur Instance
côté backend. Générés automatiquement à la création (slug depuis le nom, clé ap_xxx).
Endpoint POST /api/instance/{id}/generate-web-keys pour les instances existantes.
Multi-langue & persistance
- Langue sélectionnée stockée dans un React context au niveau du
layout.tsxde[slug] - Persistée en
localStoragepour survivre aux reloads - Toutes les fonctions de traduction lisent depuis ce context
- Le sélecteur de langue (drapeaux) est accessible depuis toutes les pages
QR Code
- Bouton flottant sur la page de configuration (même comportement que dans
mymuseum-visitapp) - Scan via
html5-qrcodeoujsQR+ accès caméragetUserMedia - Fonctionne sur mobile web, Safari iOS inclus (depuis iOS 14)
- Comportement : scan → valide l'ID contre la liste des sections → redirige vers la section
Notes d'implémentation
- Audio player flottant : utilisé uniquement dans
ArticleDetail, pas besoin de persister au niveau du layout — composant local à la page article. - Theming CSS variables : plus puissant qu'en Flutter (gradients, shadows, hover,
transitions natifs CSS). Le visuel sera dans la même veine que
mymuseum-visitapp.
Features explicitement hors scope (web)
- Beacons BLE — WebBluetooth non supporté sur iOS Safari
- Mode offline — Service Worker trop complexe pour le gain
- Push notifications — Firebase Web, pas prioritaire
Récapitulatif de la stack par canal
| Canal | URL | Tech | Rationale |
|---|---|---|---|
| Kiosk natif | tablet-app installée | Flutter | Existant |
| Kiosk web | kiosk.myinfomate.be/[deviceId] |
Flutter Web | Réutilise tablet-app, contexte OK |
| Visiteur natif | mymuseum-visitapp installée | Flutter | Existant |
| Visiteur web SaaS | app.myinfomate.be/[client] |
Next.js | Performance mobile critique |
| Back-office | manager-app | Flutter | Existant |
| Landings | myinfomate-landing, unov-landing | Next.js | Existant |
Conséquence sur l'organisation du code Flutter
Pour que le kiosk web fonctionne proprement et que tablet-app + mymuseum-visitapp restent cohérents, créer un package Dart partagé :
manager-app/ (génère client API)
└── manager_api_new/
myinfomate_shared/ ← nouveau package Dart
├── widgets/ (composants UI réutilisables)
├── models/ (DTOs métier hors API)
├── theme/
└── utils/
tablet-app/ → consomme manager_api_new + myinfomate_shared
mymuseum-visitapp/ → consomme manager_api_new + myinfomate_shared
Le canal visiteur web Next.js ne consomme pas ce package (techno différente)
mais consomme le même backend manager-service. La duplication est limitée à
la couche de présentation visiteur.
Déploiement Docker
Existant
L'infra est déjà en place dans manager-service/docker-compose-myinfomate.yml :
- Traefik v2.8 — reverse proxy + SSL Let's Encrypt automatique
- manager-service — API .NET 8, image versionnée (
2.0.0) - manager-app — Flutter Web, image versionnée (
2.0.0) - MongoDB — base de données
- Mosquitto MQTT — ports 1883/9001
- demo.myinfomate.be — build Flutter Web de tablet-app, déjà présent
Chaque projet a son propre Dockerfile multi-stage (Flutter → Nginx ou Node → Alpine).
Pas de CI/CD déclaratif — déploiement manuel via docker compose.
Ce qui manque pour la nouvelle archi
| À ajouter | Effort | Base |
|---|---|---|
Entrée kiosk.myinfomate.be dans le docker-compose |
Trivial | Copier/adapter l'entrée demo.myinfomate.be |
Dockerfile pour visitapp-web (Next.js) |
Faible | Copier le Dockerfile de myinfomate-landing |
Entrée app.myinfomate.be dans le docker-compose |
Trivial | Même pattern Traefik que les autres |
Développement local
Trois fichiers .env gèrent les environnements :
.env.local → dev local → backend local (gitignore)
.env.production.local → build local → backend prod (gitignore)
.env.production → valeurs prod par défaut (commité)
| Commande | Fichier lu | Usage |
|---|---|---|
npm run dev |
.env.local |
Dev quotidien |
npm run build && npm run start |
.env.production.local si présent |
Tester un build prod en local |
Déploiement
Build et déploiement manuel sur le serveur, cohérent avec l'existant :
docker compose -f docker-compose-myinfomate.yml build visitapp-web
docker compose -f docker-compose-myinfomate.yml up -d visitapp-web
CI/CD à envisager plus tard si le flow manuel devient trop lourd.
Point de vigilance
Les versions d'image sont fixées à 2.0.0 dans le docker-compose. À synchroniser
avec les versions réelles des projets avant tout déploiement.
État d'avancement global
manager-service ✅
WebSlug+PublicApiKeysurInstance(migration appliquée)- Endpoint
GET /api/instance/slug/{slug}(AllowAnonymous) - Endpoint
POST /api/instance/{id}/generate-web-keys ApiKeyAuthenticationHandlersupporte laPublicApiKey- CORS prod :
https://app.myinfomate.beajouté
manager-app ✅
InstanceDTOmis à jour avecwebSlugetpublicApiKey- Onglet Web dans le menu (existait déjà, branché sur
WebAppScreen) WebAppScreen: affiche URL visiteurs + clé publique (lecture seule, bouton copier)
visitapp-web ✅ (repo GITEA/visitapp-web)
- Bootstrap Next.js 16, App Router, TypeScript, Tailwind
- Routing
[slug]→[configId]→sections/[sectionId] - Theming runtime CSS variables (couleurs instance + override config)
- VisitorContext : langue persistée en localStorage
- AppBar gradient + sélecteur de langue
- 9 types de sections implémentés : Article, Agenda, Menu, Slider, Video, PDF, Weather, Web
- Sections stub (affiche "à venir") : Map, Quiz, Game, GuidedPath
À faire
| Tâche | Priorité |
|---|---|
| Section Map (Leaflet) | Haute — prévoir Opus |
| Section Quiz | Moyenne |
| Section Game (puzzle) | Basse |
| Section GuidedPath | Basse |
| QR Scanner sur page configuration | Moyenne |
Dockerfile + entrée docker-compose app.myinfomate.be |
Haute avant déploiement |
| Tester en local + ajuster le visuel | Immédiat |
Pour tester en local
POST /api/instance/{id}/generate-web-keys→ récupérerwebSlugetpublicApiKey- Configurer
visitapp-web/.env.localavecNEXT_PUBLIC_API_URL=http://localhost:5000 cd visitapp-web && npm run dev- Ouvrir
http://localhost:3000/{webSlug}