xpeditis2.0/apps/frontend/app/dashboard/booking/[id]/pay/page.tsx
2026-03-18 15:11:09 +01:00

287 lines
10 KiB
TypeScript

/**
* Commission Payment Page
*
* Shows booking summary and commission amount, allows payment via Stripe or bank transfer
*/
'use client';
import { useState, useEffect } from 'react';
import { useRouter, useParams } from 'next/navigation';
import { CreditCard, Building2, ArrowLeft, Loader2, AlertTriangle, CheckCircle } from 'lucide-react';
import { getCsvBooking, payBookingCommission } from '@/lib/api/bookings';
interface BookingData {
id: string;
bookingNumber?: string;
carrierName: string;
carrierEmail: string;
origin: string;
destination: string;
volumeCBM: number;
weightKG: number;
palletCount: number;
priceEUR: number;
priceUSD: number;
primaryCurrency: string;
transitDays: number;
containerType: string;
status: string;
commissionRate?: number;
commissionAmountEur?: number;
}
export default function PayCommissionPage() {
const router = useRouter();
const params = useParams();
const bookingId = params.id as string;
const [booking, setBooking] = useState<BookingData | null>(null);
const [loading, setLoading] = useState(true);
const [paying, setPaying] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchBooking() {
try {
const data = await getCsvBooking(bookingId);
setBooking(data as any);
// If booking is not in PENDING_PAYMENT status, redirect
if (data.status !== 'PENDING_PAYMENT') {
router.replace('/dashboard/bookings');
}
} catch (err) {
console.error('Failed to fetch booking:', err);
setError('Impossible de charger les details du booking');
} finally {
setLoading(false);
}
}
if (bookingId) {
fetchBooking();
}
}, [bookingId, router]);
const handlePayByCard = async () => {
setPaying(true);
setError(null);
try {
const result = await payBookingCommission(bookingId);
// Redirect to Stripe Checkout
window.location.href = result.sessionUrl;
} catch (err) {
console.error('Payment error:', err);
setError(err instanceof Error ? err.message : 'Erreur lors de la creation du paiement');
setPaying(false);
}
};
const formatPrice = (price: number, currency: string) => {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency,
}).format(price);
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="flex items-center space-x-3">
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
<span className="text-gray-700">Chargement...</span>
</div>
</div>
);
}
if (error && !booking) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="bg-white rounded-lg shadow-md p-8 max-w-md">
<AlertTriangle className="h-12 w-12 text-red-500 mx-auto mb-4" />
<p className="text-center text-gray-700">{error}</p>
<button
onClick={() => router.push('/dashboard/bookings')}
className="mt-4 w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Retour aux bookings
</button>
</div>
</div>
);
}
if (!booking) return null;
const commissionAmount = booking.commissionAmountEur || 0;
const commissionRate = booking.commissionRate || 0;
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4">
<div className="max-w-2xl mx-auto">
{/* Header */}
<div className="mb-8">
<button
onClick={() => router.push('/dashboard/bookings')}
className="mb-4 flex items-center text-blue-600 hover:text-blue-800 font-medium"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Retour aux bookings
</button>
<div className="bg-white rounded-lg shadow-md p-6">
<h1 className="text-2xl font-bold text-gray-900 mb-2">Paiement de la commission</h1>
<p className="text-gray-600">
Finalisez votre booking en payant la commission de service
</p>
</div>
</div>
{/* Error */}
{error && (
<div className="mb-6 bg-red-50 border-2 border-red-200 rounded-lg p-4">
<div className="flex items-start">
<AlertTriangle className="h-5 w-5 mr-3 text-red-500 flex-shrink-0" />
<p className="text-red-700">{error}</p>
</div>
</div>
)}
{/* Booking Summary */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Recapitulatif du booking</h2>
<div className="space-y-3 text-sm">
{booking.bookingNumber && (
<div className="flex justify-between">
<span className="text-gray-600">Numero :</span>
<span className="font-semibold text-gray-900">{booking.bookingNumber}</span>
</div>
)}
<div className="flex justify-between">
<span className="text-gray-600">Transporteur :</span>
<span className="font-semibold text-gray-900">{booking.carrierName}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Trajet :</span>
<span className="font-semibold text-gray-900">
{booking.origin} &rarr; {booking.destination}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Volume / Poids :</span>
<span className="font-semibold text-gray-900">
{booking.volumeCBM} CBM / {booking.weightKG} kg
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Transit :</span>
<span className="font-semibold text-gray-900">{booking.transitDays} jours</span>
</div>
<div className="flex justify-between border-t pt-3">
<span className="text-gray-600">Prix transport :</span>
<span className="font-bold text-gray-900">
{formatPrice(booking.priceEUR, 'EUR')}
</span>
</div>
</div>
</div>
{/* Commission Details */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Commission de service</h2>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
<div className="flex justify-between items-center">
<div>
<p className="text-sm text-gray-600">
Commission ({commissionRate}% du prix transport)
</p>
<p className="text-xs text-gray-500 mt-1">
{formatPrice(booking.priceEUR, 'EUR')} x {commissionRate}%
</p>
</div>
<p className="text-2xl font-bold text-blue-600">
{formatPrice(commissionAmount, 'EUR')}
</p>
</div>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-start space-x-2">
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<p className="text-xs text-gray-600">
Apres le paiement, votre demande sera envoyee par email au transporteur ({booking.carrierEmail}).
Vous recevrez une notification des que le transporteur repond.
</p>
</div>
</div>
</div>
{/* Payment Methods */}
<div className="space-y-4">
{/* Pay by Card (Stripe) */}
<button
onClick={handlePayByCard}
disabled={paying}
className="w-full bg-blue-600 text-white rounded-lg p-4 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
<div className="flex items-center justify-center">
{paying ? (
<>
<Loader2 className="h-5 w-5 mr-3 animate-spin" />
Redirection vers Stripe...
</>
) : (
<>
<CreditCard className="h-5 w-5 mr-3" />
Payer {formatPrice(commissionAmount, 'EUR')} par carte
</>
)}
</div>
</button>
{/* Pay by Bank Transfer (informational) */}
<div className="bg-white rounded-lg shadow-lg p-6">
<div className="flex items-center mb-4">
<Building2 className="h-5 w-5 mr-3 text-gray-600" />
<h3 className="font-semibold text-gray-900">Payer par virement bancaire</h3>
</div>
<div className="bg-gray-50 rounded-lg p-4 space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">Beneficiaire :</span>
<span className="font-medium text-gray-900">XPEDITIS SAS</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">IBAN :</span>
<span className="font-mono text-gray-900">FR76 XXXX XXXX XXXX XXXX XXXX XXX</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">BIC :</span>
<span className="font-mono text-gray-900">XXXXXXXX</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Montant :</span>
<span className="font-bold text-gray-900">{formatPrice(commissionAmount, 'EUR')}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Reference :</span>
<span className="font-mono text-gray-900">{booking.bookingNumber || booking.id.slice(0, 8)}</span>
</div>
</div>
<p className="mt-3 text-xs text-gray-500">
Le traitement du virement peut prendre 1 a 3 jours ouvrables.
Votre booking sera active une fois le paiement recu et verifie.
</p>
</div>
</div>
</div>
</div>
);
}