Files
carmanagement/apps/marketplace/src/components/BookingForm.tsx
T
2026-05-24 01:53:27 -04:00

252 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import { marketplacePost } from '@/lib/api'
import { useMarketplacePreferences } from '@/components/MarketplaceShell'
type Props = {
vehicleId: string
companySlug: string
dailyRate: number
}
const copy = {
en: {
title: 'Reserve this vehicle',
firstName: 'First name',
lastName: 'Last name',
email: 'Email',
phone: 'Phone',
startDate: 'Pick-up date',
endDate: 'Return date',
notes: 'Notes (optional)',
submit: 'Request reservation',
submitting: 'Sending…',
successTitle: 'Request received!',
successBody: 'Your reservation request has been sent. The company will review it and contact you shortly.',
invalidDates: 'Return date must be after pick-up date.',
required: 'Please fill in all required fields.',
perDay: '/ day',
estimatedTotal: 'Estimated total',
days: 'day(s)',
genericError: 'Something went wrong. Please try again.',
},
fr: {
title: 'Réserver ce véhicule',
firstName: 'Prénom',
lastName: 'Nom',
email: 'E-mail',
phone: 'Téléphone',
startDate: 'Date de départ',
endDate: 'Date de retour',
notes: 'Notes (optionnelles)',
submit: 'Demander une réservation',
submitting: 'Envoi…',
successTitle: 'Demande reçue !',
successBody: "Votre demande de réservation a été envoyée. Lentreprise va lexaminer et vous contactera prochainement.",
invalidDates: 'La date de retour doit être après la date de départ.',
required: 'Veuillez remplir tous les champs obligatoires.',
perDay: '/ jour',
estimatedTotal: 'Total estimé',
days: 'jour(s)',
genericError: 'Une erreur est survenue. Veuillez réessayer.',
},
ar: {
title: 'احجز هذه السيارة',
firstName: 'الاسم الأول',
lastName: 'اسم العائلة',
email: 'البريد الإلكتروني',
phone: 'الهاتف',
startDate: 'تاريخ الاستلام',
endDate: 'تاريخ الإرجاع',
notes: 'ملاحظات (اختيارية)',
submit: 'طلب حجز',
submitting: 'جارٍ الإرسال…',
successTitle: 'تم استلام الطلب!',
successBody: 'تم إرسال طلب الحجز. ستراجعه الشركة وستتواصل معك قريباً.',
invalidDates: 'يجب أن يكون تاريخ الإعادة بعد تاريخ الاستلام.',
required: 'يرجى تعبئة جميع الحقول المطلوبة.',
perDay: '/ يوم',
estimatedTotal: 'الإجمالي التقديري',
days: 'يوم',
genericError: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.',
},
} as const
export default function BookingForm({ vehicleId, companySlug, dailyRate }: Props) {
const { language } = useMarketplacePreferences()
const t = copy[language]
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [notes, setNotes] = useState('')
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
const totalDays =
startDate && endDate
? Math.max(0, Math.ceil((new Date(endDate).getTime() - new Date(startDate).getTime()) / 86_400_000))
: 0
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError(null)
if (!firstName.trim() || !lastName.trim() || !email.trim() || !phone.trim() || !startDate || !endDate) {
setError(t.required)
return
}
const start = new Date(startDate)
const end = new Date(endDate)
if (end <= start) {
setError(t.invalidDates)
return
}
setSubmitting(true)
try {
await marketplacePost('/marketplace/reservations', {
vehicleId,
companySlug,
firstName: firstName.trim(),
lastName: lastName.trim(),
email: email.trim(),
phone: phone.trim(),
startDate: start.toISOString(),
endDate: end.toISOString(),
notes: notes.trim() || undefined,
language,
})
setSuccess(true)
} catch (err: any) {
setError(err.message ?? t.genericError)
} finally {
setSubmitting(false)
}
}
if (success) {
return (
<div className="rounded-2xl border border-green-200 bg-green-50 p-6 dark:border-green-800 dark:bg-green-950/40">
<p className="text-base font-semibold text-green-800 dark:text-green-300">{t.successTitle}</p>
<p className="mt-2 text-sm text-green-700 dark:text-green-400">{t.successBody}</p>
</div>
)
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<p className="text-sm font-semibold text-stone-900 dark:text-stone-100">{t.title}</p>
{error ? (
<p className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950/40 dark:text-red-400">
{error}
</p>
) : null}
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.firstName} <span className="text-red-500">*</span></label>
<input
required
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.lastName} <span className="text-red-500">*</span></label>
<input
required
value={lastName}
onChange={(e) => setLastName(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.email} <span className="text-red-500">*</span></label>
<input
required
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.phone} <span className="text-red-500">*</span></label>
<input
required
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.startDate} <span className="text-red-500">*</span></label>
<input
required
type="date"
value={startDate}
min={new Date().toISOString().slice(0, 10)}
onChange={(e) => setStartDate(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.endDate} <span className="text-red-500">*</span></label>
<input
required
type="date"
value={endDate}
min={startDate || new Date().toISOString().slice(0, 10)}
onChange={(e) => setEndDate(e.target.value)}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
</div>
{totalDays > 0 ? (
<p className="text-sm text-stone-600 dark:text-stone-400">
{t.estimatedTotal}:{' '}
<strong className="text-stone-900 dark:text-stone-100">
{(dailyRate * totalDays).toLocaleString()} MAD
</strong>{' '}
({totalDays} {t.days})
</p>
) : null}
<div className="space-y-1">
<label className="text-xs font-medium text-stone-600 dark:text-stone-400">{t.notes}</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={3}
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-[#162038] dark:text-stone-100 dark:focus:border-stone-500"
/>
</div>
<button
type="submit"
disabled={submitting}
className="w-full rounded-full bg-stone-900 px-6 py-3 text-center text-sm font-semibold text-white transition hover:bg-orange-700 disabled:opacity-60 dark:bg-orange-400 dark:text-white dark:hover:bg-orange-300"
>
{submitting ? t.submitting : t.submit}
</button>
</form>
)
}