mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 20:05:41 +00:00
226 lines
8.3 KiB
TypeScript
226 lines
8.3 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
|
|
export interface SearchCriteria {
|
|
originStationId: string;
|
|
destinationStationId: string;
|
|
departureDate: string;
|
|
returnDate?: string;
|
|
tripType: 'ONE_WAY' | 'ROUND_TRIP';
|
|
adultCount: number;
|
|
childCount: number;
|
|
nationality: 'ETHIOPIAN' | 'DJIBOUTIAN' | 'OTHER';
|
|
promoCode?: string;
|
|
}
|
|
|
|
export interface PassengerDetail {
|
|
name: string;
|
|
dateOfBirth: string;
|
|
nationality: string;
|
|
faydaVerified?: boolean;
|
|
nationalId?: string;
|
|
faydaSub?: string;
|
|
passportNumber?: string;
|
|
passportCountry?: string;
|
|
passportIssueDate?: string;
|
|
passportExpiryDate?: string;
|
|
passportIssuingAuthority?: string;
|
|
idDocumentType?: string;
|
|
isPrimaryPassenger: boolean;
|
|
seatId?: string;
|
|
seatNumber?: string;
|
|
coachNumber?: string;
|
|
// Fare for this passenger's actual assigned seat (minor units). Bed coaches price
|
|
// Upper/Middle/Lower differently, so this can differ from the schedule's flat
|
|
// baseFareAdult (which is only the coach type's cheapest class) — review/payment
|
|
// should prefer this per-seat fare when it's available.
|
|
seatFareMinor?: number;
|
|
// Raw bed position ("lower"/"middle"/"upper") of the assigned seat, if it's a berth —
|
|
// used to resolve this passenger's actual seat class server-side (see review page's
|
|
// fare-breakdown request), since a shared coach-level class can't distinguish berths.
|
|
bedPosition?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
gender?: string;
|
|
// Round-trip specific seat assignments
|
|
outboundSeatId?: string;
|
|
outboundSeatNumber?: string;
|
|
outboundCoachNumber?: string;
|
|
outboundSeatFareMinor?: number;
|
|
outboundBedPosition?: string;
|
|
inboundSeatId?: string;
|
|
inboundSeatNumber?: string;
|
|
inboundCoachNumber?: string;
|
|
inboundSeatFareMinor?: number;
|
|
inboundBedPosition?: string;
|
|
returnSeatId?: string;
|
|
returnSeatNumber?: string;
|
|
}
|
|
|
|
export interface SelectedSchedule {
|
|
id: string;
|
|
trainNumber: string;
|
|
origin: string;
|
|
destination: string;
|
|
originStationId?: string;
|
|
destinationStationId?: string;
|
|
departureTime: string;
|
|
arrivalTime: string;
|
|
duration: string;
|
|
baseFareAdult: number;
|
|
baseFareChild: number;
|
|
displayCurrency: string;
|
|
selectedSeatClass?: string;
|
|
selectedSeatClassName?: string;
|
|
seatClassName?: string;
|
|
selectedCoachTypeId?: string;
|
|
selectedCoachTypeCode?: string;
|
|
selectedCoachTypeName?: string;
|
|
// All coach types (with per-class fares) offered on this schedule at selection time —
|
|
// kept so the seat map's coach preview can compute the fare difference before letting
|
|
// a passenger switch to a different coach type.
|
|
coachTypes?: Array<{
|
|
coachId: string;
|
|
coachTypeId: string;
|
|
coachTypeName: string;
|
|
coachTypeCode: string;
|
|
classes: Array<{
|
|
name: string;
|
|
baseFareMinor: number;
|
|
displayCurrency?: string;
|
|
displayAmountMinor?: number;
|
|
}>;
|
|
}>;
|
|
}
|
|
|
|
export interface SeatHold {
|
|
holdId: string;
|
|
expiresAt: string;
|
|
returnHoldId?: string;
|
|
returnExpiresAt?: string;
|
|
}
|
|
|
|
interface BookingState {
|
|
searchCriteria: SearchCriteria | null;
|
|
selectedSchedule: SelectedSchedule | null;
|
|
outboundSchedule: SelectedSchedule | null;
|
|
inboundSchedule: SelectedSchedule | null;
|
|
passengers: PassengerDetail[];
|
|
seatHold: SeatHold | null;
|
|
bookingId: string | null;
|
|
pnr: string | null;
|
|
// The hold id(s) the current bookingId was actually created with — lets the review
|
|
// page detect "user came back from the payment gateway with the same booking still
|
|
// valid" and reuse it instead of creating a duplicate booking.
|
|
bookingHoldId: string | null;
|
|
bookingReturnHoldId: string | null;
|
|
selectedPaymentMethod: string | null;
|
|
createAccount: boolean;
|
|
passengerId: string | null;
|
|
packageId: string | null;
|
|
packageName: string | null;
|
|
priceTierId: string | null;
|
|
packageTierPriceMinor: number | null;
|
|
packageDepartureStationId: string | null;
|
|
packageDepartureStationName: string | null;
|
|
// The exact total (minor units, ETB) computed on the review page — used as the
|
|
// single source of truth on payment and confirmation pages to prevent drift.
|
|
reviewedTotalMinor: number | null;
|
|
// Per-passenger fare breakdown computed on the review page — guarantees line items
|
|
// on the payment page sum to exactly reviewedTotalMinor.
|
|
reviewedPassengerFares: Array<{ fareMinor: number; isFree: boolean; outboundFareMinor?: number; inboundFareMinor?: number }> | null;
|
|
|
|
setSearchCriteria: (criteria: SearchCriteria) => void;
|
|
setSelectedSchedule: (schedule: SelectedSchedule) => void;
|
|
setOutboundSchedule: (schedule: SelectedSchedule) => void;
|
|
setInboundSchedule: (schedule: SelectedSchedule) => void;
|
|
setPassengers: (passengers: PassengerDetail[]) => void;
|
|
setSeatHold: (hold: SeatHold | null) => void;
|
|
setBookingId: (id: string) => void;
|
|
setPNR: (pnr: string) => void;
|
|
setBookingHoldReference: (holdId: string | null, returnHoldId?: string | null) => void;
|
|
setPaymentMethod: (method: string) => void;
|
|
setCreateAccount: (create: boolean) => void;
|
|
setPassengerId: (id: string | null) => void;
|
|
setPackageContext: (packageId: string, priceTierId: string, priceMinor: number, packageName?: string, departureStationId?: string, departureStationName?: string) => void;
|
|
setReviewedTotal: (totalMinor: number, passengerFares: Array<{ fareMinor: number; isFree: boolean; outboundFareMinor?: number; inboundFareMinor?: number }>) => void;
|
|
clearBooking: () => void;
|
|
}
|
|
|
|
export const useBookingStore = create<BookingState>()(persist(
|
|
(set) => (({
|
|
searchCriteria: null,
|
|
selectedSchedule: null,
|
|
outboundSchedule: null,
|
|
inboundSchedule: null,
|
|
passengers: [],
|
|
seatHold: null,
|
|
bookingId: null,
|
|
pnr: null,
|
|
bookingHoldId: null,
|
|
bookingReturnHoldId: null,
|
|
selectedPaymentMethod: null,
|
|
createAccount: false,
|
|
passengerId: null,
|
|
packageId: null,
|
|
packageName: null,
|
|
priceTierId: null,
|
|
packageTierPriceMinor: null,
|
|
packageDepartureStationId: null,
|
|
packageDepartureStationName: null,
|
|
reviewedTotalMinor: null,
|
|
reviewedPassengerFares: null,
|
|
|
|
setSearchCriteria: (criteria) => set({ searchCriteria: criteria }),
|
|
setPackageContext: (packageId, priceTierId, priceMinor, packageName, departureStationId, departureStationName) => set({ packageId, packageName: packageName ?? null, priceTierId, packageTierPriceMinor: priceMinor, packageDepartureStationId: departureStationId ?? null, packageDepartureStationName: departureStationName ?? null }),
|
|
setSelectedSchedule: (schedule) => set({ selectedSchedule: schedule }),
|
|
setOutboundSchedule: (schedule) => set({ outboundSchedule: schedule }),
|
|
setInboundSchedule: (schedule) => set({ inboundSchedule: schedule }),
|
|
setPassengers: (passengers) => set({ passengers }),
|
|
setSeatHold: (hold) => set({ seatHold: hold }),
|
|
setBookingId: (id) => set({ bookingId: id }),
|
|
setPNR: (pnr) => set({ pnr }),
|
|
setBookingHoldReference: (holdId, returnHoldId) => set({ bookingHoldId: holdId, bookingReturnHoldId: returnHoldId ?? null }),
|
|
setPaymentMethod: (method) => set({ selectedPaymentMethod: method }),
|
|
setCreateAccount: (create) => set({ createAccount: create }),
|
|
setPassengerId: (id) => set({ passengerId: id }),
|
|
setReviewedTotal: (totalMinor, passengerFares) => set({ reviewedTotalMinor: totalMinor, reviewedPassengerFares: passengerFares }),
|
|
clearBooking: () => set({
|
|
searchCriteria: null,
|
|
selectedSchedule: null,
|
|
outboundSchedule: null,
|
|
inboundSchedule: null,
|
|
passengers: [],
|
|
seatHold: null,
|
|
bookingId: null,
|
|
pnr: null,
|
|
bookingHoldId: null,
|
|
bookingReturnHoldId: null,
|
|
selectedPaymentMethod: null,
|
|
createAccount: false,
|
|
passengerId: null,
|
|
packageId: null,
|
|
packageName: null,
|
|
priceTierId: null,
|
|
packageTierPriceMinor: null,
|
|
packageDepartureStationId: null,
|
|
packageDepartureStationName: null,
|
|
reviewedTotalMinor: null,
|
|
reviewedPassengerFares: null,
|
|
}),
|
|
} as BookingState)),
|
|
{
|
|
name: 'booking-storage',
|
|
storage: createJSONStorage(() => {
|
|
if (typeof window !== 'undefined') {
|
|
return localStorage;
|
|
}
|
|
return {
|
|
getItem: () => null,
|
|
setItem: () => {},
|
|
removeItem: () => {},
|
|
};
|
|
}),
|
|
}
|
|
));
|