@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,333 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo, useRef } from "react";
3
+ import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, StyleSheet, } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import Svg, { Path, Polygon, Line } from "react-native-svg";
6
+ import HotelsSkeleton from "./HotelsSkeleton";
7
+ import HotelList from "./components/HotelList";
8
+ import HotelEmptyState from "./components/HotelEmptyState";
9
+ import HotelMap, { pinsFromOffers } from "./components/HotelMap";
10
+ import ProvidersLoader from "../flight/flight-results/ProvidersLoader";
11
+ import Pagination from "../Pagination";
12
+ import { HotelFilters, QUICK_FILTERS } from "./hotel-filters";
13
+ import { Colors, Spacing, FontSize, BorderRadius } from "../../../theme/colors";
14
+ const HotelResults = ({ offers, allHotels, loading, isStreaming = false, isPageLoading = false, error, translations, lang, searchParams = null, total = 0, offset = 0, limit = 20, fetchPage, filterSearch, currency = "USD", headerComponent, }) => {
15
+ const insets = useSafeAreaInsets();
16
+ const [hasSearched, setHasSearched] = useState(false);
17
+ const [localOffers, setLocalOffers] = useState(offers ?? []);
18
+ const [isMapOpen, setIsMapOpen] = useState(false);
19
+ const listRef = useRef(null);
20
+ const prevLoadingRef = useRef(false);
21
+ const headerHeightRef = useRef(0);
22
+ const [isFiltersOpen, setIsFiltersOpen] = useState(false);
23
+ const [activeFilters, setActiveFilters] = useState([]);
24
+ const [popupValues, setPopupValues] = useState(null);
25
+ const loadingInitial = loading && localOffers.length === 0;
26
+ const loadingRefetch = isPageLoading;
27
+ useEffect(() => {
28
+ if (loading && searchParams) {
29
+ setHasSearched(true);
30
+ }
31
+ }, [loading, searchParams]);
32
+ useEffect(() => {
33
+ const isSearchStarting = (loading || isStreaming) && !prevLoadingRef.current;
34
+ if (isSearchStarting) {
35
+ const scrollOffset = headerHeightRef.current > 0 ? headerHeightRef.current - 120 : 400;
36
+ setTimeout(() => {
37
+ listRef.current?.scrollToOffset({ offset: scrollOffset, animated: true });
38
+ }, 300);
39
+ }
40
+ prevLoadingRef.current = loading || isStreaming;
41
+ }, [loading, isStreaming]);
42
+ useEffect(() => {
43
+ if (offers && offers.length > 0) {
44
+ setLocalOffers(offers);
45
+ }
46
+ else if (!loading && !isStreaming && !isPageLoading && offers?.length === 0) {
47
+ setLocalOffers([]);
48
+ }
49
+ }, [offers, loading, isStreaming, isPageLoading]);
50
+ useEffect(() => {
51
+ if (loading && searchParams && !isPageLoading) {
52
+ setActiveFilters([]);
53
+ setPopupValues(null);
54
+ }
55
+ }, [loading, searchParams, isPageLoading]);
56
+ const derivedValuesFromQuickFilters = (filters = activeFilters) => {
57
+ const boardDef = QUICK_FILTERS.find((f) => f.type === "boards" && filters.includes(f.key));
58
+ const boards = boardDef?.value || null;
59
+ const payDef = QUICK_FILTERS.find((f) => f.type === "payments" && filters.includes(f.key));
60
+ const payments = payDef?.value || null;
61
+ const starDef = QUICK_FILTERS.find((f) => f.type === "starFrom" && filters.includes(f.key));
62
+ let starRating = [];
63
+ if (starDef) {
64
+ const from = starDef.value;
65
+ starRating = Array.from({ length: 5 - from + 1 }, (_, i) => from + i);
66
+ }
67
+ return { boards, payments, starRating };
68
+ };
69
+ const mapPins = useMemo(() => {
70
+ if (!allHotels || allHotels.length === 0)
71
+ return [];
72
+ return pinsFromOffers(allHotels, currency);
73
+ }, [allHotels, currency]);
74
+ const hasResults = localOffers.length > 0;
75
+ const showMapButton = hasResults && mapPins.length > 0 && !loadingInitial;
76
+ const isEmptyResult = hasSearched && !loadingInitial && !loadingRefetch && !loading && !isStreaming && localOffers.length === 0;
77
+ const isErrorState = !!error && hasSearched && !loadingInitial && !loadingRefetch;
78
+ const buildFilterRequest = (quickFilters, popup) => {
79
+ if (!searchParams)
80
+ return null;
81
+ const quickValues = derivedValuesFromQuickFilters(quickFilters);
82
+ const popupStars = popup?.starRating;
83
+ const finalFilter = {
84
+ ...(popup?.minPrice !== undefined && popup.minPrice > 0
85
+ ? { minPrice: popup.minPrice }
86
+ : {}),
87
+ ...(popup?.maxPrice !== undefined && popup.maxPrice !== 1000
88
+ ? { maxPrice: popup.maxPrice }
89
+ : {}),
90
+ ...(popupStars?.length ? { starRating: popupStars } : {}),
91
+ ...(!popupStars?.length && quickValues.starRating.length
92
+ ? { starRating: quickValues.starRating }
93
+ : {}),
94
+ ...(quickValues.boards
95
+ ? { boards: [quickValues.boards] }
96
+ : popup?.boards
97
+ ? { boards: Array.isArray(popup.boards) ? popup.boards : [popup.boards] }
98
+ : {}),
99
+ ...(quickValues.payments
100
+ ? { payments: quickValues.payments }
101
+ : popup?.payments
102
+ ? { payments: popup.payments }
103
+ : {}),
104
+ };
105
+ return {
106
+ ...searchParams,
107
+ offset: 0,
108
+ limit: 20,
109
+ ...(Object.keys(finalFilter).length ? { filter: finalFilter } : {}),
110
+ };
111
+ };
112
+ const handleQuickFilter = (key) => {
113
+ const def = QUICK_FILTERS.find((f) => f.key === key);
114
+ if (!def)
115
+ return;
116
+ setActiveFilters((prev) => {
117
+ let next = [...prev];
118
+ const isActive = next.includes(key);
119
+ if (def.type === "boards") {
120
+ next = next.filter((k) => {
121
+ const f = QUICK_FILTERS.find((q) => q.key === k);
122
+ return f?.type !== "boards";
123
+ });
124
+ }
125
+ if (def.type === "payments") {
126
+ next = next.filter((k) => {
127
+ const f = QUICK_FILTERS.find((q) => q.key === k);
128
+ return f?.type !== "payments";
129
+ });
130
+ }
131
+ if (def.type === "starFrom") {
132
+ next = next.filter((k) => {
133
+ const f = QUICK_FILTERS.find((q) => q.key === k);
134
+ return f?.type !== "starFrom";
135
+ });
136
+ }
137
+ if (isActive) {
138
+ next = next.filter((k) => k !== key);
139
+ }
140
+ else {
141
+ next.push(key);
142
+ }
143
+ let updatedPopupValues = popupValues;
144
+ if (popupValues) {
145
+ updatedPopupValues = { ...popupValues };
146
+ if (def.type === "starFrom")
147
+ delete updatedPopupValues.starRating;
148
+ if (def.type === "boards")
149
+ delete updatedPopupValues.boards;
150
+ if (def.type === "payments")
151
+ delete updatedPopupValues.payments;
152
+ if (Object.keys(updatedPopupValues).length === 0)
153
+ updatedPopupValues = null;
154
+ }
155
+ setPopupValues(updatedPopupValues);
156
+ if (hasSearched && searchParams && filterSearch) {
157
+ const requestBody = buildFilterRequest(next, updatedPopupValues);
158
+ if (requestBody) {
159
+ filterSearch(requestBody);
160
+ }
161
+ }
162
+ return next;
163
+ });
164
+ };
165
+ const handleApplyAdvancedFilters = async (sid, filterValues) => {
166
+ if (!filterValues || !searchParams || !filterSearch)
167
+ return;
168
+ setPopupValues(filterValues);
169
+ const nextFilters = [];
170
+ if (filterValues.boards) {
171
+ const boardDef = QUICK_FILTERS.find((f) => f.type === "boards" && f.value === filterValues.boards);
172
+ if (boardDef)
173
+ nextFilters.push(boardDef.key);
174
+ }
175
+ if (filterValues.payments) {
176
+ const payDef = QUICK_FILTERS.find((f) => f.type === "payments" && f.value === filterValues.payments);
177
+ if (payDef)
178
+ nextFilters.push(payDef.key);
179
+ }
180
+ setActiveFilters(nextFilters);
181
+ const minPrice = filterValues.minPrice ?? 0;
182
+ const maxPrice = filterValues.maxPrice === 1000 ? undefined : filterValues.maxPrice;
183
+ const filterObj = {
184
+ ...(minPrice > 0 ? { minPrice } : {}),
185
+ ...(maxPrice !== undefined ? { maxPrice } : {}),
186
+ ...(filterValues.starRating?.length ? { starRating: filterValues.starRating } : {}),
187
+ ...(filterValues.boards ? { boards: [filterValues.boards] } : {}),
188
+ ...(filterValues.payments ? { payments: filterValues.payments } : {}),
189
+ };
190
+ const requestBody = {
191
+ ...searchParams,
192
+ offset: 0,
193
+ limit: 20,
194
+ ...(Object.keys(filterObj).length ? { filter: filterObj } : {}),
195
+ };
196
+ filterSearch(requestBody);
197
+ };
198
+ const handleGlobalReset = () => {
199
+ setActiveFilters([]);
200
+ setPopupValues(null);
201
+ if (!searchParams || !filterSearch)
202
+ return;
203
+ const requestBody = {
204
+ ...searchParams,
205
+ offset: 0,
206
+ limit: 20,
207
+ };
208
+ filterSearch(requestBody);
209
+ };
210
+ const popupExternalValues = useMemo(() => {
211
+ const quickValues = derivedValuesFromQuickFilters();
212
+ return { ...quickValues, ...popupValues };
213
+ }, [activeFilters, popupValues]);
214
+ const currentPage = Math.floor(offset / limit) + 1;
215
+ const totalPages = Math.ceil(total / limit);
216
+ const handlePageChange = (page) => {
217
+ fetchPage?.(page);
218
+ };
219
+ const handleToggleDescription = (index) => {
220
+ const updated = [...localOffers];
221
+ updated[index] = {
222
+ ...updated[index],
223
+ showFullDescription: !updated[index].showFullDescription,
224
+ };
225
+ setLocalOffers(updated);
226
+ };
227
+ const renderHeader = () => (_jsxs(View, { onLayout: (e) => { headerHeightRef.current = e.nativeEvent.layout.height; }, children: [headerComponent, !loadingInitial && hasSearched && (_jsxs(View, { style: styles.topInfoBlock, children: [_jsxs(Text, { style: styles.totalCountText, children: [translations["results_found"] || "Found", " ", isErrorState ? 0 : total || 0, " ", lang === "ru"
228
+ ? total === 1
229
+ ? translations["hotel_singular"]
230
+ : total && total < 5
231
+ ? translations["hotel_few"]
232
+ : translations["hotel_many"]
233
+ : total === 1
234
+ ? translations["hotel_singular"] || "hotel"
235
+ : translations["hotel_many"] || "hotels"] }), !isStreaming &&
236
+ (total > 1 || activeFilters.length > 0 || popupValues !== null) && (_jsx(HotelFilters, { translations: translations, activeFilters: activeFilters, onQuickFilter: handleQuickFilter, onOpenPopup: () => setIsFiltersOpen(true), isPopupOpen: isFiltersOpen, onClosePopup: () => setIsFiltersOpen(false), onApplyFilters: handleApplyAdvancedFilters, onReset: handleGlobalReset, externalValues: popupExternalValues, sessionId: "hotel_session", filters: [
237
+ "minprice",
238
+ "maxprice",
239
+ "price",
240
+ "starRating",
241
+ "boards",
242
+ "payments",
243
+ ] }))] })), (loadingInitial || isStreaming) && (_jsx(ProvidersLoader, { active: loading || isStreaming, type: "hotels", translations: translations })), loadingInitial && !isStreaming && _jsx(HotelsSkeleton, { translations: translations }), (isErrorState || isEmptyResult) && !loadingInitial && !loadingRefetch && (_jsx(HotelEmptyState, { translations: translations }))] }));
244
+ const renderFooter = () => {
245
+ if (totalPages <= 1 || !hasResults || loadingInitial)
246
+ return null;
247
+ return (_jsx(View, { style: styles.paginationWrapper, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: handlePageChange }) }));
248
+ };
249
+ return (_jsxs(View, { style: styles.container, children: [loadingRefetch && (_jsxs(View, { style: styles.overlayLoader, children: [_jsx(ActivityIndicator, { size: "large", color: Colors.primary }), _jsx(Text, { style: styles.overlayText, children: translations["loading"] || "Loading..." })] })), _jsx(FlatList, { ref: listRef, data: !loadingInitial && hasResults && !isErrorState ? localOffers : [], renderItem: ({ item, index }) => (_jsx(HotelList, { offers: [item], translations: translations, lang: lang, onToggleDescription: () => handleToggleDescription(index) })), keyExtractor: (item, index) => String(item.id || item.propertyId || index), ListHeaderComponent: renderHeader(), ListFooterComponent: renderFooter(), contentContainerStyle: styles.listContent, showsVerticalScrollIndicator: false, removeClippedSubviews: true, maxToRenderPerBatch: 10, windowSize: 5 }), showMapButton && (_jsxs(TouchableOpacity, { style: styles.mapButton, onPress: () => setIsMapOpen(true), activeOpacity: 0.8, children: [_jsxs(Svg, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: Colors.white, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx(Polygon, { points: "1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6" }), _jsx(Line, { x1: 8, y1: 2, x2: 8, y2: 18 }), _jsx(Line, { x1: 16, y1: 6, x2: 16, y2: 22 })] }), _jsx(Text, { style: styles.mapButtonText, children: translations["map_button"] || "Map" })] })), isMapOpen && (_jsxs(View, { style: StyleSheet.absoluteFill, children: [_jsx(HotelMap, { pins: mapPins, allHotels: allHotels || [], streamCompleted: !isStreaming && !loading, loading: loading || isStreaming, translations: translations, lang: lang, searchParams: searchParams, onHotelSelect: (hotelId) => {
250
+ }, onRefreshSearch: () => {
251
+ if (searchParams && filterSearch) {
252
+ filterSearch(searchParams);
253
+ }
254
+ } }), _jsx(TouchableOpacity, { style: [styles.mapCloseButton, { top: insets.top }], onPress: () => setIsMapOpen(false), activeOpacity: 0.7, children: _jsx(Svg, { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", children: _jsx(Path, { d: "M18.3 5.71a1 1 0 00-1.41 0L12 10.59 7.11 5.7A1 1 0 105.7 7.11L10.59 12 5.7 16.89a1 1 0 101.41 1.41L12 13.41l4.89 4.89a1 1 0 001.41-1.41L13.41 12l4.89-4.89a1 1 0 000-1.4z", fill: Colors.text }) }) })] }))] }));
255
+ };
256
+ const styles = StyleSheet.create({
257
+ container: {
258
+ flex: 1,
259
+ position: "relative",
260
+ },
261
+ overlayLoader: {
262
+ position: "absolute",
263
+ top: 0,
264
+ left: 0,
265
+ right: 0,
266
+ bottom: 0,
267
+ backgroundColor: "rgba(255,255,255,0.7)",
268
+ zIndex: 10,
269
+ justifyContent: "center",
270
+ alignItems: "center",
271
+ },
272
+ overlayText: {
273
+ marginTop: Spacing.sm,
274
+ fontSize: FontSize.md,
275
+ color: Colors.textSecondary,
276
+ },
277
+ listContent: {
278
+ paddingBottom: Spacing.lg,
279
+ },
280
+ topInfoBlock: {
281
+ paddingHorizontal: Spacing.lg,
282
+ paddingVertical: Spacing.md,
283
+ },
284
+ totalCountText: {
285
+ fontSize: FontSize.xxl,
286
+ fontWeight: "600",
287
+ color: Colors.text,
288
+ marginBottom: Spacing.lg,
289
+ },
290
+ paginationWrapper: {
291
+ paddingVertical: Spacing.lg,
292
+ alignItems: "center",
293
+ },
294
+ mapButton: {
295
+ position: "absolute",
296
+ bottom: Spacing.xxl,
297
+ alignSelf: "center",
298
+ flexDirection: "row",
299
+ alignItems: "center",
300
+ gap: Spacing.xs,
301
+ backgroundColor: Colors.primary,
302
+ paddingVertical: Spacing.sm + 2,
303
+ paddingHorizontal: Spacing.lg,
304
+ borderRadius: BorderRadius.full,
305
+ elevation: 6,
306
+ shadowColor: Colors.black,
307
+ shadowOffset: { width: 0, height: 3 },
308
+ shadowOpacity: 0.25,
309
+ shadowRadius: 6,
310
+ },
311
+ mapButtonText: {
312
+ fontSize: FontSize.md,
313
+ fontWeight: "600",
314
+ color: Colors.white,
315
+ },
316
+ mapCloseButton: {
317
+ position: "absolute",
318
+ right: Spacing.md,
319
+ backgroundColor: Colors.surface,
320
+ width: 40,
321
+ height: 40,
322
+ borderRadius: 20,
323
+ justifyContent: "center",
324
+ alignItems: "center",
325
+ elevation: 6,
326
+ shadowColor: Colors.black,
327
+ shadowOffset: { width: 0, height: 2 },
328
+ shadowOpacity: 0.2,
329
+ shadowRadius: 4,
330
+ zIndex: 100,
331
+ },
332
+ });
333
+ export default HotelResults;
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { useSearchExpiration } from "../../hooks/useSearchExpiration";
3
+ type HotelSearchFormProps = {
4
+ translations: Record<string, string>;
5
+ fetchHotelOffers: (params: any) => Promise<void>;
6
+ loading?: boolean;
7
+ onSearchParamsChange?: (params: Record<string, any>) => void;
8
+ isPreview: boolean;
9
+ expiration?: ReturnType<typeof useSearchExpiration>;
10
+ hasSearched?: boolean;
11
+ };
12
+ declare const HotelSearchForm: React.FC<HotelSearchFormProps>;
13
+ export default HotelSearchForm;
@@ -0,0 +1,130 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { View, Text, TouchableOpacity, StyleSheet, ScrollView, ActivityIndicator, } from "react-native";
4
+ import { format, addDays, differenceInCalendarDays, parseISO } from "date-fns";
5
+ import DatePicker from "../flight/DatePicker";
6
+ import NationalityInput from "../NationalityInput";
7
+ import { useWidget } from "../../../context/WidgetContext";
8
+ import HotelLocation from "./HotelLocation";
9
+ import GuestSelector from "./GuestSelector";
10
+ import SearchExpiredPopup from "../../search-form/SearchExpiredPopup";
11
+ import { useSearchExpiration } from "../../hooks/useSearchExpiration";
12
+ import { useDefaultHotelLocation } from "../../hooks/useDefaultHotelLocation";
13
+ import { useDetectNationality } from "../../hooks/useDetectNationality";
14
+ import { Colors } from "../../../theme/colors";
15
+ import { formStyles } from "../../../theme/formStyles";
16
+ const HotelSearchForm = ({ translations, fetchHotelOffers, loading, onSearchParamsChange, isPreview, expiration, hasSearched = false, }) => {
17
+ const { lang, currency, config, colors } = useWidget();
18
+ const today = new Date();
19
+ const defaultCheckin = format(addDays(today, 3), "yyyy-MM-dd");
20
+ const defaultCheckout = format(addDays(today, 5), "yyyy-MM-dd");
21
+ const [location, setLocation] = useState(null);
22
+ const [dates, setDates] = useState([
23
+ defaultCheckin,
24
+ defaultCheckout,
25
+ ]);
26
+ const [nationality, setNationality] = useState("");
27
+ const [rooms, setRooms] = useState([{ adults: 2, children: 0 }]);
28
+ const [formErrors, setFormErrors] = useState({
29
+ location: "",
30
+ date: "",
31
+ nationality: "",
32
+ });
33
+ const detectedNationality = useDetectNationality();
34
+ useEffect(() => {
35
+ if (!nationality && detectedNationality) {
36
+ setNationality(detectedNationality);
37
+ }
38
+ }, [detectedNationality]);
39
+ const localExpiration = useSearchExpiration(30);
40
+ const { isExpired: isSearchExpired, startTimer, ignore: closeSearchPopup, } = expiration || localExpiration;
41
+ const refreshSearch = () => {
42
+ startTimer();
43
+ closeSearchPopup();
44
+ handleSearch();
45
+ };
46
+ const defaultLocationId = config?.defaults?.hotels?.city || null;
47
+ const { defaultItem, loadingDefault } = useDefaultHotelLocation(defaultLocationId, lang);
48
+ useEffect(() => {
49
+ if (defaultItem && defaultItem.id) {
50
+ setLocation({ id: defaultItem.id });
51
+ }
52
+ }, [defaultItem]);
53
+ const handleSearch = async () => {
54
+ if (isPreview)
55
+ return;
56
+ const dayDiff = differenceInCalendarDays(parseISO(dates[1]), parseISO(dates[0]));
57
+ const errors = {
58
+ location: !location?.id
59
+ ? translations["hotel_form_error_location"] ||
60
+ "Select a city or hotel"
61
+ : "",
62
+ date: !dates[0]
63
+ ? translations["hotel_form_error_date"] || "Select stay dates"
64
+ : dayDiff > 27
65
+ ? translations["hotel_form_error_max_nights"] ||
66
+ "Stay cannot exceed 28 days"
67
+ : "",
68
+ nationality: !nationality
69
+ ? translations["hotel_form_error_nationality"] ||
70
+ "Specify nationality"
71
+ : "",
72
+ };
73
+ setFormErrors(errors);
74
+ if (Object.values(errors).some((e) => e))
75
+ return;
76
+ const params = {
77
+ countryCode: nationality,
78
+ guests: rooms,
79
+ offset: 0,
80
+ limit: 20,
81
+ location: Number(location?.id),
82
+ checkin: format(parseISO(dates[0]), "dd.MM.yyyy"),
83
+ checkout: format(parseISO(dates[1]), "dd.MM.yyyy"),
84
+ lang,
85
+ };
86
+ await fetchHotelOffers(params);
87
+ startTimer();
88
+ if (onSearchParamsChange)
89
+ onSearchParamsChange(params);
90
+ };
91
+ return (_jsxs(ScrollView, { style: styles.container, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: [_jsxs(View, { style: [
92
+ formStyles.formCard,
93
+ { backgroundColor: colors.form || Colors.surface },
94
+ ], children: [_jsxs(View, { children: [_jsx(HotelLocation, { label: translations["hotel_form_city"] || "City or hotel", value: location?.id || "", onChange: (val) => {
95
+ setLocation(val);
96
+ if (formErrors.location)
97
+ setFormErrors((prev) => ({ ...prev, location: "" }));
98
+ }, translations: translations, lang: lang, defaultItem: defaultItem }), formErrors.location ? (_jsx(Text, { style: formStyles.errorText, children: formErrors.location })) : null] }), _jsxs(View, { children: [_jsx(DatePicker, { value: dates, onChange: (val) => {
99
+ setDates(val);
100
+ if (formErrors.date)
101
+ setFormErrors((prev) => ({ ...prev, date: "" }));
102
+ if (val[0] && val[1]) {
103
+ const diff = differenceInCalendarDays(parseISO(val[1]), parseISO(val[0]));
104
+ if (diff > 27) {
105
+ setFormErrors((prev) => ({
106
+ ...prev,
107
+ date: translations["hotel_form_error_max_nights"] ||
108
+ "Stay cannot exceed 28 days",
109
+ }));
110
+ }
111
+ else {
112
+ setFormErrors((prev) => ({ ...prev, date: "" }));
113
+ }
114
+ }
115
+ }, lang: lang, translations: translations, mode: "hotel" }), formErrors.date ? (_jsx(Text, { style: formStyles.errorText, children: formErrors.date })) : null] }), _jsxs(View, { children: [_jsx(NationalityInput, { label: translations["hotel_form_nationality"] || "Nationality", value: nationality, onChange: (val) => {
116
+ setNationality(val);
117
+ if (formErrors.nationality)
118
+ setFormErrors((prev) => ({ ...prev, nationality: "" }));
119
+ }, translations: translations, defaultNationality: detectedNationality, defaultNationalities: config?.nationalities || null, lang: lang }), formErrors.nationality ? (_jsx(Text, { style: formStyles.errorText, children: formErrors.nationality })) : null] }), _jsx(GuestSelector, { translations: translations, onChange: setRooms }), _jsx(TouchableOpacity, { style: [
120
+ formStyles.searchButton,
121
+ { backgroundColor: colors.primary || Colors.primary },
122
+ (loading || loadingDefault) && formStyles.searchButtonDisabled,
123
+ ], onPress: handleSearch, disabled: loading || loadingDefault, activeOpacity: 0.8, children: loading || loadingDefault ? (_jsxs(View, { style: formStyles.searchButtonContent, children: [_jsx(ActivityIndicator, { size: "small", color: Colors.textOnPrimary }), _jsx(Text, { style: formStyles.searchButtonText, children: translations["hotel_form_searching"] || "Searching..." })] })) : (_jsx(Text, { style: formStyles.searchButtonText, children: translations["hotel_form_search"] || "Search hotels" })) })] }), !isPreview && hasSearched && (_jsx(SearchExpiredPopup, { isOpen: isSearchExpired, onRefresh: refreshSearch, onClose: closeSearchPopup, translations: translations, type: "hotel" }))] }));
124
+ };
125
+ const styles = StyleSheet.create({
126
+ container: {
127
+ flex: 1,
128
+ },
129
+ });
130
+ export default HotelSearchForm;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ type HotelSkeletonProps = {
3
+ translations: Record<string, string>;
4
+ };
5
+ declare const HotelSkeleton: React.FC<HotelSkeletonProps>;
6
+ export default HotelSkeleton;
@@ -0,0 +1,40 @@
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
+ const HotelSkeletonCard = () => (_jsxs(View, { style: styles.card, children: [_jsx(Skeleton, { width: "100%", height: 180, borderRadius: 0 }), _jsxs(View, { style: styles.content, children: [_jsx(Skeleton, { width: "70%", height: 18 }), _jsx(View, { style: styles.spacerSm }), _jsx(Skeleton, { width: "40%", height: 12 }), _jsx(View, { style: styles.spacerMd }), _jsx(Skeleton, { width: "100%", height: 12 }), _jsx(View, { style: styles.spacerSm }), _jsx(Skeleton, { width: "80%", height: 12 })] }), _jsxs(View, { style: styles.priceSection, children: [_jsx(Skeleton, { width: "50%", height: 20 }), _jsx(View, { style: styles.spacerSm }), _jsx(Skeleton, { width: "35%", height: 12 }), _jsx(View, { style: styles.spacerMd }), _jsx(Skeleton, { width: "100%", height: 40, borderRadius: BorderRadius.md })] })] }));
6
+ const HotelSkeleton = ({ translations }) => {
7
+ return (_jsx(View, { style: styles.container, children: [1, 2, 3, 4].map((i) => (_jsx(HotelSkeletonCard, {}, i))) }));
8
+ };
9
+ const styles = StyleSheet.create({
10
+ container: {
11
+ paddingVertical: Spacing.md,
12
+ },
13
+ card: {
14
+ backgroundColor: Colors.surface,
15
+ borderRadius: BorderRadius.lg,
16
+ marginHorizontal: Spacing.lg,
17
+ marginBottom: Spacing.md,
18
+ shadowColor: Colors.black,
19
+ shadowOffset: { width: 0, height: 2 },
20
+ shadowOpacity: 0.05,
21
+ shadowRadius: 4,
22
+ elevation: 2,
23
+ overflow: "hidden",
24
+ },
25
+ content: {
26
+ padding: Spacing.md,
27
+ },
28
+ priceSection: {
29
+ borderTopWidth: 1,
30
+ borderTopColor: Colors.borderLight,
31
+ padding: Spacing.md,
32
+ },
33
+ spacerSm: {
34
+ height: Spacing.sm,
35
+ },
36
+ spacerMd: {
37
+ height: Spacing.md,
38
+ },
39
+ });
40
+ export default HotelSkeleton;
@@ -0,0 +1,2 @@
1
+ declare const _default: null;
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export default null;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ type HotelCardProps = {
3
+ hotel: any;
4
+ index: number;
5
+ total: number;
6
+ translations: Record<string, string>;
7
+ lang: string;
8
+ onToggleDescription: () => void;
9
+ };
10
+ declare const HotelCard: React.FC<HotelCardProps>;
11
+ export default HotelCard;