@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,411 @@
1
+ import { useState, useRef, useCallback } from "react";
2
+ import { useApiToken } from "../../utils/getToken";
3
+ import { fetchSSE, mergeHotels } from "../../utils/fetchSSE";
4
+ const STREAM_URL = "https://api.travel-code.com/v1/search/hotels/stream";
5
+ const CURRENCY_SIGNS = {
6
+ USD: "$", EUR: "€", GBP: "£", RUB: "₽", TRY: "₺",
7
+ AED: "د.إ", SAR: "﷼", KZT: "₸", JPY: "¥", CNY: "¥",
8
+ };
9
+ function getCurrencySign(code) {
10
+ return CURRENCY_SIGNS[code?.toUpperCase()] || code || "$";
11
+ }
12
+ function normalizeHotel(hotel, currency) {
13
+ if (!hotel)
14
+ return hotel;
15
+ return {
16
+ ...hotel,
17
+ nightlyPrice: hotel.nightlyPrice ?? hotel.price ?? null,
18
+ totalPrice: hotel.totalPrice ?? hotel.total ?? null,
19
+ currencySign: hotel.currencySign || getCurrencySign(currency),
20
+ ratings: {
21
+ ...hotel.ratings,
22
+ star: hotel.ratings?.star ?? hotel.starRating ?? 0,
23
+ },
24
+ };
25
+ }
26
+ function normalizeBatch(hotels, currencySign) {
27
+ return hotels.map((h) => normalizeHotel(h, currencySign));
28
+ }
29
+ export function useHotelSearchSSE() {
30
+ const token = useApiToken();
31
+ const [offers, setOffers] = useState([]);
32
+ const [allHotels, setAllHotels] = useState([]);
33
+ const [loading, setLoading] = useState(false);
34
+ const [isStreaming, setIsStreaming] = useState(false);
35
+ const [isPageLoading, setIsPageLoading] = useState(false);
36
+ const [error, setError] = useState(null);
37
+ const [total, setTotal] = useState(0);
38
+ const [offset, setOffset] = useState(0);
39
+ const [limit, setLimit] = useState(20);
40
+ const [cacheKey, setCacheKey] = useState(null);
41
+ const [streamCompleted, setStreamCompleted] = useState(false);
42
+ const [searchParams, setSearchParams] = useState(null);
43
+ const abortRef = useRef(null);
44
+ const allHotelsRef = useRef([]);
45
+ const hotelMapRef = useRef(new Map());
46
+ const currencySignRef = useRef("$");
47
+ const cleanup = () => {
48
+ if (abortRef.current) {
49
+ abortRef.current.abort();
50
+ abortRef.current = null;
51
+ }
52
+ };
53
+ const startSearch = useCallback((params) => {
54
+ cleanup();
55
+ const controller = new AbortController();
56
+ abortRef.current = controller;
57
+ setLoading(true);
58
+ setIsStreaming(true);
59
+ setStreamCompleted(false);
60
+ setError(null);
61
+ setOffers([]);
62
+ setAllHotels([]);
63
+ allHotelsRef.current = [];
64
+ hotelMapRef.current = new Map();
65
+ setTotal(0);
66
+ setOffset(params.offset ?? 0);
67
+ setLimit(params.limit ?? 20);
68
+ setSearchParams(params);
69
+ setCacheKey(null);
70
+ setIsPageLoading(false);
71
+ currencySignRef.current = params.currencySign || params.currency || "$";
72
+ const pageLimit = params.limit || 20;
73
+ const cs = currencySignRef.current;
74
+ fetchSSE({
75
+ url: STREAM_URL,
76
+ body: params,
77
+ token,
78
+ signal: controller.signal,
79
+ onEvent: (event, data) => {
80
+ if (controller.signal.aborted)
81
+ return;
82
+ if (event === "connected") {
83
+ setLoading(false);
84
+ return;
85
+ }
86
+ if (event === "count") {
87
+ setLoading(false);
88
+ try {
89
+ const payload = JSON.parse(data);
90
+ if (typeof payload.count === "number") {
91
+ setTotal(payload.count);
92
+ }
93
+ }
94
+ catch { }
95
+ return;
96
+ }
97
+ if (event === "hotels") {
98
+ setLoading(false);
99
+ try {
100
+ const payload = JSON.parse(data);
101
+ const batch = Array.isArray(payload.hotels) ? payload.hotels : [];
102
+ if (batch.length > 0) {
103
+ const normalized = normalizeBatch(batch, cs);
104
+ const result = mergeHotels(hotelMapRef.current, normalized);
105
+ hotelMapRef.current = result.merged;
106
+ const all = Array.from(result.merged.values());
107
+ allHotelsRef.current = all;
108
+ setAllHotels(all);
109
+ setOffers(all.slice(0, pageLimit));
110
+ setTotal(all.length);
111
+ }
112
+ }
113
+ catch { }
114
+ return;
115
+ }
116
+ if (event === "completed") {
117
+ setStreamCompleted(true);
118
+ try {
119
+ const payload = JSON.parse(data);
120
+ if (payload.cacheKey) {
121
+ setCacheKey(payload.cacheKey);
122
+ }
123
+ const finalHotels = normalizeBatch(Array.isArray(payload.hotels) ? payload.hotels : [], cs);
124
+ const accumulated = Array.from(hotelMapRef.current.values());
125
+ if (finalHotels.length > 0) {
126
+ setOffers(finalHotels);
127
+ }
128
+ else if (accumulated.length > 0) {
129
+ setOffers(accumulated.slice(0, pageLimit));
130
+ }
131
+ let mapData;
132
+ if (finalHotels.length > 0 && accumulated.length > finalHotels.length) {
133
+ const finalMap = new Map(finalHotels.map((h) => [String(h.id), h]));
134
+ mapData = accumulated.map((h) => finalMap.get(String(h.id)) || h);
135
+ }
136
+ else {
137
+ mapData = finalHotels.length > 0 ? finalHotels : accumulated;
138
+ }
139
+ allHotelsRef.current = mapData;
140
+ setAllHotels(mapData);
141
+ const effectiveTotal = mapData.length === 0
142
+ ? 0
143
+ : typeof payload.count === "number"
144
+ ? payload.count
145
+ : mapData.length;
146
+ setTotal(effectiveTotal);
147
+ }
148
+ catch {
149
+ const result = Array.from(hotelMapRef.current.values());
150
+ allHotelsRef.current = result;
151
+ setAllHotels(result);
152
+ setOffers(result.slice(0, pageLimit));
153
+ setTotal(result.length);
154
+ }
155
+ return;
156
+ }
157
+ if (event === "error") {
158
+ try {
159
+ const payload = JSON.parse(data);
160
+ setError(payload.message || "Search error");
161
+ }
162
+ catch {
163
+ setError("Search error");
164
+ }
165
+ return;
166
+ }
167
+ },
168
+ onError: (err) => {
169
+ if (err.name !== "AbortError") {
170
+ setError(err.message || "Search failed");
171
+ }
172
+ },
173
+ onComplete: () => {
174
+ if (abortRef.current === controller) {
175
+ setLoading(false);
176
+ setIsStreaming(false);
177
+ }
178
+ },
179
+ });
180
+ }, [token]);
181
+ const filterSearch = useCallback((params) => {
182
+ cleanup();
183
+ const controller = new AbortController();
184
+ abortRef.current = controller;
185
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
186
+ setIsPageLoading(true);
187
+ setStreamCompleted(false);
188
+ setError(null);
189
+ setOffset(params.offset ?? 0);
190
+ setLimit(params.limit ?? 20);
191
+ setSearchParams(params);
192
+ setCacheKey(null);
193
+ hotelMapRef.current = new Map();
194
+ currencySignRef.current = params.currencySign || params.currency || currencySignRef.current;
195
+ const pageLimit = params.limit || 20;
196
+ const cs = currencySignRef.current;
197
+ let gotCompleted = false;
198
+ fetchSSE({
199
+ url: STREAM_URL,
200
+ body: params,
201
+ token,
202
+ signal: controller.signal,
203
+ onEvent: (event, data) => {
204
+ if (controller.signal.aborted)
205
+ return;
206
+ if (event === "connected")
207
+ return;
208
+ if (event === "count") {
209
+ try {
210
+ const payload = JSON.parse(data);
211
+ if (typeof payload.count === "number") {
212
+ setTotal(payload.count);
213
+ }
214
+ }
215
+ catch { }
216
+ return;
217
+ }
218
+ if (event === "hotels") {
219
+ try {
220
+ const payload = JSON.parse(data);
221
+ const batch = Array.isArray(payload.hotels) ? payload.hotels : [];
222
+ for (const hotel of batch) {
223
+ const normalized = normalizeHotel(hotel, cs);
224
+ hotelMapRef.current.set(String(normalized.id), normalized);
225
+ }
226
+ }
227
+ catch { }
228
+ return;
229
+ }
230
+ if (event === "completed") {
231
+ gotCompleted = true;
232
+ setStreamCompleted(true);
233
+ try {
234
+ const payload = JSON.parse(data);
235
+ if (payload.cacheKey) {
236
+ setCacheKey(payload.cacheKey);
237
+ }
238
+ const finalHotels = normalizeBatch(Array.isArray(payload.hotels) ? payload.hotels : [], cs);
239
+ const accumulated = Array.from(hotelMapRef.current.values());
240
+ if (finalHotels.length > 0) {
241
+ setOffers(finalHotels);
242
+ }
243
+ else if (accumulated.length > 0) {
244
+ setOffers(accumulated.slice(0, pageLimit));
245
+ }
246
+ else {
247
+ setOffers([]);
248
+ }
249
+ let mapData;
250
+ if (finalHotels.length > 0 && accumulated.length > finalHotels.length) {
251
+ const finalMap = new Map(finalHotels.map((h) => [String(h.id), h]));
252
+ mapData = accumulated.map((h) => finalMap.get(String(h.id)) || h);
253
+ }
254
+ else {
255
+ mapData = finalHotels.length > 0 ? finalHotels : accumulated;
256
+ }
257
+ allHotelsRef.current = mapData;
258
+ setAllHotels(mapData);
259
+ const effectiveTotal = mapData.length === 0
260
+ ? 0
261
+ : typeof payload.count === "number"
262
+ ? payload.count
263
+ : mapData.length;
264
+ setTotal(effectiveTotal);
265
+ }
266
+ catch {
267
+ const result = Array.from(hotelMapRef.current.values());
268
+ allHotelsRef.current = result;
269
+ setAllHotels(result);
270
+ setOffers(result.slice(0, pageLimit));
271
+ setTotal(result.length);
272
+ }
273
+ setOffset(0);
274
+ return;
275
+ }
276
+ if (event === "error") {
277
+ try {
278
+ const payload = JSON.parse(data);
279
+ setError(payload.message || "Filter search error");
280
+ }
281
+ catch {
282
+ setError("Filter search error");
283
+ }
284
+ return;
285
+ }
286
+ },
287
+ onError: (err) => {
288
+ if (err.name !== "AbortError") {
289
+ setError(err.message || "Filter search failed");
290
+ setOffers([]);
291
+ setAllHotels([]);
292
+ allHotelsRef.current = [];
293
+ setTotal(0);
294
+ }
295
+ else if (allHotelsRef.current.length === 0) {
296
+ setOffers([]);
297
+ setAllHotels([]);
298
+ setTotal(0);
299
+ }
300
+ },
301
+ onComplete: () => {
302
+ clearTimeout(timeoutId);
303
+ if (!gotCompleted) {
304
+ const accumulated = Array.from(hotelMapRef.current.values());
305
+ if (accumulated.length > 0) {
306
+ allHotelsRef.current = accumulated;
307
+ setAllHotels(accumulated);
308
+ setOffers(accumulated.slice(0, pageLimit));
309
+ setTotal(accumulated.length);
310
+ setOffset(0);
311
+ }
312
+ else {
313
+ setOffers([]);
314
+ setAllHotels([]);
315
+ allHotelsRef.current = [];
316
+ setTotal(0);
317
+ }
318
+ }
319
+ if (abortRef.current === controller) {
320
+ setIsPageLoading(false);
321
+ }
322
+ },
323
+ });
324
+ }, [token]);
325
+ const fetchPage = useCallback((page) => {
326
+ const newOffset = (page - 1) * limit;
327
+ setOffset(newOffset);
328
+ if (streamCompleted && allHotelsRef.current.length > 0) {
329
+ const pageData = allHotelsRef.current.slice(newOffset, newOffset + limit);
330
+ setOffers(pageData);
331
+ return;
332
+ }
333
+ if (!searchParams)
334
+ return;
335
+ setIsPageLoading(true);
336
+ const controller = new AbortController();
337
+ fetchSSE({
338
+ url: STREAM_URL,
339
+ body: { ...searchParams, offset: newOffset, limit },
340
+ token,
341
+ signal: controller.signal,
342
+ onEvent: (event, data) => {
343
+ const cs = currencySignRef.current;
344
+ if (event === "completed") {
345
+ try {
346
+ const payload = JSON.parse(data);
347
+ const hotels = normalizeBatch(Array.isArray(payload.hotels) ? payload.hotels : [], cs);
348
+ setOffers(hotels);
349
+ if (typeof payload.count === "number")
350
+ setTotal(payload.count);
351
+ if (payload.cacheKey)
352
+ setCacheKey(payload.cacheKey);
353
+ }
354
+ catch { }
355
+ }
356
+ if (event === "hotels") {
357
+ try {
358
+ const payload = JSON.parse(data);
359
+ const batch = normalizeBatch(Array.isArray(payload.hotels) ? payload.hotels : [], cs);
360
+ if (batch.length > 0)
361
+ setOffers(batch);
362
+ }
363
+ catch { }
364
+ }
365
+ },
366
+ onError: (err) => {
367
+ if (err.name !== "AbortError") {
368
+ setError(err.message || "Page fetch failed");
369
+ }
370
+ },
371
+ onComplete: () => {
372
+ setIsPageLoading(false);
373
+ },
374
+ });
375
+ }, [searchParams, limit, streamCompleted, token]);
376
+ const reset = useCallback(() => {
377
+ cleanup();
378
+ setOffers([]);
379
+ setAllHotels([]);
380
+ allHotelsRef.current = [];
381
+ hotelMapRef.current = new Map();
382
+ setLoading(false);
383
+ setIsStreaming(false);
384
+ setIsPageLoading(false);
385
+ setError(null);
386
+ setTotal(0);
387
+ setOffset(0);
388
+ setLimit(20);
389
+ setSearchParams(null);
390
+ setCacheKey(null);
391
+ setStreamCompleted(false);
392
+ }, []);
393
+ return {
394
+ offers,
395
+ allHotels,
396
+ loading,
397
+ isStreaming,
398
+ isPageLoading,
399
+ error,
400
+ total,
401
+ offset,
402
+ limit,
403
+ cacheKey,
404
+ streamCompleted,
405
+ searchParams,
406
+ startSearch,
407
+ filterSearch,
408
+ fetchPage,
409
+ reset,
410
+ };
411
+ }
@@ -0,0 +1,6 @@
1
+ export declare function useSearchExpiration(minutes?: number): {
2
+ isExpired: boolean;
3
+ startTimer: () => void;
4
+ ignore: () => void;
5
+ reset: () => void;
6
+ };
@@ -0,0 +1,35 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ export function useSearchExpiration(minutes = 30) {
3
+ const [isExpired, setIsExpired] = useState(false);
4
+ const lastSearchRef = useRef(null);
5
+ const startTimer = () => {
6
+ lastSearchRef.current = Date.now();
7
+ setIsExpired(false);
8
+ };
9
+ const ignore = () => {
10
+ lastSearchRef.current = Date.now();
11
+ setIsExpired(false);
12
+ };
13
+ const reset = () => {
14
+ lastSearchRef.current = null;
15
+ setIsExpired(false);
16
+ };
17
+ useEffect(() => {
18
+ const interval = setInterval(() => {
19
+ if (!lastSearchRef.current)
20
+ return;
21
+ const diff = Date.now() - lastSearchRef.current;
22
+ const limit = minutes * 60 * 1000;
23
+ if (diff >= limit) {
24
+ setIsExpired(true);
25
+ }
26
+ }, 15000);
27
+ return () => clearInterval(interval);
28
+ }, [minutes]);
29
+ return {
30
+ isExpired,
31
+ startTimer,
32
+ ignore,
33
+ reset,
34
+ };
35
+ }
@@ -0,0 +1,5 @@
1
+ export declare function getTranslationsHub(): Record<string, string>;
2
+ export declare function useTranslationsHub(code: string, lang: string): {
3
+ loading: boolean;
4
+ error: string | null;
5
+ };
@@ -0,0 +1,48 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useApiToken } from "../../utils/getToken";
3
+ import { defaultTranslations } from "../../locales/widgetTranslations";
4
+ let translationsHub = {};
5
+ export function getTranslationsHub() {
6
+ return translationsHub;
7
+ }
8
+ export function useTranslationsHub(code, lang) {
9
+ const [loading, setLoading] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const token = useApiToken();
12
+ useEffect(() => {
13
+ if (!lang || !code)
14
+ return;
15
+ const local = defaultTranslations[lang] || defaultTranslations["en"];
16
+ translationsHub = local;
17
+ const controller = new AbortController();
18
+ async function fetchTranslations() {
19
+ setLoading(true);
20
+ setError(null);
21
+ try {
22
+ const res = await fetch(`https://api.travel-code.com/v1/data/vocabulary?code=${code}&lang=${lang}`, {
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ Authorization: `Bearer ${token}`,
26
+ },
27
+ signal: controller.signal,
28
+ });
29
+ if (!res.ok)
30
+ throw new Error(`Error ${res.status}`);
31
+ const data = await res.json();
32
+ translationsHub = data;
33
+ }
34
+ catch (err) {
35
+ if (err.name !== "AbortError") {
36
+ console.warn("Failed to update translations:", err);
37
+ setError(err.message || "Translation load error");
38
+ }
39
+ }
40
+ finally {
41
+ setLoading(false);
42
+ }
43
+ }
44
+ fetchTranslations();
45
+ return () => controller.abort();
46
+ }, [code, lang, token]);
47
+ return { loading, error };
48
+ }
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ type NavbarProps = {
3
+ activeTab?: string;
4
+ onTabChange?: (tab: string) => void;
5
+ translations: Record<string, string>;
6
+ };
7
+ export declare const Navbar: React.FC<NavbarProps>;
8
+ export default Navbar;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, TouchableOpacity, StyleSheet, } from "react-native";
3
+ import { useWidget } from "../../context/WidgetContext";
4
+ import { FlightIcon, HotelIcon } from "../../store";
5
+ import { Colors, Spacing, BorderRadius, FontSize } from "../../theme/colors";
6
+ export const Navbar = ({ activeTab = "flights", onTabChange, translations, }) => {
7
+ const { colors } = useWidget();
8
+ const visibleTabs = [
9
+ {
10
+ key: "flights",
11
+ label: translations["tab_flights"] || "Flights",
12
+ Icon: FlightIcon,
13
+ },
14
+ {
15
+ key: "hotels",
16
+ label: translations["tab_hotels"] || "Hotels",
17
+ Icon: HotelIcon,
18
+ },
19
+ ];
20
+ const primaryColor = colors.primary || Colors.primary;
21
+ return (_jsx(View, { style: styles.container, children: _jsx(View, { style: styles.tabsRow, children: visibleTabs.map(({ key, label, Icon }) => {
22
+ const isActive = activeTab === key;
23
+ return (_jsxs(TouchableOpacity, { activeOpacity: 0.7, onPress: () => onTabChange?.(key), style: [
24
+ styles.tab,
25
+ isActive
26
+ ? [styles.tabActive, { backgroundColor: primaryColor }]
27
+ : styles.tabInactive,
28
+ ], children: [_jsx(Icon, { size: 18, color: isActive ? Colors.white : primaryColor }), _jsx(Text, { style: [
29
+ styles.tabLabel,
30
+ { color: isActive ? Colors.white : primaryColor },
31
+ ], children: label })] }, key));
32
+ }) }) }));
33
+ };
34
+ const styles = StyleSheet.create({
35
+ container: {
36
+ backgroundColor: Colors.transparent,
37
+ paddingHorizontal: Spacing.lg,
38
+ paddingVertical: Spacing.md,
39
+ zIndex: 1,
40
+ },
41
+ tabsRow: {
42
+ flexDirection: "row",
43
+ alignItems: "center",
44
+ gap: 10,
45
+ },
46
+ tab: {
47
+ flexDirection: "row",
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ paddingVertical: Spacing.md,
51
+ paddingHorizontal: Spacing.lg,
52
+ borderRadius: BorderRadius.lg,
53
+ gap: Spacing.sm,
54
+ shadowColor: Colors.black,
55
+ shadowOffset: { width: 0, height: 0 },
56
+ shadowOpacity: 0.05,
57
+ shadowRadius: 15,
58
+ elevation: 2,
59
+ },
60
+ tabActive: {},
61
+ tabInactive: {
62
+ backgroundColor: Colors.white,
63
+ },
64
+ tabLabel: {
65
+ fontSize: FontSize.lg,
66
+ fontWeight: "400",
67
+ },
68
+ });
69
+ export default Navbar;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ type AgeSelectorProps = {
3
+ label: string;
4
+ value: string;
5
+ onChange: (val: string) => void;
6
+ translations: Record<string, string>;
7
+ };
8
+ declare const AgeSelector: React.FC<AgeSelectorProps>;
9
+ export default AgeSelector;