xpeditis2.0/apps/frontend/app/carrier/reject/[token]/page.tsx
2025-12-11 15:04:52 +01:00

227 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useEffect, useState, useRef } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { XCircle, Loader2, CheckCircle, Truck, MessageSquare } from 'lucide-react';
export default function CarrierRejectPage() {
const params = useParams();
const router = useRouter();
const token = params.token as string;
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [bookingId, setBookingId] = useState<string | null>(null);
const [isNewAccount, setIsNewAccount] = useState(false);
const [showSuccess, setShowSuccess] = useState(false);
const [reason, setReason] = useState('');
// Prevent double API calls (React 18 StrictMode issue)
const hasCalledApi = useRef(false);
const handleReject = async () => {
// Protection contre les doubles appels
if (hasCalledApi.current) {
return;
}
hasCalledApi.current = true;
if (!token) {
setError('Token manquant');
return;
}
setLoading(true);
setError(null);
try {
// Construire l'URL avec la raison en query param si fournie
const url = new URL(`http://localhost:4000/api/v1/csv-bookings/reject/${token}`);
if (reason.trim()) {
url.searchParams.append('reason', reason.trim());
}
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
const errorData = await response.json();
// Messages d'erreur personnalisés
let errorMessage = errorData.message || 'Erreur lors du refus du booking';
if (errorMessage.includes('status REJECTED')) {
errorMessage = 'Ce booking a déjà été refusé. Vous ne pouvez pas le refuser à nouveau.';
} else if (errorMessage.includes('status ACCEPTED')) {
errorMessage = 'Ce booking a déjà été accepté. Vous ne pouvez plus le refuser.';
} else if (errorMessage.includes('not found')) {
errorMessage = 'Booking introuvable. Le lien peut avoir expiré.';
}
throw new Error(errorMessage);
}
const data = await response.json();
// Stocker le token JWT pour l'auto-login
if (data.autoLoginToken) {
localStorage.setItem('carrier_access_token', data.autoLoginToken);
}
setBookingId(data.bookingId);
setIsNewAccount(data.isNewAccount);
setShowSuccess(true);
setLoading(false);
// Rediriger vers la page de détails après 2 secondes
setTimeout(() => {
router.push(`/carrier/dashboard/bookings/${data.bookingId}`);
}, 2000);
} catch (err) {
console.error('Error rejecting booking:', err);
setError(err instanceof Error ? err.message : 'Erreur lors du refus');
setLoading(false);
}
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-orange-50">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full text-center">
<Loader2 className="w-16 h-16 text-red-600 mx-auto mb-4 animate-spin" />
<h1 className="text-2xl font-bold text-gray-900 mb-4">
Traitement en cours...
</h1>
<p className="text-gray-600">
Nous traitons votre refus de la réservation.
</p>
</div>
</div>
);
}
if (showSuccess) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-orange-50">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full text-center">
<CheckCircle className="w-16 h-16 text-orange-500 mx-auto mb-4" />
<h1 className="text-3xl font-bold text-gray-900 mb-4">
Réservation Refusée
</h1>
<div className="bg-orange-50 border-2 border-orange-200 rounded-lg p-4 mb-6">
<p className="text-orange-800 font-medium">
La réservation a é refusée
</p>
{reason && (
<p className="text-sm text-orange-700 mt-2">
Raison : {reason}
</p>
)}
</div>
{isNewAccount && (
<div className="bg-blue-50 border-2 border-blue-200 rounded-lg p-4 mb-6">
<p className="text-blue-800 font-medium mb-2">
🎉 Compte transporteur créé !
</p>
<p className="text-sm text-blue-700">
Un email avec vos identifiants de connexion vous a é envoyé.
</p>
</div>
)}
<div className="flex items-center justify-center text-gray-600 mb-6">
<Truck className="w-5 h-5 mr-2 animate-bounce" />
<p>Redirection vers les détails de la réservation...</p>
</div>
<div className="text-sm text-gray-500">
Vous serez automatiquement redirigé dans 2 secondes
</div>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-orange-50">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full text-center">
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-900 mb-4">Erreur</h1>
<p className="text-gray-600 mb-6">{error}</p>
<button
onClick={() => router.push('/carrier/login')}
className="w-full px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700"
>
Retour à la connexion
</button>
</div>
</div>
);
}
// Formulaire de refus
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-orange-50 p-4">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
<div className="text-center mb-6">
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h1 className="text-3xl font-bold text-gray-900 mb-2">
Refuser la Réservation
</h1>
<p className="text-gray-600">
Vous êtes sur le point de refuser cette demande de réservation.
</p>
</div>
<div className="mb-6">
<label className="flex items-center text-sm font-medium text-gray-700 mb-2">
<MessageSquare className="w-4 h-4 mr-2" />
Raison du refus (optionnel)
</label>
<textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Ex: Capacité insuffisante, date non disponible..."
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-transparent resize-none"
rows={4}
maxLength={500}
/>
<p className="text-xs text-gray-500 mt-1">
{reason.length}/500 caractères
</p>
</div>
<div className="space-y-3">
<button
onClick={handleReject}
disabled={loading}
className="w-full px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 font-semibold disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Confirmer le Refus
</button>
<button
onClick={() => router.back()}
disabled={loading}
className="w-full px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 font-semibold disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Annuler
</button>
</div>
<div className="mt-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-sm text-yellow-800">
Le client sera notifié de votre refus{reason.trim() ? ' avec la raison fournie' : ''}.
</p>
</div>
</div>
</div>
);
}