@devtravelcode/widget-native 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE +18 -0
  2. package/dist/App.d.ts +10 -0
  3. package/dist/App.js +192 -0
  4. package/dist/TravelHubWidget.d.ts +12 -0
  5. package/dist/TravelHubWidget.js +58 -0
  6. package/dist/components/ErrorBoundary.d.ts +18 -0
  7. package/dist/components/ErrorBoundary.js +63 -0
  8. package/dist/components/ui/Modal.d.ts +11 -0
  9. package/dist/components/ui/Modal.js +66 -0
  10. package/dist/components/ui/Skeleton.d.ts +8 -0
  11. package/dist/components/ui/Skeleton.js +33 -0
  12. package/dist/components/ui/badge.d.ts +11 -0
  13. package/dist/components/ui/badge.js +54 -0
  14. package/dist/components/ui/button.d.ts +14 -0
  15. package/dist/components/ui/button.js +101 -0
  16. package/dist/components/ui/card.d.ts +16 -0
  17. package/dist/components/ui/card.js +51 -0
  18. package/dist/components/ui/input.d.ts +8 -0
  19. package/dist/components/ui/input.js +61 -0
  20. package/dist/components/ui/label.d.ts +8 -0
  21. package/dist/components/ui/label.js +17 -0
  22. package/dist/context/WidgetContext.d.ts +29 -0
  23. package/dist/context/WidgetContext.js +64 -0
  24. package/dist/icons/CarIcon.d.ts +4 -0
  25. package/dist/icons/CarIcon.js +3 -0
  26. package/dist/icons/CloseIcon.d.ts +7 -0
  27. package/dist/icons/CloseIcon.js +3 -0
  28. package/dist/icons/FlightIcon.d.ts +4 -0
  29. package/dist/icons/FlightIcon.js +3 -0
  30. package/dist/icons/HotelIcon.d.ts +4 -0
  31. package/dist/icons/HotelIcon.js +3 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +1 -0
  34. package/dist/lib/utils.d.ts +2 -0
  35. package/dist/lib/utils.js +3 -0
  36. package/dist/locales/widgetTranslations.d.ts +1 -0
  37. package/dist/locales/widgetTranslations.js +732 -0
  38. package/dist/modules/hooks/useAirportSearch.d.ts +21 -0
  39. package/dist/modules/hooks/useAirportSearch.js +48 -0
  40. package/dist/modules/hooks/useCarOffers.d.ts +7 -0
  41. package/dist/modules/hooks/useCarOffers.js +47 -0
  42. package/dist/modules/hooks/useCountriesByCodes.d.ts +4 -0
  43. package/dist/modules/hooks/useCountriesByCodes.js +60 -0
  44. package/dist/modules/hooks/useCountriesSearch.d.ts +11 -0
  45. package/dist/modules/hooks/useCountriesSearch.js +55 -0
  46. package/dist/modules/hooks/useDefaultHotelLocation.d.ts +7 -0
  47. package/dist/modules/hooks/useDefaultHotelLocation.js +44 -0
  48. package/dist/modules/hooks/useDetectNationality.d.ts +1 -0
  49. package/dist/modules/hooks/useDetectNationality.js +20 -0
  50. package/dist/modules/hooks/useFlightSearch.d.ts +20 -0
  51. package/dist/modules/hooks/useFlightSearch.js +243 -0
  52. package/dist/modules/hooks/useHotelDetails.d.ts +7 -0
  53. package/dist/modules/hooks/useHotelDetails.js +80 -0
  54. package/dist/modules/hooks/useHotelLocations.d.ts +5 -0
  55. package/dist/modules/hooks/useHotelLocations.js +42 -0
  56. package/dist/modules/hooks/useHotelMapData.d.ts +12 -0
  57. package/dist/modules/hooks/useHotelMapData.js +78 -0
  58. package/dist/modules/hooks/useHotelOffers.d.ts +11 -0
  59. package/dist/modules/hooks/useHotelOffers.js +122 -0
  60. package/dist/modules/hooks/useHotelSearchSSE.d.ts +18 -0
  61. package/dist/modules/hooks/useHotelSearchSSE.js +411 -0
  62. package/dist/modules/hooks/useSearchExpiration.d.ts +6 -0
  63. package/dist/modules/hooks/useSearchExpiration.js +35 -0
  64. package/dist/modules/hooks/useTranslationsHub.d.ts +5 -0
  65. package/dist/modules/hooks/useTranslationsHub.js +48 -0
  66. package/dist/modules/navbar/Navbar.d.ts +8 -0
  67. package/dist/modules/navbar/Navbar.js +69 -0
  68. package/dist/modules/search-form/AgeSelector.d.ts +9 -0
  69. package/dist/modules/search-form/AgeSelector.js +109 -0
  70. package/dist/modules/search-form/CustomSelect.d.ts +14 -0
  71. package/dist/modules/search-form/CustomSelect.js +94 -0
  72. package/dist/modules/search-form/FiltersSkeleton.d.ts +6 -0
  73. package/dist/modules/search-form/FiltersSkeleton.js +35 -0
  74. package/dist/modules/search-form/NationalityInput.d.ts +18 -0
  75. package/dist/modules/search-form/NationalityInput.js +172 -0
  76. package/dist/modules/search-form/Pagination.d.ts +8 -0
  77. package/dist/modules/search-form/Pagination.js +84 -0
  78. package/dist/modules/search-form/SearchExpiredPopup.d.ts +10 -0
  79. package/dist/modules/search-form/SearchExpiredPopup.js +91 -0
  80. package/dist/modules/search-form/SkeletonWidget.d.ts +2 -0
  81. package/dist/modules/search-form/SkeletonWidget.js +33 -0
  82. package/dist/modules/search-form/TimePicker.d.ts +9 -0
  83. package/dist/modules/search-form/TimePicker.js +182 -0
  84. package/dist/modules/search-form/cars/CarEmptyState.d.ts +6 -0
  85. package/dist/modules/search-form/cars/CarEmptyState.js +42 -0
  86. package/dist/modules/search-form/cars/CarFiltersSkeleton.d.ts +6 -0
  87. package/dist/modules/search-form/cars/CarFiltersSkeleton.js +35 -0
  88. package/dist/modules/search-form/cars/CarOffersList.d.ts +43 -0
  89. package/dist/modules/search-form/cars/CarOffersList.js +214 -0
  90. package/dist/modules/search-form/cars/CarResults.d.ts +25 -0
  91. package/dist/modules/search-form/cars/CarResults.js +116 -0
  92. package/dist/modules/search-form/cars/CarSearchForm.d.ts +19 -0
  93. package/dist/modules/search-form/cars/CarSearchForm.js +173 -0
  94. package/dist/modules/search-form/cars/CarSkeleton.d.ts +6 -0
  95. package/dist/modules/search-form/cars/CarSkeleton.js +46 -0
  96. package/dist/modules/search-form/cars/CarTopOffersSlider.d.ts +20 -0
  97. package/dist/modules/search-form/cars/CarTopOffersSlider.js +100 -0
  98. package/dist/modules/search-form/cars/car-filters/CarFilters.d.ts +8 -0
  99. package/dist/modules/search-form/cars/car-filters/CarFilters.js +318 -0
  100. package/dist/modules/search-form/cars/car-filters/FilterSection.d.ts +9 -0
  101. package/dist/modules/search-form/cars/car-filters/FilterSection.js +30 -0
  102. package/dist/modules/search-form/cars/car-filters/index.d.ts +6 -0
  103. package/dist/modules/search-form/cars/car-filters/index.js +3 -0
  104. package/dist/modules/search-form/cars/car-filters/useCarFilters.d.ts +24 -0
  105. package/dist/modules/search-form/cars/car-filters/useCarFilters.js +121 -0
  106. package/dist/modules/search-form/flight/AirlinesSelector.d.ts +8 -0
  107. package/dist/modules/search-form/flight/AirlinesSelector.js +235 -0
  108. package/dist/modules/search-form/flight/CabinClassSelector.d.ts +9 -0
  109. package/dist/modules/search-form/flight/CabinClassSelector.js +63 -0
  110. package/dist/modules/search-form/flight/DatePicker.d.ts +12 -0
  111. package/dist/modules/search-form/flight/DatePicker.js +536 -0
  112. package/dist/modules/search-form/flight/FlightSearchForm.d.ts +18 -0
  113. package/dist/modules/search-form/flight/FlightSearchForm.js +147 -0
  114. package/dist/modules/search-form/flight/LocationInput.d.ts +11 -0
  115. package/dist/modules/search-form/flight/LocationInput.js +294 -0
  116. package/dist/modules/search-form/flight/PassengersPicker.d.ts +16 -0
  117. package/dist/modules/search-form/flight/PassengersPicker.js +203 -0
  118. package/dist/modules/search-form/flight/SwitchLocationBtn.d.ts +9 -0
  119. package/dist/modules/search-form/flight/SwitchLocationBtn.js +48 -0
  120. package/dist/modules/search-form/flight/flight-filters/FiltersContent.d.ts +21 -0
  121. package/dist/modules/search-form/flight/flight-filters/FiltersContent.js +145 -0
  122. package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.d.ts +11 -0
  123. package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.js +213 -0
  124. package/dist/modules/search-form/flight/flight-filters/RangeBlock.d.ts +22 -0
  125. package/dist/modules/search-form/flight/flight-filters/RangeBlock.js +164 -0
  126. package/dist/modules/search-form/flight/flight-filters/index.d.ts +1 -0
  127. package/dist/modules/search-form/flight/flight-filters/index.js +1 -0
  128. package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.d.ts +16 -0
  129. package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.js +229 -0
  130. package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.d.ts +15 -0
  131. package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.js +110 -0
  132. package/dist/modules/search-form/flight/flight-results/FlightCard.d.ts +14 -0
  133. package/dist/modules/search-form/flight/flight-results/FlightCard.js +436 -0
  134. package/dist/modules/search-form/flight/flight-results/FlightInfo.d.ts +12 -0
  135. package/dist/modules/search-form/flight/flight-results/FlightInfo.js +48 -0
  136. package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.d.ts +11 -0
  137. package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.js +36 -0
  138. package/dist/modules/search-form/flight/flight-results/FlightResults.d.ts +22 -0
  139. package/dist/modules/search-form/flight/flight-results/FlightResults.js +109 -0
  140. package/dist/modules/search-form/flight/flight-results/FlightSegments.d.ts +40 -0
  141. package/dist/modules/search-form/flight/flight-results/FlightSegments.js +162 -0
  142. package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.d.ts +6 -0
  143. package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.js +52 -0
  144. package/dist/modules/search-form/flight/flight-results/ProvidersLoader.d.ts +9 -0
  145. package/dist/modules/search-form/flight/flight-results/ProvidersLoader.js +242 -0
  146. package/dist/modules/search-form/hotel/DatePicker.d.ts +1 -0
  147. package/dist/modules/search-form/hotel/DatePicker.js +1 -0
  148. package/dist/modules/search-form/hotel/GuestSelector.d.ts +13 -0
  149. package/dist/modules/search-form/hotel/GuestSelector.js +272 -0
  150. package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.d.ts +2 -0
  151. package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.js +1 -0
  152. package/dist/modules/search-form/hotel/HotelLocation.d.ts +17 -0
  153. package/dist/modules/search-form/hotel/HotelLocation.js +178 -0
  154. package/dist/modules/search-form/hotel/HotelLocationMobilePopup.d.ts +2 -0
  155. package/dist/modules/search-form/hotel/HotelLocationMobilePopup.js +1 -0
  156. package/dist/modules/search-form/hotel/HotelResults.d.ts +21 -0
  157. package/dist/modules/search-form/hotel/HotelResults.js +333 -0
  158. package/dist/modules/search-form/hotel/HotelSearchForm.d.ts +13 -0
  159. package/dist/modules/search-form/hotel/HotelSearchForm.js +130 -0
  160. package/dist/modules/search-form/hotel/HotelsSkeleton.d.ts +6 -0
  161. package/dist/modules/search-form/hotel/HotelsSkeleton.js +40 -0
  162. package/dist/modules/search-form/hotel/PassengersPicker.d.ts +2 -0
  163. package/dist/modules/search-form/hotel/PassengersPicker.js +1 -0
  164. package/dist/modules/search-form/hotel/components/HotelCard.d.ts +11 -0
  165. package/dist/modules/search-form/hotel/components/HotelCard.js +180 -0
  166. package/dist/modules/search-form/hotel/components/HotelEmptyState.d.ts +6 -0
  167. package/dist/modules/search-form/hotel/components/HotelEmptyState.js +34 -0
  168. package/dist/modules/search-form/hotel/components/HotelGallery.d.ts +6 -0
  169. package/dist/modules/search-form/hotel/components/HotelGallery.js +77 -0
  170. package/dist/modules/search-form/hotel/components/HotelList.d.ts +9 -0
  171. package/dist/modules/search-form/hotel/components/HotelList.js +12 -0
  172. package/dist/modules/search-form/hotel/components/HotelMap.d.ts +26 -0
  173. package/dist/modules/search-form/hotel/components/HotelMap.js +707 -0
  174. package/dist/modules/search-form/hotel/components/HotelPin.d.ts +7 -0
  175. package/dist/modules/search-form/hotel/components/HotelPin.js +1 -0
  176. package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.d.ts +29 -0
  177. package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.js +339 -0
  178. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.d.ts +3 -0
  179. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.js +2 -0
  180. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.d.ts +2 -0
  181. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.js +1 -0
  182. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.d.ts +2 -0
  183. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.js +1 -0
  184. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.d.ts +2 -0
  185. package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.js +1 -0
  186. package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.d.ts +1 -0
  187. package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.js +1 -0
  188. package/dist/modules/search-form/hotel/hotel-filters/index.d.ts +1 -0
  189. package/dist/modules/search-form/hotel/hotel-filters/index.js +1 -0
  190. package/dist/modules/tabs/FlightsTab.d.ts +1 -0
  191. package/dist/modules/tabs/FlightsTab.js +1 -0
  192. package/dist/modules/tabs/HotelsTab.d.ts +1 -0
  193. package/dist/modules/tabs/HotelsTab.js +1 -0
  194. package/dist/store/index.d.ts +22 -0
  195. package/dist/store/index.js +19 -0
  196. package/dist/theme/colors.d.ts +56 -0
  197. package/dist/theme/colors.js +56 -0
  198. package/dist/theme/formStyles.d.ts +76 -0
  199. package/dist/theme/formStyles.js +78 -0
  200. package/dist/utils/applyWidgetColors.d.ts +1 -0
  201. package/dist/utils/applyWidgetColors.js +2 -0
  202. package/dist/utils/dateTime.d.ts +2 -0
  203. package/dist/utils/dateTime.js +23 -0
  204. package/dist/utils/fetchSSE.d.ts +23 -0
  205. package/dist/utils/fetchSSE.js +104 -0
  206. package/dist/utils/getToken.d.ts +3 -0
  207. package/dist/utils/getToken.js +12 -0
  208. package/package.json +64 -0
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { Skeleton } from "../../components/ui/Skeleton";
4
+ import { Colors, Spacing, BorderRadius } from "../../theme/colors";
5
+ export const SkeletonWidget = () => {
6
+ return (_jsxs(View, { style: styles.container, children: [_jsx(Skeleton, { width: "100%", height: 120, borderRadius: BorderRadius.lg, style: styles.banner }), _jsxs(View, { style: styles.inner, children: [_jsxs(View, { style: styles.tabs, children: [_jsx(Skeleton, { width: 80, height: 36, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: 80, height: 36, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: 80, height: 36, borderRadius: BorderRadius.md })] }), _jsxs(View, { style: styles.form, children: [_jsxs(View, { style: styles.row, children: [_jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md })] }), _jsxs(View, { style: styles.row, children: [_jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md })] }), _jsxs(View, { style: styles.row, children: [_jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md }), _jsx(Skeleton, { width: "30%", height: 44, borderRadius: BorderRadius.md })] })] })] })] }));
7
+ };
8
+ const styles = StyleSheet.create({
9
+ container: {
10
+ padding: Spacing.lg,
11
+ },
12
+ banner: {
13
+ marginBottom: Spacing.lg,
14
+ },
15
+ inner: {
16
+ backgroundColor: Colors.surface,
17
+ borderRadius: BorderRadius.lg,
18
+ padding: Spacing.lg,
19
+ },
20
+ tabs: {
21
+ flexDirection: "row",
22
+ gap: Spacing.sm,
23
+ marginBottom: Spacing.lg,
24
+ },
25
+ form: {
26
+ gap: Spacing.md,
27
+ },
28
+ row: {
29
+ flexDirection: "row",
30
+ justifyContent: "space-between",
31
+ gap: Spacing.sm,
32
+ },
33
+ });
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ type TimePickerProps = {
3
+ value: [string, string];
4
+ onChange: (val: [string, string]) => void;
5
+ lang?: "ru" | "en";
6
+ translations: Record<string, string>;
7
+ };
8
+ declare const TimePicker: React.FC<TimePickerProps>;
9
+ export default TimePicker;
@@ -0,0 +1,182 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { View, Text, TouchableOpacity, Modal, StyleSheet, } from "react-native";
4
+ import { useWidget } from "../../context/WidgetContext";
5
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../theme/colors";
6
+ import { formStyles } from "../../theme/formStyles";
7
+ const TimePicker = ({ value, onChange, lang = "ru", translations, }) => {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [activeField, setActiveField] = useState("from");
10
+ const [hours, setHours] = useState(10);
11
+ const [minutes, setMinutes] = useState(0);
12
+ const [period, setPeriod] = useState("AM");
13
+ const { dateTimeFormat } = useWidget();
14
+ const to24h = (h12, p) => {
15
+ if (p === "AM")
16
+ return h12 === 12 ? 0 : h12;
17
+ return h12 === 12 ? 12 : h12 + 12;
18
+ };
19
+ const to12h = (h24) => {
20
+ const p = h24 >= 12 ? "PM" : "AM";
21
+ let h = h24 % 12;
22
+ if (h === 0)
23
+ h = 12;
24
+ return { h, p };
25
+ };
26
+ const formatValue = (h24, m) => `${String(h24).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
27
+ const formatInputTime = (time) => {
28
+ if (!time)
29
+ return "";
30
+ if (dateTimeFormat !== "usa")
31
+ return time;
32
+ const [h24, m] = time.split(":").map(Number);
33
+ const { h, p } = to12h(h24);
34
+ return `${h}:${String(m).padStart(2, "0")} ${p.toLowerCase()}`;
35
+ };
36
+ const applyTime = (h, m, p = period) => {
37
+ const h24 = dateTimeFormat === "usa" ? to24h(h, p) : h;
38
+ const next = formatValue(h24, m);
39
+ if (activeField === "from")
40
+ onChange([next, value[1]]);
41
+ if (activeField === "to")
42
+ onChange([value[0], next]);
43
+ };
44
+ const openPicker = (field) => {
45
+ setActiveField(field);
46
+ const current = field === "from" ? value[0] : value[1];
47
+ const [h24, m] = current.split(":").map(Number);
48
+ if (dateTimeFormat === "usa") {
49
+ const { h, p } = to12h(h24);
50
+ setHours(h);
51
+ setPeriod(p);
52
+ }
53
+ else {
54
+ setHours(h24);
55
+ }
56
+ setMinutes(m);
57
+ setIsOpen(true);
58
+ };
59
+ const adjustHours = (delta) => {
60
+ let h = hours + delta;
61
+ if (dateTimeFormat === "usa") {
62
+ if (h > 12)
63
+ h = 1;
64
+ if (h < 1)
65
+ h = 12;
66
+ }
67
+ else {
68
+ h = (h + 24) % 24;
69
+ }
70
+ setHours(h);
71
+ applyTime(h, minutes);
72
+ };
73
+ const adjustMinutes = (delta) => {
74
+ let m = minutes + delta;
75
+ let h = hours;
76
+ if (m >= 60) {
77
+ m = 0;
78
+ h =
79
+ dateTimeFormat === "usa"
80
+ ? h === 12
81
+ ? 1
82
+ : h + 1
83
+ : (h + 1) % 24;
84
+ }
85
+ if (m < 0) {
86
+ m = 45;
87
+ h =
88
+ dateTimeFormat === "usa"
89
+ ? h === 1
90
+ ? 12
91
+ : h - 1
92
+ : (h + 23) % 24;
93
+ }
94
+ setHours(h);
95
+ setMinutes(m);
96
+ applyTime(h, m);
97
+ };
98
+ return (_jsxs(View, { children: [_jsxs(View, { style: styles.fieldsRow, children: [_jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => openPicker("from"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_pickup_label ||
99
+ (lang === "en" ? "Pick-up time" : "\u0412\u0440\u0435\u043C\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F") }), _jsx(Text, { style: styles.fieldValue, children: formatInputTime(value[0]) })] }) }), _jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => openPicker("to"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_dropoff_label ||
100
+ (lang === "en" ? "Drop-off time" : "\u0412\u0440\u0435\u043C\u044F \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430") }), _jsx(Text, { style: styles.fieldValue, children: formatInputTime(value[1]) })] }) })] }), _jsx(Modal, { visible: isOpen, transparent: true, animationType: "fade", onRequestClose: () => setIsOpen(false), children: _jsx(TouchableOpacity, { style: styles.overlay, activeOpacity: 1, onPress: () => setIsOpen(false), children: _jsxs(View, { style: styles.pickerContainer, onStartShouldSetResponder: () => true, children: [_jsxs(View, { style: styles.pickerContent, children: [_jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustHours(1), children: _jsx(Text, { style: styles.arrowText, children: "\u25B2" }) }), _jsx(Text, { style: styles.timeValue, children: String(hours).padStart(2, "0") }), _jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustHours(-1), children: _jsx(Text, { style: styles.arrowText, children: "\u25BC" }) })] }), _jsx(Text, { style: styles.timeSeparator, children: ":" }), _jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustMinutes(15), children: _jsx(Text, { style: styles.arrowText, children: "\u25B2" }) }), _jsx(Text, { style: styles.timeValue, children: String(minutes).padStart(2, "0") }), _jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustMinutes(-15), children: _jsx(Text, { style: styles.arrowText, children: "\u25BC" }) })] }), dateTimeFormat === "usa" && (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.timeSeparatorSpace }), _jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => {
101
+ const p = period === "AM" ? "PM" : "AM";
102
+ setPeriod(p);
103
+ applyTime(hours, minutes, p);
104
+ }, children: _jsx(Text, { style: styles.arrowText, children: "\u25B2" }) }), _jsx(Text, { style: styles.timeValue, children: period }), _jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => {
105
+ const p = period === "AM" ? "PM" : "AM";
106
+ setPeriod(p);
107
+ applyTime(hours, minutes, p);
108
+ }, children: _jsx(Text, { style: styles.arrowText, children: "\u25BC" }) })] })] }))] }), _jsx(TouchableOpacity, { style: styles.doneButton, onPress: () => setIsOpen(false), activeOpacity: 0.8, children: _jsx(Text, { style: styles.doneButtonText, children: translations["done"] || "Done" }) })] }) }) })] }));
109
+ };
110
+ const styles = StyleSheet.create({
111
+ fieldsRow: {
112
+ flexDirection: "row",
113
+ gap: Spacing.md,
114
+ },
115
+ fieldContainer: {
116
+ flex: 1,
117
+ },
118
+ fieldValue: {
119
+ fontSize: FontSize.md,
120
+ color: Colors.text,
121
+ },
122
+ overlay: {
123
+ flex: 1,
124
+ backgroundColor: Colors.overlay,
125
+ justifyContent: "center",
126
+ alignItems: "center",
127
+ padding: Spacing.xxl,
128
+ },
129
+ pickerContainer: {
130
+ backgroundColor: Colors.surface,
131
+ borderRadius: BorderRadius.xl,
132
+ padding: Spacing.xxl,
133
+ width: "100%",
134
+ maxWidth: 320,
135
+ alignItems: "center",
136
+ },
137
+ pickerContent: {
138
+ flexDirection: "row",
139
+ alignItems: "center",
140
+ justifyContent: "center",
141
+ marginBottom: Spacing.xxl,
142
+ },
143
+ timeColumn: {
144
+ alignItems: "center",
145
+ minWidth: 50,
146
+ },
147
+ arrowButton: {
148
+ padding: Spacing.sm,
149
+ },
150
+ arrowText: {
151
+ fontSize: FontSize.md,
152
+ color: Colors.textTertiary,
153
+ },
154
+ timeValue: {
155
+ fontSize: FontSize.xxxl,
156
+ fontWeight: "700",
157
+ color: Colors.text,
158
+ paddingVertical: Spacing.sm,
159
+ },
160
+ timeSeparator: {
161
+ fontSize: FontSize.xxxl,
162
+ fontWeight: "700",
163
+ color: Colors.text,
164
+ paddingHorizontal: Spacing.xs,
165
+ },
166
+ timeSeparatorSpace: {
167
+ width: Spacing.md,
168
+ },
169
+ doneButton: {
170
+ backgroundColor: Colors.primary,
171
+ borderRadius: BorderRadius.md,
172
+ paddingVertical: Spacing.md,
173
+ paddingHorizontal: Spacing.xxxl,
174
+ alignItems: "center",
175
+ },
176
+ doneButtonText: {
177
+ fontSize: FontSize.md,
178
+ fontWeight: "600",
179
+ color: Colors.textOnPrimary,
180
+ },
181
+ });
182
+ export default TimePicker;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ type Props = {
3
+ translations: Record<string, string>;
4
+ };
5
+ declare const CarEmptyState: React.FC<Props>;
6
+ export default CarEmptyState;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, StyleSheet } from "react-native";
3
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../theme/colors";
4
+ const CarEmptyState = ({ translations }) => (_jsxs(View, { style: styles.container, children: [_jsx(Text, { style: styles.title, children: translations["car_not_found_title"] || "No cars found" }), _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: styles.hint, children: translations["car_not_found_hint"] ||
5
+ "Try adjusting your search parameters:" }), _jsxs(Text, { style: styles.option, children: ["\u2022", " ", translations["car_not_found_option_1"] || "Change the dates"] }), _jsxs(Text, { style: styles.option, children: ["\u2022", " ", translations["car_not_found_option_2"] || "Choose a different location"] }), _jsxs(Text, { style: styles.option, children: ["\u2022", " ", translations["car_not_found_option_3"] || "Remove some filters"] })] })] }));
6
+ const styles = StyleSheet.create({
7
+ container: {
8
+ padding: Spacing.xxl,
9
+ alignItems: "center",
10
+ backgroundColor: Colors.surface,
11
+ borderRadius: BorderRadius.lg,
12
+ marginHorizontal: Spacing.lg,
13
+ marginVertical: Spacing.md,
14
+ shadowColor: Colors.black,
15
+ shadowOffset: { width: 0, height: 2 },
16
+ shadowOpacity: 0.05,
17
+ shadowRadius: 4,
18
+ elevation: 2,
19
+ },
20
+ title: {
21
+ fontSize: FontSize.xl,
22
+ fontWeight: "600",
23
+ color: Colors.text,
24
+ marginBottom: Spacing.md,
25
+ textAlign: "center",
26
+ },
27
+ content: {
28
+ alignItems: "flex-start",
29
+ },
30
+ hint: {
31
+ fontSize: FontSize.md,
32
+ color: Colors.textSecondary,
33
+ marginBottom: Spacing.sm,
34
+ },
35
+ option: {
36
+ fontSize: FontSize.md,
37
+ color: Colors.textSecondary,
38
+ marginBottom: Spacing.xs,
39
+ paddingLeft: Spacing.sm,
40
+ },
41
+ });
42
+ export default CarEmptyState;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ type Props = {
3
+ translations: Record<string, string>;
4
+ };
5
+ declare const CarFiltersSkeleton: React.FC<Props>;
6
+ export default CarFiltersSkeleton;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
3
+ import { Skeleton } from "../../../components/ui/Skeleton";
4
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../theme/colors";
5
+ const CarFiltersSkeleton = ({ translations }) => {
6
+ return (_jsx(View, { style: styles.container, children: _jsx(View, { style: styles.card, children: _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: styles.loadingText, children: translations["loading_filters"] || "Loading filters..." }), _jsx(ActivityIndicator, { size: "small", color: Colors.primary, style: styles.loader }), _jsx(Skeleton, { width: "100%", height: 16, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "80%", height: 16, style: { marginTop: Spacing.sm } }), _jsx(Skeleton, { width: "60%", height: 16, style: { marginTop: Spacing.sm } }), _jsx(Skeleton, { width: "100%", height: 40, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "100%", height: 16, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "70%", height: 16, style: { marginTop: Spacing.sm } }), _jsx(Skeleton, { width: "90%", height: 16, style: { marginTop: Spacing.sm } })] }) }) }));
7
+ };
8
+ const styles = StyleSheet.create({
9
+ container: {
10
+ paddingHorizontal: Spacing.lg,
11
+ marginBottom: Spacing.md,
12
+ },
13
+ card: {
14
+ backgroundColor: Colors.surface,
15
+ borderRadius: BorderRadius.lg,
16
+ padding: Spacing.lg,
17
+ shadowColor: Colors.black,
18
+ shadowOffset: { width: 0, height: 2 },
19
+ shadowOpacity: 0.05,
20
+ shadowRadius: 4,
21
+ elevation: 2,
22
+ },
23
+ content: {
24
+ alignItems: "center",
25
+ },
26
+ loadingText: {
27
+ fontSize: FontSize.md,
28
+ color: Colors.textSecondary,
29
+ marginBottom: Spacing.sm,
30
+ },
31
+ loader: {
32
+ marginBottom: Spacing.md,
33
+ },
34
+ });
35
+ export default CarFiltersSkeleton;
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ type CarOffer = {
3
+ offer_id: string;
4
+ car?: {
5
+ name?: string;
6
+ image?: string;
7
+ category?: string;
8
+ transmission?: string;
9
+ seats?: number;
10
+ bags?: number;
11
+ };
12
+ price?: {
13
+ total?: number;
14
+ currency?: string;
15
+ };
16
+ supplier?: {
17
+ name?: string;
18
+ logo?: string;
19
+ };
20
+ included_options?: {
21
+ id?: string | number;
22
+ name: string;
23
+ }[];
24
+ pickup?: {
25
+ location?: string;
26
+ };
27
+ url?: {
28
+ web?: string;
29
+ };
30
+ };
31
+ type Props = {
32
+ offers: CarOffer[];
33
+ days?: number;
34
+ searchParams?: {
35
+ country?: string;
36
+ nationality?: string;
37
+ age?: number;
38
+ };
39
+ translations: Record<string, string>;
40
+ lang: string;
41
+ };
42
+ declare const CarOffersList: React.FC<Props>;
43
+ export default CarOffersList;
@@ -0,0 +1,214 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from "react";
3
+ import { View, Text, Image, TouchableOpacity, FlatList, Linking, StyleSheet, } from "react-native";
4
+ import { useWidget } from "../../../context/WidgetContext";
5
+ import { useApiToken } from "../../../utils/getToken";
6
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../theme/colors";
7
+ const CURRENCY_SIGNS = {
8
+ USD: "$",
9
+ EUR: "\u20AC",
10
+ GBP: "\u00A3",
11
+ CHF: "\u20A3",
12
+ };
13
+ const normalizeOptionKey = (name) => name
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, "_")
16
+ .replace(/^_+|_+$/g, "");
17
+ function declineDays(n, translations, lang) {
18
+ if (lang === "en") {
19
+ return n === 1
20
+ ? translations["car_offers_day_singular"] || "day"
21
+ : translations["car_offers_day_plural"] || "days";
22
+ }
23
+ const mod10 = n % 10;
24
+ const mod100 = n % 100;
25
+ if (mod10 === 1 && mod100 !== 11)
26
+ return translations["car_offers_day_1"] || "day";
27
+ if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20))
28
+ return translations["car_offers_day_2_4"] || "days";
29
+ return translations["car_offers_day_5"] || "days";
30
+ }
31
+ const CarOffersList = ({ offers, days = 1, searchParams, translations, lang, }) => {
32
+ const { config } = useWidget();
33
+ const token = useApiToken();
34
+ const HOST_URL = config?.additional?.basket_link ?? "https://travel-code.com";
35
+ const [expanded, setExpanded] = useState({});
36
+ const toggleExpand = useCallback((id) => {
37
+ setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
38
+ }, []);
39
+ const handleBook = useCallback((offer) => {
40
+ const { country, nationality, age } = searchParams || {};
41
+ const bookingUrl = `${HOST_URL}/site/add-service-to-cart?offerId=${encodeURIComponent(offer.offer_id)}&type=car&serviceName=discoverCars&country=${country || "France"}&nationality=${nationality || "US"}&age=${age || 30}&accessToken=${token}&language=${lang}`;
42
+ Linking.openURL(bookingUrl);
43
+ }, [HOST_URL, searchParams, token, lang]);
44
+ const renderItem = useCallback(({ item: offer }) => {
45
+ const car = offer.car || {};
46
+ const price = offer.price || {};
47
+ const included = offer.included_options || [];
48
+ const visibleOptions = included.map((opt) => ({
49
+ ...opt,
50
+ key: normalizeOptionKey(opt.name),
51
+ }));
52
+ const basePrice = price.total || 0;
53
+ const currencyCode = price.currency || "USD";
54
+ const currencySign = CURRENCY_SIGNS[currencyCode] || currencyCode;
55
+ const isExpanded = expanded[offer.offer_id] || false;
56
+ const maxVisible = 3;
57
+ return (_jsxs(View, { style: styles.card, children: [_jsxs(View, { style: styles.cardTop, children: [_jsx(Image, { source: { uri: car.image || undefined }, style: styles.carImage, resizeMode: "contain" }), _jsxs(View, { style: styles.cardInfo, children: [car.category ? (_jsx(Text, { style: styles.category, children: translations[`car_category_${normalizeOptionKey(car.category)}`] || car.category })) : null, _jsx(Text, { style: styles.carName, numberOfLines: 2, children: car.name ||
58
+ translations["car_offers_no_name"] ||
59
+ "No name" }), offer.supplier?.logo ? (_jsx(Image, { source: { uri: offer.supplier.logo }, style: styles.supplierLogo, resizeMode: "contain" })) : null] })] }), _jsxs(View, { style: styles.featuresRow, children: [_jsxs(View, { style: styles.featureItem, children: [_jsx(Text, { style: styles.featureIcon, children: "\u{1F464}" }), _jsxs(Text, { style: styles.featureText, children: [car.seats || "-", " ", translations["car_offers_seats"] || "seats"] })] }), _jsxs(View, { style: styles.featureItem, children: [_jsx(Text, { style: styles.featureIcon, children: "\u{1F4BC}" }), _jsxs(Text, { style: styles.featureText, children: [car.bags || "-", " ", translations["car_offers_bags"] || "bags"] })] }), _jsxs(View, { style: styles.featureItem, children: [_jsx(Text, { style: styles.featureIcon, children: "\u2699" }), _jsx(Text, { style: styles.featureText, children: car.transmission === "manual"
60
+ ? translations["car_offers_transmission_manual"] || "Manual"
61
+ : translations["car_offers_transmission_auto"] || "Auto" })] })] }), included.length > 0 ? (_jsxs(View, { style: styles.optionsSection, children: [visibleOptions
62
+ .slice(0, isExpanded ? undefined : maxVisible)
63
+ .map((opt, i) => (_jsxs(View, { style: styles.optionItem, children: [_jsx(Text, { style: styles.optionCheck, children: "\u2713" }), _jsx(Text, { style: styles.optionText, children: translations[`car_option_${opt.key}`] || opt.name })] }, i))), included.length > maxVisible ? (_jsx(TouchableOpacity, { onPress: () => toggleExpand(offer.offer_id), activeOpacity: 0.7, children: _jsx(Text, { style: styles.toggleText, children: isExpanded
64
+ ? translations["car_offers_hide_options"] || "Hide"
65
+ : translations["car_offers_show_more"] || "Show more" }) })) : null] })) : null, offer.pickup?.location ? (_jsxs(View, { style: styles.locationRow, children: [_jsx(Text, { style: styles.locationIcon, children: "\uD83D\uDCCD" }), _jsx(Text, { style: styles.locationText, numberOfLines: 1, children: offer.pickup.location })] })) : null, _jsxs(View, { style: styles.priceSection, children: [_jsxs(Text, { style: styles.priceLabel, children: [translations["car_offers_price_for"] || "Price for", " ", days, " ", declineDays(days, translations, lang)] }), _jsxs(Text, { style: styles.priceValue, children: [currencySign, basePrice ? basePrice.toFixed(2) : "-"] }), _jsx(Text, { style: styles.priceNote, children: translations["car_offers_taxes_included"] ||
66
+ "(taxes & fees included)" }), _jsx(TouchableOpacity, { style: styles.bookButton, onPress: () => handleBook(offer), activeOpacity: 0.8, children: _jsx(Text, { style: styles.bookButtonText, children: translations["car_offers_book_now"] || "Book now" }) })] })] }));
67
+ }, [expanded, days, searchParams, translations, lang, token, HOST_URL, handleBook, toggleExpand]);
68
+ const keyExtractor = useCallback((item) => item.offer_id, []);
69
+ return (_jsx(FlatList, { data: offers, renderItem: renderItem, keyExtractor: keyExtractor, scrollEnabled: false, contentContainerStyle: styles.listContent, showsVerticalScrollIndicator: false }));
70
+ };
71
+ const styles = StyleSheet.create({
72
+ listContent: {
73
+ paddingHorizontal: Spacing.lg,
74
+ gap: Spacing.md,
75
+ },
76
+ card: {
77
+ backgroundColor: Colors.surface,
78
+ borderRadius: BorderRadius.lg,
79
+ padding: Spacing.lg,
80
+ shadowColor: Colors.black,
81
+ shadowOffset: { width: 0, height: 2 },
82
+ shadowOpacity: 0.06,
83
+ shadowRadius: 6,
84
+ elevation: 3,
85
+ },
86
+ cardTop: {
87
+ flexDirection: "row",
88
+ gap: Spacing.md,
89
+ },
90
+ carImage: {
91
+ width: 120,
92
+ height: 80,
93
+ borderRadius: BorderRadius.md,
94
+ backgroundColor: Colors.surfaceSecondary,
95
+ },
96
+ cardInfo: {
97
+ flex: 1,
98
+ justifyContent: "center",
99
+ },
100
+ category: {
101
+ fontSize: FontSize.xs,
102
+ color: Colors.textSecondary,
103
+ fontWeight: "500",
104
+ textTransform: "uppercase",
105
+ letterSpacing: 0.5,
106
+ marginBottom: Spacing.xs,
107
+ },
108
+ carName: {
109
+ fontSize: FontSize.lg,
110
+ fontWeight: "600",
111
+ color: Colors.text,
112
+ },
113
+ supplierLogo: {
114
+ width: 60,
115
+ height: 20,
116
+ marginTop: Spacing.xs,
117
+ },
118
+ featuresRow: {
119
+ flexDirection: "row",
120
+ gap: Spacing.lg,
121
+ marginTop: Spacing.md,
122
+ paddingVertical: Spacing.sm,
123
+ borderTopWidth: StyleSheet.hairlineWidth,
124
+ borderTopColor: Colors.borderLight,
125
+ },
126
+ featureItem: {
127
+ flexDirection: "row",
128
+ alignItems: "center",
129
+ gap: Spacing.xs,
130
+ },
131
+ featureIcon: {
132
+ fontSize: FontSize.sm,
133
+ },
134
+ featureText: {
135
+ fontSize: FontSize.sm,
136
+ color: Colors.textSecondary,
137
+ },
138
+ optionsSection: {
139
+ marginTop: Spacing.sm,
140
+ gap: Spacing.xs,
141
+ },
142
+ optionItem: {
143
+ flexDirection: "row",
144
+ alignItems: "center",
145
+ gap: Spacing.sm,
146
+ },
147
+ optionCheck: {
148
+ fontSize: FontSize.sm,
149
+ color: Colors.success,
150
+ fontWeight: "700",
151
+ },
152
+ optionText: {
153
+ fontSize: FontSize.sm,
154
+ color: Colors.textSecondary,
155
+ flex: 1,
156
+ },
157
+ toggleText: {
158
+ fontSize: FontSize.sm,
159
+ color: Colors.primary,
160
+ fontWeight: "500",
161
+ marginTop: Spacing.xs,
162
+ },
163
+ locationRow: {
164
+ flexDirection: "row",
165
+ alignItems: "center",
166
+ gap: Spacing.xs,
167
+ marginTop: Spacing.sm,
168
+ },
169
+ locationIcon: {
170
+ fontSize: FontSize.sm,
171
+ },
172
+ locationText: {
173
+ fontSize: FontSize.sm,
174
+ color: Colors.textTertiary,
175
+ flex: 1,
176
+ },
177
+ priceSection: {
178
+ borderTopWidth: 1,
179
+ borderTopColor: Colors.borderLight,
180
+ marginTop: Spacing.md,
181
+ paddingTop: Spacing.md,
182
+ alignItems: "center",
183
+ },
184
+ priceLabel: {
185
+ fontSize: FontSize.sm,
186
+ color: Colors.textSecondary,
187
+ marginBottom: Spacing.xs,
188
+ },
189
+ priceValue: {
190
+ fontSize: FontSize.xxl,
191
+ fontWeight: "700",
192
+ color: Colors.text,
193
+ },
194
+ priceNote: {
195
+ fontSize: FontSize.xs,
196
+ color: Colors.textTertiary,
197
+ marginTop: 2,
198
+ marginBottom: Spacing.md,
199
+ },
200
+ bookButton: {
201
+ backgroundColor: Colors.primary,
202
+ borderRadius: BorderRadius.md,
203
+ paddingVertical: Spacing.md,
204
+ paddingHorizontal: Spacing.xxl,
205
+ width: "100%",
206
+ alignItems: "center",
207
+ },
208
+ bookButtonText: {
209
+ color: Colors.textOnPrimary,
210
+ fontSize: FontSize.lg,
211
+ fontWeight: "600",
212
+ },
213
+ });
214
+ export default CarOffersList;
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ type Props = {
3
+ offers: any[];
4
+ filteredOffers: any[];
5
+ paginatedOffers: any[];
6
+ loading: boolean;
7
+ error?: string | null;
8
+ isFiltering: boolean;
9
+ hasAppliedFilters: boolean;
10
+ onFilterChange: (filtered: any[]) => void;
11
+ onFiltersApplied: () => void;
12
+ days: number;
13
+ searchParams: any;
14
+ translations: Record<string, string>;
15
+ hasCarSearched: boolean;
16
+ lang: string;
17
+ currency: string;
18
+ currentPage: number;
19
+ totalPages: number;
20
+ onPageChange: (page: number) => void;
21
+ isPageLoading: boolean;
22
+ headerComponent?: React.ReactElement;
23
+ };
24
+ declare const CarResults: React.FC<Props>;
25
+ export default CarResults;