mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
Additional UAT issue resolutions
This commit is contained in:
@@ -144,22 +144,33 @@ export class GuestBookingService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate fare
|
// Calculate fare — package bookings use the fixed tier price, bypassing the fare engine
|
||||||
const primaryNationality = passengersData[0]?.nationality;
|
const isPackageOneway = !!dto.packageId && !!dto.priceTierId;
|
||||||
const baseFareMinor = await this.getBaseFare(
|
let baseFareMinor: number;
|
||||||
dto.scheduleId,
|
let paidChildrenCount: number;
|
||||||
dto.seatClassId,
|
let childUnitFare: number;
|
||||||
segmentRoute,
|
|
||||||
fullRoute,
|
if (isPackageOneway) {
|
||||||
primaryNationality,
|
const tier = await this.prisma.packagePriceTier.findUniqueOrThrow({ where: { id: dto.priceTierId! } });
|
||||||
dto.originStationId,
|
baseFareMinor = tier.priceMinor;
|
||||||
dto.destinationStationId,
|
paidChildrenCount = childCount;
|
||||||
);
|
childUnitFare = Math.round(baseFareMinor * 0.1);
|
||||||
|
} else {
|
||||||
|
const primaryNationality = passengersData[0]?.nationality;
|
||||||
|
baseFareMinor = await this.getBaseFare(
|
||||||
|
dto.scheduleId,
|
||||||
|
dto.seatClassId,
|
||||||
|
segmentRoute,
|
||||||
|
fullRoute,
|
||||||
|
primaryNationality,
|
||||||
|
dto.originStationId,
|
||||||
|
dto.destinationStationId,
|
||||||
|
);
|
||||||
|
paidChildrenCount = Math.max(0, childCount - 1);
|
||||||
|
childUnitFare = baseFareMinor;
|
||||||
|
}
|
||||||
|
|
||||||
const adultFareMinor = baseFareMinor * adultCount;
|
const adultFareMinor = baseFareMinor * adultCount;
|
||||||
const isPackageOneway = !!dto.packageId;
|
|
||||||
const paidChildrenCount = isPackageOneway ? childCount : Math.max(0, childCount - 1);
|
|
||||||
const childUnitFare = isPackageOneway ? Math.round(baseFareMinor * 0.1) : baseFareMinor;
|
|
||||||
const childFareMinor = childUnitFare * paidChildrenCount;
|
const childFareMinor = childUnitFare * paidChildrenCount;
|
||||||
const totalBaseFareMinor = adultFareMinor + childFareMinor;
|
const totalBaseFareMinor = adultFareMinor + childFareMinor;
|
||||||
|
|
||||||
@@ -369,19 +380,34 @@ export class GuestBookingService {
|
|||||||
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate fares for both legs
|
// Calculate fares for both legs — package bookings use the fixed tier price split across legs
|
||||||
const returnSeatClassId = dto.returnSeatClassId || dto.seatClassId;
|
const returnSeatClassId = dto.returnSeatClassId || dto.seatClassId;
|
||||||
const primaryNationality = passengersData[0]?.nationality;
|
const isPackageRoundTrip = !!dto.packageId && !!dto.priceTierId;
|
||||||
|
let outboundBaseFare: number;
|
||||||
|
let returnBaseFare: number;
|
||||||
|
let paidChildrenCount: number;
|
||||||
|
let outboundChildUnitFare: number;
|
||||||
|
let returnChildUnitFare: number;
|
||||||
|
|
||||||
const [outboundBaseFare, returnBaseFare] = await Promise.all([
|
if (isPackageRoundTrip) {
|
||||||
this.getBaseFare(dto.scheduleId, dto.seatClassId, outboundSegmentRoute, outboundFullRoute, primaryNationality, dto.originStationId, dto.destinationStationId),
|
const tier = await this.prisma.packagePriceTier.findUniqueOrThrow({ where: { id: dto.priceTierId! } });
|
||||||
this.getBaseFare(dto.returnScheduleId, returnSeatClassId, returnSegmentRoute, returnFullRoute, primaryNationality, dto.returnOriginStationId, dto.returnDestinationStationId),
|
// tier.priceMinor is the full round-trip price per adult; split evenly across legs
|
||||||
]);
|
const halfMinor = Math.round(tier.priceMinor / 2);
|
||||||
|
outboundBaseFare = halfMinor;
|
||||||
const isPackageRoundTrip = !!dto.packageId;
|
returnBaseFare = tier.priceMinor - halfMinor;
|
||||||
const paidChildrenCount = isPackageRoundTrip ? childCount : Math.max(0, childCount - 1);
|
paidChildrenCount = childCount;
|
||||||
const outboundChildUnitFare = isPackageRoundTrip ? Math.round(outboundBaseFare * 0.1) : outboundBaseFare;
|
outboundChildUnitFare = Math.round(outboundBaseFare * 0.1);
|
||||||
const returnChildUnitFare = isPackageRoundTrip ? Math.round(returnBaseFare * 0.1) : returnBaseFare;
|
returnChildUnitFare = Math.round(returnBaseFare * 0.1);
|
||||||
|
} else {
|
||||||
|
const primaryNationality = passengersData[0]?.nationality;
|
||||||
|
[outboundBaseFare, returnBaseFare] = await Promise.all([
|
||||||
|
this.getBaseFare(dto.scheduleId, dto.seatClassId, outboundSegmentRoute, outboundFullRoute, primaryNationality, dto.originStationId, dto.destinationStationId),
|
||||||
|
this.getBaseFare(dto.returnScheduleId, returnSeatClassId, returnSegmentRoute, returnFullRoute, primaryNationality, dto.returnOriginStationId, dto.returnDestinationStationId),
|
||||||
|
]);
|
||||||
|
paidChildrenCount = Math.max(0, childCount - 1);
|
||||||
|
outboundChildUnitFare = outboundBaseFare;
|
||||||
|
returnChildUnitFare = returnBaseFare;
|
||||||
|
}
|
||||||
const outboundTotalBase = outboundBaseFare * adultCount + outboundChildUnitFare * paidChildrenCount;
|
const outboundTotalBase = outboundBaseFare * adultCount + outboundChildUnitFare * paidChildrenCount;
|
||||||
const returnTotalBase = returnBaseFare * adultCount + returnChildUnitFare * paidChildrenCount;
|
const returnTotalBase = returnBaseFare * adultCount + returnChildUnitFare * paidChildrenCount;
|
||||||
const combinedBaseFareMinor = outboundTotalBase + returnTotalBase;
|
const combinedBaseFareMinor = outboundTotalBase + returnTotalBase;
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ export class PackagesService {
|
|||||||
remainingSeats: remaining,
|
remainingSeats: remaining,
|
||||||
outboundSchedule: {
|
outboundSchedule: {
|
||||||
scheduleId: pkg.outboundScheduleId,
|
scheduleId: pkg.outboundScheduleId,
|
||||||
originStationId: pkg.originStationId,
|
originStationId: pkg.outboundSchedule.originStationId,
|
||||||
destinationStationId: pkg.destinationStationId,
|
destinationStationId: pkg.outboundSchedule.destinationStationId,
|
||||||
departureAt: pkg.outboundSchedule.departureAt,
|
departureAt: pkg.outboundSchedule.departureAt,
|
||||||
arrivalAt: pkg.outboundSchedule.arrivalAt,
|
arrivalAt: pkg.outboundSchedule.arrivalAt,
|
||||||
originStation: pkg.outboundSchedule.originStation,
|
originStation: pkg.outboundSchedule.originStation,
|
||||||
@@ -204,7 +204,14 @@ export class PackagesService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
priceTiers: true,
|
priceTiers: true,
|
||||||
outboundSchedule: { include: { originStation: true, destinationStation: true, train: true } },
|
outboundSchedule: {
|
||||||
|
include: {
|
||||||
|
originStation: true,
|
||||||
|
destinationStation: true,
|
||||||
|
train: true,
|
||||||
|
stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
returnSchedule: { include: { originStation: true, destinationStation: true, train: true } },
|
returnSchedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ export class CreateScheduleDto {
|
|||||||
{ sequence: 6, plannedArrivalAt: '2026-06-15T20:00:00Z' },
|
{ sequence: 6, plannedArrivalAt: '2026-06-15T20:00:00Z' },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@IsArray() @ValidateNested({ each: true }) @Type(() => PlannedStopTimeDto)
|
@IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => PlannedStopTimeDto)
|
||||||
plannedTimes: PlannedStopTimeDto[];
|
plannedTimes?: PlannedStopTimeDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateScheduleDto {
|
export class UpdateScheduleDto {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export class SchedulesService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const providedSeqs = new Set(plannedTimes.map(t => t.sequence));
|
const providedSeqs = new Set((plannedTimes ?? []).map(t => t.sequence));
|
||||||
const missingSeqs = route.stops.map(s => s.sequence).filter(seq => !providedSeqs.has(seq));
|
const missingSeqs = route.stops.map(s => s.sequence).filter(seq => !providedSeqs.has(seq));
|
||||||
if (missingSeqs.length > 0) {
|
if (missingSeqs.length > 0) {
|
||||||
throw new BadRequestException(`Missing planned times for stop sequences: ${missingSeqs.join(', ')}`);
|
throw new BadRequestException(`Missing planned times for stop sequences: ${missingSeqs.join(', ')}`);
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export default function ConfirmationPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!bookingId || !pnr) {
|
if (!bookingId || !pnr) {
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
}, [bookingId, pnr, router]);
|
}, [bookingId, pnr, router]);
|
||||||
|
|
||||||
@@ -370,19 +370,19 @@ export default function ConfirmationPage() {
|
|||||||
<p className="font-semibold text-gray-900 dark:text-gray-100">{passenger.nationality}</p>
|
<p className="font-semibold text-gray-900 dark:text-gray-100">{passenger.nationality}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-600 dark:text-gray-400">Seat</p>
|
<p className="text-gray-600 dark:text-gray-400">Seat(s)</p>
|
||||||
{isRoundTrip ? (
|
{isRoundTrip ? (
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
Outbound: {(passenger as any).outboundSeatNumber || 'Auto-assigned at boarding'}{(passenger as any).outboundCoachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>(Coach {(passenger as any).outboundCoachNumber})</span>}
|
Outbound: {(passenger as any).outboundCoachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>{(passenger as any).outboundCoachNumber}</span>} — {(passenger as any).outboundSeatNumber || 'Auto-assigned at boarding'}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
Return: {(passenger as any).inboundSeatNumber || 'Auto-assigned at boarding'}{(passenger as any).inboundCoachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>(Coach {(passenger as any).inboundCoachNumber})</span>}
|
Return: {(passenger as any).inboundCoachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>{(passenger as any).inboundCoachNumber}</span>} — {(passenger as any).inboundSeatNumber || 'Auto-assigned at boarding'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{passenger.seatNumber || 'Auto-assigned at boarding'}{passenger.coachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>(Coach {passenger.coachNumber})</span>}
|
{passenger.coachNumber && <span className='text-xs text-gray-500 dark:text-gray-400 ml-1'>(Coach {passenger.coachNumber})</span>} — {passenger.seatNumber || 'Auto-assigned at boarding'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -962,7 +962,7 @@ export default function PassengersPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchCriteria) {
|
if (!searchCriteria) {
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
}, [searchCriteria, router]);
|
}, [searchCriteria, router]);
|
||||||
|
|
||||||
|
|||||||
@@ -69,19 +69,24 @@ export default function PaymentPage() {
|
|||||||
enabled: !!selectedMethod && !!bookingId,
|
enabled: !!selectedMethod && !!bookingId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fallback: estimate from local store while API hasn't responded yet.
|
// Per-leg totals across all passengers.
|
||||||
// Uses the same first-child-free calculation as the review page so the
|
// Package: one leg = pkgAdultFare/pkgChildFare (already ×1 per leg; pkgAdultFare already has ×2 for round-trip baked in via pkgRoundTripMultiplier — so per-leg is packageTierPriceMinor).
|
||||||
// breakdown shown here matches what the passenger already saw there.
|
const adultCount = searchCriteria?.adultCount ?? passengers.filter(p => !isChild(p)).length;
|
||||||
const outboundBaseFare = !isPackage && isRoundTrip && outboundSchedule ? passengers.reduce((sum, _, i) => {
|
const childCount = searchCriteria?.childCount ?? passengers.filter(p => isChild(p)).length;
|
||||||
return sum + calculatePassengerFare(passengers, i, outboundSchedule.baseFareAdult || 0);
|
const pkgPerLegAdultFare = isPackage ? packageTierPriceMinor! : 0;
|
||||||
}, 0) : 0;
|
const pkgPerLegChildFare = isPackage ? Math.round(pkgPerLegAdultFare * 0.1) : 0;
|
||||||
|
const pkgPerLegTotal = isPackage ? adultCount * pkgPerLegAdultFare + childCount * pkgPerLegChildFare : 0;
|
||||||
|
|
||||||
const inboundBaseFare = !isPackage && isRoundTrip && inboundSchedule ? passengers.reduce((sum, _, i) => {
|
const outboundBaseFare = isPackage
|
||||||
return sum + calculatePassengerFare(passengers, i, inboundSchedule.baseFareAdult || 0);
|
? pkgPerLegTotal
|
||||||
}, 0) : 0;
|
: (isRoundTrip && outboundSchedule ? passengers.reduce((sum, _, i) => sum + calculatePassengerFare(passengers, i, outboundSchedule.baseFareAdult || 0), 0) : 0);
|
||||||
|
|
||||||
|
const inboundBaseFare = isPackage
|
||||||
|
? pkgPerLegTotal
|
||||||
|
: (isRoundTrip && inboundSchedule ? passengers.reduce((sum, _, i) => sum + calculatePassengerFare(passengers, i, inboundSchedule.baseFareAdult || 0), 0) : 0);
|
||||||
|
|
||||||
const baseFare = isPackage
|
const baseFare = isPackage
|
||||||
? (searchCriteria?.adultCount ?? 0) * pkgAdultFare + (searchCriteria?.childCount ?? 0) * pkgChildFare
|
? adultCount * pkgAdultFare + childCount * pkgChildFare
|
||||||
: isRoundTrip ? (outboundBaseFare + inboundBaseFare) : passengers.reduce((sum, _, i) => {
|
: isRoundTrip ? (outboundBaseFare + inboundBaseFare) : passengers.reduce((sum, _, i) => {
|
||||||
const farePerPassenger = selectedSchedule?.baseFareAdult || (selectedSchedule as any)?.fareAdult || (selectedSchedule as any)?.price || 0;
|
const farePerPassenger = selectedSchedule?.baseFareAdult || (selectedSchedule as any)?.fareAdult || (selectedSchedule as any)?.price || 0;
|
||||||
return sum + calculatePassengerFare(passengers, i, farePerPassenger);
|
return sum + calculatePassengerFare(passengers, i, farePerPassenger);
|
||||||
@@ -299,15 +304,25 @@ export default function PaymentPage() {
|
|||||||
{formatFare(passengerTotal, displayCurrency)}
|
{formatFare(passengerTotal, displayCurrency)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{!isPackage && isRoundTrip && (
|
{isRoundTrip && (
|
||||||
<div className="pl-3 space-y-0.5 text-xs text-gray-500 dark:text-gray-400">
|
<div className="pl-3 space-y-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>Outbound {isFreeChild ? '(Free)' : ''}</span>
|
<span>Outbound {!isPackage && isFreeChild ? '(Free)' : ''}</span>
|
||||||
<span>{formatFare(calculatePassengerFare(passengers, i, outboundSchedule?.baseFareAdult || 0), displayCurrency)}</span>
|
<span>{formatFare(
|
||||||
|
isPackage
|
||||||
|
? (isChildPassenger ? pkgPerLegChildFare : pkgPerLegAdultFare)
|
||||||
|
: calculatePassengerFare(passengers, i, outboundSchedule?.baseFareAdult || 0),
|
||||||
|
displayCurrency
|
||||||
|
)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>Return {isFreeChild ? '(Free)' : ''}</span>
|
<span>Return {!isPackage && isFreeChild ? '(Free)' : ''}</span>
|
||||||
<span>{formatFare(calculatePassengerFare(passengers, i, inboundSchedule?.baseFareAdult || 0), displayCurrency)}</span>
|
<span>{formatFare(
|
||||||
|
isPackage
|
||||||
|
? (isChildPassenger ? pkgPerLegChildFare : pkgPerLegAdultFare)
|
||||||
|
: calculatePassengerFare(passengers, i, inboundSchedule?.baseFareAdult || 0),
|
||||||
|
displayCurrency
|
||||||
|
)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export default function ReviewPage() {
|
|||||||
|
|
||||||
if (!searchCriteria?.originStationId || !searchCriteria?.destinationStationId) {
|
if (!searchCriteria?.originStationId || !searchCriteria?.destinationStationId) {
|
||||||
alert('Missing search criteria. Please start over.');
|
alert('Missing search criteria. Please start over.');
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,13 +372,13 @@ export default function ReviewPage() {
|
|||||||
if (isRoundTrip) {
|
if (isRoundTrip) {
|
||||||
if (!outboundSchedule || !inboundSchedule || !passengers.length) {
|
if (!outboundSchedule || !inboundSchedule || !passengers.length) {
|
||||||
if (!createBookingMutation.isPending && !createBookingMutation.isSuccess) {
|
if (!createBookingMutation.isPending && !createBookingMutation.isSuccess) {
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!selectedSchedule || !passengers.length) {
|
if (!selectedSchedule || !passengers.length) {
|
||||||
if (!createBookingMutation.isPending && !createBookingMutation.isSuccess) {
|
if (!createBookingMutation.isPending && !createBookingMutation.isSuccess) {
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,8 +441,8 @@ export default function ReviewPage() {
|
|||||||
const pkgRoundTripMultiplier = isPackageBooking && isRoundTrip ? 2 : 1;
|
const pkgRoundTripMultiplier = isPackageBooking && isRoundTrip ? 2 : 1;
|
||||||
const pkgAdultFare = isPackageBooking ? packageTierPriceMinor! * pkgRoundTripMultiplier : 0;
|
const pkgAdultFare = isPackageBooking ? packageTierPriceMinor! * pkgRoundTripMultiplier : 0;
|
||||||
const pkgChildFare = isPackageBooking ? Math.round(pkgAdultFare * 0.1) : 0;
|
const pkgChildFare = isPackageBooking ? Math.round(pkgAdultFare * 0.1) : 0;
|
||||||
const adultPassengerCount = searchCriteria?.adultCount ?? passengers.length;
|
const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p => !isChild(p)).length;
|
||||||
const childPassengerCount = searchCriteria?.childCount ?? 0;
|
const childPassengerCount = searchCriteria?.childCount ?? passengers.filter(p => isChild(p)).length;
|
||||||
|
|
||||||
const total = isPackageBooking
|
const total = isPackageBooking
|
||||||
? adultPassengerCount * pkgAdultFare + childPassengerCount * pkgChildFare
|
? adultPassengerCount * pkgAdultFare + childPassengerCount * pkgChildFare
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ interface TrainInfo {
|
|||||||
operatorName: string;
|
operatorName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface StopTime {
|
||||||
|
sequence: number;
|
||||||
|
station: Station;
|
||||||
|
}
|
||||||
|
|
||||||
interface Schedule {
|
interface Schedule {
|
||||||
id: string;
|
id: string;
|
||||||
departureAt: string;
|
departureAt: string;
|
||||||
@@ -50,6 +55,7 @@ interface Schedule {
|
|||||||
originStation: Station;
|
originStation: Station;
|
||||||
destinationStation: Station;
|
destinationStation: Station;
|
||||||
train: TrainInfo;
|
train: TrainInfo;
|
||||||
|
stopTimes?: StopTime[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PriceTier {
|
interface PriceTier {
|
||||||
@@ -432,25 +438,10 @@ export default function PackageDetailPage() {
|
|||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: stationsData } = useQuery({
|
// Build departure station list from the outbound schedule's route stops (ordered by sequence)
|
||||||
queryKey: ["stations-simple"],
|
const routeStopStations: Station[] = pkg?.outboundSchedule?.stopTimes?.length
|
||||||
queryFn: async () => (await apiClient.get(`/stations?pageSize=100`)) as any,
|
? pkg.outboundSchedule.stopTimes.map((st) => st.station).filter(Boolean)
|
||||||
});
|
: [];
|
||||||
|
|
||||||
const allStations: Station[] = Array.isArray(stationsData) ? stationsData : stationsData?.items || stationsData?.data?.items || [];
|
|
||||||
|
|
||||||
// Fetch stops for the outbound schedule to filter stations to only those on the route
|
|
||||||
const outboundScheduleId = pkg?.outboundSchedule?.id;
|
|
||||||
const { data: scheduleStops } = useQuery({
|
|
||||||
queryKey: ["schedule-stops", outboundScheduleId],
|
|
||||||
queryFn: async () => (await apiClient.get(`/schedules/${outboundScheduleId}/stops`)) as any[],
|
|
||||||
enabled: !!outboundScheduleId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only show stations that are actual stops on the route
|
|
||||||
const stations: Station[] = scheduleStops?.length
|
|
||||||
? allStations.filter((s) => scheduleStops.some((stop: any) => stop.stationId === s.id || stop.station?.id === s.id))
|
|
||||||
: allStations;
|
|
||||||
|
|
||||||
const selectedTier = pkg?.priceTiers?.find((t) => t.id === selectedTierId);
|
const selectedTier = pkg?.priceTiers?.find((t) => t.id === selectedTierId);
|
||||||
|
|
||||||
@@ -574,7 +565,7 @@ export default function PackageDetailPage() {
|
|||||||
loading={bookingContextLoading}
|
loading={bookingContextLoading}
|
||||||
error={bookingContextError}
|
error={bookingContextError}
|
||||||
priceMultiplier={isRoundTripPkg ? 2 : 1}
|
priceMultiplier={isRoundTripPkg ? 2 : 1}
|
||||||
stations={stations}
|
stations={routeStopStations}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export default function ProfilePage() {
|
|||||||
mutationFn: () => apiClient.delete('/auth/account'),
|
mutationFn: () => apiClient.delete('/auth/account'),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
logout();
|
logout();
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Suspense, useState } from 'react';
|
import { Suspense, useState } from 'react';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Train, ArrowLeft, ShieldCheck } from 'lucide-react';
|
import { Train, ArrowLeft, ShieldCheck } from 'lucide-react';
|
||||||
import { iamAuthApi } from '@/lib/api/auth';
|
import { iamAuthApi } from '@/lib/api/auth';
|
||||||
@@ -20,7 +20,6 @@ function isStrongPassword(pw: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function VerifyAccountContent() {
|
function VerifyAccountContent() {
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const login = useAuthStore((s) => s.login);
|
const login = useAuthStore((s) => s.login);
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ function VerifyAccountContent() {
|
|||||||
});
|
});
|
||||||
// Auto-login with the freshly-set password; login lazy-provisions the passenger record.
|
// Auto-login with the freshly-set password; login lazy-provisions the passenger record.
|
||||||
await login(email, newPassword);
|
await login(email, newPassword);
|
||||||
router.push('/booking/search');
|
window.location.href = '/';
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = err.response?.data?.message || err.message || '';
|
const msg = err.response?.data?.message || err.message || '';
|
||||||
setError(msg || 'Could not verify your account. Check the code and try again, or resend it.');
|
setError(msg || 'Could not verify your account. Check the code and try again, or resend it.');
|
||||||
|
|||||||
Reference in New Issue
Block a user