Passenger portal build issue resolution

This commit is contained in:
Stephanos A
2026-06-03 16:14:41 +03:00
parent f4051aadfc
commit 8a9cc8afff
13 changed files with 17 additions and 341 deletions

View File

@@ -1,10 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
reactStrictMode: true,
transpilePackages: ['@edr/types', '@edr/ui-common'],
images: {
unoptimized: true, // Required for static export
unoptimized: true,
},
};

View File

@@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation';
import { useBookingStore } from '@/lib/booking-store';
import { useMutation, useQuery } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';
import { useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';
import { CheckCircle, Download, Share2, Copy, Printer, Mail, Train } from 'lucide-react';
import { QRCodeSVG } from 'qrcode.react';
import { format } from 'date-fns';
@@ -15,6 +15,7 @@ export default function ConfirmationPage() {
const router = useRouter();
const { bookingId, pnr, selectedSchedule, passengers, clearBooking } = useBookingStore();
const [copied, setCopied] = useState(false);
const confirmAttempted = useRef(false);
const confirmMutation = useMutation({
mutationFn: () => apiClient.patch(`/bookings/${bookingId}/confirm`, { status: 'SUCCEEDED' }),
@@ -27,7 +28,6 @@ export default function ConfirmationPage() {
return await apiClient.get(`/bookings/${bookingId}`);
} catch (error) {
console.log('Booking API not available, using local data');
// Return mock booking data
return {
id: bookingId,
pnr,
@@ -40,10 +40,11 @@ export default function ConfirmationPage() {
});
useEffect(() => {
if (bookingId && !confirmMutation.isSuccess && !confirmMutation.isPending) {
if (bookingId && !confirmAttempted.current) {
confirmAttempted.current = true;
confirmMutation.mutate();
}
}, [bookingId]);
}, [bookingId, confirmMutation]);
const copyPNR = () => {
if (pnr) {
@@ -54,7 +55,6 @@ export default function ConfirmationPage() {
};
const handleDownloadTickets = () => {
// Mock download - in production this would call the API
alert('Ticket download will be available soon. Your tickets are displayed below.');
};

View File

@@ -1,75 +0,0 @@
// This is the updated onSubmit function for passengers/page.tsx
// Replace the existing onSubmit function with this one
const onSubmit = async (data: FormData) => {
setSaving(true);
try {
let passengerId = '';
console.log('[Passengers] onSubmit called, isAuthenticated:', isAuthenticated, 'user:', user);
// For authenticated users, fetch the passenger profile to get the passengerId
if (isAuthenticated && user?.id) {
try {
console.log('[Passengers] Fetching passenger profile from /passengers/me');
const passengerProfile: any = await apiClient.get('/passengers/me');
console.log('[Passengers] Passenger profile response:', passengerProfile);
passengerId = passengerProfile?.id || '';
console.log('[Passengers] Extracted passengerId:', passengerId);
} catch (error) {
console.error('[Passengers] Failed to fetch passenger profile:', error);
}
}
console.log('[Passengers] passengerId before saving:', passengerId);
const passengerDetails = data.passengers.map((p, i) => ({
name: p.name,
dateOfBirth: p.dateOfBirth,
gender: p.gender,
nationality: p.nationality,
nationalId: p.nationalId,
passportNumber: p.passportNumber,
passportCountry: p.passportCountry,
phone: p.phone,
email: p.email,
isPrimaryPassenger: i === 0,
passengerId: i === 0 && passengerId ? passengerId : undefined,
}));
const deviceId = typeof window !== 'undefined'
? (localStorage.getItem('deviceId') || crypto.randomUUID())
: crypto.randomUUID();
await apiClient.post('/passengers/save-details', {
passengers: passengerDetails,
userId: user?.id,
deviceId,
});
setPassengers(passengerDetails);
setCreateAccount(data.createAccount);
// Save passengerId to booking store for later use
if (isAuthenticated && passengerId) {
const { setPassengerId } = useBookingStore.getState();
setPassengerId(passengerId);
console.log('[Passengers] Saved passengerId to booking store:', passengerId);
} else {
console.warn('[Passengers] Not saving passengerId - isAuthenticated:', isAuthenticated, 'passengerId:', passengerId);
}
// Store in localStorage as additional backup
if (typeof window !== 'undefined' && passengerId) {
localStorage.setItem('booking_passengerId', passengerId);
console.log('[Passengers] Stored passengerId in localStorage:', passengerId);
}
router.push('/booking/seats');
} catch (error) {
console.error('Failed to save passenger details:', error);
alert('Failed to save passenger details. Please try again.');
} finally {
setSaving(false);
}
};

View File

@@ -1,58 +0,0 @@
const onSubmit = async (data: FormData) => {
setSaving(true);
try {
let passengerId = '';
// For authenticated users, fetch the passenger profile to get the passengerId
if (isAuthenticated && user?.id) {
try {
const passengerProfile: any = await apiClient.get('/passengers/me');
passengerId = passengerProfile?.id || '';
console.log('Fetched passengerId:', passengerId);
} catch (error) {
console.error('Failed to fetch passenger profile:', error);
}
}
const passengerDetails = data.passengers.map((p, i) => ({
name: p.name,
dateOfBirth: p.dateOfBirth,
gender: p.gender,
nationality: p.nationality,
nationalId: p.nationalId,
passportNumber: p.passportNumber,
passportCountry: p.passportCountry,
phone: p.phone,
email: p.email,
isPrimaryPassenger: i === 0,
passengerId: i === 0 && passengerId ? passengerId : undefined,
}));
const deviceId = typeof window !== 'undefined'
? (localStorage.getItem('deviceId') || crypto.randomUUID())
: crypto.randomUUID();
await apiClient.post('/passengers/save-details', {
passengers: passengerDetails,
userId: user?.id,
deviceId,
});
setPassengers(passengerDetails);
setCreateAccount(data.createAccount);
// Save passengerId to booking store for later use
if (isAuthenticated && passengerId) {
const { setPassengerId } = useBookingStore.getState();
setPassengerId(passengerId);
console.log('Saved passengerId to booking store:', passengerId);
}
router.push('/booking/seats');
} catch (error) {
console.error('Failed to save passenger details:', error);
alert('Failed to save passenger details. Please try again.');
} finally {
setSaving(false);
}
};

View File

@@ -1,4 +0,0 @@
const onSubmit = async (data: FormData) => {
setSaving(true);
try {
const passengerDetails = data.passengers.map((p, i) => ({\n name: p.name,\n dateOfBirth: p.dateOfBirth,\n gender: p.gender,\n nationality: p.nationality,\n nationalId: p.nationalId,\n passportNumber: p.passportNumber,\n passportCountry: p.passportCountry,\n phone: p.phone,\n email: p.email,\n isPrimaryPassenger: i === 0,\n passengerId: i === 0 && isAuthenticated ? user?.passenger?.id : undefined,\n }));\n\n const deviceId = typeof window !== 'undefined'\n ? (localStorage.getItem('deviceId') || crypto.randomUUID())\n : crypto.randomUUID();\n\n await apiClient.post('/passengers/save-details', {\n passengers: passengerDetails,\n userId: user?.id,\n deviceId,\n });\n\n setPassengers(passengerDetails);\n setCreateAccount(data.createAccount);\n \n // Save passengerId from authenticated user to booking store\n if (isAuthenticated && user?.passenger?.id) {\n const { setPassengerId } = useBookingStore.getState();\n setPassengerId(user.passenger.id);\n }\n \n router.push('/booking/seats');\n } catch (error) {\n console.error('Failed to save passenger details:', error);\n alert('Failed to save passenger details. Please try again.');\n } finally {\n setSaving(false);\n }\n };

View File

@@ -1,6 +0,0 @@
'use client';
export const dynamic = 'force-dynamic';
import { useForm, useFieldArray } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

View File

@@ -52,7 +52,6 @@ export default function PassengersPage() {
const { user, isAuthenticated, updateUser } = useAuthStore();
const [faydaEnabled, setFaydaEnabled] = useState(true);
const [verificationStatus, setVerificationStatus] = useState<Record<number, 'success' | 'error'>>({});
const [updatingUser, setUpdatingUser] = useState(false);
const [saving, setSaving] = useState(false);
const [formInitialized, setFormInitialized] = useState(false);
const [nationalityMismatch, setNationalityMismatch] = useState(false);
@@ -309,7 +308,9 @@ export default function PassengersPage() {
<button
onClick={() => {
clearBooking();
if (typeof window !== 'undefined') {
window.location.href = '/booking/search';
}
}}
className="btn-primary w-full flex items-center justify-center gap-2"
>
@@ -378,14 +379,9 @@ export default function PassengersPage() {
type="button"
onClick={() => openFaydaVerification(index)}
className="btn-primary flex items-center justify-center gap-2 mx-auto"
disabled={updatingUser}
>
{updatingUser ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<ExternalLink className="w-5 h-5" />
)}
{updatingUser ? 'Updating Profile...' : 'Verify with Fayda'}
{isLoggedInNotVerified ? 'Verify with Fayda' : 'Verify with Fayda'}
</button>
<button
type="button"

View File

@@ -1,12 +0,0 @@
// Save passengerId to booking store for later use
if (isAuthenticated && passengerId) {
const { setPassengerId } = useBookingStore.getState();
setPassengerId(passengerId);
console.log('Saved passengerId to booking store:', passengerId);
// Also save to localStorage as backup
if (typeof window !== 'undefined') {
localStorage.setItem('booking_passengerId', passengerId);
console.log('Saved passengerId to localStorage:', passengerId);
}
}

View File

@@ -1,43 +0,0 @@
// Key changes needed in passengers/page.tsx onSubmit function:
const onSubmit = async (data: FormData) => {
setSaving(true);
try {
// Build passenger details - ALWAYS create new records, don't reuse
const passengerDetails = data.passengers.map((p, i) => ({
name: p.name,
dateOfBirth: p.dateOfBirth,
gender: p.gender,
nationality: p.nationality,
nationalId: p.nationalId,
passportNumber: p.passportNumber,
passportCountry: p.passportCountry,
phone: p.phone,
email: p.email,
isPrimaryPassenger: i === 0,
// IMPORTANT: Don't include passengerId here - it's only needed in booking creation
}));
const deviceId = typeof window !== 'undefined'
? (localStorage.getItem('deviceId') || crypto.randomUUID())
: crypto.randomUUID();
// Save passenger details (this is for UI reference, not booking creation)
await apiClient.post('/passengers/save-details', {
passengers: passengerDetails,
userId: user?.id,
deviceId,
});
// Store in booking store for the next step (seats selection)
setPassengers(passengerDetails);
setCreateAccount(data.createAccount);
router.push('/booking/seats');
} catch (error) {
console.error('Failed to save passenger details:', error);
alert('Failed to save passenger details. Please try again.');
} finally {
setSaving(false);
}
};

View File

@@ -1,114 +0,0 @@
// Key changes needed in review/page.tsx handleConfirm function:
const handleConfirm = async () => {
console.log('handleConfirm called');
try {
const { searchCriteria } = useBookingStore.getState();
// Validate required data
if (!seatHold?.holdId) {
alert('Please select seats before continuing.');
router.push('/booking/seats');
return;
}
if (!searchCriteria?.originStationId || !searchCriteria?.destinationStationId) {
alert('Missing search criteria. Please start over.');
router.push('/booking/search');
return;
}
// Get seat class
let seatClassId = 'default-seat-class-id';
try {
const seatClasses: any = await apiClient.get('/seat-classes');
if (seatClasses && seatClasses.length > 0) {
seatClassId = seatClasses[0].id;
}
} catch (err) {
console.error('Failed to fetch seat classes:', err);
}
let bookingData: any;
if (isAuthenticated) {
// For authenticated users: get passengerId from user profile
let passengerId = '';
try {
// Fetch user's passenger profile
const passengerProfile: any = await apiClient.get('/passengers/me');
passengerId = passengerProfile?.id;
console.log('Got passengerId from profile:', passengerId);
} catch (error) {
console.error('Failed to get passenger profile:', error);
throw new Error('Unable to retrieve your passenger profile. Please try again.');
}
if (!passengerId) {
throw new Error('Passenger profile not found. Please update your profile and try again.');
}
bookingData = {
scheduleId: selectedSchedule?.id || '',
holdId: seatHold.holdId,
originStationId: searchCriteria.originStationId,
destinationStationId: searchCriteria.destinationStationId,
seatClassId: seatClassId,
displayCurrency: 'ETB',
passengerId: passengerId,
passengers: passengers.map((p) => {
const isEthiopian = p.nationality === 'ETHIOPIAN' || p.nationality === 'Ethiopian';
return {
seatId: p.seatId || '',
passengerName: p.name,
dateOfBirth: p.dateOfBirth,
idDocumentType: isEthiopian ? 'NATIONAL_ID' : 'PASSPORT',
idDocumentNumber: isEthiopian ? (p.nationalId || '') : '',
passportNumber: !isEthiopian ? (p.passportNumber || '') : '',
passportCountry: !isEthiopian ? (p.passportCountry || '') : '',
nationality: p.nationality,
};
}),
};
} else {
// For guests: send full passenger details
bookingData = {
scheduleId: selectedSchedule?.id || '',
holdId: seatHold.holdId,
originStationId: searchCriteria.originStationId,
destinationStationId: searchCriteria.destinationStationId,
seatClassId: seatClassId,
displayCurrency: 'ETB',
passengers: passengers.map(p => {
const isEthiopian = p.nationality === 'ETHIOPIAN' || p.nationality === 'Ethiopian';
return {
seatId: p.seatId || '',
passengerName: p.name,
dateOfBirth: p.dateOfBirth,
idDocumentType: isEthiopian ? 'NATIONAL_ID' : 'PASSPORT',
idDocumentNumber: isEthiopian ? (p.nationalId || '') : '',
passportNumber: !isEthiopian ? (p.passportNumber || '') : '',
passportCountry: !isEthiopian ? (p.passportCountry || '') : '',
nationality: p.nationality,
phone: p.phone || '',
email: p.email || '',
};
}),
createAccount: createAccount || false,
savePassengerDetails: true,
deviceId: typeof window !== 'undefined' ? (localStorage.getItem('deviceId') || `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`) : undefined,
};
}
if (typeof window !== 'undefined' && !isAuthenticated && bookingData.deviceId && !localStorage.getItem('deviceId')) {
localStorage.setItem('deviceId', bookingData.deviceId);
}
console.log('Creating booking with payload:', bookingData);
await createBookingMutation.mutateAsync(bookingData);
} catch (error) {
console.error('Error in handleConfirm:', error);
alert(error instanceof Error ? error.message : 'An unexpected error occurred. Please try again.');
}
};

View File

@@ -77,7 +77,7 @@ export default function SeatsPage() {
},
});
const coaches = (seatMapData as any)?.coaches || [];
const coaches = useMemo(() => (seatMapData as any)?.coaches || [], [seatMapData]);
const filteredCoaches = useMemo(() => {
return selectedSchedule?.selectedSeatClass
@@ -221,7 +221,7 @@ export default function SeatsPage() {
) : seats.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<p>No seats available in this coach</p>
<p className="text-sm mt-2">Please select a different coach</p>
<p className="text-sm mt-2\">Please select a different coach</p>
</div>
) : (
<>

View File

@@ -5,7 +5,6 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore } from '@/lib/auth-store';
import { useBookingStore } from '@/lib/booking-store';
import { useState, Suspense } from 'react';
import { Train } from 'lucide-react';
@@ -20,7 +19,6 @@ function LoginContent() {
const router = useRouter();
const searchParams = useSearchParams();
const login = useAuthStore((s) => s.login);
const { searchCriteria } = useBookingStore();
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);

View File

@@ -1,6 +1,4 @@
import { format, toZonedTime } from 'date-fns-tz';
const ADDIS_TZ = 'Africa/Addis_Ababa';
import { format } from 'date-fns';
export const formatCurrency = (amount: number, currency: string = 'ETB'): string => {
return new Intl.NumberFormat('en-US', {
@@ -11,16 +9,13 @@ export const formatCurrency = (amount: number, currency: string = 'ETB'): string
};
export const formatDate = (date: string | Date, formatStr: string = 'MMM dd, yyyy'): string => {
const zonedDate = toZonedTime(new Date(date), ADDIS_TZ);
return format(zonedDate, formatStr, { timeZone: ADDIS_TZ });
return format(new Date(date), formatStr);
};
export const formatDateTime = (date: string | Date): string => {
const zonedDate = toZonedTime(new Date(date), ADDIS_TZ);
return format(zonedDate, 'MMM dd, yyyy HH:mm', { timeZone: ADDIS_TZ });
return format(new Date(date), 'MMM dd, yyyy HH:mm');
};
export const formatTime = (date: string | Date): string => {
const zonedDate = toZonedTime(new Date(date), ADDIS_TZ);
return format(zonedDate, 'HH:mm', { timeZone: ADDIS_TZ });
return format(new Date(date), 'HH:mm');
};