add text input validation
Build & Push / Pipeline Tests (push) Failing after 1m5s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 52s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Push / Pipeline Tests (push) Failing after 1m5s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 52s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
This commit is contained in:
@@ -42,13 +42,13 @@ export default function CarplaceSearchForm({ cities, copy, initial = {}, compact
|
||||
{!compact ? <h2 className="text-xl font-black text-blue-950 dark:text-white">{copy.search.title}</h2> : null}
|
||||
<div className={fieldsGridClass}>
|
||||
<SearchField icon={MapPin} label={copy.search.pickup}>
|
||||
<input list="carplace-cities" name="pickupLocation" defaultValue={initial.pickupLocation ?? ''} placeholder={copy.search.locationPlaceholder} required className="carplace-input" />
|
||||
<input list="carplace-cities" name="pickupLocation" defaultValue={initial.pickupLocation ?? ''} placeholder={copy.search.locationPlaceholder} required maxLength={255} className="carplace-input" />
|
||||
</SearchField>
|
||||
<SearchField icon={MapPin} label={copy.search.returnLocation}>
|
||||
{dropoffMode === 'same' ? (
|
||||
<div className="carplace-input flex items-center text-stone-500 dark:text-slate-400">{copy.search.sameReturn}</div>
|
||||
) : (
|
||||
<input list="carplace-cities" name="dropoffLocation" defaultValue={initial.dropoffLocation ?? ''} placeholder={copy.search.locationPlaceholder} required className="carplace-input" />
|
||||
<input list="carplace-cities" name="dropoffLocation" defaultValue={initial.dropoffLocation ?? ''} placeholder={copy.search.locationPlaceholder} required maxLength={255} className="carplace-input" />
|
||||
)}
|
||||
</SearchField>
|
||||
<SearchField icon={CalendarDays} label={copy.search.pickupDate}>
|
||||
@@ -86,7 +86,7 @@ export default function CarplaceSearchForm({ cities, copy, initial = {}, compact
|
||||
</label>
|
||||
<label className="grid gap-1 text-xs font-bold text-stone-600 dark:text-slate-300">
|
||||
{copy.search.promo} <span className="font-normal text-stone-400">({copy.search.optional})</span>
|
||||
<input name="promoCode" defaultValue={initial.promoCode ?? ''} className="carplace-input" />
|
||||
<input name="promoCode" defaultValue={initial.promoCode ?? ''} maxLength={30} pattern="^[A-Z0-9_-]+$" className="carplace-input" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ type FormState = Required<Omit<BookingInitialValues, 'promoCode'>> & {
|
||||
consent: boolean
|
||||
}
|
||||
|
||||
const MOROCCAN_PHONE_PATTERN = '^(?:(?:\\+|00)212|0)\\s?[5-7](?:\\s?\\d){8}$'
|
||||
const LOCALIZED_NAME_PATTERNS: Record<CarplaceLanguage, string> = {
|
||||
en: "^[A-Za-z\\s'-]+$",
|
||||
fr: "^[A-Za-zÀ-ÖØ-öø-ÿŒœ\\s'-]+$",
|
||||
ar: "^[\\u0600-\\u06FF\\u0750-\\u077F\\s،؛؟ـ'-]+$",
|
||||
}
|
||||
|
||||
export default function ProgressiveBookingFlow({
|
||||
vehicleId,
|
||||
companySlug,
|
||||
@@ -138,6 +145,12 @@ export default function ProgressiveBookingFlow({
|
||||
setError(copy.booking.error)
|
||||
return
|
||||
}
|
||||
const namePattern = new RegExp(LOCALIZED_NAME_PATTERNS[language], 'u')
|
||||
const phonePattern = /^(?:(?:\+|00)212|0)\s?[5-7](?:\s?\d){8}$/
|
||||
if (!namePattern.test(form.firstName.trim()) || !namePattern.test(form.lastName.trim()) || !phonePattern.test(form.phone.trim())) {
|
||||
setError(copy.booking.error)
|
||||
return
|
||||
}
|
||||
setStep(3)
|
||||
void track('contact_details_started')
|
||||
}
|
||||
@@ -256,7 +269,7 @@ export default function ProgressiveBookingFlow({
|
||||
</BookingField>
|
||||
</div>
|
||||
<label className="grid gap-1 text-xs font-bold text-stone-600 dark:text-slate-300">{copy.search.age}<select className="carplace-input" value={form.driverAge} onChange={(event) => update('driverAge', event.target.value)}><option value="18">18+</option><option value="21">21+</option><option value="23">23+</option><option value="25">25+</option><option value="30">30+</option></select></label>
|
||||
<label className="grid gap-1 text-xs font-bold text-stone-600 dark:text-slate-300">{copy.search.promo} <span className="font-normal text-stone-400">({copy.search.optional})</span><input className="carplace-input" value={form.promoCode} onChange={(event) => update('promoCode', event.target.value)} /></label>
|
||||
<label className="grid gap-1 text-xs font-bold text-stone-600 dark:text-slate-300">{copy.search.promo} <span className="font-normal text-stone-400">({copy.search.optional})</span><input className="carplace-input" value={form.promoCode} maxLength={30} pattern="^[A-Z0-9_-]+$" onChange={(event) => update('promoCode', event.target.value.toUpperCase())} /></label>
|
||||
{quote ? <QuoteBox quote={quote} language={language} copy={copy} /> : null}
|
||||
<button type="button" className="carplace-primary-button justify-center" onClick={validateTrip} disabled={loading}>{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <ChevronRight className="h-4 w-4 rtl:rotate-180" />}{copy.actions.continue}</button>
|
||||
</div>
|
||||
@@ -266,11 +279,11 @@ export default function ProgressiveBookingFlow({
|
||||
<div className="mt-5 grid gap-4">
|
||||
<p className="text-sm leading-6 text-stone-600 dark:text-slate-300">{copy.booking.contactIntro}</p>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<BookingField label={copy.booking.firstName} icon={UserRound}><input className="carplace-input" value={form.firstName} onChange={(event) => update('firstName', event.target.value)} autoComplete="given-name" /></BookingField>
|
||||
<BookingField label={copy.booking.lastName} icon={UserRound}><input className="carplace-input" value={form.lastName} onChange={(event) => update('lastName', event.target.value)} autoComplete="family-name" /></BookingField>
|
||||
<BookingField label={copy.booking.firstName} icon={UserRound}><input className="carplace-input" value={form.firstName} maxLength={50} pattern={LOCALIZED_NAME_PATTERNS[language]} dir={language === 'ar' ? 'rtl' : 'ltr'} onChange={(event) => update('firstName', event.target.value)} autoComplete="given-name" /></BookingField>
|
||||
<BookingField label={copy.booking.lastName} icon={UserRound}><input className="carplace-input" value={form.lastName} maxLength={50} pattern={LOCALIZED_NAME_PATTERNS[language]} dir={language === 'ar' ? 'rtl' : 'ltr'} onChange={(event) => update('lastName', event.target.value)} autoComplete="family-name" /></BookingField>
|
||||
</div>
|
||||
<BookingField label={copy.booking.email} icon={Mail}><input type="email" className="carplace-input" value={form.email} onChange={(event) => update('email', event.target.value)} autoComplete="email" /></BookingField>
|
||||
<BookingField label={copy.booking.phone} icon={Phone}><input type="tel" className="carplace-input" value={form.phone} onChange={(event) => update('phone', event.target.value)} autoComplete="tel" /></BookingField>
|
||||
<BookingField label={copy.booking.email} icon={Mail}><input type="email" className="carplace-input" value={form.email} maxLength={254} onChange={(event) => update('email', event.target.value)} autoComplete="email" /></BookingField>
|
||||
<BookingField label={copy.booking.phone} icon={Phone}><input type="tel" className="carplace-input" value={form.phone} maxLength={20} pattern={MOROCCAN_PHONE_PATTERN} onChange={(event) => update('phone', event.target.value)} autoComplete="tel" /></BookingField>
|
||||
<label className="grid gap-1 text-xs font-bold text-stone-600 dark:text-slate-300">{copy.booking.notes} <span className="font-normal text-stone-400">({copy.search.optional})</span><textarea className="carplace-input min-h-24 resize-y" value={form.notes} onChange={(event) => update('notes', event.target.value)} placeholder={copy.booking.notesPlaceholder} maxLength={500} /></label>
|
||||
<div className="grid grid-cols-2 gap-2"><button type="button" className="carplace-secondary-button justify-center" onClick={() => setStep(1)}><ChevronLeft className="h-4 w-4 rtl:rotate-180" />{copy.actions.back}</button><button type="button" className="carplace-primary-button justify-center" onClick={validateContact}><ChevronRight className="h-4 w-4 rtl:rotate-180" />{copy.actions.continue}</button></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user