Skip to content

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 possible

Bascule liste d'attente : si bookingState indique complet (ou forceWaitingList), l'étape Payment est remplacée par WaitingListFormpublicApi.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 native

5. 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 / .delete

6. 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é).

Contributors

No contributors

Changelog

No recent changes