@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.
- package/LICENSE +18 -0
- package/dist/App.d.ts +10 -0
- package/dist/App.js +192 -0
- package/dist/TravelHubWidget.d.ts +12 -0
- package/dist/TravelHubWidget.js +58 -0
- package/dist/components/ErrorBoundary.d.ts +18 -0
- package/dist/components/ErrorBoundary.js +63 -0
- package/dist/components/ui/Modal.d.ts +11 -0
- package/dist/components/ui/Modal.js +66 -0
- package/dist/components/ui/Skeleton.d.ts +8 -0
- package/dist/components/ui/Skeleton.js +33 -0
- package/dist/components/ui/badge.d.ts +11 -0
- package/dist/components/ui/badge.js +54 -0
- package/dist/components/ui/button.d.ts +14 -0
- package/dist/components/ui/button.js +101 -0
- package/dist/components/ui/card.d.ts +16 -0
- package/dist/components/ui/card.js +51 -0
- package/dist/components/ui/input.d.ts +8 -0
- package/dist/components/ui/input.js +61 -0
- package/dist/components/ui/label.d.ts +8 -0
- package/dist/components/ui/label.js +17 -0
- package/dist/context/WidgetContext.d.ts +29 -0
- package/dist/context/WidgetContext.js +64 -0
- package/dist/icons/CarIcon.d.ts +4 -0
- package/dist/icons/CarIcon.js +3 -0
- package/dist/icons/CloseIcon.d.ts +7 -0
- package/dist/icons/CloseIcon.js +3 -0
- package/dist/icons/FlightIcon.d.ts +4 -0
- package/dist/icons/FlightIcon.js +3 -0
- package/dist/icons/HotelIcon.d.ts +4 -0
- package/dist/icons/HotelIcon.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +3 -0
- package/dist/locales/widgetTranslations.d.ts +1 -0
- package/dist/locales/widgetTranslations.js +732 -0
- package/dist/modules/hooks/useAirportSearch.d.ts +21 -0
- package/dist/modules/hooks/useAirportSearch.js +48 -0
- package/dist/modules/hooks/useCarOffers.d.ts +7 -0
- package/dist/modules/hooks/useCarOffers.js +47 -0
- package/dist/modules/hooks/useCountriesByCodes.d.ts +4 -0
- package/dist/modules/hooks/useCountriesByCodes.js +60 -0
- package/dist/modules/hooks/useCountriesSearch.d.ts +11 -0
- package/dist/modules/hooks/useCountriesSearch.js +55 -0
- package/dist/modules/hooks/useDefaultHotelLocation.d.ts +7 -0
- package/dist/modules/hooks/useDefaultHotelLocation.js +44 -0
- package/dist/modules/hooks/useDetectNationality.d.ts +1 -0
- package/dist/modules/hooks/useDetectNationality.js +20 -0
- package/dist/modules/hooks/useFlightSearch.d.ts +20 -0
- package/dist/modules/hooks/useFlightSearch.js +243 -0
- package/dist/modules/hooks/useHotelDetails.d.ts +7 -0
- package/dist/modules/hooks/useHotelDetails.js +80 -0
- package/dist/modules/hooks/useHotelLocations.d.ts +5 -0
- package/dist/modules/hooks/useHotelLocations.js +42 -0
- package/dist/modules/hooks/useHotelMapData.d.ts +12 -0
- package/dist/modules/hooks/useHotelMapData.js +78 -0
- package/dist/modules/hooks/useHotelOffers.d.ts +11 -0
- package/dist/modules/hooks/useHotelOffers.js +122 -0
- package/dist/modules/hooks/useHotelSearchSSE.d.ts +18 -0
- package/dist/modules/hooks/useHotelSearchSSE.js +411 -0
- package/dist/modules/hooks/useSearchExpiration.d.ts +6 -0
- package/dist/modules/hooks/useSearchExpiration.js +35 -0
- package/dist/modules/hooks/useTranslationsHub.d.ts +5 -0
- package/dist/modules/hooks/useTranslationsHub.js +48 -0
- package/dist/modules/navbar/Navbar.d.ts +8 -0
- package/dist/modules/navbar/Navbar.js +69 -0
- package/dist/modules/search-form/AgeSelector.d.ts +9 -0
- package/dist/modules/search-form/AgeSelector.js +109 -0
- package/dist/modules/search-form/CustomSelect.d.ts +14 -0
- package/dist/modules/search-form/CustomSelect.js +94 -0
- package/dist/modules/search-form/FiltersSkeleton.d.ts +6 -0
- package/dist/modules/search-form/FiltersSkeleton.js +35 -0
- package/dist/modules/search-form/NationalityInput.d.ts +18 -0
- package/dist/modules/search-form/NationalityInput.js +172 -0
- package/dist/modules/search-form/Pagination.d.ts +8 -0
- package/dist/modules/search-form/Pagination.js +84 -0
- package/dist/modules/search-form/SearchExpiredPopup.d.ts +10 -0
- package/dist/modules/search-form/SearchExpiredPopup.js +91 -0
- package/dist/modules/search-form/SkeletonWidget.d.ts +2 -0
- package/dist/modules/search-form/SkeletonWidget.js +33 -0
- package/dist/modules/search-form/TimePicker.d.ts +9 -0
- package/dist/modules/search-form/TimePicker.js +182 -0
- package/dist/modules/search-form/cars/CarEmptyState.d.ts +6 -0
- package/dist/modules/search-form/cars/CarEmptyState.js +42 -0
- package/dist/modules/search-form/cars/CarFiltersSkeleton.d.ts +6 -0
- package/dist/modules/search-form/cars/CarFiltersSkeleton.js +35 -0
- package/dist/modules/search-form/cars/CarOffersList.d.ts +43 -0
- package/dist/modules/search-form/cars/CarOffersList.js +214 -0
- package/dist/modules/search-form/cars/CarResults.d.ts +25 -0
- package/dist/modules/search-form/cars/CarResults.js +116 -0
- package/dist/modules/search-form/cars/CarSearchForm.d.ts +19 -0
- package/dist/modules/search-form/cars/CarSearchForm.js +173 -0
- package/dist/modules/search-form/cars/CarSkeleton.d.ts +6 -0
- package/dist/modules/search-form/cars/CarSkeleton.js +46 -0
- package/dist/modules/search-form/cars/CarTopOffersSlider.d.ts +20 -0
- package/dist/modules/search-form/cars/CarTopOffersSlider.js +100 -0
- package/dist/modules/search-form/cars/car-filters/CarFilters.d.ts +8 -0
- package/dist/modules/search-form/cars/car-filters/CarFilters.js +318 -0
- package/dist/modules/search-form/cars/car-filters/FilterSection.d.ts +9 -0
- package/dist/modules/search-form/cars/car-filters/FilterSection.js +30 -0
- package/dist/modules/search-form/cars/car-filters/index.d.ts +6 -0
- package/dist/modules/search-form/cars/car-filters/index.js +3 -0
- package/dist/modules/search-form/cars/car-filters/useCarFilters.d.ts +24 -0
- package/dist/modules/search-form/cars/car-filters/useCarFilters.js +121 -0
- package/dist/modules/search-form/flight/AirlinesSelector.d.ts +8 -0
- package/dist/modules/search-form/flight/AirlinesSelector.js +235 -0
- package/dist/modules/search-form/flight/CabinClassSelector.d.ts +9 -0
- package/dist/modules/search-form/flight/CabinClassSelector.js +63 -0
- package/dist/modules/search-form/flight/DatePicker.d.ts +12 -0
- package/dist/modules/search-form/flight/DatePicker.js +536 -0
- package/dist/modules/search-form/flight/FlightSearchForm.d.ts +18 -0
- package/dist/modules/search-form/flight/FlightSearchForm.js +147 -0
- package/dist/modules/search-form/flight/LocationInput.d.ts +11 -0
- package/dist/modules/search-form/flight/LocationInput.js +294 -0
- package/dist/modules/search-form/flight/PassengersPicker.d.ts +16 -0
- package/dist/modules/search-form/flight/PassengersPicker.js +203 -0
- package/dist/modules/search-form/flight/SwitchLocationBtn.d.ts +9 -0
- package/dist/modules/search-form/flight/SwitchLocationBtn.js +48 -0
- package/dist/modules/search-form/flight/flight-filters/FiltersContent.d.ts +21 -0
- package/dist/modules/search-form/flight/flight-filters/FiltersContent.js +145 -0
- package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.d.ts +11 -0
- package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.js +213 -0
- package/dist/modules/search-form/flight/flight-filters/RangeBlock.d.ts +22 -0
- package/dist/modules/search-form/flight/flight-filters/RangeBlock.js +164 -0
- package/dist/modules/search-form/flight/flight-filters/index.d.ts +1 -0
- package/dist/modules/search-form/flight/flight-filters/index.js +1 -0
- package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.d.ts +16 -0
- package/dist/modules/search-form/flight/flight-results/FlightAdditionalInfo.js +229 -0
- package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.d.ts +15 -0
- package/dist/modules/search-form/flight/flight-results/FlightBaggageToggle.js +110 -0
- package/dist/modules/search-form/flight/flight-results/FlightCard.d.ts +14 -0
- package/dist/modules/search-form/flight/flight-results/FlightCard.js +436 -0
- package/dist/modules/search-form/flight/flight-results/FlightInfo.d.ts +12 -0
- package/dist/modules/search-form/flight/flight-results/FlightInfo.js +48 -0
- package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.d.ts +11 -0
- package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.js +36 -0
- package/dist/modules/search-form/flight/flight-results/FlightResults.d.ts +22 -0
- package/dist/modules/search-form/flight/flight-results/FlightResults.js +109 -0
- package/dist/modules/search-form/flight/flight-results/FlightSegments.d.ts +40 -0
- package/dist/modules/search-form/flight/flight-results/FlightSegments.js +162 -0
- package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.d.ts +6 -0
- package/dist/modules/search-form/flight/flight-results/FlightsSkeleton.js +52 -0
- package/dist/modules/search-form/flight/flight-results/ProvidersLoader.d.ts +9 -0
- package/dist/modules/search-form/flight/flight-results/ProvidersLoader.js +242 -0
- package/dist/modules/search-form/hotel/DatePicker.d.ts +1 -0
- package/dist/modules/search-form/hotel/DatePicker.js +1 -0
- package/dist/modules/search-form/hotel/GuestSelector.d.ts +13 -0
- package/dist/modules/search-form/hotel/GuestSelector.js +272 -0
- package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.d.ts +2 -0
- package/dist/modules/search-form/hotel/GuestSelectorMobilePopup.js +1 -0
- package/dist/modules/search-form/hotel/HotelLocation.d.ts +17 -0
- package/dist/modules/search-form/hotel/HotelLocation.js +178 -0
- package/dist/modules/search-form/hotel/HotelLocationMobilePopup.d.ts +2 -0
- package/dist/modules/search-form/hotel/HotelLocationMobilePopup.js +1 -0
- package/dist/modules/search-form/hotel/HotelResults.d.ts +21 -0
- package/dist/modules/search-form/hotel/HotelResults.js +333 -0
- package/dist/modules/search-form/hotel/HotelSearchForm.d.ts +13 -0
- package/dist/modules/search-form/hotel/HotelSearchForm.js +130 -0
- package/dist/modules/search-form/hotel/HotelsSkeleton.d.ts +6 -0
- package/dist/modules/search-form/hotel/HotelsSkeleton.js +40 -0
- package/dist/modules/search-form/hotel/PassengersPicker.d.ts +2 -0
- package/dist/modules/search-form/hotel/PassengersPicker.js +1 -0
- package/dist/modules/search-form/hotel/components/HotelCard.d.ts +11 -0
- package/dist/modules/search-form/hotel/components/HotelCard.js +180 -0
- package/dist/modules/search-form/hotel/components/HotelEmptyState.d.ts +6 -0
- package/dist/modules/search-form/hotel/components/HotelEmptyState.js +34 -0
- package/dist/modules/search-form/hotel/components/HotelGallery.d.ts +6 -0
- package/dist/modules/search-form/hotel/components/HotelGallery.js +77 -0
- package/dist/modules/search-form/hotel/components/HotelList.d.ts +9 -0
- package/dist/modules/search-form/hotel/components/HotelList.js +12 -0
- package/dist/modules/search-form/hotel/components/HotelMap.d.ts +26 -0
- package/dist/modules/search-form/hotel/components/HotelMap.js +707 -0
- package/dist/modules/search-form/hotel/components/HotelPin.d.ts +7 -0
- package/dist/modules/search-form/hotel/components/HotelPin.js +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.d.ts +29 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.js +339 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.d.ts +3 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersBar.js +2 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.d.ts +2 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersContent.js +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.d.ts +2 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersDynamic.js +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.d.ts +2 -0
- package/dist/modules/search-form/hotel/hotel-filters/HotelFiltersMobilePopup.js +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.d.ts +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/RangeBlock.js +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/index.d.ts +1 -0
- package/dist/modules/search-form/hotel/hotel-filters/index.js +1 -0
- package/dist/modules/tabs/FlightsTab.d.ts +1 -0
- package/dist/modules/tabs/FlightsTab.js +1 -0
- package/dist/modules/tabs/HotelsTab.d.ts +1 -0
- package/dist/modules/tabs/HotelsTab.js +1 -0
- package/dist/store/index.d.ts +22 -0
- package/dist/store/index.js +19 -0
- package/dist/theme/colors.d.ts +56 -0
- package/dist/theme/colors.js +56 -0
- package/dist/theme/formStyles.d.ts +76 -0
- package/dist/theme/formStyles.js +78 -0
- package/dist/utils/applyWidgetColors.d.ts +1 -0
- package/dist/utils/applyWidgetColors.js +2 -0
- package/dist/utils/dateTime.d.ts +2 -0
- package/dist/utils/dateTime.js +23 -0
- package/dist/utils/fetchSSE.d.ts +23 -0
- package/dist/utils/fetchSSE.js +104 -0
- package/dist/utils/getToken.d.ts +3 -0
- package/dist/utils/getToken.js +12 -0
- package/package.json +64 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { useApiToken } from "../../utils/getToken";
|
|
3
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
4
|
+
const CACHE_KEY = "hotelDetailsCache";
|
|
5
|
+
const CACHE_TTL = 1000 * 60 * 60;
|
|
6
|
+
export const useHotelDetails = (searchParams) => {
|
|
7
|
+
const token = useApiToken();
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [hotelData, setHotelData] = useState(null);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const readCache = async () => {
|
|
12
|
+
try {
|
|
13
|
+
const raw = await AsyncStorage.getItem(CACHE_KEY);
|
|
14
|
+
const cache = JSON.parse(raw || "{}");
|
|
15
|
+
for (const key in cache) {
|
|
16
|
+
if (Date.now() - cache[key]._ts > CACHE_TTL) {
|
|
17
|
+
delete cache[key];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return cache;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const writeCache = async (cache) => AsyncStorage.setItem(CACHE_KEY, JSON.stringify(cache));
|
|
27
|
+
const clearCache = () => AsyncStorage.removeItem(CACHE_KEY);
|
|
28
|
+
const fetchHotelDetails = useCallback(async (hotelId) => {
|
|
29
|
+
if (!searchParams)
|
|
30
|
+
return null;
|
|
31
|
+
setLoading(true);
|
|
32
|
+
setError(null);
|
|
33
|
+
const cache = await readCache();
|
|
34
|
+
const cachedEntry = cache[hotelId];
|
|
35
|
+
if (cachedEntry && Object.keys(cachedEntry).length > 1) {
|
|
36
|
+
const cached = cachedEntry;
|
|
37
|
+
console.log("[useHotelDetails] cached data for", hotelId, ":", JSON.stringify(cached, null, 2));
|
|
38
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
39
|
+
setHotelData(cached);
|
|
40
|
+
setLoading(false);
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch("https://api.travel-code.com/v1/search/hotels/list", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
Authorization: `Bearer ${token}`,
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
...searchParams,
|
|
52
|
+
id: [hotelId],
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
console.log("[useHotelDetails] /list response:", JSON.stringify(data, null, 2));
|
|
57
|
+
const hotel = data?.hotels?.[0] || data?.data?.[0] || null;
|
|
58
|
+
if (!hotel) {
|
|
59
|
+
setError("Failed to load hotel data");
|
|
60
|
+
setHotelData(null);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
setHotelData(hotel);
|
|
64
|
+
await writeCache({
|
|
65
|
+
...cache,
|
|
66
|
+
[hotelId]: { ...hotel, _ts: Date.now() },
|
|
67
|
+
});
|
|
68
|
+
return hotel;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error("Hotel details error:", err);
|
|
72
|
+
setError("Request error");
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
}, [searchParams, token]);
|
|
79
|
+
return { hotelData, loading, error, fetchHotelDetails, clearCache };
|
|
80
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { useApiToken } from "../../utils/getToken";
|
|
3
|
+
const API_URL = "https://api.travel-code.com/v1/data/hotel-locations";
|
|
4
|
+
export function useHotelLocations(query) {
|
|
5
|
+
const token = useApiToken();
|
|
6
|
+
const [results, setResults] = useState([]);
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
const fetchLocations = async (term) => {
|
|
9
|
+
try {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
const res = await fetch(`${API_URL}?search=${encodeURIComponent(term)}`, {
|
|
12
|
+
headers: {
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
Authorization: `Bearer ${token}`,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
throw new Error(`Ошибка ${res.status}`);
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
setResults(data.items || []);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.warn("⚠️ Ошибка загрузки локаций:", err);
|
|
24
|
+
setResults([]);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
setLoading(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!query || query.length < 2) {
|
|
32
|
+
setResults([]);
|
|
33
|
+
setLoading(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const debounced = setTimeout(() => {
|
|
37
|
+
fetchLocations(query);
|
|
38
|
+
}, 400);
|
|
39
|
+
return () => clearTimeout(debounced);
|
|
40
|
+
}, [query]);
|
|
41
|
+
return { results, loading, fetchLocations };
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type HotelPin = {
|
|
2
|
+
id: string;
|
|
3
|
+
latitude: number;
|
|
4
|
+
longitude: number;
|
|
5
|
+
total: number | null;
|
|
6
|
+
soldOut: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const useHotelMapData: (params: Record<string, any>, shouldFetch?: boolean) => {
|
|
9
|
+
pins: HotelPin[];
|
|
10
|
+
loading: boolean;
|
|
11
|
+
fetchFailed: boolean;
|
|
12
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import { useApiToken } from "../../utils/getToken";
|
|
3
|
+
const API_BASE = "https://api.travel-code.com/v1/search/hotels/map";
|
|
4
|
+
export const useHotelMapData = (params, shouldFetch = true) => {
|
|
5
|
+
const token = useApiToken();
|
|
6
|
+
const [pins, setPins] = useState([]);
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
const [fetchFailed, setFetchFailed] = useState(false);
|
|
9
|
+
const lastParamsRef = useRef("");
|
|
10
|
+
const fetchPins = useCallback(async () => {
|
|
11
|
+
console.log("%c[useHotelMapData] 🚀 СТАРТ ЗАПРОСА", "color:#fff;background:#43a047;padding:3px 6px;border-radius:4px", { params });
|
|
12
|
+
setLoading(true);
|
|
13
|
+
setFetchFailed(false);
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(API_BASE, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Authorization: `Bearer ${token}`,
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify(params),
|
|
22
|
+
});
|
|
23
|
+
console.log("%c[useHotelMapData] 📥 ОТВЕТ ПОЛУЧЕН", "color:#fff;background:#5e35b1;padding:3px 6px;border-radius:4px", { status: response.status });
|
|
24
|
+
if (!response.ok)
|
|
25
|
+
throw new Error(`Ошибка ${response.status}`);
|
|
26
|
+
const result = await response.json();
|
|
27
|
+
console.log("%c[useHotelMapData] 🟢 pins:", "color:#fff;background:#00897b;padding:3px 6px;border-radius:4px", result?.data?.length);
|
|
28
|
+
if (result?.data?.length) {
|
|
29
|
+
const formatted = result.data
|
|
30
|
+
.filter((item) => {
|
|
31
|
+
const lat = parseFloat(item.latitude);
|
|
32
|
+
const lng = parseFloat(item.longitude);
|
|
33
|
+
return lat && lng && lat !== 0 && lng !== 0;
|
|
34
|
+
})
|
|
35
|
+
.map((item) => ({
|
|
36
|
+
id: String(item.propertyId),
|
|
37
|
+
latitude: parseFloat(item.latitude),
|
|
38
|
+
longitude: parseFloat(item.longitude),
|
|
39
|
+
total: typeof item.total === "number" ? item.total : null,
|
|
40
|
+
soldOut: item.soldOut === true ||
|
|
41
|
+
item.total === null,
|
|
42
|
+
}));
|
|
43
|
+
setPins(formatted);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log("%c[useHotelMapData] ⚠️ Нет данных", "color:#fb8c00");
|
|
47
|
+
setPins([]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error("%c[useHotelMapData] 🔥 ОШИБКА", "background:red;color:white", err);
|
|
52
|
+
setFetchFailed(true);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
console.log("%c[useHotelMapData] ⏹ ЗАПРОС ЗАВЕРШЕН", "color:#fff;background:#6d4c41;padding:3px 6px;border-radius:4px");
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [params, token]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
console.log("%c[useHotelMapData] EFFECT TRIGGERED", "color:#fff;background:#1e88e5;padding:3px 6px;border-radius:4px", { params, shouldFetch });
|
|
61
|
+
if (!shouldFetch) {
|
|
62
|
+
console.log("%c[useHotelMapData] ❌ shouldFetch = false → запрос НЕ отправлен", "color:#ef5350");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!params || Object.keys(params).length === 0) {
|
|
66
|
+
console.log("%c[useHotelMapData] ❌ Пустые params → запрос НЕ отправлен", "color:#ef5350");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const key = JSON.stringify(params);
|
|
70
|
+
if (key === lastParamsRef.current) {
|
|
71
|
+
console.log("%c[useHotelMapData] 🔁 Те же params → запрос пропущен", "color:#ffa726");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
lastParamsRef.current = key;
|
|
75
|
+
fetchPins();
|
|
76
|
+
}, [params, shouldFetch, fetchPins]);
|
|
77
|
+
return { pins, loading, fetchFailed };
|
|
78
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const useHotelOffers: () => {
|
|
2
|
+
offers: any[];
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: string | null;
|
|
5
|
+
total: number;
|
|
6
|
+
offset: number;
|
|
7
|
+
limit: number;
|
|
8
|
+
fetchHotelOffers: (params: any) => Promise<void>;
|
|
9
|
+
fetchPage: (page: number, baseParams: Record<string, any>) => Promise<void>;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useState, useRef } from "react";
|
|
2
|
+
import { useApiToken } from "../../utils/getToken";
|
|
3
|
+
const POLL_INTERVAL = 1000;
|
|
4
|
+
const POLL_TIMEOUT = 180000;
|
|
5
|
+
export const useHotelOffers = () => {
|
|
6
|
+
const token = useApiToken();
|
|
7
|
+
const [offers, setOffers] = useState([]);
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const [total, setTotal] = useState(0);
|
|
11
|
+
const [offset, setOffset] = useState(0);
|
|
12
|
+
const [limit, setLimit] = useState(20);
|
|
13
|
+
const pollTimer = useRef(null);
|
|
14
|
+
const applyResult = (data, params) => {
|
|
15
|
+
setOffers(Array.isArray(data?.data) ? data.data : []);
|
|
16
|
+
setTotal(Number(data?.count || 0));
|
|
17
|
+
setOffset(params.offset ?? 0);
|
|
18
|
+
setLimit(params.limit ?? 20);
|
|
19
|
+
};
|
|
20
|
+
const reset = () => {
|
|
21
|
+
if (pollTimer.current) {
|
|
22
|
+
clearTimeout(pollTimer.current);
|
|
23
|
+
}
|
|
24
|
+
setOffers([]);
|
|
25
|
+
setLoading(false);
|
|
26
|
+
setError(null);
|
|
27
|
+
setTotal(0);
|
|
28
|
+
setOffset(0);
|
|
29
|
+
setLimit(20);
|
|
30
|
+
};
|
|
31
|
+
const pollTask = async (taskId, params) => {
|
|
32
|
+
const start = Date.now();
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const check = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const pollBody = {
|
|
37
|
+
...params,
|
|
38
|
+
taskId,
|
|
39
|
+
};
|
|
40
|
+
const response = await fetch("https://api.travel-code.com/v1/search/hotels/task", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
Authorization: `Bearer ${token}`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(pollBody),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
reject(new Error("Ошибка при запросе /task"));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const json = await response.json();
|
|
53
|
+
if (json.status === "done" && json.data) {
|
|
54
|
+
applyResult(json, params);
|
|
55
|
+
resolve();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (json.status === "lost" || json.status === "timeout") {
|
|
59
|
+
reject(new Error("Поиск завершился со статусом lost/timeout"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (Date.now() - start > POLL_TIMEOUT) {
|
|
63
|
+
reject(new Error("Истекло время ожидания taskId"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
pollTimer.current = setTimeout(check, POLL_INTERVAL);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
reject(e);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
check();
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const fetchHotelOffers = async (params) => {
|
|
76
|
+
try {
|
|
77
|
+
setLoading(true);
|
|
78
|
+
setError(null);
|
|
79
|
+
const response = await fetch("https://api.travel-code.com/v1/search/hotels", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
Authorization: `Bearer ${token}`,
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(params),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok)
|
|
88
|
+
throw new Error("Ошибка при запуске поиска отелей");
|
|
89
|
+
const json = await response.json();
|
|
90
|
+
if (json.data) {
|
|
91
|
+
applyResult(json, params);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (json.taskId) {
|
|
95
|
+
await pollTask(json.taskId, params);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw new Error("Неизвестный формат ответа от API");
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
setError(err.message);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const fetchPage = async (page, baseParams) => {
|
|
108
|
+
const newOffset = (page - 1) * limit;
|
|
109
|
+
await fetchHotelOffers({ ...baseParams, offset: newOffset, limit });
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
offers,
|
|
113
|
+
loading,
|
|
114
|
+
error,
|
|
115
|
+
total,
|
|
116
|
+
offset,
|
|
117
|
+
limit,
|
|
118
|
+
fetchHotelOffers,
|
|
119
|
+
fetchPage,
|
|
120
|
+
reset
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function useHotelSearchSSE(): {
|
|
2
|
+
offers: any[];
|
|
3
|
+
allHotels: any[];
|
|
4
|
+
loading: boolean;
|
|
5
|
+
isStreaming: boolean;
|
|
6
|
+
isPageLoading: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
total: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
limit: number;
|
|
11
|
+
cacheKey: string | null;
|
|
12
|
+
streamCompleted: boolean;
|
|
13
|
+
searchParams: Record<string, any> | null;
|
|
14
|
+
startSearch: (params: Record<string, any>) => void;
|
|
15
|
+
filterSearch: (params: Record<string, any>) => void;
|
|
16
|
+
fetchPage: (page: number) => void;
|
|
17
|
+
reset: () => void;
|
|
18
|
+
};
|