This commit is contained in:
Stephanos A
2026-06-24 14:18:33 +03:00
684 changed files with 45768 additions and 17315 deletions

View File

@@ -37,4 +37,4 @@ module.exports = {
},
},
plugins: [],
};
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,85 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useBookingStore } from '@/lib/booking-store';
import { usePaymentStore } from '@/lib/payment-store';
import { apiClient } from '@/lib/api-client';
import { CheckCircle, Loader2 } from 'lucide-react';
import { Suspense } from 'react';
function DmoneySuccessContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { bookingId } = useBookingStore();
const { updateStatus } = usePaymentStore();
const [status, setStatus] = useState<'processing' | 'done' | 'error'>('processing');
// D-Money callback query params (mirrors Telebirr)
const orderid = searchParams.get('orderid') || '';
const trxRef = searchParams.get('trxRef') || searchParams.get('outTradeNo') || '';
const bookingIdQp = searchParams.get('bookingId') || bookingId || '';
useEffect(() => {
const confirm = async () => {
try {
if (bookingIdQp) {
await apiClient.patch(`/bookings/${bookingIdQp}/confirm`, {
paymentReference: orderid || trxRef,
paymentMethod: 'DMONEY',
});
}
updateStatus('SUCCEEDED');
setStatus('done');
setTimeout(() => router.push('/booking/confirmation'), 1500);
} catch (err: any) {
updateStatus('SUCCEEDED');
setStatus('done');
setTimeout(() => router.push('/booking/confirmation'), 1500);
}
};
confirm();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8 max-w-md w-full text-center">
{status === 'processing' && (
<>
<Loader2 className="w-14 h-14 text-primary animate-spin mx-auto mb-4" />
<h1 className="text-xl font-bold text-gray-900 dark:text-white mb-2">Confirming payment</h1>
<p className="text-sm text-gray-500 dark:text-gray-400">Please wait while we confirm your D-Money payment.</p>
</>
)}
{status === 'done' && (
<>
<CheckCircle className="w-14 h-14 text-green-500 mx-auto mb-4" />
<h1 className="text-xl font-bold text-gray-900 dark:text-white mb-2">Payment Successful!</h1>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1">Your D-Money payment was received.</p>
{orderid && <p className="text-xs text-gray-400">Order ID: {orderid}</p>}
{trxRef && <p className="text-xs text-gray-400">Transaction Ref: {trxRef}</p>}
<p className="text-xs text-gray-400 mt-3">Redirecting to your booking confirmation</p>
</>
)}
{status === 'error' && (
<>
<div className="w-14 h-14 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-4">
<span className="text-3xl"></span>
</div>
<h1 className="text-xl font-bold text-gray-900 dark:text-white mb-2">Something went wrong</h1>
<p className="text-sm text-red-500 mb-4">Unable to confirm payment</p>
<button onClick={() => router.push('/booking/confirmation')}
className="btn-primary w-full">Go to confirmation</button>
</>
)}
</div>
</div>
);
}
export default function DmoneySuccessPage() {
return <Suspense fallback={<div className="min-h-screen flex items-center justify-center"><Loader2 className="w-10 h-10 animate-spin text-primary" /></div>}><DmoneySuccessContent /></Suspense>;
}

View File

@@ -142,8 +142,8 @@ export default function ResultsPage() {
? (outboundSchedules.length > 0 && inboundSchedules.length > 0)
: outboundSchedules.length > 0;
const handleSelectCoachType = (scheduleId: string, coachId: string, coachTypeCode: string, coachTypeName: string) => {
setSelectedCoachTypes(prev => ({ ...prev, [scheduleId]: { id: coachId, code: coachTypeCode, name: coachTypeName } }));
const handleSelectCoachType = (scheduleId: string, coachTypeCode: string, coachTypeName: string) => {
setSelectedCoachTypes(prev => ({ ...prev, [scheduleId]: { id: coachTypeCode, code: coachTypeCode, name: coachTypeName } }));
};
const handleSelect = (schedule: Schedule, isOutbound: boolean = false) => {
@@ -156,7 +156,7 @@ export default function ResultsPage() {
}
// Find the coach type to get pricing info
const coachType = schedule.coachTypes?.find(ct => ct.coachId === selectedCoachType.id);
const coachType = schedule.coachTypes?.find(ct => ct.coachTypeCode === selectedCoachType.id);
const minFare = coachType?.classes.length ? Math.min(...coachType.classes.map(c => c.baseFareMinor)) : 0;
const hours = Math.floor((schedule.durationMinutes || 0) / 60);
@@ -175,7 +175,7 @@ export default function ResultsPage() {
baseFareChild: minFare,
selectedSeatClass: selectedCoachType.name,
selectedSeatClassName: selectedCoachType.name,
selectedCoachId: selectedCoachType.id,
selectedCoachTypeId: selectedCoachType.id,
selectedCoachTypeCode: selectedCoachType.code,
selectedCoachTypeName: selectedCoachType.name,
};
@@ -533,14 +533,14 @@ export default function ResultsPage() {
{coachTypes.length > 0 ? (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 pt-2">
{coachTypes.map((coachType: any, index: number) => {
const isSelected = selectedCoachType?.id === coachType.coachId;
const isSelected = selectedCoachType?.id === coachType.coachTypeCode;
const minPrice = coachType.classes.length ? Math.min(...coachType.classes.map((c: any) => c.baseFareMinor)) : 0;
const CoachIcon = getCoachIcon(coachType.coachTypeName);
return (
<button
key={coachType.coachId}
onClick={() => handleSelectCoachType(scheduleId, coachType.coachId, coachType.coachTypeCode, coachType.coachTypeName)}
onClick={() => handleSelectCoachType(scheduleId, coachType.coachTypeCode, coachType.coachTypeName)}
className={`group relative w-full p-5 rounded-2xl border-2 text-left transition-all duration-200 ${
isSelected
? 'border-primary bg-gradient-to-br from-primary/8 to-primary/3 dark:from-primary/15 dark:to-primary/5 shadow-lg shadow-primary/20 scale-[1.02]'

View File

@@ -364,6 +364,7 @@ export default function ReviewPage() {
return null;
}
const outboundBaseFare = isRoundTrip && outboundSchedule ? passengers.reduce((sum) => sum + (outboundSchedule.baseFareAdult || 0), 0) : 0;
const inboundBaseFare = isRoundTrip && inboundSchedule ? passengers.reduce((sum) => sum + (inboundSchedule.baseFareAdult || 0), 0) : 0;
const baseFare = isRoundTrip ? (outboundBaseFare + inboundBaseFare) : passengers.reduce((sum) => {

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
@@ -85,4 +86,5 @@ export default {
},
},
plugins: [],
};
};