PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 All 85 releases
← All changes | resources/js/pages/BookingForm.tsx +353 -198 3.0.7 → 3.0.17 View file →
@@ -21,10 +21,12 @@
21 21 import { __ } from "../lib/i18n";
22 22 import {
23 23 formatDate as formatDateUtil,
24 24 formatDateForInput,
25 + todayYmd,
25 26 } from "../lib/dateFormat";
26 -import { getCountryOptions } from "../lib/countries";
27 +import { getCountrySelectOptions } from "../lib/countries";
28 +import { SearchableSelect } from "../components/ui/searchable-select";
27 29 import { apiService } from "../lib/api-client";
28 30 import { usePermissions } from "../hooks/usePermissions";
29 31 import { getCurrencySymbol } from "../data/currencies";
30 32 import { Button } from "../components/ui/button";
@@ -57,8 +59,10 @@
57 59 order: number;
58 60 width?: string;
59 61 options?: { value: string; label: string }[];
60 62 section?: string;
63 + /** Traveler section: "lead" shows only for the lead traveler; "all"/absent = every traveler. */
64 + applies_to?: "all" | "lead";
61 65 }
62 66
63 67 interface FormSectionConfig {
64 68 title: string;
@@ -80,8 +84,9 @@
80 84 customer_country: string;
81 85 trip_id: string;
82 86 booking_date: string;
83 87 travel_date: string;
88 + departure_time: string;
84 89 travelers: string;
85 90 subtotal: string;
86 91 tax_amount: string;
87 92 total_amount: string;
@@ -96,13 +101,19 @@
96 101 // FormatHelper::getCountries() → localized to window.yatraAdmin.countries).
97 102 // One source of truth; operators that want a curated subset apply
98 103 // the `yatra_countries_list` PHP filter once and every dropdown
99 104 // (admin + public booking + Pro modules) picks it up.
100 -const countryList = getCountryOptions();
105 +const countrySelectOptions = getCountrySelectOptions();
101 106
102 107 // Core contact fields rendered explicitly (name/email/phone/country); everything
103 108 // else in the contact form is treated as an "extra/custom" field.
104 -const CORE_CONTACT_IDS = ["first_name", "last_name", "email", "phone", "country"];
109 +const CORE_CONTACT_IDS = [
110 + "first_name",
111 + "last_name",
112 + "email",
113 + "phone",
114 + "country",
115 +];
105 116
106 117 // Normalize ISO/date-like string to formatted date using shared date library
107 118 const normalizeDateInput = (value?: string | null) => {
108 119 if (!value) return "";
@@ -123,10 +134,11 @@
123 134 customer_email: "",
124 135 customer_phone: "",
125 136 customer_country: "",
126 137 trip_id: "",
127 - booking_date: normalizeDateInput(new Date().toISOString()),
138 + booking_date: todayYmd(),
128 139 travel_date: "",
140 + departure_time: "",
129 141 travelers: "1",
130 142 subtotal: "",
131 143 tax_amount: "",
132 144 total_amount: "",
@@ -168,13 +180,15 @@
168 180 document.addEventListener("mousedown", handleClickOutside);
169 181 return () => document.removeEventListener("mousedown", handleClickOutside);
170 182 }, []);
171 183
172 - // Fetch booking form configuration
184 + // Fetch booking form configuration, resolved for the selected trip so a
185 + // per-trip form version (Pro conditions) shows the right fields here too.
186 + const selectedTripId = formData.trip_id ? Number(formData.trip_id) : null;
173 187 const { data: formConfig } = useQuery<BookingFormConfig>({
174 - queryKey: ["booking-form-config"],
188 + queryKey: ["booking-form-config", selectedTripId],
175 189 queryFn: async () => {
176 - const response = await apiService.getSettings();
190 + const response = await apiService.getBookingFormConfig(selectedTripId);
177 191 return (
178 192 response?.data?.booking_form_config ||
179 193 response?.booking_form_config ||
180 194 null
@@ -260,8 +274,37 @@
260 274 enabled: can("yatra_view_bookings") || can("yatra_view_trips"),
261 275 retry: 1,
262 276 });
263 277
278 + // Departures available on the chosen date. A trip can run several a day, and
279 + // capacity is tracked per departure — so the operator has to say which one this
280 + // booking is for, otherwise it occupies no slot at all.
281 + const { data: availableDatesData } = useQuery({
282 + queryKey: ["booking-form-available-dates", formData.trip_id],
283 + queryFn: async () => {
284 + if (!formData.trip_id) return null;
285 + return await apiService.getTripAvailableDates(formData.trip_id);
286 + },
287 + enabled: !!formData.trip_id,
288 + retry: 1,
289 + });
290 +
291 + const departureTimesForDate = React.useMemo(() => {
292 + const rows =
293 + (availableDatesData as any)?.data ?? (availableDatesData as any) ?? [];
294 + if (!Array.isArray(rows) || !formData.travel_date) return [];
295 + const row = rows.find(
296 + (r: any) => (r?.date ?? r?.departure_date) === formData.travel_date,
297 + );
298 + if (!row) return [];
299 + // `departure_times` lists every departure the date runs; `time` is the
300 + // single-slot fallback for dates that carry one.
301 + const times: string[] = Array.isArray((row as any).departure_times)
302 + ? (row as any).departure_times
303 + : [(row as any).time ?? (row as any).departure_time ?? ""];
304 + return Array.from(new Set(times.filter((t) => !!t)));
305 + }, [availableDatesData, formData.travel_date]);
306 +
264 307 // Fetch booking data if editing
265 308 const { data: bookingData, isLoading: isLoadingBooking } = useQuery({
266 309 queryKey: ["booking", bookingId],
267 310 queryFn: async () => {
@@ -407,11 +450,11 @@
407 450 (bookingData as any).contact_country ||
408 451 (bookingData as any).customer_country ||
409 452 "",
410 453 trip_id: String(bookingData.trip_id || ""),
411 - booking_date:
412 - bookingData.booking_date || new Date().toISOString().split("T")[0],
454 + booking_date: bookingData.booking_date || todayYmd(),
413 455 travel_date: bookingData.travel_date || "",
456 + departure_time: (bookingData as any).departure_time || "",
414 457 travelers: String(
415 458 (bookingData as any).travelers_count ||
416 459 bookingData.travelers_data?.length ||
417 460 "1",
@@ -530,9 +573,13 @@
530 573 ) => {
531 574 const id = `${idPrefix}-${field.id}`;
532 575 if (field.type === "select") {
533 576 return (
534 - <Select id={id} value={value} onChange={(e) => onChange(e.target.value)}>
577 + <Select
578 + id={id}
579 + value={value}
580 + onChange={(e) => onChange(e.target.value)}
581 + >
535 582 <option value="">
536 583 {field.placeholder || `Select ${field.label}`}
537 584 </option>
538 585 {field.options?.map((opt) => (
@@ -543,17 +590,18 @@
543 590 </Select>
544 591 );
545 592 }
546 593 if (field.type === "country") {
594 + // Searchable list with national flags, matching the public booking form
595 + // and the phone country-code control.
547 596 return (
548 - <Select id={id} value={value} onChange={(e) => onChange(e.target.value)}>
549 - <option value="">{field.placeholder || "Select Country"}</option>
550 - {countryList.map((c) => (
551 - <option key={c.code} value={c.code}>
552 - {c.name}
553 - </option>
554 - ))}
555 - </Select>
597 + <SearchableSelect
598 + value={value}
599 + onChange={onChange}
600 + options={countrySelectOptions}
601 + placeholder={field.placeholder || __("Select Country", "yatra")}
602 + searchPlaceholder={__("Search country", "yatra")}
603 + />
556 604 );
557 605 }
558 606 if (field.type === "textarea") {
559 607 return (
@@ -613,8 +661,47 @@
613 661 return updated;
614 662 });
615 663 };
616 664
665 + // Typing in "Number of Travelers" has to resize the traveller list, not just
666 + // the number itself: the booking is saved with `travelers_count:
667 + // travelersData.length`, so a number typed here used to be dropped on save and
668 + // the change looked like it had never been made. Mirrors addTraveler /
669 + // removeTraveler so both routes stay in step.
670 + const handleTravelersCountChange = (value: string) => {
671 + setFormData((prev) => ({ ...prev, travelers: value }));
672 + if (errors.travelers) {
673 + setErrors((prev) => ({ ...prev, travelers: "" }));
674 + }
675 +
676 + // Allow the field to be cleared while typing without collapsing the list.
677 + if (value.trim() === "") {
678 + return;
679 + }
680 +
681 + const parsed = parseInt(value, 10);
682 + if (isNaN(parsed) || parsed < 1) {
683 + return;
684 + }
685 +
686 + setTravelersData((prev) => {
687 + if (parsed === prev.length) {
688 + return prev;
689 + }
690 + if (parsed < prev.length) {
691 + // Trim from the end so details already entered for earlier travellers stay.
692 + return prev.slice(0, parsed);
693 + }
694 + const grown = [...prev];
695 + while (grown.length < parsed) {
696 + grown.push(createEmptyTraveler());
697 + }
698 + return grown;
699 + });
700 +
701 + setExpandedTravelers((prev) => prev.filter((i) => i < parsed));
702 + };
703 +
617 704 const addTraveler = () => {
618 705 const newTraveler = createEmptyTraveler();
619 706 setTravelersData((prev) => [...prev, newTraveler]);
620 707 setFormData((prev) => ({
@@ -662,8 +749,11 @@
662 749 if (!formData.booking_date) {
663 750 newErrors.booking_date = __("Booking date is required", "yatra");
664 751 }
665 752
753 + if (departureTimesForDate.length > 1 && !formData.departure_time) {
754 + newErrors.departure_time = __("Departure time is required", "yatra");
755 + }
666 756 if (!formData.travel_date) {
667 757 newErrors.travel_date = __("Travel date is required", "yatra");
668 758 }
669 759
@@ -701,8 +791,12 @@
701 791 // contact_country column (previously not submitted, so edits were lost).
702 792 contact_country: data.customer_country || "",
703 793 trip_id: parseInt(data.trip_id),
704 794 travel_date: data.travel_date,
795 + // Which departure the booking occupies. A trip running several
796 + // departures a day counts capacity per (date, time), so a booking saved
797 + // without this matches no slot and never reduces frontend availability.
798 + departure_time: data.departure_time || "",
705 799 travelers_count: travelersData.length,
706 800 total_amount: parseFloat(data.total_amount),
707 801 payment_status: data.payment_status,
708 802 status: data.booking_status,
@@ -733,12 +827,33 @@
733 827 // Redirect to bookings list
734 828 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=bookings`;
735 829 },
736 830 onError: (error: any) => {
831 + // The REST API returns field-level reasons under
832 + // response.data.data.validation_errors (WP_Error shape). The generic
833 + // "Booking validation failed" message alone hid which field was at fault
834 + // (e.g. a locale-formatted amount), so surface the specific reasons.
835 + const validationErrors =
836 + error?.response?.data?.data?.validation_errors ||
837 + error?.response?.data?.validation_errors;
838 +
839 + const fieldErrors: Record<string, string> = {};
840 + let detail = "";
841 + if (validationErrors && typeof validationErrors === "object") {
842 + for (const [field, messages] of Object.entries(validationErrors)) {
843 + const msg = Array.isArray(messages)
844 + ? String(messages[0])
845 + : String(messages);
846 + fieldErrors[field] = msg;
847 + detail = detail ? `${detail} ${msg}` : msg;
848 + }
849 + }
850 +
737 851 const errorMessage =
852 + detail ||
738 853 error?.message ||
739 854 __("An error occurred while saving the booking", "yatra");
740 - setErrors({ submit: errorMessage });
855 + setErrors({ ...fieldErrors, submit: errorMessage });
741 856 setIsSubmitting(false);
742 857 },
743 858 });
744 859
@@ -992,23 +1107,15 @@
992 1107 className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5"
993 1108 >
994 1109 {__("Country", "yatra")}
995 1110 </label>
996 - <select
997 - id="customer_country"
1111 + <SearchableSelect
998 1112 value={formData.customer_country}
999 - onChange={(e) =>
1000 - handleFieldChange("customer_country", e.target.value)
1001 - }
1002 - className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
1003 - >
1004 - <option value="">{__("Select country", "yatra")}</option>
1005 - {countryList.map((country) => (
1006 - <option key={country.code} value={country.code}>
1007 - {country.name}
1008 - </option>
1009 - ))}
1010 - </select>
1113 + onChange={(v) => handleFieldChange("customer_country", v)}
1114 + options={countrySelectOptions}
1115 + placeholder={__("Select country", "yatra")}
1116 + searchPlaceholder={__("Search country", "yatra")}
1117 + />
1011 1118 </div>
1012 1119
1013 1120 {/* Extra / custom contact fields (nationality, address, and
1014 1121 any fields the operator added via the Dynamic Form module). */}
@@ -1236,11 +1343,13 @@
1236 1343 <span className="text-red-500">*</span>
1237 1344 </label>
1238 1345 <DatePicker
1239 1346 value={formData.travel_date}
1240 - onChange={(value: string) =>
1241 - handleFieldChange("travel_date", value)
1242 - }
1347 + onChange={(value: string) => {
1348 + handleFieldChange("travel_date", value);
1349 + // A time from the previous date no longer applies.
1350 + handleFieldChange("departure_time", "");
1351 + }}
1243 1352 placeholder={__("Select travel date", "yatra")}
1244 1353 error={!!errors.travel_date}
1245 1354 />
1246 1355 {errors.travel_date && (
@@ -1248,8 +1357,58 @@
1248 1357 {errors.travel_date}
1249 1358 </p>
1250 1359 )}
1251 1360 </div>
1361 +
1362 + {/* Departure time — only when the chosen date actually runs
1363 + more than one departure. Capacity is tracked per departure,
1364 + so without this the booking occupies no slot. */}
1365 + {departureTimesForDate.length > 1 && (
1366 + <div>
1367 + <label
1368 + htmlFor="departure_time"
1369 + className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5"
1370 + >
1371 + {__("Departure Time", "yatra")}{" "}
1372 + <span className="text-red-500">*</span>
1373 + </label>
1374 + <select
1375 + id="departure_time"
1376 + name="departure_time"
1377 + value={formData.departure_time}
1378 + onChange={(e) =>
1379 + handleFieldChange("departure_time", e.target.value)
1380 + }
1381 + className={`w-full rounded-md border px-3 py-2 text-sm bg-white dark:bg-gray-800 dark:text-white ${
1382 + errors.departure_time
1383 + ? "border-red-500"
1384 + : "border-gray-300 dark:border-gray-600"
1385 + }`}
1386 + required
1387 + >
1388 + <option value="">
1389 + {__("Select departure time", "yatra")}
1390 + </option>
1391 + {departureTimesForDate.map((t: string) => (
1392 + <option key={t} value={t}>
1393 + {t}
1394 + </option>
1395 + ))}
1396 + </select>
1397 + {errors.departure_time ? (
1398 + <p className="mt-1 text-sm text-red-500">
1399 + {errors.departure_time}
1400 + </p>
1401 + ) : (
1402 + <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
1403 + {__(
1404 + "This date has several departures. Pick the one this booking is for so its seats are reserved.",
1405 + "yatra",
1406 + )}
1407 + </p>
1408 + )}
1409 + </div>
1410 + )}
1252 1411 </div>
1253 1412
1254 1413 <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
1255 1414 {/* Number of Travelers */}
@@ -1266,9 +1425,9 @@
1266 1425 type="number"
1267 1426 min="1"
1268 1427 value={formData.travelers}
1269 1428 onChange={(e) =>
1270 - handleFieldChange("travelers", e.target.value)
1429 + handleTravelersCountChange(e.target.value)
1271 1430 }
1272 1431 className={errors.travelers ? "border-red-500" : ""}
1273 1432 required
1274 1433 />
@@ -1466,30 +1625,23 @@
1466 1625 </option>
1467 1626 ))}
1468 1627 </Select>
1469 1628 ) : field.type === "country" ? (
1470 - <Select
1471 - id={`emergency-${field.id}`}
1629 + <SearchableSelect
1472 1630 value={emergencyContactData[field.id] || ""}
1473 - onChange={(e) =>
1474 - handleEmergencyContactChange(
1475 - field.id,
1476 - e.target.value,
1477 - )
1631 + onChange={(v) =>
1632 + handleEmergencyContactChange(field.id, v)
1478 1633 }
1479 - >
1480 - <option value="">
1481 - {field.placeholder || "Select Country"}
1482 - </option>
1483 - {countryList.map((country) => (
1484 - <option
1485 - key={country.code}
1486 - value={country.code}
1487 - >
1488 - {country.name}
1489 - </option>
1490 - ))}
1491 - </Select>
1634 + options={countrySelectOptions}
1635 + placeholder={
1636 + field.placeholder ||
1637 + __("Select Country", "yatra")
1638 + }
1639 + searchPlaceholder={__(
1640 + "Search country",
1641 + "yatra",
1642 + )}
1643 + />
1492 1644 ) : field.type === "textarea" ? (
1493 1645 <textarea
1494 1646 id={`emergency-${field.id}`}
1495 1647 value={emergencyContactData[field.id] || ""}
@@ -1676,160 +1828,163 @@
1676 1828 {/* Traveler Fields - Collapsible Content */}
1677 1829 {expandedTravelers.includes(travelerIndex) && (
1678 1830 <div className="p-3 pt-0 border-t border-gray-100 dark:border-gray-700/50">
1679 1831 <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
1680 - {travelerFields.map((field) => (
1681 - <div
1682 - key={field.id}
1683 - className={
1684 - field.width === "full"
1685 - ? "md:col-span-2"
1686 - : ""
1687 - }
1688 - >
1689 - <label
1690 - htmlFor={`traveler-${travelerIndex}-${field.id}`}
1691 - className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
1832 + {travelerFields
1833 + .filter(
1834 + (field) =>
1835 + (field.applies_to ?? "all") !== "lead" ||
1836 + travelerIndex === 0,
1837 + )
1838 + .map((field) => (
1839 + <div
1840 + key={field.id}
1841 + className={
1842 + field.width === "full"
1843 + ? "md:col-span-2"
1844 + : ""
1845 + }
1692 1846 >
1693 - {field.label}
1694 - {field.required && (
1695 - <span className="text-red-500 ml-1">
1696 - *
1697 - </span>
1698 - )}
1699 - </label>
1847 + <label
1848 + htmlFor={`traveler-${travelerIndex}-${field.id}`}
1849 + className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
1850 + >
1851 + {field.label}
1852 + {field.required && (
1853 + <span className="text-red-500 ml-1">
1854 + *
1855 + </span>
1856 + )}
1857 + </label>
1700 1858
1701 - {/* Render field based on type */}
1702 - {field.type === "select" ? (
1703 - <Select
1704 - id={`traveler-${travelerIndex}-${field.id}`}
1705 - value={traveler[field.id] || ""}
1706 - onChange={(e) =>
1707 - handleTravelerChange(
1708 - travelerIndex,
1709 - field.id,
1710 - e.target.value,
1711 - )
1712 - }
1713 - >
1714 - <option value="">
1715 - {field.placeholder ||
1716 - `Select ${field.label}`}
1717 - </option>
1718 - {field.options?.map((opt) => (
1719 - <option
1720 - key={opt.value}
1721 - value={opt.value}
1722 - >
1723 - {opt.label}
1859 + {/* Render field based on type */}
1860 + {field.type === "select" ? (
1861 + <Select
1862 + id={`traveler-${travelerIndex}-${field.id}`}
1863 + value={traveler[field.id] || ""}
1864 + onChange={(e) =>
1865 + handleTravelerChange(
1866 + travelerIndex,
1867 + field.id,
1868 + e.target.value,
1869 + )
1870 + }
1871 + >
1872 + <option value="">
1873 + {field.placeholder ||
1874 + `Select ${field.label}`}
1724 1875 </option>
1725 - ))}
1726 - </Select>
1727 - ) : field.type === "country" ? (
1728 - <Select
1729 - id={`traveler-${travelerIndex}-${field.id}`}
1730 - value={traveler[field.id] || ""}
1731 - onChange={(e) =>
1732 - handleTravelerChange(
1733 - travelerIndex,
1734 - field.id,
1735 - e.target.value,
1736 - )
1737 - }
1738 - >
1739 - <option value="">
1740 - {field.placeholder ||
1741 - "Select Country"}
1742 - </option>
1743 - {countryList.map((country) => (
1744 - <option
1745 - key={country.code}
1746 - value={country.code}
1747 - >
1748 - {country.name}
1749 - </option>
1750 - ))}
1751 - </Select>
1752 - ) : field.type === "textarea" ? (
1753 - <textarea
1754 - id={`traveler-${travelerIndex}-${field.id}`}
1755 - value={traveler[field.id] || ""}
1756 - onChange={(e) =>
1757 - handleTravelerChange(
1758 - travelerIndex,
1759 - field.id,
1760 - e.target.value,
1761 - )
1762 - }
1763 - placeholder={field.placeholder}
1764 - rows={2}
1765 - className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:placeholder:text-gray-400 resize-none"
1766 - />
1767 - ) : field.type === "checkbox" ? (
1768 - <label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
1769 - <input
1770 - type="checkbox"
1771 - className="rounded border-gray-300"
1772 - checked={
1773 - traveler[field.id] === "1" ||
1774 - traveler[field.id] === "true"
1876 + {field.options?.map((opt) => (
1877 + <option
1878 + key={opt.value}
1879 + value={opt.value}
1880 + >
1881 + {opt.label}
1882 + </option>
1883 + ))}
1884 + </Select>
1885 + ) : field.type === "country" ? (
1886 + <SearchableSelect
1887 + value={traveler[field.id] || ""}
1888 + onChange={(v) =>
1889 + handleTravelerChange(
1890 + travelerIndex,
1891 + field.id,
1892 + v,
1893 + )
1775 1894 }
1895 + options={countrySelectOptions}
1896 + placeholder={
1897 + field.placeholder ||
1898 + __("Select Country", "yatra")
1899 + }
1900 + searchPlaceholder={__(
1901 + "Search country",
1902 + "yatra",
1903 + )}
1904 + />
1905 + ) : field.type === "textarea" ? (
1906 + <textarea
1907 + id={`traveler-${travelerIndex}-${field.id}`}
1908 + value={traveler[field.id] || ""}
1776 1909 onChange={(e) =>
1777 1910 handleTravelerChange(
1778 1911 travelerIndex,
1779 1912 field.id,
1780 - e.target.checked ? "1" : "",
1913 + e.target.value,
1781 1914 )
1782 1915 }
1916 + placeholder={field.placeholder}
1917 + rows={2}
1918 + className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:placeholder:text-gray-400 resize-none"
1783 1919 />
1784 - <span>
1785 - {field.placeholder || field.label}
1786 - </span>
1787 - </label>
1788 - ) : field.type === "date" ||
1789 - field.id.toLowerCase().includes("date") ||
1790 - field.id
1791 - .toLowerCase()
1792 - .includes("expiry") ? (
1793 - <DatePicker
1794 - value={traveler[field.id] || ""}
1795 - onChange={(value: string) =>
1796 - handleTravelerChange(
1797 - travelerIndex,
1798 - field.id,
1799 - value,
1800 - )
1801 - }
1802 - placeholder={
1803 - field.placeholder ||
1804 - __("Select date", "yatra")
1805 - }
1806 - />
1807 - ) : (
1808 - <Input
1809 - id={`traveler-${travelerIndex}-${field.id}`}
1810 - type={
1811 - field.type === "email"
1812 - ? "email"
1813 - : field.type === "tel"
1814 - ? "tel"
1815 - : field.type === "number"
1816 - ? "number"
1817 - : "text"
1818 - }
1819 - value={traveler[field.id] || ""}
1820 - onChange={(e) =>
1821 - handleTravelerChange(
1822 - travelerIndex,
1823 - field.id,
1824 - e.target.value,
1825 - )
1826 - }
1827 - placeholder={field.placeholder}
1828 - />
1829 - )}
1830 - </div>
1831 - ))}
1920 + ) : field.type === "checkbox" ? (
1921 + <label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
1922 + <input
1923 + type="checkbox"
1924 + className="rounded border-gray-300"
1925 + checked={
1926 + traveler[field.id] === "1" ||
1927 + traveler[field.id] === "true"
1928 + }
1929 + onChange={(e) =>
1930 + handleTravelerChange(
1931 + travelerIndex,
1932 + field.id,
1933 + e.target.checked ? "1" : "",
1934 + )
1935 + }
1936 + />
1937 + <span>
1938 + {field.placeholder || field.label}
1939 + </span>
1940 + </label>
1941 + ) : field.type === "date" ||
1942 + field.id
1943 + .toLowerCase()
1944 + .includes("date") ||
1945 + field.id
1946 + .toLowerCase()
1947 + .includes("expiry") ? (
1948 + <DatePicker
1949 + value={traveler[field.id] || ""}
1950 + onChange={(value: string) =>
1951 + handleTravelerChange(
1952 + travelerIndex,
1953 + field.id,
1954 + value,
1955 + )
1956 + }
1957 + placeholder={
1958 + field.placeholder ||
1959 + __("Select date", "yatra")
1960 + }
1961 + />
1962 + ) : (
1963 + <Input
1964 + id={`traveler-${travelerIndex}-${field.id}`}
1965 + type={
1966 + field.type === "email"
1967 + ? "email"
1968 + : field.type === "tel"
1969 + ? "tel"
1970 + : field.type === "number"
1971 + ? "number"
1972 + : "text"
1973 + }
1974 + value={traveler[field.id] || ""}
1975 + onChange={(e) =>
1976 + handleTravelerChange(
1977 + travelerIndex,
1978 + field.id,
1979 + e.target.value,
1980 + )
1981 + }
1982 + placeholder={field.placeholder}
1983 + />
1984 + )}
1985 + </div>
1986 + ))}
1832 1987 </div>
1833 1988 </div>
1834 1989 )}
1835 1990 </div>