Architecture — Mobile (mobile-staging/)
Executive summary
App Expo SDK 54 / React Native 0.81.5 ciblant iOS + Android uniquement (jsEngine
Hermes, app.json.platforms = ["ios", "android"] — pas de cible web, voir BUG-134).
File-based routing via expo-router v6. Architecture 2 espaces in-app : client
(5 onglets (tabs)/) + gérant (vitrine-pro/ non-Stack, basculé via toggle Compte).
Source de vérité dev : ~/DEVELOPPEMENT/ sur la machine fabrice (192.168.1.152,
user fabrice). Le dossier mobile-staging/ sur le VPS est un miroir. Repo
mobile : github.com/ouattaradfabrice/wari-mobile (branche main SSH).
iOS bundle : pro.wari.mobile (Team 9HTKV7262K, ascAppId 6768885815). Slug Expo :
wari-mobile. EAS project : 4d8772a8-77f0-4fb1-818e-576a2f755fe1.
Technology stack
| Catégorie | Technologie | Version | Pourquoi ce choix |
|---|---|---|---|
| Framework | Expo SDK | 54 | Outillage natif (EAS, expo-camera, expo-notifications, expo-secure-store) sans toucher iOS/Android natifs |
| RN | React Native | 0.81.5 | Aligné Expo 54 ; jsEngine Hermes obligatoire |
| Router | expo-router | 6.0.23 | File-based, gère stacks + tabs + modals |
| Langage | TypeScript | 5.9 | Strict |
| State | Zustand | 5.0 | 16 stores, simple et performant (vs Redux/MobX) |
| Data fetching | @tanstack/react-query | 5.62 | Cache automatique, invalidations, staleTime tuné 60s sur Android mid-range |
| Auth storage | expo-secure-store | 15.0 | Keychain iOS / Keystore Android + cache mémoire (perf Android) |
| Local storage | @react-native-async-storage | 2.2 | Favoris, historique, queue offline |
| Notif | expo-notifications | ~0.32 | Push iOS+Android (FCM via google-services.json, APNs Production via Apple Push Cert) |
| Camera | expo-camera | 17.0 | Scan code-barres + photo prise |
| Image picker | expo-image-picker | 17.0 | Sélection photos upload |
| Localization | expo-localization + i18n-js | 17.0 / 4.5 | Détection locale device |
| i18n | i18next + react-i18next | 26.2 / 17.0 | Traductions strings (fr default, en/ar/pt préparées) |
| Icons | phosphor-react-native | 3.0 | Lib unique — re-export via lib/icons.ts (DEC-133/134/135) |
| Animations | react-native-reanimated | 4.1 + worklets 0.5 | TopBar absolute, transitions tabs, gestes |
| Gestures | react-native-gesture-handler | 2.28 | Drag-to-close modals, swipe stories |
| Modals | react-native-screens | 4.16 | Modals natives perf |
| Safe area | react-native-safe-area-context | 5.6 | insets.bottom / top (CTA dynamic padding) |
| SVG | react-native-svg | 15.12 | Sparklines DIY (DEC-189), icônes |
| QR | react-native-qrcode-svg | 6.3 | QR tables restau + partage vitrine + iCal |
| WebView | react-native-webview | 13.15 | Builder vitrine embedded (mobile builder) + paiement PayDunya |
| Network | @react-native-community/netinfo | 11.4 | Détection offline (banner) |
| Monitoring | @sentry/react-native | 7.2 | Crash + perf |
| Tests | Jest 29 + jest-expo 55 + @testing-library/react-native 13 + @testing-library/jest-native 5.4 | Unit + composants | |
| Builds | EAS Build | CLI ≥ 18 | development (apk dev client) / preview (apk internal) / production (app-bundle Android + ios store) |
Architecture pattern
Pattern : deux espaces in-app fusionnés dans une seule app.
App root (_layout.tsx)
│
├── Espace CLIENT (par défaut)
│ └── (tabs)/_layout.tsx — 5 onglets : Accueil · Activité · Panier · Explorer · Compte
│ (TopBar absolue glass + SearchOverlay + AnimatedTabBar)
│
└── Espace GÉRANT (modal Animated.View, zIndex 200)
└── vitrine-pro/_layout.tsx — guard isTenant + GerantLayout + GerantTabBar dynamique
Tabs selon modulesActifs : Tableau / Activité (Commandes+RDV) / Catalogue (Produits|Services) /
Clientes / Cuisine (restau) / Carte / Tables / Réglages / StoriesLe toggle Mode Gérant se fait depuis l'onglet Compte → bouton "Ma Vitrine" (visible
si isTenant). Animated.View overlay couvre l'écran complet pour passer au-dessus de
la TopBar absolue (DEC-153).
Sous-patterns :
- TopBar
position: absolute(zIndex 100) avec effet glass translucide. Tous les ScrollView des écrans tabs doivent lireuseUIStore().topbarHeightet appliquerpaddingTop: topbarHeight + SPACING.sm(DEC-153). - Bottom sheets clavier-safe : règles strictes DEC-170/174 (
keyboardShouldPersistTaps, pas deKeyboardAvoidingViewsur position absolute, 2 variantes ctaPaddingBottom selon contexte Modal vs GerantLayout). - Filtres réutilisables :
<FilterChip />+<FilterSheet />partout (jamais d'UI ad-hoc, DEC-137/138/139). - Icônes unifiées Phosphor via
lib/icons.tscentral (DEC-133→136). - API client
lib/api.ts apiFetch(): ne throw jamais, retourne{ data, error, status }. - Routes constants dans
lib/constants.ts:VITRINE_DASHBOARD,VITRINE_CATALOGUE, ... - Hooks métier React Query dans
hooks/: 1 fichier par domaine (useAuth, useCart, useVitrine, useRdv, useRestaurant, useStoriesFeed, ...). - Type guards côté écrans pour gérer payloads teaser (vitrines INVITATION).
State management — Zustand stores
16 stores Zustand, organisés par domaine :
| Store | Rôle |
|---|---|
authStore | user, tenant, token, isAuthenticated, selectIsTenant, hydrate via expo-secure-store |
cartStore | Panier produits multi-tenants (groupé à l'affichage, checkout par vitrine) |
panierRepasStore | Panier restau séparé (DEC-219, jamais mélangé avec cartStore) — reset auto si tenantId change |
favorisStore | Favoris locaux (AsyncStorage). V2 sync vers Wishlist backend |
historyStore | 20 derniers produits consultés (AsyncStorage) |
gerantStore | activeTab, modeGerant, selectedCommande, activeReglagesTab (WP-232) |
explorerStore | activeTab persistant + pendingCategorieId (navigation cross-tabs) |
inboxArchiveStore | Archives Inbox gérant |
notesClientsStore | Notes locales par cliente |
offlineQueueStore | Queue actions hors ligne (V2) |
networkStore | NetInfo connecté/déconnecté |
themeStore | Light/dark/system (DEC-101) |
uiStore | topbarHeight + autres dimensions mesurées dynamiquement |
villeStore + geoStore | Géoloc + ville sélectionnée |
Pas de Redux, pas de Context API pour le state global métier.
Routing — expo-router v6 (file-based)
app/
├── _layout.tsx # Root : QueryClient + Sentry + i18n + AuthHydrator + Stack natif
├── (tabs)/ # Group "tabs" → AnimatedTabBar
│ ├── _layout.tsx # TopBar absolute + tabs
│ ├── index.tsx # /(tabs)
│ ├── activite.tsx # /(tabs)/activite
│ ├── panier.tsx
│ ├── explorer.tsx
│ ├── compte.tsx
│ ├── commandes.tsx
│ └── recherche.tsx
├── (onboarding)/ # Group "onboarding" — flow post-magic link
├── auth/ # /auth/index, /auth/canal, /auth/otp, /auth/profil, /auth/verify
├── produit/[id].tsx # /produit/:id (Zalando-like Stack screen)
├── prestation/[id].tsx
├── prestation/[id]/creneaux.tsx
├── vitrine/[slug].tsx # Vitrine mini-site
├── restaurant/[slug]/... # carte, panier, suivi/[id], table/[qrToken]
├── commande/checkout.tsx
├── reservation/confirmer.tsx
├── vitrine-pro/ # **Espace gérant — NON expo-router Stack** (in-app overlay)
│ ├── _layout.tsx # Custom GerantLayout + GerantTabBar
│ ├── dashboard.tsx
│ ├── commandes.tsx
│ └── ...
└── conversation/, evenement/, story/, search/, favoris.tsx, messages.tsx, ...Tous les Stack.Screen headerShown: false (BUG-116 fix) ; navigation pure custom.
API client
lib/api.ts :
async function apiFetch<T>(url, opts?): Promise<{ data: T|null; error: string|null; status: number }>
// Ne throw jamais. Retry singleton 401 (refresh token). Timeout 10s.Routes en constants (lib/constants.ts) :
const API_URL = "https://wari.pro"; // prod
// dev override via app.json.expo.extra.apiUrl si besoin
const VITRINE_DASHBOARD = "/api/mobile/vitrine/dashboard";
const VITRINE_CATALOGUE = "/api/mobile/vitrine/catalogue";
// ...60+ endpointsAuthentification : Bearer JWT via header Authorization: Bearer <token> lu depuis
expo-secure-store (cache mémoire pour Android perf). Voir lib/auth.ts.
Push notifications
Stack :
- Expo dev build (pas Expo Go) — pkg
expo-notificationsne fonctionne pas en Expo Go SDK 53+. - Firebase FCM (Android) —
google-services.jsoncommité (clé restreinte par fingerprint). - APNs Production (iOS) — cert configuré côté Apple Developer + Expo.
- Register flow :
- Mobile :
useNotificationshook s'enregistre au mount auth → récupèreExponentPushToken[...]. - POST
/api/mobile/push-tokens/registeravec{token, platform}. Backend stocke selonsession.role:- TENANT_ADMIN/SUPER_ADMIN →
PushToken.userId - CLIENT →
PushToken.clientAccountId
- TENANT_ADMIN/SUPER_ADMIN →
- Mobile :
- Envoi : backend
lib/push.ts sendPushToTenantAdmin/sendPushToClientviaexpo-server-sdk. Cleanup auto surDeviceNotRegistered. - Tap handler :
useNotificationsenregistreNotifications.addNotificationResponseReceivedListener→ router selondata.type(commande / rdv / paiement / acces / story).
⚠️ Expo Go : lib/notifications-import.ts (pattern BUG-118 fix) — import statique remplacé par require() conditionnel Constants.executionEnvironment !== "storeClient" pour éviter ERROR log au boot.
i18n + International Foundation
- i18next + react-i18next : strings dans
locales/<lng>/translation.json. - Default
fr;en,ar,ptpréparés (Wave 4-7 markets). - Markets : table backend
Market(24 markets ISO Wave 1-8 BF→UEMOA→CEDEAO→EU→Maghreb→US→Lusophonie). - Money pattern :
lib/money.tscôté backend ; côté mobile, formatage par devise via Intl.NumberFormat.
Builds & deployment
EAS profiles (eas.json) :
| Profile | Android | iOS | Channel |
|---|---|---|---|
development | apk + dev client | (n/a — dev local) | development |
preview | apk internal | non-simulator | preview |
production | app-bundle (Play Store) | autoIncrement buildNumber + Release | production |
Submit :
eas submit --platform ios→ ascAppId 6768885815 (TestFlight → App Store)- Android : pas encore configuré (Play Store pas lancé V1)
OTA :
- 95% des releases mobile =
eas update --branch production(~5 min, pas de rebuild natif) - Native build seulement quand : changement permissions, ajout package natif, version Expo bump, icône
- Voir
reference_mobile_commandes_prod.mddans la mémoire
Design system
- Couleurs : palette WARI 12 + hex libre (DEC-173). Pas de color picker natif.
- Charte tenant :
themeCouleurdu Tenant utilisé comme accent dans la vitrine mobile. - Icônes : Phosphor uniquement (DEC-133), tailles fixes 16/20/22/24/32-48, poids selon état.
- Filtres :
<FilterChip />(variantsfilter/cta) +<FilterSheet />(sections pliables + sticky CTA). - Glass surfaces :
useTheme().glass.bg(5-8% opacity) pour conteneurs sticky.withAlpha(color, opacity)helper. - Tabs underline : pattern Apple/Spotify pour tabs internes écran (Explorer, VitrineTabs, ReglagesTabs).
- Touch targets : min 44×44 partout.
Architecture client (5 onglets)
| Onglet | Icône | Rôle |
|---|---|---|
| Accueil | House | Découverte curatée (3 horizontales + grille catégories + banner) — pas de filtres |
| Activité | Bell | Commandes en cours + nouveautés vitrines suivies + favoris + mes RDV + mes demandes accès |
| Panier | ShoppingCart | Panier local Zustand, multi-tenants groupés |
| Explorer | Compass | 3 tabs internes (Vitrines/Produits/Prestations) + FilterSheet par tab |
| Compte | User | Auth + profil + paramètres + accès Ma Vitrine (si isTenant) |
Architecture gérant (vitrine-pro)
TabBar dynamique selon modulesActifs du tenant :
| Module actif | Onglets |
|---|---|
catalogue seul | Tableau / Activité / Catalogue / Clientes / Réglages |
catalogue+services (MIXTE) | Tableau / Activité / Catalogue (Produits |
restaurant | Tableau / Cuisine / Carte / Tables / Réglages |
rdv actif | Activité avec sous-tabs Commandes/RDV |
evenements | Onglet supplémentaire |
stories | Stories listing + composer |
Composants pivots :
GerantLayout.tsx— Animated.View overlay + header + slotGerantTabBar.tsx— TabBar custom dynamique + badges (nbAttente, nbDemandes, cuisine count)BoutiqueActivite.tsx— pivot sous-tabs Commandes/RDVBoutiqueCatalogue.tsx— pivot sous-tabs Produits/ServicesGerantReglages.tsx— 4 tabs WP-232 (Vitrine / Contact / Accès / Avancé)
Testing strategy
- Jest 29 + jest-expo 55 + @testing-library/react-native 13
- Configuration :
jest.config.js+jest.setup.js - Tests existants : composants UI, stores Zustand, hooks (couverture partielle)
- Commande :
npm testounpm run test:watch - Typecheck :
npm run typecheck(tsc --noEmit) - Lint :
npm run lint/lint:fix
Pas de tests E2E (Detox / Maestro pas configurés). QA pratique = TestFlight + sessions test (tests-pratiques/).
Source tree
Voir source-tree-analysis.md section "Part mobile".
Development workflow
Voir development-guide-mobile.md.
Deployment
Voir deployment-guide.md section "EAS".