'use client' import { useEffect, useRef, useState } from 'react' import { formatCurrency } from '@rentaldrivego/types' import { MarketplaceApiError, marketplaceFetch, marketplacePost } from '@/lib/api' import { useMarketplacePreferences } from '@/components/MarketplaceShell' type Props = { vehicleId: string companySlug: string dailyRate: number pickupLocations: string[] allowDifferentDropoff: boolean dropoffLocations: string[] } type DropoffMode = 'same' | 'different' type BookingFields = { firstName: string lastName: string email: string phone: string pickupLocation: string dropoffMode: DropoffMode returnLocation: string startDate: string endDate: string notes: string dateOfBirth: string nationality: string identityDocumentNumber: string fullAddress: string driverLicense: string licenseIssuedAt: string licenseExpiry: string licenseCountry: string licenseCategory: string internationalLicenseNumber: string } type AvailabilityState = { status: 'idle' | 'checking' | 'available' | 'unavailable' | 'error' nextAvailableAt: string | null } type AvailabilityResponse = { available: boolean availabilityStatus: string nextAvailableAt: string | null blockingReason: string | null } type BookingResponse = { reservationId: string bookingReference?: string | null companyName?: string vehicleName?: string startDate?: string endDate?: string status?: string message?: string } type FunnelEventName = | 'booking_form_viewed' | 'trip_dates_selected' | 'contact_details_started' | 'driver_details_expanded' | 'booking_request_submitted' | 'booking_request_failed' | 'booking_request_success' const copy = { en: { title: 'Request this car in 1 minute', subtitle: 'Send the essentials now. Driver documents can be added later if the company needs them.', tripDetails: 'Trip details', contactDetails: 'Contact details', optionalDetails: 'Driver details, optional now', optionalDetailsHint: 'Skip this unless you want to speed up final approval. The company can collect it after confirming availability.', showDriverDetails: 'Add driver details now', hideDriverDetails: 'Hide driver details', firstName: 'First name', lastName: 'Last name', email: 'Email', phone: 'Phone', dateOfBirth: 'Date of birth', nationality: 'Nationality', identityDocumentNumber: 'CIN / Passport number', fullAddress: 'Full address', driverLicense: 'License number', licenseIssuedAt: 'Issue date', licenseExpiry: 'Expiry date', licenseCountry: 'Country of issue', licenseCategory: 'License category', internationalLicenseNumber: 'International permit number', pickupLocation: 'Pick-up location', returnLocation: 'Drop-off location', sameDropoff: 'Same drop-off', differentDropoff: 'Different drop-off', sameAsPickup: 'Return to the same location', startDate: 'Pick-up date', endDate: 'Return date', notes: 'Anything the company should know? Optional.', submit: 'Send booking request', submitting: 'Sending…', successTitle: 'Request sent.', successBody: 'This first step is a request, not an instant booking. Here is what happens next.', bookingReference: 'Booking reference', pendingStatus: 'Pending', invalidDates: 'Return date must be after pick-up date.', required: 'Add your trip dates, name, email, and phone.', perDay: '/ day', estimatedTotal: 'Estimated total', days: 'day(s)', genericError: 'Something went wrong. Please try again.', locationRequired: 'Select the required pick-up and drop-off locations.', checkingAvailability: 'Checking availability for these dates…', likelyAvailable: 'Likely available for these dates. Final confirmation still depends on the rental company.', unavailableWithDate: 'Unavailable for these dates. Next likely availability: {date}.', unavailableWithoutDate: 'Unavailable for these dates. Choose different trip dates to continue.', availabilityError: 'Availability could not be checked right now. You can still try again in a moment.', nextStepRequestSent: 'Request sent', nextStepRequestSentBody: 'The company receives your dates and contact details first.', nextStepAvailability: 'Company confirms availability', nextStepAvailabilityBody: 'They review the request and accept it if the car is still open.', nextStepDocuments: 'Driver documents if needed', nextStepDocumentsBody: 'License and identity details can be collected after the company accepts the request.', nextStepPayment: 'Payment or deposit', nextStepPaymentBody: 'Any required payment is handled only after availability is confirmed.', nextStepConfirmed: 'Booking confirmed', nextStepConfirmedBody: 'The booking is final once availability, documents, and payment are all cleared.', }, fr: { title: 'Demander cette voiture en 1 minute', subtitle: 'Envoyez l’essentiel maintenant. Les documents conducteur peuvent être ajoutés plus tard si l’agence les demande.', tripDetails: 'Détails du trajet', contactDetails: 'Coordonnées', optionalDetails: 'Détails conducteur, optionnels maintenant', optionalDetailsHint: 'Ignorez cette partie sauf si vous voulez accélérer la validation finale. L’agence pourra les demander après confirmation de disponibilité.', showDriverDetails: 'Ajouter les détails conducteur', hideDriverDetails: 'Masquer les détails conducteur', firstName: 'Prénom', lastName: 'Nom', email: 'E-mail', phone: 'Téléphone', dateOfBirth: 'Date de naissance', nationality: 'Nationalité', identityDocumentNumber: 'N° CIN / passeport', fullAddress: 'Adresse complète', driverLicense: 'Numéro du permis', licenseIssuedAt: 'Date de délivrance', licenseExpiry: 'Date d’expiration', licenseCountry: 'Pays de délivrance', licenseCategory: 'Catégorie du permis', internationalLicenseNumber: 'N° de permis international', pickupLocation: 'Lieu de départ', returnLocation: 'Lieu de retour', sameDropoff: 'Même retour', differentDropoff: 'Retour différent', sameAsPickup: 'Retour au même lieu', startDate: 'Date de départ', endDate: 'Date de retour', notes: 'Une information utile pour l’agence ? Optionnel.', submit: 'Envoyer la demande', submitting: 'Envoi…', successTitle: 'Demande envoyée.', successBody: 'Cette première étape reste une demande, pas une réservation instantanée. Voici la suite.', bookingReference: 'Référence de réservation', pendingStatus: 'En attente', invalidDates: 'La date de retour doit être après la date de départ.', required: 'Ajoutez vos dates, votre nom, votre e-mail et votre téléphone.', perDay: '/ jour', estimatedTotal: 'Total estimé', days: 'jour(s)', genericError: 'Une erreur est survenue. Veuillez réessayer.', locationRequired: 'Sélectionnez les lieux de départ et de retour requis.', checkingAvailability: 'Vérification de la disponibilité pour ces dates…', likelyAvailable: 'Probablement disponible pour ces dates. La confirmation finale dépend encore de l’agence.', unavailableWithDate: 'Indisponible pour ces dates. Prochaine disponibilité probable : {date}.', unavailableWithoutDate: 'Indisponible pour ces dates. Choisissez d’autres dates pour continuer.', availabilityError: 'La disponibilité ne peut pas être vérifiée pour le moment. Réessayez dans un instant.', nextStepRequestSent: 'Demande envoyée', nextStepRequestSentBody: 'L’agence reçoit d’abord vos dates et vos coordonnées.', nextStepAvailability: 'Confirmation de disponibilité', nextStepAvailabilityBody: 'L’agence vérifie la demande et l’accepte si la voiture est toujours libre.', nextStepDocuments: 'Documents conducteur si nécessaires', nextStepDocumentsBody: 'Le permis et les pièces d’identité peuvent être demandés après l’acceptation de la demande.', nextStepPayment: 'Paiement ou dépôt', nextStepPaymentBody: 'Le paiement demandé intervient seulement après la confirmation de disponibilité.', nextStepConfirmed: 'Réservation confirmée', nextStepConfirmedBody: 'La réservation devient finale une fois la disponibilité, les documents et le paiement validés.', }, ar: { title: 'اطلب هذه السيارة خلال دقيقة', subtitle: 'أرسل المعلومات الأساسية الآن. يمكن إضافة وثائق السائق لاحقاً إذا احتاجت الشركة إليها.', tripDetails: 'تفاصيل الرحلة', contactDetails: 'بيانات التواصل', optionalDetails: 'بيانات السائق، اختيارية الآن', optionalDetailsHint: 'تجاوز هذا الجزء إلا إذا أردت تسريع الموافقة النهائية. يمكن للشركة طلبه بعد تأكيد التوفر.', showDriverDetails: 'إضافة بيانات السائق الآن', hideDriverDetails: 'إخفاء بيانات السائق', firstName: 'الاسم الأول', lastName: 'اسم العائلة', email: 'البريد الإلكتروني', phone: 'الهاتف', dateOfBirth: 'تاريخ الميلاد', nationality: 'الجنسية', identityDocumentNumber: 'رقم البطاقة الوطنية / جواز السفر', fullAddress: 'العنوان الكامل', driverLicense: 'رقم الرخصة', licenseIssuedAt: 'تاريخ الإصدار', licenseExpiry: 'تاريخ الانتهاء', licenseCountry: 'بلد الإصدار', licenseCategory: 'فئة الرخصة', internationalLicenseNumber: 'رقم الرخصة الدولية', pickupLocation: 'موقع الاستلام', returnLocation: 'موقع الإرجاع', sameDropoff: 'نفس موقع الإرجاع', differentDropoff: 'موقع إرجاع مختلف', sameAsPickup: 'الإرجاع في نفس موقع الاستلام', startDate: 'تاريخ الاستلام', endDate: 'تاريخ الإرجاع', notes: 'أي معلومة تريد إبلاغ الشركة بها؟ اختياري.', submit: 'إرسال طلب الحجز', submitting: 'جارٍ الإرسال…', successTitle: 'تم إرسال الطلب.', successBody: 'هذه الخطوة الأولى عبارة عن طلب وليست حجزاً فورياً. هذه هي الخطوات التالية.', bookingReference: 'مرجع الحجز', pendingStatus: 'قيد المراجعة', invalidDates: 'يجب أن يكون تاريخ الإرجاع بعد تاريخ الاستلام.', required: 'أضف تواريخ الرحلة والاسم والبريد والهاتف.', perDay: '/ يوم', estimatedTotal: 'الإجمالي التقديري', days: 'يوم', genericError: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.', locationRequired: 'اختر مواقع الاستلام والإرجاع المطلوبة.', checkingAvailability: 'جارٍ التحقق من التوفر لهذه التواريخ…', likelyAvailable: 'السيارة متاحة غالباً لهذه التواريخ، لكن التأكيد النهائي يعود إلى شركة التأجير.', unavailableWithDate: 'غير متاحة لهذه التواريخ. أقرب توفر متوقع: {date}.', unavailableWithoutDate: 'غير متاحة لهذه التواريخ. اختر تواريخ مختلفة للمتابعة.', availabilityError: 'تعذر التحقق من التوفر الآن. حاول مرة أخرى بعد قليل.', nextStepRequestSent: 'تم إرسال الطلب', nextStepRequestSentBody: 'تتلقى الشركة أولاً التواريخ وبيانات التواصل.', nextStepAvailability: 'تأكيد التوفر', nextStepAvailabilityBody: 'تراجع الشركة الطلب وتقبله إذا كانت السيارة ما زالت متاحة.', nextStepDocuments: 'وثائق السائق عند الحاجة', nextStepDocumentsBody: 'يمكن طلب الرخصة ووثائق الهوية بعد قبول الطلب.', nextStepPayment: 'الدفع أو العربون', nextStepPaymentBody: 'أي دفعة مطلوبة تتم فقط بعد تأكيد التوفر.', nextStepConfirmed: 'تأكيد الحجز', nextStepConfirmedBody: 'يصبح الحجز نهائياً بعد اعتماد التوفر والوثائق والدفع.', }, } as const const emptyFields: BookingFields = { firstName: '', lastName: '', email: '', phone: '', pickupLocation: '', dropoffMode: 'same', returnLocation: '', startDate: '', endDate: '', notes: '', dateOfBirth: '', nationality: '', identityDocumentNumber: '', fullAddress: '', driverLicense: '', licenseIssuedAt: '', licenseExpiry: '', licenseCountry: '', licenseCategory: '', internationalLicenseNumber: '', } export function calculateBookingTotalDays(startDate: string, endDate: string): number { if (!startDate || !endDate) return 0 return Math.max(0, Math.ceil((new Date(endDate).getTime() - new Date(startDate).getTime()) / 86_400_000)) } export function resolveReturnLocation({ dropoffMode, returnLocation, pickupLocation, }: { dropoffMode: DropoffMode returnLocation: string pickupLocation: string }): string | undefined { if (dropoffMode === 'different') return returnLocation || undefined return pickupLocation || undefined } export function normalizeOptionalText(value: string): string | undefined { const trimmed = value.trim() return trimmed || undefined } function interpolate(template: string, replacements: Record) { return Object.entries(replacements).reduce( (current, [key, value]) => current.replace(`{${key}}`, value), template, ) } function formatLocalizedDate(value: string, language: 'en' | 'fr' | 'ar') { return new Intl.DateTimeFormat(language, { dateStyle: 'medium' }).format(new Date(value)) } function getBookingSessionId() { if (typeof window === 'undefined') return 'server' const existing = window.sessionStorage.getItem('marketplace-booking-session-id') if (existing) return existing const created = typeof window.crypto?.randomUUID === 'function' ? window.crypto.randomUUID() : `booking-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` window.sessionStorage.setItem('marketplace-booking-session-id', created) return created } function RequiredMark() { return * } type FieldProps = { label: string value: string onChange: (value: string) => void required?: boolean type?: string textarea?: boolean } function Field({ label, value, onChange, required = false, type = 'text', textarea = false }: FieldProps) { const className = 'w-full rounded-xl border border-stone-200 bg-white px-3 py-2 text-sm text-stone-900 outline-none focus:border-stone-400 dark:border-blue-800 dark:bg-blue-900/30 dark:text-stone-100 dark:focus:border-stone-500' return (
{textarea ? (