Workflows (mobile)
Parcours utilisateurs principaux sur l'app mobile, en pseudo-séquence. À mettre à jour si un parcours change.
1. Recherche → détail → réservation → paiement
[Home] (recommandations via getRandom + UpcomingDepartures)
│
├─→ SearchBar / DestinationSearch / DepartureCalendar / Interests
│ publicApi.travels.search(...) ou .query(...)
│
└─→ [TravelDetails] (TravelIntro, TravelHighlights, TravelHotel, TravelDatePrices, TravelRatingComments)
publicApi.travels.getBySlug + publicApi.travels.getCatalog (cache 5 min)
│
├─→ sous-pages plein écran (routes, ex-overlays — cf. ADR 0012) :
│ /voyage/:slug/itinerary [TravelItinerary]
│ /voyage/:slug/departures [TravelDepartures]
│ /voyage/:slug/comments [TravelComments]
│
└─→ "Réserver" → [TravelBooking] (wizard, :stepName = étape active)
│
│ — Étape 1 : DepartureDate (sélection Occurrence)
│ └─→ fetchOccurrence + fetchVehicleOccupancy en parallèle
│
│ — Étape 2 : DepartureLocation (si travel dynamique)
│ └─→ choix d'un Stop sur les drives de l'occurrence
│
│ — Étape 3 : PassengerCounts (#Adult / Junior / Child / Baby)
│
│ — Étape 4 : Accommodation (skippée si pas d'hôtel)
│ └─→ getRoomTypeAvailability
│
│ — Étape 5 : AccommodationPassengersInformation
│ └─→ répartition passagers/chambres + mealPlan (si Seaside)
│
│ — Étape 6 : PassengerCountsInformation
│ └─→ infos passager (Civility, nom, naissance...)
│ └─→ option: pré-remplir depuis MyPassengers
│
│ — Étape 7 : SeatSelection
│ └─→ TBSeatMap, vérifie l'occupancy
│
│ — Étape 8 : Payment
│ └─→ (option) saisie code cadeau → publicApi.gifts.validate
│ └─→ paymentApi.initialize(booking, customer, returnUrl)
│ └─→ webview Saferpay (redirectUrl)
│
└─→ [PaymentCallback]
paymentApi.callback(bookingId) (cacheable)
→ transactionStatus AUTHORIZED|CAPTURED → confirmation
→ CANCELED|ERROR → erreur + retour possibleBascule liste d'attente : si bookingState indique complet (ou forceWaitingList), l'étape Payment est remplacée par WaitingListForm → publicApi.mail.addWaitingList → [WaitingListConfirmation]. Pas de Booking créé.
Variante balnéaire (Seaside) : l'onglet « Balnéaires » de la BottomNavigation mène à [Seaside] (/seasides), même chrome que Home (search bar, filtres, cards) mais listing restreint aux balnéaires.
[Seaside] (searchSeaside → publicApi.travels.search({ seaside: true }) → ?seaside=true)
└─→ SeasideSearchBar / SeasideSearch / SeasideInterestsFilterBar (état seasideFilterState)
└─→ [TravelDetails] → wizard (parcours identique ci-dessus)Home, lui, appelle searchTrips (?seaside=false). États de recherche indépendants entre les deux pages.
2. Création de compte
[Login] → "Créer un compte" → [CreateAccount]
│
├─ Saisie email
│ └─→ publicApi.customer.exists(email) (HTTP 200 = pris)
│ └─→ si pris : retour à Login
│
├─ Infos personnelles (Civility, nom, prénom, birthdate, phone)
│ └─→ validation Regle (utils/regle-validators.ts)
│
└─ Validation
└─→ publicApi.customer.create({ ...customer, birthdate: yyyy-MM-dd })
└─→ Login auto (authApi.login) + setCustomer + redirect [Account]3. Reset de mot de passe (logged-out)
[Login] → "Mot de passe oublié" → [PasswordForgot]
└─→ authApi.forgot(email)
(mail envoyé avec lien → /reinitialiser-mon-mot-de-passe?token=...)
│
└─→ [PasswordReset] (web/deep link)
└─→ authApi.resetPassword(email, newPassword, token)4. Mes voyages → facture
[MyTravels]
└─→ protectedApi.bookings.getAll()
└─→ tap sur une carte
└─→ [BookingDetails]
└─→ "Télécharger facture"
└─→ protectedApi.bookings.getInvoice(bookingId) (base64 PDF)
└─→ @capacitor/file-viewer pour ouverture native5. Avis sur un voyage
[BookingDetails] (voyage passé) → "Laisser un avis" → [CommentFormOverlay]
└─→ protectedApi.comments.create({ travelId, comment, rating })
(côté backend : isApproved = false forcé)
│
└─→ visible dans [MyComments] tout de suite, modéré ensuite
[MyComments]
└─→ protectedApi.comments.getMine()
(décorateur : croise avec bookings store pour récupérer travelName/slug/image)
└─→ édition / suppression : .update / .delete6. Initialisation app (chaque cold start)
main.ts
├─ CapApp.getInfo() → setChannel(dev|staging|production) selon bundle id
├─ CapacitorUpdater.notifyAppReady() (Capgo OTA)
├─ SplashScreen.show({ autoHide: false })
├─ ScreenOrientation.lock('portrait')
├─ pinia + persistedstate (hydrate stores)
├─ setBackButtonRouter(router) (avant app.use pour casser cycle)
├─ app.use(router) + router.isReady()
├─ app.mount('#app')
└─ SplashScreen.hide()Toute exception dans ce flow est catchée par try/finally et loggée [main] FATAL: — le splash reste alors visible (symptôme « white screen » remplacé par splash bloqué).

