xpeditis2.0/apps/frontend/app/[locale]/carrier/accept/[token]/page.tsx
David ec0173483a
All checks were successful
Dev CI / Backend — Lint (push) Successful in 10m23s
Dev CI / Backend — Unit Tests (push) Successful in 10m17s
Dev CI / Frontend — Lint & Type-check (push) Successful in 11m3s
Dev CI / Frontend — Unit Tests (push) Successful in 10m33s
Dev CI / Notify Failure (push) Has been skipped
fix language
2026-04-21 18:04:02 +02:00

154 lines
5.0 KiB
TypeScript

'use client';
import { useEffect, useState, useRef } from 'react';
import { useParams } from 'next/navigation';
import { useRouter } from '@/i18n/navigation';
import { useTranslations } from 'next-intl';
import { CheckCircle, Loader2, XCircle } from 'lucide-react';
export default function CarrierAcceptPage() {
const params = useParams();
const router = useRouter();
const token = params.token as string;
const t = useTranslations('carrierPortal');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [countdown, setCountdown] = useState(5);
// Prevent double API calls (React 18 StrictMode issue)
const hasCalledApi = useRef(false);
useEffect(() => {
const acceptBooking = async () => {
if (hasCalledApi.current) {
return;
}
hasCalledApi.current = true;
if (!token) {
setError(t('common.tokenMissing'));
setLoading(false);
return;
}
try {
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';
const response = await fetch(`${apiUrl}/api/v1/csv-booking-actions/accept/${token}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch (e) {
errorData = { message: `HTTP ${response.status}` };
}
let errorMessage = errorData.message || t('accept.errorFallback');
if (errorMessage.includes('status ACCEPTED') || errorMessage.includes('ACCEPTED')) {
errorMessage = t('common.bookingAlreadyAccepted');
} else if (errorMessage.includes('status REJECTED')) {
errorMessage = t('common.bookingAlreadyRejected');
} else if (errorMessage.includes('not found') || errorMessage.includes('Booking not found')) {
errorMessage = t('common.bookingNotFound');
}
throw new Error(errorMessage);
}
setLoading(false);
const timer = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
clearInterval(timer);
router.push('/');
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
} catch (err) {
console.error('Error accepting booking:', err);
setError(err instanceof Error ? err.message : t('accept.errorGeneric'));
setLoading(false);
}
};
acceptBooking();
}, [token, router, t]);
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-green-50 to-blue-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-green-600 mx-auto mb-4 animate-spin" />
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{t('accept.loadingTitle')}
</h1>
<p className="text-gray-600">
{t('accept.loadingMessage')}
</p>
</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">{t('common.errorTitle')}</h1>
<p className="text-gray-600 mb-6">{error}</p>
<button
onClick={() => router.push('/')}
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
{t('common.backHome')}
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-green-50 to-blue-50">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full text-center">
<CheckCircle className="w-20 h-20 text-green-500 mx-auto mb-6" />
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{t('accept.thanksTitle')}
</h1>
<div className="bg-green-50 border-2 border-green-200 rounded-lg p-6 mb-6">
<p className="text-green-800 font-medium text-lg mb-2">
{t('accept.successHeadline')}
</p>
<p className="text-green-700 text-sm">
{t('accept.successBody')}
</p>
</div>
<p className="text-gray-500 text-sm mb-4">
{t('common.redirecting', { countdown })}
</p>
<button
onClick={() => router.push('/')}
className="w-full px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold transition-colors"
>
{t('common.backHome')}
</button>
</div>
</div>
);
}