@devtravelcode/widget-native 1.0.2 → 1.0.3
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/dist/App.js +8 -5
- package/dist/modules/hooks/useHotelMapData.d.ts +1 -1
- package/dist/modules/hooks/useHotelMapData.js +26 -32
- package/dist/modules/search-form/AgeSelector.js +4 -1
- package/dist/modules/search-form/NationalityInput.js +7 -3
- package/dist/modules/search-form/TimePicker.js +4 -3
- package/dist/modules/search-form/flight/AirlinesSelector.js +7 -3
- package/dist/modules/search-form/flight/DatePicker.js +14 -2
- package/dist/modules/search-form/flight/FlightSearchForm.js +2 -1
- package/dist/modules/search-form/flight/LocationInput.js +6 -2
- package/dist/modules/search-form/flight/PassengersPicker.js +9 -4
- package/dist/modules/search-form/flight/SwitchLocationBtn.js +4 -1
- package/dist/modules/search-form/flight/flight-filters/FiltersContent.d.ts +1 -0
- package/dist/modules/search-form/flight/flight-filters/FiltersContent.js +14 -13
- package/dist/modules/search-form/flight/flight-filters/FlightFiltersDynamic.js +3 -2
- package/dist/modules/search-form/flight/flight-filters/RangeBlock.d.ts +1 -0
- package/dist/modules/search-form/flight/flight-filters/RangeBlock.js +5 -3
- package/dist/modules/search-form/flight/flight-results/FlightCard.js +3 -2
- package/dist/modules/search-form/flight/flight-results/FlightPriceBlock.js +4 -1
- package/dist/modules/search-form/flight/flight-results/ProvidersLoader.js +4 -1
- package/dist/modules/search-form/hotel/GuestSelector.js +14 -9
- package/dist/modules/search-form/hotel/HotelLocation.js +6 -2
- package/dist/modules/search-form/hotel/HotelResults.d.ts +1 -0
- package/dist/modules/search-form/hotel/HotelResults.js +32 -6
- package/dist/modules/search-form/hotel/components/HotelCard.d.ts +1 -0
- package/dist/modules/search-form/hotel/components/HotelCard.js +39 -5
- package/dist/modules/search-form/hotel/components/HotelList.d.ts +1 -0
- package/dist/modules/search-form/hotel/components/HotelList.js +2 -2
- package/dist/modules/search-form/hotel/components/HotelMap.js +40 -9
- package/dist/modules/search-form/hotel/hotel-filters/HotelFilters.js +12 -5
- package/index.ts +7 -0
- package/package.json +2 -2
package/dist/App.js
CHANGED
|
@@ -58,7 +58,10 @@ const App = ({ lang, currency, onFlightSelect, onHotelSelect, onCarSelect }) =>
|
|
|
58
58
|
return t.banner_car ?? defaultTexts.car;
|
|
59
59
|
return "";
|
|
60
60
|
};
|
|
61
|
-
const
|
|
61
|
+
const bannerText = getBannerText();
|
|
62
|
+
const showHeadings = config?.texts?.headings_visible !== false &&
|
|
63
|
+
config?.headings_visible !== false &&
|
|
64
|
+
!!bannerText;
|
|
62
65
|
const { results: flightResults, filtersData, airlinesData, dictionaries, currencySign, loading: flightLoading, isFiltering: flightIsFiltering, isPageLoading: flightIsPageLoading, error: flightError, status: flightStatus, total: flightTotal, limit: flightLimit, offset: flightOffset, sessionId, startSearch: startFlightSearch, applyFilters: applyFlightFilters, fetchPage: fetchFlightPage, reset: resetFlights, } = useFlightSearch();
|
|
63
66
|
const [hasFlightSearched, setHasFlightSearched] = useState(false);
|
|
64
67
|
useEffect(() => {
|
|
@@ -133,14 +136,14 @@ const App = ({ lang, currency, onFlightSelect, onHotelSelect, onCarSelect }) =>
|
|
|
133
136
|
{ color: colors.textHeadings || Colors.white },
|
|
134
137
|
];
|
|
135
138
|
if (activeTab === "hotels") {
|
|
136
|
-
const hotelHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children:
|
|
137
|
-
return (_jsx(View, { style: styles.container, children: _jsx(HotelResults, { offers: hotelOffers, allHotels: hotelAllHotels, loading: hotelLoading, isStreaming: hotelIsStreaming, isPageLoading: hotelIsPageLoading, error: hotelError, translations: translations, lang: appLang, searchParams: hotelSearchParams, total: hotelTotal, offset: hotelOffset, limit: hotelLimit, fetchPage: fetchHotelPage, filterSearch: filterHotelSearch, currency: appCurrency, headerComponent: hotelHeader }) }));
|
|
139
|
+
const hotelHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: bannerText }) })), _jsx(Navbar, { activeTab: activeTab, onTabChange: setActiveTab, translations: translations }), _jsx(HotelSearchForm, { translations: translations, fetchHotelOffers: isPreview ? async () => { } : handleHotelSearch, loading: hotelLoading && !isPreview, onSearchParamsChange: setHotelSearchParams, isPreview: isPreview, expiration: hotelExpiration, hasSearched: hasHotelSearched })] }));
|
|
140
|
+
return (_jsx(View, { style: styles.container, children: _jsx(HotelResults, { offers: hotelOffers, allHotels: hotelAllHotels, loading: hotelLoading, isStreaming: hotelIsStreaming, isPageLoading: hotelIsPageLoading, error: hotelError, translations: translations, lang: appLang, searchParams: hotelSearchParams, total: hotelTotal, offset: hotelOffset, limit: hotelLimit, fetchPage: fetchHotelPage, filterSearch: filterHotelSearch, currency: appCurrency, headerComponent: hotelHeader, streamCompleted: hotelStreamCompleted }) }));
|
|
138
141
|
}
|
|
139
142
|
if (activeTab === "cars") {
|
|
140
|
-
const carHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children:
|
|
143
|
+
const carHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: bannerText }) })), _jsx(Navbar, { activeTab: activeTab, onTabChange: setActiveTab, translations: translations }), _jsx(CarSearchForm, { startSearch: isPreview ? async () => null : handleCarSearch, loading: carLoading && !isPreview, error: carError, onDaysChange: setCarDays, onSearchParamsChange: setCarSearchParams, translations: translations, isPreview: isPreview, expiration: carExpiration, hasSearched: hasCarSearched })] }));
|
|
141
144
|
return (_jsx(View, { style: styles.container, children: _jsx(CarResults, { offers: carOffers, filteredOffers: carFilteredOffers, paginatedOffers: carPaginatedOffers, loading: carLoading, error: carError, isFiltering: carIsFiltering, hasAppliedFilters: carHasAppliedFilters, onFilterChange: () => { }, onFiltersApplied: () => setCarHasAppliedFilters(true), days: carDays, searchParams: carSearchParams, translations: translations, hasCarSearched: hasCarSearched, lang: appLang, currency: appCurrency, currentPage: carCurrentPage, totalPages: carTotalPages, onPageChange: setCarCurrentPage, isPageLoading: false, headerComponent: carHeader }) }));
|
|
142
145
|
}
|
|
143
|
-
const flightHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children:
|
|
146
|
+
const flightHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: bannerText }) })), _jsx(Navbar, { activeTab: activeTab, onTabChange: setActiveTab, translations: translations }), _jsx(FlightSearchForm, { startSearch: isPreview ? async () => null : startFlightSearch, loading: flightLoading && !isPreview, error: flightError, currency: appCurrency, translations: translations, isPreview: isPreview, expiration: flightExpiration, hasSearched: hasFlightSearched }), flightLoading && !flightIsFiltering && (_jsx(ProvidersLoader, { active: flightLoading, type: "flights", translations: translations })), hasFlightSearched && !flightLoading && (flightTotal > 0 || hasFlightFilters) && (_jsx(View, { style: styles.totalCount, children: _jsxs(Text, { style: styles.totalCountText, children: [translations["results_found"], " ", flightTotal, " ", flightTotal === 1 ? translations["flight_singular"] : translations["flight_many"]] }) })), isFlightEmptyAfterSearch && (_jsxs(View, { style: styles.emptyState, children: [_jsx(Text, { style: styles.emptyTitle, children: translations["flights_not_found_title"] || "No flights found" }), _jsx(Text, { style: styles.emptyHint, children: translations["flights_not_found_hint"] || "Try adjusting your search parameters" })] })), isFlightEmptyAfterFilters && (_jsxs(_Fragment, { children: [_jsx(FlightFilters, { filters: filtersData, airlines: airlinesData, sessionId: sessionId, onApplyFilters: applyFlightFilters, translations: translations }), _jsxs(View, { style: styles.emptyState, children: [_jsx(Text, { style: styles.emptyTitle, children: translations["flights_not_found_title"] || "No flights found" }), _jsx(Text, { style: styles.emptyHint, children: translations["flights_not_found_hint"] || "Try adjusting your search parameters" })] })] })), !isFlightEmptyAfterSearch && !isFlightEmptyAfterFilters && (_jsx(_Fragment, { children: flightLoading && !flightIsFiltering ? (_jsx(FlightsSkeleton, { translations: translations })) : ((filtersData || flightResults.length > 0) && (_jsx(FlightFilters, { filters: filtersData, airlines: airlinesData, sessionId: sessionId, onApplyFilters: applyFlightFilters, translations: translations }))) }))] }));
|
|
144
147
|
return (_jsx(View, { style: styles.container, children: _jsx(FlightResults, { results: flightResults, loading: flightLoading, isFiltering: flightIsFiltering, dictionaries: dictionaries, currencySign: currencySign, airlines: airlinesData, sessionId: sessionId, filtersData: filtersData, total: flightTotal, limit: flightLimit, offset: flightOffset, isPageLoading: flightIsPageLoading, fetchPage: fetchFlightPage, translations: translations, lang: appLang, onFlightSelect: onFlightSelect, headerComponent: flightHeader }) }));
|
|
145
148
|
};
|
|
146
149
|
const styles = StyleSheet.create({
|
|
@@ -5,7 +5,7 @@ export type HotelPin = {
|
|
|
5
5
|
total: number | null;
|
|
6
6
|
soldOut: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare const useHotelMapData: (params: Record<string, any
|
|
8
|
+
export declare const useHotelMapData: (params: Record<string, any> | null, shouldFetch?: boolean) => {
|
|
9
9
|
pins: HotelPin[];
|
|
10
10
|
loading: boolean;
|
|
11
11
|
fetchFailed: boolean;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
2
|
import { useApiToken } from "../../utils/getToken";
|
|
3
3
|
const API_BASE = "https://api.travel-code.com/v1/search/hotels/map";
|
|
4
|
-
export const useHotelMapData = (params, shouldFetch =
|
|
4
|
+
export const useHotelMapData = (params, shouldFetch = false) => {
|
|
5
5
|
const token = useApiToken();
|
|
6
6
|
const [pins, setPins] = useState([]);
|
|
7
7
|
const [loading, setLoading] = useState(false);
|
|
8
8
|
const [fetchFailed, setFetchFailed] = useState(false);
|
|
9
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 });
|
|
10
|
+
const fetchPins = useCallback(async (searchParams) => {
|
|
12
11
|
setLoading(true);
|
|
13
12
|
setFetchFailed(false);
|
|
14
13
|
try {
|
|
@@ -18,61 +17,56 @@ export const useHotelMapData = (params, shouldFetch = true) => {
|
|
|
18
17
|
"Content-Type": "application/json",
|
|
19
18
|
Authorization: `Bearer ${token}`,
|
|
20
19
|
},
|
|
21
|
-
body: JSON.stringify(
|
|
20
|
+
body: JSON.stringify(searchParams),
|
|
22
21
|
});
|
|
23
|
-
console.log("%c[useHotelMapData] 📥 ОТВЕТ ПОЛУЧЕН", "color:#fff;background:#5e35b1;padding:3px 6px;border-radius:4px", { status: response.status });
|
|
24
22
|
if (!response.ok)
|
|
25
|
-
throw new Error(
|
|
23
|
+
throw new Error(`Error ${response.status}`);
|
|
26
24
|
const result = await response.json();
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
const formatted =
|
|
25
|
+
const hotels = Array.isArray(result?.data) ? result.data : Array.isArray(result?.hotels) ? result.hotels : [];
|
|
26
|
+
if (hotels.length) {
|
|
27
|
+
const formatted = hotels
|
|
30
28
|
.filter((item) => {
|
|
31
29
|
const lat = parseFloat(item.latitude);
|
|
32
30
|
const lng = parseFloat(item.longitude);
|
|
33
31
|
return lat && lng && lat !== 0 && lng !== 0;
|
|
34
32
|
})
|
|
35
|
-
.map((item) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
.map((item) => {
|
|
34
|
+
const price = item.total ?? item.price ?? null;
|
|
35
|
+
return {
|
|
36
|
+
id: String(item.propertyId || item.id),
|
|
37
|
+
latitude: parseFloat(item.latitude),
|
|
38
|
+
longitude: parseFloat(item.longitude),
|
|
39
|
+
total: typeof price === "number" ? price : null,
|
|
40
|
+
soldOut: item.soldOut === true || price == null,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
43
|
setPins(formatted);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
|
-
console.log("%c[useHotelMapData] ⚠️ Нет данных", "color:#fb8c00");
|
|
47
46
|
setPins([]);
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
|
-
catch
|
|
51
|
-
console.error("%c[useHotelMapData] 🔥 ОШИБКА", "background:red;color:white", err);
|
|
49
|
+
catch {
|
|
52
50
|
setFetchFailed(true);
|
|
53
51
|
}
|
|
54
52
|
finally {
|
|
55
|
-
console.log("%c[useHotelMapData] ⏹ ЗАПРОС ЗАВЕРШЕН", "color:#fff;background:#6d4c41;padding:3px 6px;border-radius:4px");
|
|
56
53
|
setLoading(false);
|
|
57
54
|
}
|
|
58
|
-
}, [
|
|
55
|
+
}, [token]);
|
|
59
56
|
useEffect(() => {
|
|
60
|
-
console.log("%c[useHotelMapData] EFFECT TRIGGERED", "color:#fff;background:#1e88e5;padding:3px 6px;border-radius:4px", { params, shouldFetch });
|
|
61
57
|
if (!shouldFetch) {
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
setPins([]);
|
|
59
|
+
lastParamsRef.current = "";
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
}, [shouldFetch]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!shouldFetch || !params || Object.keys(params).length === 0)
|
|
67
64
|
return;
|
|
68
|
-
}
|
|
69
65
|
const key = JSON.stringify(params);
|
|
70
|
-
if (key === lastParamsRef.current)
|
|
71
|
-
console.log("%c[useHotelMapData] 🔁 Те же params → запрос пропущен", "color:#ffa726");
|
|
66
|
+
if (key === lastParamsRef.current)
|
|
72
67
|
return;
|
|
73
|
-
}
|
|
74
68
|
lastParamsRef.current = key;
|
|
75
|
-
fetchPins();
|
|
69
|
+
fetchPins(params);
|
|
76
70
|
}, [params, shouldFetch, fetchPins]);
|
|
77
71
|
return { pins, loading, fetchFailed };
|
|
78
72
|
};
|
|
@@ -6,6 +6,7 @@ import Svg, { Path } from "react-native-svg";
|
|
|
6
6
|
import { Colors, Spacing, FontSize } from "../../theme/colors";
|
|
7
7
|
import { CloseIcon } from "../../store";
|
|
8
8
|
import { formStyles } from "../../theme/formStyles";
|
|
9
|
+
import { useWidget } from "../../context/WidgetContext";
|
|
9
10
|
const ChevronDown = () => (_jsx(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: _jsx(Path, { d: "M5 8L10 12.5L15 8", stroke: Colors.primary, strokeWidth: 2, strokeLinecap: "round" }) }));
|
|
10
11
|
const AGE_OPTIONS = [
|
|
11
12
|
...Array.from({ length: 12 }, (_, i) => ({
|
|
@@ -20,6 +21,8 @@ const AGE_OPTIONS = [
|
|
|
20
21
|
{ id: "80+", label: "80+" },
|
|
21
22
|
];
|
|
22
23
|
const AgeSelector = ({ label, value, onChange, translations, }) => {
|
|
24
|
+
const { colors } = useWidget();
|
|
25
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
23
26
|
const insets = useSafeAreaInsets();
|
|
24
27
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
25
28
|
const [displayText, setDisplayText] = useState("");
|
|
@@ -40,7 +43,7 @@ const AgeSelector = ({ label, value, onChange, translations, }) => {
|
|
|
40
43
|
const isActive = item.id === value;
|
|
41
44
|
return (_jsxs(TouchableOpacity, { style: [styles.optionItem, isActive && styles.optionItemActive], onPress: () => handleSelect(item), activeOpacity: 0.7, children: [_jsx(Text, { style: [styles.optionText, isActive && styles.optionTextActive], children: item.label }), isActive && _jsx(Text, { style: styles.checkMark, children: "\u2713" })] }));
|
|
42
45
|
};
|
|
43
|
-
return (_jsxs(View, { children: [_jsx(View, { style: styles.inputContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => setIsModalOpen(true), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations["age_selector_label"] || label }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsx(Text, { style: [
|
|
46
|
+
return (_jsxs(View, { children: [_jsx(View, { style: styles.inputContainer, children: _jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: () => setIsModalOpen(true), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations["age_selector_label"] || label }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsx(Text, { style: [
|
|
44
47
|
formStyles.inputTriggerText,
|
|
45
48
|
!displayText && formStyles.inputTriggerPlaceholder,
|
|
46
49
|
], children: displayText ||
|
|
@@ -6,10 +6,11 @@ import Svg, { Circle, Path } from "react-native-svg";
|
|
|
6
6
|
import { SvgUri } from "react-native-svg";
|
|
7
7
|
import { useCountriesSearch } from "../hooks/useCountriesSearch";
|
|
8
8
|
import { useCountriesByCodes } from "../hooks/useCountriesByCodes";
|
|
9
|
+
import { useWidget } from "../../context/WidgetContext";
|
|
9
10
|
import { Colors, Spacing, FontSize } from "../../theme/colors";
|
|
10
11
|
import { CloseIcon } from "../../store";
|
|
11
12
|
import { formStyles } from "../../theme/formStyles";
|
|
12
|
-
const SearchIcon = () => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke:
|
|
13
|
+
const SearchIcon = ({ color = Colors.primary }) => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke: color, strokeWidth: 2 }), _jsx(Path, { d: "M12 11L17.2857 16.0902", stroke: color, strokeWidth: 2, strokeLinecap: "round" })] }));
|
|
13
14
|
const FLAG_BASE = "https://cdn.travel-code.com/images/remaster/flags";
|
|
14
15
|
const POPULAR_FALLBACK = [
|
|
15
16
|
{ code: "PL", title: "\u041F\u043E\u043B\u044C\u0448\u0430", titleEn: "Poland", flag: `${FLAG_BASE}/PL.svg` },
|
|
@@ -19,6 +20,9 @@ const POPULAR_FALLBACK = [
|
|
|
19
20
|
{ code: "TR", title: "\u0422\u0443\u0440\u0446\u0438\u044F", titleEn: "Turkey", flag: `${FLAG_BASE}/TR.svg` },
|
|
20
21
|
];
|
|
21
22
|
const NationalityInput = ({ label, value, onChange, translations, defaultNationality, defaultNationalities, lang = "ru", }) => {
|
|
23
|
+
const { colors } = useWidget();
|
|
24
|
+
const btnColor = colors.primary || Colors.primary;
|
|
25
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
22
26
|
const insets = useSafeAreaInsets();
|
|
23
27
|
const [query, setQuery] = useState("");
|
|
24
28
|
const [selectedCountry, setSelectedCountry] = useState(null);
|
|
@@ -78,10 +82,10 @@ const NationalityInput = ({ label, value, onChange, translations, defaultNationa
|
|
|
78
82
|
const isActive = query.trim().toLowerCase() === countryLabel.toLowerCase();
|
|
79
83
|
return (_jsxs(TouchableOpacity, { style: [styles.countryItem, isActive && styles.countryItemActive], onPress: () => handleSelect(item), activeOpacity: 0.7, children: [_jsx(View, { style: styles.flagContainer, children: _jsx(SvgUri, { uri: item.flag || `${FLAG_BASE}/${item.code}.svg`, width: 24, height: 18 }) }), _jsxs(View, { style: styles.countryInfo, children: [_jsx(Text, { style: styles.countryName, children: countryLabel }), _jsx(Text, { style: styles.countryCode, children: item.code })] })] }));
|
|
80
84
|
};
|
|
81
|
-
return (_jsxs(View, { children: [_jsx(View, { style: styles.inputContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: openModal, activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: label }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsx(Text, { style: [
|
|
85
|
+
return (_jsxs(View, { children: [_jsx(View, { style: styles.inputContainer, children: _jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: openModal, activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: label }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsx(Text, { style: [
|
|
82
86
|
formStyles.inputTriggerText,
|
|
83
87
|
!query && formStyles.inputTriggerPlaceholder,
|
|
84
|
-
], numberOfLines: 1, children: query || label }), selectedCountry?.code && (_jsx(Text, { style: formStyles.inputTriggerCode, children: selectedCountry.code }))] })] }) }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, {}), _jsx(TextInput, { style: styles.searchInput, value: modalQuery, onChangeText: (val) => {
|
|
88
|
+
], numberOfLines: 1, children: query || label }), selectedCountry?.code && (_jsx(Text, { style: formStyles.inputTriggerCode, children: selectedCountry.code }))] })] }) }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, { color: btnColor }), _jsx(TextInput, { style: styles.searchInput, value: modalQuery, onChangeText: (val) => {
|
|
85
89
|
setModalQuery(val);
|
|
86
90
|
if (!val.trim()) {
|
|
87
91
|
setSelectedCountry(null);
|
|
@@ -10,7 +10,8 @@ const TimePicker = ({ value, onChange, lang = "ru", translations, }) => {
|
|
|
10
10
|
const [hours, setHours] = useState(10);
|
|
11
11
|
const [minutes, setMinutes] = useState(0);
|
|
12
12
|
const [period, setPeriod] = useState("AM");
|
|
13
|
-
const { dateTimeFormat } = useWidget();
|
|
13
|
+
const { dateTimeFormat, colors } = useWidget();
|
|
14
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
14
15
|
const to24h = (h12, p) => {
|
|
15
16
|
if (p === "AM")
|
|
16
17
|
return h12 === 12 ? 0 : h12;
|
|
@@ -95,8 +96,8 @@ const TimePicker = ({ value, onChange, lang = "ru", translations, }) => {
|
|
|
95
96
|
setMinutes(m);
|
|
96
97
|
applyTime(h, m);
|
|
97
98
|
};
|
|
98
|
-
return (_jsxs(View, { children: [_jsxs(View, { style: styles.fieldsRow, children: [_jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => openPicker("from"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_pickup_label ||
|
|
99
|
-
(lang === "en" ? "Pick-up time" : "\u0412\u0440\u0435\u043C\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F") }), _jsx(Text, { style: styles.fieldValue, children: formatInputTime(value[0]) })] }) }), _jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => openPicker("to"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_dropoff_label ||
|
|
99
|
+
return (_jsxs(View, { children: [_jsxs(View, { style: styles.fieldsRow, children: [_jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: () => openPicker("from"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_pickup_label ||
|
|
100
|
+
(lang === "en" ? "Pick-up time" : "\u0412\u0440\u0435\u043C\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F") }), _jsx(Text, { style: styles.fieldValue, children: formatInputTime(value[0]) })] }) }), _jsx(View, { style: styles.fieldContainer, children: _jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: () => openPicker("to"), activeOpacity: 0.7, children: [_jsx(Text, { style: formStyles.label, children: translations.timepicker_dropoff_label ||
|
|
100
101
|
(lang === "en" ? "Drop-off time" : "\u0412\u0440\u0435\u043C\u044F \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430") }), _jsx(Text, { style: styles.fieldValue, children: formatInputTime(value[1]) })] }) })] }), _jsx(Modal, { visible: isOpen, transparent: true, animationType: "fade", onRequestClose: () => setIsOpen(false), children: _jsx(TouchableOpacity, { style: styles.overlay, activeOpacity: 1, onPress: () => setIsOpen(false), children: _jsxs(View, { style: styles.pickerContainer, onStartShouldSetResponder: () => true, children: [_jsxs(View, { style: styles.pickerContent, children: [_jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustHours(1), children: _jsx(Text, { style: styles.arrowText, children: "\u25B2" }) }), _jsx(Text, { style: styles.timeValue, children: String(hours).padStart(2, "0") }), _jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustHours(-1), children: _jsx(Text, { style: styles.arrowText, children: "\u25BC" }) })] }), _jsx(Text, { style: styles.timeSeparator, children: ":" }), _jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustMinutes(15), children: _jsx(Text, { style: styles.arrowText, children: "\u25B2" }) }), _jsx(Text, { style: styles.timeValue, children: String(minutes).padStart(2, "0") }), _jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => adjustMinutes(-15), children: _jsx(Text, { style: styles.arrowText, children: "\u25BC" }) })] }), dateTimeFormat === "usa" && (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.timeSeparatorSpace }), _jsxs(View, { style: styles.timeColumn, children: [_jsx(TouchableOpacity, { style: styles.arrowButton, onPress: () => {
|
|
101
102
|
const p = period === "AM" ? "PM" : "AM";
|
|
102
103
|
setPeriod(p);
|
|
@@ -4,6 +4,7 @@ import { View, Text, TextInput, TouchableOpacity, Modal, FlatList, StyleSheet, A
|
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
5
|
import Svg, { Circle, Path } from "react-native-svg";
|
|
6
6
|
import { useApiToken } from "../../../utils/getToken";
|
|
7
|
+
import { useWidget } from "../../../context/WidgetContext";
|
|
7
8
|
import { Colors, Spacing, BorderRadius, FontSize } from "../../../theme/colors";
|
|
8
9
|
import { CloseIcon } from "../../../store";
|
|
9
10
|
import { formStyles } from "../../../theme/formStyles";
|
|
@@ -16,8 +17,11 @@ const POPULAR_AIRLINES = [
|
|
|
16
17
|
{ id: 6, code: "W6", title: "Wizz Air" },
|
|
17
18
|
];
|
|
18
19
|
const API_URL = "https://api.travel-code.com/v1/data/airlines";
|
|
19
|
-
const SearchIcon = () => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke:
|
|
20
|
+
const SearchIcon = ({ color = Colors.primary }) => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke: color, strokeWidth: 2 }), _jsx(Path, { d: "M12 11L17.2857 16.0902", stroke: color, strokeWidth: 2, strokeLinecap: "round" })] }));
|
|
20
21
|
const AirlinesSelector = ({ value, onChange, translations, }) => {
|
|
22
|
+
const { colors } = useWidget();
|
|
23
|
+
const btnColor = colors.primary || Colors.primary;
|
|
24
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
21
25
|
const token = useApiToken();
|
|
22
26
|
const insets = useSafeAreaInsets();
|
|
23
27
|
const [query, setQuery] = useState("");
|
|
@@ -82,11 +86,11 @@ const AirlinesSelector = ({ value, onChange, translations, }) => {
|
|
|
82
86
|
isSelected && styles.checkboxSelected,
|
|
83
87
|
], children: isSelected && _jsx(Text, { style: styles.checkmark, children: "\u2713" }) })] }) }));
|
|
84
88
|
};
|
|
85
|
-
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => setIsModalOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: translations["airlines_label"] || "Airlines" }), value.length > 0 ? (_jsx(View, { style: styles.chipsRow, children: value.map((code) => {
|
|
89
|
+
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: () => setIsModalOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: translations["airlines_label"] || "Airlines" }), value.length > 0 ? (_jsx(View, { style: styles.chipsRow, children: value.map((code) => {
|
|
86
90
|
const airline = POPULAR_AIRLINES.find((a) => a.code === code) ||
|
|
87
91
|
airlines.find((a) => a.code === code);
|
|
88
92
|
return (_jsxs(View, { style: styles.chip, children: [_jsx(Text, { style: styles.chipText, children: airline?.title || code }), _jsx(TouchableOpacity, { onPress: () => toggleAirline(code), hitSlop: { top: 4, bottom: 4, left: 4, right: 4 }, children: _jsx(Text, { style: styles.chipRemove, children: "x" }) })] }, code));
|
|
89
|
-
}) })) : (_jsx(Text, { style: formStyles.inputTriggerPlaceholder, children: translations["all_airlines_placeholder"] || "All airlines" }))] }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, {}), _jsx(TextInput, { autoFocus: true, style: styles.searchInput, value: query, onChangeText: setQuery, placeholder: translations["airlines_popup_placeholder"] ||
|
|
93
|
+
}) })) : (_jsx(Text, { style: formStyles.inputTriggerPlaceholder, children: translations["all_airlines_placeholder"] || "All airlines" }))] }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, { color: btnColor }), _jsx(TextInput, { autoFocus: true, style: styles.searchInput, value: query, onChangeText: setQuery, placeholder: translations["airlines_popup_placeholder"] ||
|
|
90
94
|
"Search airlines", placeholderTextColor: Colors.textTertiary, returnKeyType: "search" })] }), selectedAirlines.length > 0 && (_jsx(View, { style: styles.selectedChipsContainer, children: selectedAirlines.map((airline) => (_jsxs(View, { style: styles.chip, children: [_jsx(Text, { style: styles.chipText, children: airline.title }), _jsx(TouchableOpacity, { onPress: () => toggleAirline(airline.code), hitSlop: { top: 4, bottom: 4, left: 4, right: 4 }, children: _jsx(Text, { style: styles.chipRemove, children: "x" }) })] }, airline.code))) })), _jsxs(View, { style: styles.modalContent, children: [loading && (_jsxs(View, { style: styles.loadingContainer, children: [_jsx(ActivityIndicator, { size: "small", color: Colors.primary }), _jsx(Text, { style: styles.loadingText, children: translations["airlines_popup_loading"] || "Loading..." })] })), !loading &&
|
|
91
95
|
!showPopular &&
|
|
92
96
|
airlines.length === 0 &&
|
|
@@ -14,7 +14,9 @@ const DAY_SIZE = Math.floor((SCREEN_WIDTH - Spacing.lg * 2) / 7);
|
|
|
14
14
|
const ChevronLeft = ({ color = Colors.textTertiary }) => (_jsx(Svg, { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", children: _jsx(Path, { d: "M4 7.5 9.5 2l1.25 1.25L6 8l4.75 4.75L9.5 14 4 8.5z", fill: color }) }));
|
|
15
15
|
const ChevronRight = ({ color = Colors.textTertiary }) => (_jsx(Svg, { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", children: _jsx(Path, { d: "M12 8.5 6.5 14l-1.25-1.25L10 8 5.25 3.25 6.5 2 12 7.5z", fill: color }) }));
|
|
16
16
|
const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mode = "flight", }) => {
|
|
17
|
-
const { dateTimeFormat } = useWidget();
|
|
17
|
+
const { dateTimeFormat, colors } = useWidget();
|
|
18
|
+
const btnColor = colors.primary || Colors.primary;
|
|
19
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
18
20
|
const insets = useSafeAreaInsets();
|
|
19
21
|
const [isOpen, setIsOpen] = useState(false);
|
|
20
22
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
|
@@ -162,6 +164,7 @@ const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mod
|
|
|
162
164
|
return (_jsx(TouchableOpacity, { disabled: isDisabled, style: [
|
|
163
165
|
styles.monthBtn,
|
|
164
166
|
isActive && styles.monthBtnActive,
|
|
167
|
+
isActive && { backgroundColor: btnColor, borderColor: btnColor },
|
|
165
168
|
isDisabled && styles.monthBtnDisabled,
|
|
166
169
|
], onPress: () => {
|
|
167
170
|
if (isDisabled)
|
|
@@ -203,11 +206,13 @@ const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mod
|
|
|
203
206
|
dayCells.push(_jsx(TouchableOpacity, { disabled: !!isDisabled, style: [
|
|
204
207
|
styles.dayCell,
|
|
205
208
|
isInRange && !isSelectedStart && !isSelectedEnd && styles.dayInRange,
|
|
209
|
+
isInRange && !isSelectedStart && !isSelectedEnd && { backgroundColor: btnColor + '1A' },
|
|
206
210
|
isSelectedStart && styles.daySelected,
|
|
207
211
|
isSelectedEnd && styles.daySelected,
|
|
208
212
|
isOneDay && styles.dayOneDay,
|
|
209
213
|
isSelectedStart && selectedEnd && styles.daySelectedStart,
|
|
210
214
|
isSelectedEnd && selectedStart && styles.daySelectedEnd,
|
|
215
|
+
(isSelectedStart || isSelectedEnd || isOneDay) && { backgroundColor: btnColor },
|
|
211
216
|
], onPress: () => !isDisabled && handleDateClick(cellDate), activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
212
217
|
styles.dayText,
|
|
213
218
|
isDisabled && styles.dayTextDisabled,
|
|
@@ -226,10 +231,14 @@ const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mod
|
|
|
226
231
|
};
|
|
227
232
|
return (_jsxs(_Fragment, { children: [_jsxs(View, { style: styles.dateInputsWrapper, children: [_jsxs(View, { style: styles.dateInputsRow, children: [_jsxs(TouchableOpacity, { style: [
|
|
228
233
|
styles.dateField,
|
|
234
|
+
{ backgroundColor: inputBg },
|
|
229
235
|
activeField === "departure" && styles.dateFieldActive,
|
|
236
|
+
activeField === "departure" && { backgroundColor: btnColor + '1A' },
|
|
230
237
|
], onPress: () => openPicker("departure"), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: depLabelFinal }), _jsx(Text, { style: [styles.dateFieldValue, !value[0] && styles.dateFieldPlaceholder], children: value[0] ? formatDateDisplay(value[0]) : depPlaceholder })] }), _jsxs(TouchableOpacity, { style: [
|
|
231
238
|
styles.dateField,
|
|
239
|
+
{ backgroundColor: inputBg },
|
|
232
240
|
activeField === "return" && styles.dateFieldActive,
|
|
241
|
+
activeField === "return" && { backgroundColor: btnColor + '1A' },
|
|
233
242
|
!value[0] && styles.dateFieldDisabled,
|
|
234
243
|
oneWay && styles.dateFieldSoftDisabled,
|
|
235
244
|
], onPress: () => openPicker("return"), activeOpacity: value[0] ? 0.8 : 1, children: [_jsx(Text, { style: formStyles.label, children: retLabelFinal }), _jsx(Text, { style: [
|
|
@@ -242,10 +251,12 @@ const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mod
|
|
|
242
251
|
: retPlaceholder })] })] }), (value[0] || value[1]) && (_jsx(TouchableOpacity, { style: styles.clearButton, onPress: handleClear, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, {}) }))] }), _jsx(Modal, { visible: isOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: closePicker, children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.modalCloseButton, onPress: closePicker, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.modalDateInputs, children: [_jsxs(TouchableOpacity, { style: [
|
|
243
252
|
styles.modalDateField,
|
|
244
253
|
activeField === "departure" && styles.modalDateFieldActive,
|
|
254
|
+
activeField === "departure" && { borderColor: btnColor, backgroundColor: btnColor + '1A' },
|
|
245
255
|
], onPress: () => setActiveField("departure"), children: [_jsx(Text, { style: styles.modalDateFieldLabel, children: lang === "en" ? "Departure" : depLabelFinal }), _jsx(Text, { style: styles.modalDateFieldValue, children: value[0] ? formatMobileDisplay(value[0]) : (lang === "en" ? "Choose date" : "Choose") })] }), _jsxs(TouchableOpacity, { style: [
|
|
246
256
|
styles.modalDateField,
|
|
247
257
|
(oneWay || !value[0]) && styles.modalDateFieldDisabled,
|
|
248
258
|
activeField === "return" && styles.modalDateFieldActive,
|
|
259
|
+
activeField === "return" && { borderColor: btnColor, backgroundColor: btnColor + '1A' },
|
|
249
260
|
], onPress: () => {
|
|
250
261
|
if (oneWay || !value[0])
|
|
251
262
|
return;
|
|
@@ -264,9 +275,10 @@ const DatePicker = ({ lang, value, onChange, minDate, maxDate, translations, mod
|
|
|
264
275
|
: (_jsxs(_Fragment, { children: [_jsxs(View, { style: styles.navRow, children: [_jsx(TouchableOpacity, { disabled: isBefore(addMonths(currentMonth, -1), startOfMonth(today)), onPress: () => setCurrentMonth((prev) => startOfMonth(addMonths(prev, -1))), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, style: [
|
|
265
276
|
styles.navButton,
|
|
266
277
|
isBefore(addMonths(currentMonth, -1), startOfMonth(today)) && styles.navButtonDisabled,
|
|
267
|
-
], children: _jsx(ChevronLeft, {}) }), _jsx(TouchableOpacity, { onPress: () => setCurrentMonth((prev) => startOfMonth(addMonths(prev, 1))), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, style: styles.navButton, children: _jsx(ChevronRight, {}) })] }), renderCalendarContent()] })) }), mode === "flight" && (_jsx(View, { style: styles.onewayContainer, children: _jsx(TouchableOpacity, { style: [styles.onewayBtn, oneWay && styles.onewayBtnActive], onPress: toggleOneWay, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
278
|
+
], children: _jsx(ChevronLeft, {}) }), _jsx(TouchableOpacity, { onPress: () => setCurrentMonth((prev) => startOfMonth(addMonths(prev, 1))), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, style: styles.navButton, children: _jsx(ChevronRight, {}) })] }), renderCalendarContent()] })) }), mode === "flight" && (_jsx(View, { style: styles.onewayContainer, children: _jsx(TouchableOpacity, { style: [styles.onewayBtn, oneWay && styles.onewayBtnActive, oneWay && { backgroundColor: btnColor + '1A' }], onPress: toggleOneWay, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
268
279
|
styles.onewayBtnText,
|
|
269
280
|
oneWay && styles.onewayBtnTextActive,
|
|
281
|
+
oneWay && { color: btnColor },
|
|
270
282
|
], children: onewayText }) }) }))] }) })] }));
|
|
271
283
|
};
|
|
272
284
|
const styles = StyleSheet.create({
|
|
@@ -13,6 +13,7 @@ import { Colors, Spacing, BorderRadius, FontSize } from "../../../theme/colors";
|
|
|
13
13
|
import { formStyles } from "../../../theme/formStyles";
|
|
14
14
|
const FlightSearchForm = ({ startSearch, loading, error, translations = {}, defaults = null, isPreview, expiration, hasSearched, }) => {
|
|
15
15
|
const { lang, currency, config, colors } = useWidget();
|
|
16
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
16
17
|
const defaultFromLocation = config?.defaults?.flights?.from || "VNO";
|
|
17
18
|
const defaultToLocation = config?.defaults?.flights?.to || "BCN";
|
|
18
19
|
const today = new Date();
|
|
@@ -92,7 +93,7 @@ const FlightSearchForm = ({ startSearch, loading, error, translations = {}, defa
|
|
|
92
93
|
setLocationFrom(from);
|
|
93
94
|
setLocationTo(to);
|
|
94
95
|
}, [config?.defaults?.flights]);
|
|
95
|
-
return (_jsxs(ScrollView, { style: local.container, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: [_jsxs(View, { style: [formStyles.formCard, { backgroundColor: colors.form || Colors.surface }], children: [_jsxs(View, { children: [_jsxs(View, { style: local.locationCard, children: [_jsx(Text, { style: local.labelInner, children: translations["from_label_flight"] || "From" }), _jsx(LocationInput, { label: translations["from_label_flight"] || "From", value: locationFrom, onChange: (val) => {
|
|
96
|
+
return (_jsxs(ScrollView, { style: local.container, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: [_jsxs(View, { style: [formStyles.formCard, { backgroundColor: colors.form || Colors.surface }], children: [_jsxs(View, { children: [_jsxs(View, { style: [local.locationCard, { backgroundColor: inputBg }], children: [_jsx(Text, { style: local.labelInner, children: translations["from_label_flight"] || "From" }), _jsx(LocationInput, { label: translations["from_label_flight"] || "From", value: locationFrom, onChange: (val) => {
|
|
96
97
|
setLocationFrom(val);
|
|
97
98
|
setUserEdited(true);
|
|
98
99
|
if (formErrors.locationFrom)
|
|
@@ -5,6 +5,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
5
5
|
import Svg, { Circle, Path } from "react-native-svg";
|
|
6
6
|
import { useAirportSearch } from "../../hooks/useAirportSearch";
|
|
7
7
|
import { useApiToken } from "../../../utils/getToken";
|
|
8
|
+
import { useWidget } from "../../../context/WidgetContext";
|
|
8
9
|
import { Colors, Spacing, FontSize } from "../../../theme/colors";
|
|
9
10
|
import { formStyles } from "../../../theme/formStyles";
|
|
10
11
|
import { CloseIcon } from "../../../store";
|
|
@@ -51,8 +52,11 @@ const POPULAR_LOCATIONS = [
|
|
|
51
52
|
},
|
|
52
53
|
];
|
|
53
54
|
const API_URL = "https://api.travel-code.com/v1/data/airports";
|
|
54
|
-
const SearchIcon = () => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke:
|
|
55
|
+
const SearchIcon = ({ color = Colors.primary }) => (_jsxs(Svg, { width: 19, height: 18, viewBox: "0 0 19 18", fill: "none", children: [_jsx(Circle, { cx: 7, cy: 7, r: 6, stroke: color, strokeWidth: 2 }), _jsx(Path, { d: "M12 11L17.2857 16.0902", stroke: color, strokeWidth: 2, strokeLinecap: "round" })] }));
|
|
55
56
|
const LocationInput = ({ label, value, onChange, lang, translations, minimal = false, }) => {
|
|
57
|
+
const { colors } = useWidget();
|
|
58
|
+
const btnColor = colors.primary || Colors.primary;
|
|
59
|
+
const inputBg = colors.input || Colors.inputBg;
|
|
56
60
|
const token = useApiToken();
|
|
57
61
|
const insets = useSafeAreaInsets();
|
|
58
62
|
const getLabel = (loc) => lang === "en" ? loc.titleEn || loc.title : loc.title;
|
|
@@ -182,7 +186,7 @@ const LocationInput = ({ label, value, onChange, lang, translations, minimal = f
|
|
|
182
186
|
minimal ? styles.minimalText : formStyles.inputTriggerText,
|
|
183
187
|
!query && formStyles.inputTriggerPlaceholder,
|
|
184
188
|
], numberOfLines: 1, children: query || label }), savedValue.code ? (_jsx(Text, { style: formStyles.inputTriggerCode, children: savedValue.code })) : null] }));
|
|
185
|
-
return (_jsxs(_Fragment, { children: [minimal ? (_jsx(TouchableOpacity, { style: styles.minimalContainer, onPress: () => setIsPopupOpen(true), activeOpacity: 0.8, children: triggerContent })) : (_jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => setIsPopupOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: label }), triggerContent] })), _jsx(Modal, { visible: isPopupOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: handleClose, children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: handleClose, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, {}), _jsx(TextInput, { autoFocus: true, style: styles.searchInput, value: query, onChangeText: (text) => {
|
|
189
|
+
return (_jsxs(_Fragment, { children: [minimal ? (_jsx(TouchableOpacity, { style: styles.minimalContainer, onPress: () => setIsPopupOpen(true), activeOpacity: 0.8, children: triggerContent })) : (_jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: inputBg }], onPress: () => setIsPopupOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: label }), triggerContent] })), _jsx(Modal, { visible: isPopupOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: handleClose, children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButton, onPress: handleClose, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), _jsxs(View, { style: styles.searchHeader, children: [_jsx(SearchIcon, { color: btnColor }), _jsx(TextInput, { autoFocus: true, style: styles.searchInput, value: query, onChangeText: (text) => {
|
|
186
190
|
setQuery(text);
|
|
187
191
|
setAllowSearch(true);
|
|
188
192
|
}, placeholder: translations["location_popup_placeholder"] ||
|
|
@@ -3,6 +3,7 @@ import { useState } from "react";
|
|
|
3
3
|
import { View, Text, TouchableOpacity, Modal, StyleSheet, ScrollView, } from "react-native";
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
5
|
import Svg, { Path } from "react-native-svg";
|
|
6
|
+
import { useWidget } from "../../../context/WidgetContext";
|
|
6
7
|
import { Colors, Spacing, BorderRadius, FontSize } from "../../../theme/colors";
|
|
7
8
|
import { formStyles } from "../../../theme/formStyles";
|
|
8
9
|
const MAX_COUNT = 10;
|
|
@@ -16,8 +17,10 @@ const CABIN_OPTIONS = [
|
|
|
16
17
|
{ value: "economy_premium", labelKey: "cabin_premium_label", fallback: "Premium Economy" },
|
|
17
18
|
{ value: "business", labelKey: "cabin_business_label", fallback: "Business" },
|
|
18
19
|
];
|
|
19
|
-
const ChevronDown = () => (_jsx(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: _jsx(Path, { d: "M5 8L10 12.5L15 8", stroke:
|
|
20
|
+
const ChevronDown = ({ color = Colors.primary }) => (_jsx(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: _jsx(Path, { d: "M5 8L10 12.5L15 8", stroke: color, strokeWidth: 2, strokeLinecap: "round" }) }));
|
|
20
21
|
const PassengersPicker = ({ label, value, onChange, cabinClass, onCabinClassChange, translations, }) => {
|
|
22
|
+
const { colors } = useWidget();
|
|
23
|
+
const btnColor = colors.primary || Colors.primary;
|
|
21
24
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
22
25
|
const insets = useSafeAreaInsets();
|
|
23
26
|
const updateCount = (key, delta, min, max) => {
|
|
@@ -34,25 +37,27 @@ const PassengersPicker = ({ label, value, onChange, cabinClass, onCabinClassChan
|
|
|
34
37
|
: cabinClass === "economy_premium"
|
|
35
38
|
? translations["cabin_premium"] || "Premium Economy"
|
|
36
39
|
: translations["cabin_economy"] || "Economy";
|
|
37
|
-
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { style: formStyles.inputTrigger, onPress: () => setIsModalOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: labelFinal }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsxs(Text, { style: styles.triggerText, children: [_jsxs(Text, { style: styles.triggerCount, children: [total, " "] }), _jsxs(Text, { children: [labelText, ", "] }), _jsx(Text, { children: cabinLabel })] }), _jsx(ChevronDown, {})] })] }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsxs(View, { style: styles.modalHeader, children: [_jsx(TouchableOpacity, { onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(Svg, { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", children: _jsx(Path, { d: "M15 18L9 12L15 6", stroke: Colors.textSecondary, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs(View, { style: styles.modalHeaderInfo, children: [_jsx(Text, { style: styles.modalTitle, children: translations["passengers_popup_title"] || "Passengers & Class" }), _jsxs(Text, { style: styles.modalSubtitle, children: [total, " ", labelText, ", ", cabinLabel] })] })] }), _jsxs(ScrollView, { style: styles.modalContent, showsVerticalScrollIndicator: false, children: [_jsx(Text, { style: styles.sectionTitle, children: translations["passengers_label"] || "Passengers" }), PASSENGER_TYPES.map(({ key, labelKey, fallback, min }) => (_jsxs(View, { style: styles.counterRow, children: [_jsx(Text, { style: styles.counterLabel, children: translations[labelKey] || fallback }), _jsxs(View, { style: styles.counterControls, children: [_jsx(TouchableOpacity, { style: [
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { style: [formStyles.inputTrigger, { backgroundColor: colors.input || Colors.inputBg }], onPress: () => setIsModalOpen(true), activeOpacity: 0.8, children: [_jsx(Text, { style: formStyles.label, children: labelFinal }), _jsxs(View, { style: formStyles.inputTriggerRow, children: [_jsxs(Text, { style: styles.triggerText, children: [_jsxs(Text, { style: styles.triggerCount, children: [total, " "] }), _jsxs(Text, { children: [labelText, ", "] }), _jsx(Text, { children: cabinLabel })] }), _jsx(ChevronDown, { color: btnColor })] })] }), _jsx(Modal, { visible: isModalOpen, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setIsModalOpen(false), children: _jsxs(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: [_jsxs(View, { style: styles.modalHeader, children: [_jsx(TouchableOpacity, { onPress: () => setIsModalOpen(false), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(Svg, { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", children: _jsx(Path, { d: "M15 18L9 12L15 6", stroke: Colors.textSecondary, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs(View, { style: styles.modalHeaderInfo, children: [_jsx(Text, { style: styles.modalTitle, children: translations["passengers_popup_title"] || "Passengers & Class" }), _jsxs(Text, { style: styles.modalSubtitle, children: [total, " ", labelText, ", ", cabinLabel] })] })] }), _jsxs(ScrollView, { style: styles.modalContent, showsVerticalScrollIndicator: false, children: [_jsx(Text, { style: styles.sectionTitle, children: translations["passengers_label"] || "Passengers" }), PASSENGER_TYPES.map(({ key, labelKey, fallback, min }) => (_jsxs(View, { style: styles.counterRow, children: [_jsx(Text, { style: styles.counterLabel, children: translations[labelKey] || fallback }), _jsxs(View, { style: styles.counterControls, children: [_jsx(TouchableOpacity, { style: [
|
|
38
41
|
styles.counterButton,
|
|
39
42
|
value[key] <= min && styles.counterButtonDisabled,
|
|
40
43
|
], onPress: () => updateCount(key, -1, min, MAX_COUNT), disabled: value[key] <= min, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
41
44
|
styles.counterButtonText,
|
|
45
|
+
{ color: btnColor },
|
|
42
46
|
value[key] <= min && styles.counterButtonTextDisabled,
|
|
43
47
|
], children: "-" }) }), _jsx(Text, { style: styles.counterValue, children: value[key] }), _jsx(TouchableOpacity, { style: [
|
|
44
48
|
styles.counterButton,
|
|
45
49
|
value[key] >= MAX_COUNT && styles.counterButtonDisabled,
|
|
46
50
|
], onPress: () => updateCount(key, 1, min, MAX_COUNT), disabled: value[key] >= MAX_COUNT, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
47
51
|
styles.counterButtonText,
|
|
52
|
+
{ color: btnColor },
|
|
48
53
|
value[key] >= MAX_COUNT && styles.counterButtonTextDisabled,
|
|
49
54
|
], children: "+" }) })] })] }, key))), _jsx(Text, { style: [styles.sectionTitle, { marginTop: Spacing.xxl }], children: translations["cabin_label"] || "Class" }), CABIN_OPTIONS.map((option) => {
|
|
50
55
|
const isSelected = cabinClass === option.value;
|
|
51
|
-
return (_jsxs(TouchableOpacity, { style: styles.radioRow, onPress: () => onCabinClassChange(option.value), activeOpacity: 0.7, children: [_jsx(View, { style: [styles.radio, isSelected && styles.radioSelected], children: isSelected && _jsx(View, { style: styles.radioInner }) }), _jsx(Text, { style: [
|
|
56
|
+
return (_jsxs(TouchableOpacity, { style: styles.radioRow, onPress: () => onCabinClassChange(option.value), activeOpacity: 0.7, children: [_jsx(View, { style: [styles.radio, isSelected && styles.radioSelected, isSelected && { borderColor: btnColor }], children: isSelected && _jsx(View, { style: [styles.radioInner, { backgroundColor: btnColor }] }) }), _jsx(Text, { style: [
|
|
52
57
|
styles.radioLabel,
|
|
53
58
|
isSelected && styles.radioLabelSelected,
|
|
54
59
|
], children: translations[option.labelKey] || option.fallback })] }, option.value));
|
|
55
|
-
})] }), _jsx(View, { style: styles.modalFooter, children: _jsx(TouchableOpacity, { style: styles.saveButton, onPress: () => setIsModalOpen(false), activeOpacity: 0.8, children: _jsx(Text, { style: styles.saveButtonText, children: translations["save_button"] || "Save" }) }) })] }) })] }));
|
|
60
|
+
})] }), _jsx(View, { style: styles.modalFooter, children: _jsx(TouchableOpacity, { style: [styles.saveButton, { backgroundColor: btnColor }], onPress: () => setIsModalOpen(false), activeOpacity: 0.8, children: _jsx(Text, { style: styles.saveButtonText, children: translations["save_button"] || "Save" }) }) })] }) })] }));
|
|
56
61
|
};
|
|
57
62
|
const styles = StyleSheet.create({
|
|
58
63
|
triggerText: {
|
|
@@ -2,8 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRef } from "react";
|
|
3
3
|
import { TouchableOpacity, StyleSheet, Animated, Easing, } from "react-native";
|
|
4
4
|
import Svg, { Path } from "react-native-svg";
|
|
5
|
+
import { useWidget } from "../../../context/WidgetContext";
|
|
5
6
|
import { Colors, BorderRadius } from "../../../theme/colors";
|
|
6
7
|
const SwitchLocationBtn = ({ from, to, setFrom, setTo, }) => {
|
|
8
|
+
const { colors } = useWidget();
|
|
9
|
+
const btnColor = colors.primary || Colors.primary;
|
|
7
10
|
const rotation = useRef(new Animated.Value(0)).current;
|
|
8
11
|
const handleSwitch = () => {
|
|
9
12
|
const prevFrom = from;
|
|
@@ -25,7 +28,7 @@ const SwitchLocationBtn = ({ from, to, setFrom, setTo, }) => {
|
|
|
25
28
|
return (_jsx(Animated.View, { style: [
|
|
26
29
|
styles.container,
|
|
27
30
|
{ transform: [{ rotate: rotateInterpolate }] },
|
|
28
|
-
], children: _jsx(TouchableOpacity, { onPress: handleSwitch, style: styles.button, activeOpacity: 0.7, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsxs(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: [_jsx(Path, { opacity: 0.3, d: "M13.3334 14.1667C13.7937 14.1667 14.1667 13.7936 14.1667 13.3333C14.1667 12.8731 13.7937 12.5 13.3334 12.5L5.00008 12.5C4.53984 12.5 4.16675 12.8731 4.16675 13.3333C4.16675 13.7936 4.53984 14.1667 5.00008 14.1667L13.3334 14.1667Z", fill:
|
|
31
|
+
], children: _jsx(TouchableOpacity, { onPress: handleSwitch, style: styles.button, activeOpacity: 0.7, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsxs(Svg, { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", children: [_jsx(Path, { opacity: 0.3, d: "M13.3334 14.1667C13.7937 14.1667 14.1667 13.7936 14.1667 13.3333C14.1667 12.8731 13.7937 12.5 13.3334 12.5L5.00008 12.5C4.53984 12.5 4.16675 12.8731 4.16675 13.3333C4.16675 13.7936 4.53984 14.1667 5.00008 14.1667L13.3334 14.1667Z", fill: btnColor }), _jsx(Path, { d: "M11.9108 11.4224C11.5854 11.097 11.5854 10.5694 11.9108 10.2439C12.2363 9.91848 12.7639 9.91848 13.0893 10.2439L15.5893 12.7439C15.9148 13.0694 15.9148 13.597 15.5893 13.9224L13.0893 16.4224C12.7639 16.7479 12.2363 16.7479 11.9108 16.4224C11.5854 16.097 11.5854 15.5694 11.9108 15.2439L13.8216 13.3332L11.9108 11.4224Z", fill: btnColor }), _jsx(Path, { opacity: 0.3, d: "M6.66659 7.50016C6.20635 7.50016 5.83325 7.12707 5.83325 6.66683C5.83325 6.20659 6.20635 5.8335 6.66659 5.8335L14.9999 5.8335C15.4602 5.8335 15.8333 6.20659 15.8333 6.66683C15.8333 7.12707 15.4602 7.50016 14.9999 7.50016L6.66659 7.50016Z", fill: btnColor }), _jsx(Path, { d: "M8.08917 4.75592C8.41461 4.43049 8.41461 3.90285 8.08917 3.57741C7.76374 3.25197 7.2361 3.25197 6.91066 3.57741L4.41066 6.07741C4.08523 6.40285 4.08523 6.93049 4.41066 7.25592L6.91066 9.75592C7.2361 10.0814 7.76374 10.0814 8.08917 9.75592C8.41461 9.43049 8.41461 8.90285 8.08917 8.57741L6.17843 6.66667L8.08917 4.75592Z", fill: btnColor })] }) }) }));
|
|
29
32
|
};
|
|
30
33
|
const styles = StyleSheet.create({
|
|
31
34
|
container: {
|