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 +322 -196 3.0.8 → 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";
@@ -82,8 +84,9 @@
82 84 customer_country: string;
83 85 trip_id: string;
84 86 booking_date: string;
85 87 travel_date: string;
88 + departure_time: string;
86 89 travelers: string;
87 90 subtotal: string;
88 91 tax_amount: string;
89 92 total_amount: string;
@@ -98,13 +101,19 @@
98 101 // FormatHelper::getCountries() → localized to window.yatraAdmin.countries).
99 102 // One source of truth; operators that want a curated subset apply
100 103 // the `yatra_countries_list` PHP filter once and every dropdown
101 104 // (admin + public booking + Pro modules) picks it up.
102 -const countryList = getCountryOptions();
105 +const countrySelectOptions = getCountrySelectOptions();
103 106
104 107 // Core contact fields rendered explicitly (name/email/phone/country); everything
105 108 // else in the contact form is treated as an "extra/custom" field.
106 -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 +];
107 116
108 117 // Normalize ISO/date-like string to formatted date using shared date library
109 118 const normalizeDateInput = (value?: string | null) => {
110 119 if (!value) return "";
@@ -125,10 +134,11 @@
125 134 customer_email: "",
126 135 customer_phone: "",
127 136 customer_country: "",
128 137 trip_id: "",
129 - booking_date: normalizeDateInput(new Date().toISOString()),
138 + booking_date: todayYmd(),
130 139 travel_date: "",
140 + departure_time: "",
131 141 travelers: "1",
132 142 subtotal: "",
133 143 tax_amount: "",
134 144 total_amount: "",
@@ -170,13 +180,15 @@
170 180 document.addEventListener("mousedown", handleClickOutside);
171 181 return () => document.removeEventListener("mousedown", handleClickOutside);
172 182 }, []);
173 183
174 - // 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;
175 187 const { data: formConfig } = useQuery<BookingFormConfig>({
176 - queryKey: ["booking-form-config"],
188 + queryKey: ["booking-form-config", selectedTripId],
177 189 queryFn: async () => {
178 - const response = await apiService.getSettings();
190 + const response = await apiService.getBookingFormConfig(selectedTripId);
179 191 return (
180 192 response?.data?.booking_form_config ||
181 193 response?.booking_form_config ||
182 194 null
@@ -262,8 +274,37 @@
262 274 enabled: can("yatra_view_bookings") || can("yatra_view_trips"),
263 275 retry: 1,
264 276 });
265 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 +
266 307 // Fetch booking data if editing
267 308 const { data: bookingData, isLoading: isLoadingBooking } = useQuery({
268 309 queryKey: ["booking", bookingId],
269 310 queryFn: async () => {
@@ -409,11 +450,11 @@
409 450 (bookingData as any).contact_country ||
410 451 (bookingData as any).customer_country ||
411 452 "",
412 453 trip_id: String(bookingData.trip_id || ""),
413 - booking_date:
414 - bookingData.booking_date || new Date().toISOString().split("T")[0],
454 + booking_date: bookingData.booking_date || todayYmd(),
415 455 travel_date: bookingData.travel_date || "",
456 + departure_time: (bookingData as any).departure_time || "",
416 457 travelers: String(
417 458 (bookingData as any).travelers_count ||
418 459 bookingData.travelers_data?.length ||
419 460 "1",
@@ -532,9 +573,13 @@
532 573 ) => {
533 574 const id = `${idPrefix}-${field.id}`;
534 575 if (field.type === "select") {
535 576 return (
536 - <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 + >
537 582 <option value="">
538 583 {field.placeholder || `Select ${field.label}`}
539 584 </option>
540 585 {field.options?.map((opt) => (
@@ -545,17 +590,18 @@
545 590 </Select>
546 591 );
547 592 }
548 593 if (field.type === "country") {
594 + // Searchable list with national flags, matching the public booking form
595 + // and the phone country-code control.
549 596 return (
550 - <Select id={id} value={value} onChange={(e) => onChange(e.target.value)}>
551 - <option value="">{field.placeholder || "Select Country"}</option>
552 - {countryList.map((c) => (
553 - <option key={c.code} value={c.code}>
554 - {c.name}
555 - </option>
556 - ))}
557 - </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 + />
558 604 );
559 605 }
560 606 if (field.type === "textarea") {
561 607 return (
@@ -615,8 +661,47 @@
615 661 return updated;
616 662 });
617 663 };
618 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 +
619 704 const addTraveler = () => {
620 705 const newTraveler = createEmptyTraveler();
621 706 setTravelersData((prev) => [...prev, newTraveler]);
622 707 setFormData((prev) => ({
@@ -664,8 +749,11 @@
664 749 if (!formData.booking_date) {
665 750 newErrors.booking_date = __("Booking date is required", "yatra");
666 751 }
667 752
753 + if (departureTimesForDate.length > 1 && !formData.departure_time) {
754 + newErrors.departure_time = __("Departure time is required", "yatra");
755 + }
668 756 if (!formData.travel_date) {
669 757 newErrors.travel_date = __("Travel date is required", "yatra");
670 758 }
671 759
@@ -703,8 +791,12 @@
703 791 // contact_country column (previously not submitted, so edits were lost).
704 792 contact_country: data.customer_country || "",
705 793 trip_id: parseInt(data.trip_id),
706 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 || "",
707 799 travelers_count: travelersData.length,
708 800 total_amount: parseFloat(data.total_amount),
709 801 payment_status: data.payment_status,
710 802 status: data.booking_status,
@@ -1015,23 +1107,15 @@
1015 1107 className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5"
1016 1108 >
1017 1109 {__("Country", "yatra")}
1018 1110 </label>
1019 - <select
1020 - id="customer_country"
1111 + <SearchableSelect
1021 1112 value={formData.customer_country}
1022 - onChange={(e) =>
1023 - handleFieldChange("customer_country", e.target.value)
1024 - }
1025 - 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"
1026 - >
1027 - <option value="">{__("Select country", "yatra")}</option>
1028 - {countryList.map((country) => (
1029 - <option key={country.code} value={country.code}>
1030 - {country.name}
1031 - </option>
1032 - ))}
1033 - </select>
1113 + onChange={(v) => handleFieldChange("customer_country", v)}
1114 + options={countrySelectOptions}
1115 + placeholder={__("Select country", "yatra")}
1116 + searchPlaceholder={__("Search country", "yatra")}
1117 + />
1034 1118 </div>
1035 1119
1036 1120 {/* Extra / custom contact fields (nationality, address, and
1037 1121 any fields the operator added via the Dynamic Form module). */}
@@ -1259,11 +1343,13 @@
1259 1343 <span className="text-red-500">*</span>
1260 1344 </label>
1261 1345 <DatePicker
1262 1346 value={formData.travel_date}
1263 - onChange={(value: string) =>
1264 - handleFieldChange("travel_date", value)
1265 - }
1347 + onChange={(value: string) => {
1348 + handleFieldChange("travel_date", value);
1349 + // A time from the previous date no longer applies.
1350 + handleFieldChange("departure_time", "");
1351 + }}
1266 1352 placeholder={__("Select travel date", "yatra")}
1267 1353 error={!!errors.travel_date}
1268 1354 />
1269 1355 {errors.travel_date && (
@@ -1271,8 +1357,58 @@
1271 1357 {errors.travel_date}
1272 1358 </p>
1273 1359 )}
1274 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 + )}
1275 1411 </div>
1276 1412
1277 1413 <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
1278 1414 {/* Number of Travelers */}
@@ -1289,9 +1425,9 @@
1289 1425 type="number"
1290 1426 min="1"
1291 1427 value={formData.travelers}
1292 1428 onChange={(e) =>
1293 - handleFieldChange("travelers", e.target.value)
1429 + handleTravelersCountChange(e.target.value)
1294 1430 }
1295 1431 className={errors.travelers ? "border-red-500" : ""}
1296 1432 required
1297 1433 />
@@ -1489,30 +1625,23 @@
1489 1625 </option>
1490 1626 ))}
1491 1627 </Select>
1492 1628 ) : field.type === "country" ? (
1493 - <Select
1494 - id={`emergency-${field.id}`}
1629 + <SearchableSelect
1495 1630 value={emergencyContactData[field.id] || ""}
1496 - onChange={(e) =>
1497 - handleEmergencyContactChange(
1498 - field.id,
1499 - e.target.value,
1500 - )
1631 + onChange={(v) =>
1632 + handleEmergencyContactChange(field.id, v)
1501 1633 }
1502 - >
1503 - <option value="">
1504 - {field.placeholder || "Select Country"}
1505 - </option>
1506 - {countryList.map((country) => (
1507 - <option
1508 - key={country.code}
1509 - value={country.code}
1510 - >
1511 - {country.name}
1512 - </option>
1513 - ))}
1514 - </Select>
1634 + options={countrySelectOptions}
1635 + placeholder={
1636 + field.placeholder ||
1637 + __("Select Country", "yatra")
1638 + }
1639 + searchPlaceholder={__(
1640 + "Search country",
1641 + "yatra",
1642 + )}
1643 + />
1515 1644 ) : field.type === "textarea" ? (
1516 1645 <textarea
1517 1646 id={`emergency-${field.id}`}
1518 1647 value={emergencyContactData[field.id] || ""}
@@ -1706,159 +1835,156 @@
1706 1835 (field.applies_to ?? "all") !== "lead" ||
1707 1836 travelerIndex === 0,
1708 1837 )
1709 1838 .map((field) => (
1710 - <div
1711 - key={field.id}
1712 - className={
1713 - field.width === "full"
1714 - ? "md:col-span-2"
1715 - : ""
1716 - }
1717 - >
1718 - <label
1719 - htmlFor={`traveler-${travelerIndex}-${field.id}`}
1720 - className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
1839 + <div
1840 + key={field.id}
1841 + className={
1842 + field.width === "full"
1843 + ? "md:col-span-2"
1844 + : ""
1845 + }
1721 1846 >
1722 - {field.label}
1723 - {field.required && (
1724 - <span className="text-red-500 ml-1">
1725 - *
1726 - </span>
1727 - )}
1728 - </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>
1729 1858
1730 - {/* Render field based on type */}
1731 - {field.type === "select" ? (
1732 - <Select
1733 - id={`traveler-${travelerIndex}-${field.id}`}
1734 - value={traveler[field.id] || ""}
1735 - onChange={(e) =>
1736 - handleTravelerChange(
1737 - travelerIndex,
1738 - field.id,
1739 - e.target.value,
1740 - )
1741 - }
1742 - >
1743 - <option value="">
1744 - {field.placeholder ||
1745 - `Select ${field.label}`}
1746 - </option>
1747 - {field.options?.map((opt) => (
1748 - <option
1749 - key={opt.value}
1750 - value={opt.value}
1751 - >
1752 - {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}`}
1753 1875 </option>
1754 - ))}
1755 - </Select>
1756 - ) : field.type === "country" ? (
1757 - <Select
1758 - id={`traveler-${travelerIndex}-${field.id}`}
1759 - value={traveler[field.id] || ""}
1760 - onChange={(e) =>
1761 - handleTravelerChange(
1762 - travelerIndex,
1763 - field.id,
1764 - e.target.value,
1765 - )
1766 - }
1767 - >
1768 - <option value="">
1769 - {field.placeholder ||
1770 - "Select Country"}
1771 - </option>
1772 - {countryList.map((country) => (
1773 - <option
1774 - key={country.code}
1775 - value={country.code}
1776 - >
1777 - {country.name}
1778 - </option>
1779 - ))}
1780 - </Select>
1781 - ) : field.type === "textarea" ? (
1782 - <textarea
1783 - id={`traveler-${travelerIndex}-${field.id}`}
1784 - value={traveler[field.id] || ""}
1785 - onChange={(e) =>
1786 - handleTravelerChange(
1787 - travelerIndex,
1788 - field.id,
1789 - e.target.value,
1790 - )
1791 - }
1792 - placeholder={field.placeholder}
1793 - rows={2}
1794 - 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"
1795 - />
1796 - ) : field.type === "checkbox" ? (
1797 - <label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
1798 - <input
1799 - type="checkbox"
1800 - className="rounded border-gray-300"
1801 - checked={
1802 - traveler[field.id] === "1" ||
1803 - 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 + )
1804 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] || ""}
1805 1909 onChange={(e) =>
1806 1910 handleTravelerChange(
1807 1911 travelerIndex,
1808 1912 field.id,
1809 - e.target.checked ? "1" : "",
1913 + e.target.value,
1810 1914 )
1811 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"
1812 1919 />
1813 - <span>
1814 - {field.placeholder || field.label}
1815 - </span>
1816 - </label>
1817 - ) : field.type === "date" ||
1818 - field.id.toLowerCase().includes("date") ||
1819 - field.id
1820 - .toLowerCase()
1821 - .includes("expiry") ? (
1822 - <DatePicker
1823 - value={traveler[field.id] || ""}
1824 - onChange={(value: string) =>
1825 - handleTravelerChange(
1826 - travelerIndex,
1827 - field.id,
1828 - value,
1829 - )
1830 - }
1831 - placeholder={
1832 - field.placeholder ||
1833 - __("Select date", "yatra")
1834 - }
1835 - />
1836 - ) : (
1837 - <Input
1838 - id={`traveler-${travelerIndex}-${field.id}`}
1839 - type={
1840 - field.type === "email"
1841 - ? "email"
1842 - : field.type === "tel"
1843 - ? "tel"
1844 - : field.type === "number"
1845 - ? "number"
1846 - : "text"
1847 - }
1848 - value={traveler[field.id] || ""}
1849 - onChange={(e) =>
1850 - handleTravelerChange(
1851 - travelerIndex,
1852 - field.id,
1853 - e.target.value,
1854 - )
1855 - }
1856 - placeholder={field.placeholder}
1857 - />
1858 - )}
1859 - </div>
1860 - ))}
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 + ))}
1861 1987 </div>
1862 1988 </div>
1863 1989 )}
1864 1990 </div>