Additional UAT issue resolutions

This commit is contained in:
Stephanos A
2026-07-05 23:05:14 +03:00
parent ce5646235a
commit 102905d7e3
11 changed files with 119 additions and 81 deletions

View File

@@ -158,7 +158,7 @@ export default function ConfirmationPage() {
useEffect(() => {
if (!bookingId || !pnr) {
router.push('/booking/search');
window.location.href = '/';
}
}, [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>
</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 ? (
<div className="space-y-0.5">
<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 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>
</div>
) : (
<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>
)}
</div>

View File

@@ -962,7 +962,7 @@ export default function PassengersPage() {
useEffect(() => {
if (!searchCriteria) {
router.push('/booking/search');
window.location.href = '/';
}
}, [searchCriteria, router]);

View File

@@ -69,19 +69,24 @@ export default function PaymentPage() {
enabled: !!selectedMethod && !!bookingId,
});
// Fallback: estimate from local store while API hasn't responded yet.
// Uses the same first-child-free calculation as the review page so the
// breakdown shown here matches what the passenger already saw there.
const outboundBaseFare = !isPackage && isRoundTrip && outboundSchedule ? passengers.reduce((sum, _, i) => {
return sum + calculatePassengerFare(passengers, i, outboundSchedule.baseFareAdult || 0);
}, 0) : 0;
// Per-leg totals across all passengers.
// 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).
const adultCount = searchCriteria?.adultCount ?? passengers.filter(p => !isChild(p)).length;
const childCount = searchCriteria?.childCount ?? passengers.filter(p => isChild(p)).length;
const pkgPerLegAdultFare = isPackage ? packageTierPriceMinor! : 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) => {
return sum + calculatePassengerFare(passengers, i, inboundSchedule.baseFareAdult || 0);
}, 0) : 0;
const outboundBaseFare = isPackage
? pkgPerLegTotal
: (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
? (searchCriteria?.adultCount ?? 0) * pkgAdultFare + (searchCriteria?.childCount ?? 0) * pkgChildFare
? adultCount * pkgAdultFare + childCount * pkgChildFare
: isRoundTrip ? (outboundBaseFare + inboundBaseFare) : passengers.reduce((sum, _, i) => {
const farePerPassenger = selectedSchedule?.baseFareAdult || (selectedSchedule as any)?.fareAdult || (selectedSchedule as any)?.price || 0;
return sum + calculatePassengerFare(passengers, i, farePerPassenger);
@@ -299,15 +304,25 @@ export default function PaymentPage() {
{formatFare(passengerTotal, displayCurrency)}
</span>
</div>
{!isPackage && isRoundTrip && (
{isRoundTrip && (
<div className="pl-3 space-y-0.5 text-xs text-gray-500 dark:text-gray-400">
<div className="flex justify-between">
<span>Outbound {isFreeChild ? '(Free)' : ''}</span>
<span>{formatFare(calculatePassengerFare(passengers, i, outboundSchedule?.baseFareAdult || 0), displayCurrency)}</span>
<span>Outbound {!isPackage && isFreeChild ? '(Free)' : ''}</span>
<span>{formatFare(
isPackage
? (isChildPassenger ? pkgPerLegChildFare : pkgPerLegAdultFare)
: calculatePassengerFare(passengers, i, outboundSchedule?.baseFareAdult || 0),
displayCurrency
)}</span>
</div>
<div className="flex justify-between">
<span>Return {isFreeChild ? '(Free)' : ''}</span>
<span>{formatFare(calculatePassengerFare(passengers, i, inboundSchedule?.baseFareAdult || 0), displayCurrency)}</span>
<span>Return {!isPackage && isFreeChild ? '(Free)' : ''}</span>
<span>{formatFare(
isPackage
? (isChildPassenger ? pkgPerLegChildFare : pkgPerLegAdultFare)
: calculatePassengerFare(passengers, i, inboundSchedule?.baseFareAdult || 0),
displayCurrency
)}</span>
</div>
</div>
)}

View File

@@ -191,7 +191,7 @@ export default function ReviewPage() {
if (!searchCriteria?.originStationId || !searchCriteria?.destinationStationId) {
alert('Missing search criteria. Please start over.');
router.push('/booking/search');
window.location.href = '/';
return;
}
@@ -372,13 +372,13 @@ export default function ReviewPage() {
if (isRoundTrip) {
if (!outboundSchedule || !inboundSchedule || !passengers.length) {
if (!createBookingMutation.isPending && !createBookingMutation.isSuccess) {
router.push('/booking/search');
window.location.href = '/';
}
}
} else {
if (!selectedSchedule || !passengers.length) {
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 pkgAdultFare = isPackageBooking ? packageTierPriceMinor! * pkgRoundTripMultiplier : 0;
const pkgChildFare = isPackageBooking ? Math.round(pkgAdultFare * 0.1) : 0;
const adultPassengerCount = searchCriteria?.adultCount ?? passengers.length;
const childPassengerCount = searchCriteria?.childCount ?? 0;
const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p => !isChild(p)).length;
const childPassengerCount = searchCriteria?.childCount ?? passengers.filter(p => isChild(p)).length;
const total = isPackageBooking
? adultPassengerCount * pkgAdultFare + childPassengerCount * pkgChildFare

View File

@@ -40,6 +40,11 @@ interface TrainInfo {
operatorName: string;
}
interface StopTime {
sequence: number;
station: Station;
}
interface Schedule {
id: string;
departureAt: string;
@@ -50,6 +55,7 @@ interface Schedule {
originStation: Station;
destinationStation: Station;
train: TrainInfo;
stopTimes?: StopTime[];
}
interface PriceTier {
@@ -432,25 +438,10 @@ export default function PackageDetailPage() {
enabled: !!id,
});
const { data: stationsData } = useQuery({
queryKey: ["stations-simple"],
queryFn: async () => (await apiClient.get(`/stations?pageSize=100`)) as any,
});
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;
// Build departure station list from the outbound schedule's route stops (ordered by sequence)
const routeStopStations: Station[] = pkg?.outboundSchedule?.stopTimes?.length
? pkg.outboundSchedule.stopTimes.map((st) => st.station).filter(Boolean)
: [];
const selectedTier = pkg?.priceTiers?.find((t) => t.id === selectedTierId);
@@ -574,7 +565,7 @@ export default function PackageDetailPage() {
loading={bookingContextLoading}
error={bookingContextError}
priceMultiplier={isRoundTripPkg ? 2 : 1}
stations={stations}
stations={routeStopStations}
/>
)}

View File

@@ -161,7 +161,7 @@ export default function ProfilePage() {
mutationFn: () => apiClient.delete('/auth/account'),
onSuccess: () => {
logout();
router.push('/booking/search');
window.location.href = '/';
},
});

View File

@@ -1,7 +1,7 @@
'use client';
import { Suspense, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Train, ArrowLeft, ShieldCheck } from 'lucide-react';
import { iamAuthApi } from '@/lib/api/auth';
@@ -20,7 +20,6 @@ function isStrongPassword(pw: string): boolean {
}
function VerifyAccountContent() {
const router = useRouter();
const searchParams = useSearchParams();
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.
await login(email, newPassword);
router.push('/booking/search');
window.location.href = '/';
} catch (err: any) {
const msg = err.response?.data?.message || err.message || '';
setError(msg || 'Could not verify your account. Check the code and try again, or resend it.');