@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,109 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { View, Text, TouchableOpacity, FlatList, Modal, StyleSheet, } from "react-native";
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
+
import Svg, { Path } from "react-native-svg";
|
|
6
|
+
import { Colors, Spacing, FontSize } from "../../theme/colors";
|
|
7
|
+
import { CloseIcon } from "../../store";
|
|
8
|
+
import { formStyles } from "../../theme/formStyles";
|
|
9
|
+
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
|
+
const AGE_OPTIONS = [
|
|
11
|
+
...Array.from({ length: 12 }, (_, i) => ({
|
|
12
|
+
id: String(18 + i),
|
|
13
|
+
label: String(18 + i),
|
|
14
|
+
})),
|
|
15
|
+
{ id: "30-65", label: "30\u201365" },
|
|
16
|
+
...Array.from({ length: 14 }, (_, i) => ({
|
|
17
|
+
id: String(66 + i),
|
|
18
|
+
label: String(66 + i),
|
|
19
|
+
})),
|
|
20
|
+
{ id: "80+", label: "80+" },
|
|
21
|
+
];
|
|
22
|
+
const AgeSelector = ({ label, value, onChange, translations, }) => {
|
|
23
|
+
const insets = useSafeAreaInsets();
|
|
24
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
25
|
+
const [displayText, setDisplayText] = useState("");
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!value)
|
|
28
|
+
return;
|
|
29
|
+
const found = AGE_OPTIONS.find((a) => a.id === value);
|
|
30
|
+
if (found) {
|
|
31
|
+
setDisplayText(found.label);
|
|
32
|
+
}
|
|
33
|
+
}, [value]);
|
|
34
|
+
const handleSelect = (age) => {
|
|
35
|
+
onChange(age.id);
|
|
36
|
+
setDisplayText(age.label);
|
|
37
|
+
setIsModalOpen(false);
|
|
38
|
+
};
|
|
39
|
+
const renderItem = ({ item }) => {
|
|
40
|
+
const isActive = item.id === value;
|
|
41
|
+
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
|
+
};
|
|
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: [
|
|
44
|
+
formStyles.inputTriggerText,
|
|
45
|
+
!displayText && formStyles.inputTriggerPlaceholder,
|
|
46
|
+
], children: displayText ||
|
|
47
|
+
translations["age_selector_placeholder"] ||
|
|
48
|
+
label }), _jsx(ChevronDown, {})] })] }) }), _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 }) }), _jsx(View, { style: styles.modalHeader, children: _jsx(Text, { style: styles.modalTitle, children: translations["age_selector_placeholder"] || label }) }), _jsx(FlatList, { data: AGE_OPTIONS, renderItem: renderItem, keyExtractor: (item) => item.id, contentContainerStyle: styles.listContent, showsVerticalScrollIndicator: false, initialScrollIndex: value
|
|
49
|
+
? Math.max(0, AGE_OPTIONS.findIndex((a) => a.id === value))
|
|
50
|
+
: 0, getItemLayout: (_, index) => ({
|
|
51
|
+
length: 52,
|
|
52
|
+
offset: 52 * index,
|
|
53
|
+
index,
|
|
54
|
+
}) })] }) })] }));
|
|
55
|
+
};
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
inputContainer: {
|
|
58
|
+
marginBottom: Spacing.md,
|
|
59
|
+
},
|
|
60
|
+
modalContainer: {
|
|
61
|
+
flex: 1,
|
|
62
|
+
backgroundColor: Colors.surface,
|
|
63
|
+
},
|
|
64
|
+
closeButton: {
|
|
65
|
+
alignSelf: "flex-end",
|
|
66
|
+
padding: Spacing.lg,
|
|
67
|
+
},
|
|
68
|
+
modalHeader: {
|
|
69
|
+
paddingHorizontal: Spacing.lg,
|
|
70
|
+
paddingVertical: Spacing.md,
|
|
71
|
+
borderBottomWidth: 1,
|
|
72
|
+
borderBottomColor: Colors.border,
|
|
73
|
+
},
|
|
74
|
+
modalTitle: {
|
|
75
|
+
fontSize: FontSize.lg,
|
|
76
|
+
fontWeight: "700",
|
|
77
|
+
color: Colors.text,
|
|
78
|
+
},
|
|
79
|
+
listContent: {
|
|
80
|
+
paddingBottom: Spacing.xxl,
|
|
81
|
+
},
|
|
82
|
+
optionItem: {
|
|
83
|
+
flexDirection: "row",
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
justifyContent: "space-between",
|
|
86
|
+
paddingHorizontal: Spacing.lg,
|
|
87
|
+
paddingVertical: Spacing.md,
|
|
88
|
+
height: 52,
|
|
89
|
+
borderBottomWidth: 1,
|
|
90
|
+
borderBottomColor: Colors.borderLight,
|
|
91
|
+
},
|
|
92
|
+
optionItemActive: {
|
|
93
|
+
backgroundColor: Colors.primaryLight,
|
|
94
|
+
},
|
|
95
|
+
optionText: {
|
|
96
|
+
fontSize: FontSize.md,
|
|
97
|
+
color: Colors.text,
|
|
98
|
+
},
|
|
99
|
+
optionTextActive: {
|
|
100
|
+
color: Colors.primary,
|
|
101
|
+
fontWeight: "600",
|
|
102
|
+
},
|
|
103
|
+
checkMark: {
|
|
104
|
+
fontSize: FontSize.md,
|
|
105
|
+
color: Colors.primary,
|
|
106
|
+
fontWeight: "700",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
export default AgeSelector;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type CustomSelectOption = {
|
|
3
|
+
label: string | number;
|
|
4
|
+
value: string | number;
|
|
5
|
+
};
|
|
6
|
+
type CustomSelectProps = {
|
|
7
|
+
value: string | number;
|
|
8
|
+
options: CustomSelectOption[];
|
|
9
|
+
onChange: (value: string | number) => void;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const CustomSelect: React.FC<CustomSelectProps>;
|
|
14
|
+
export default CustomSelect;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { View, Text, TouchableOpacity, FlatList, Modal, StyleSheet, } from "react-native";
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
+
import { Colors, Spacing, FontSize, BorderRadius } from "../../theme/colors";
|
|
6
|
+
const CustomSelect = ({ value, options, onChange, placeholder = "Select", }) => {
|
|
7
|
+
const insets = useSafeAreaInsets();
|
|
8
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
9
|
+
const selected = options.find((o) => o.value === value);
|
|
10
|
+
const handleSelect = (opt) => {
|
|
11
|
+
onChange(opt.value);
|
|
12
|
+
setIsOpen(false);
|
|
13
|
+
};
|
|
14
|
+
const renderItem = ({ item }) => {
|
|
15
|
+
const isActive = value === item.value;
|
|
16
|
+
return (_jsxs(TouchableOpacity, { style: [styles.optionItem, isActive && styles.optionItemActive], onPress: () => handleSelect(item), activeOpacity: 0.7, children: [_jsx(Text, { style: [
|
|
17
|
+
styles.optionText,
|
|
18
|
+
isActive && styles.optionTextActive,
|
|
19
|
+
], children: item.label }), isActive && _jsx(Text, { style: styles.checkMark, children: "\u2713" })] }));
|
|
20
|
+
};
|
|
21
|
+
return (_jsxs(View, { children: [_jsxs(TouchableOpacity, { style: styles.trigger, onPress: () => setIsOpen(true), activeOpacity: 0.7, children: [_jsx(Text, { style: [
|
|
22
|
+
styles.triggerText,
|
|
23
|
+
!selected && styles.triggerPlaceholder,
|
|
24
|
+
], numberOfLines: 1, children: selected ? selected.label : placeholder }), _jsx(Text, { style: styles.chevron, children: "\u25BC" })] }), _jsx(Modal, { visible: isOpen, transparent: true, animationType: "fade", onRequestClose: () => setIsOpen(false), children: _jsx(TouchableOpacity, { style: styles.overlay, activeOpacity: 1, onPress: () => setIsOpen(false), children: _jsx(View, { style: [styles.modalContainer, { paddingTop: insets.top }], children: _jsx(View, { style: styles.modalContent, children: _jsx(FlatList, { data: options, renderItem: renderItem, keyExtractor: (item) => String(item.value), showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled" }) }) }) }) })] }));
|
|
25
|
+
};
|
|
26
|
+
const styles = StyleSheet.create({
|
|
27
|
+
trigger: {
|
|
28
|
+
flexDirection: "row",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
justifyContent: "space-between",
|
|
31
|
+
backgroundColor: Colors.surfaceSecondary,
|
|
32
|
+
borderRadius: BorderRadius.md,
|
|
33
|
+
paddingHorizontal: Spacing.md,
|
|
34
|
+
paddingVertical: Spacing.md,
|
|
35
|
+
borderWidth: 1,
|
|
36
|
+
borderColor: Colors.border,
|
|
37
|
+
},
|
|
38
|
+
triggerText: {
|
|
39
|
+
flex: 1,
|
|
40
|
+
fontSize: FontSize.md,
|
|
41
|
+
color: Colors.text,
|
|
42
|
+
},
|
|
43
|
+
triggerPlaceholder: {
|
|
44
|
+
color: Colors.textTertiary,
|
|
45
|
+
},
|
|
46
|
+
chevron: {
|
|
47
|
+
fontSize: FontSize.xs,
|
|
48
|
+
color: Colors.primary,
|
|
49
|
+
marginLeft: Spacing.sm,
|
|
50
|
+
},
|
|
51
|
+
overlay: {
|
|
52
|
+
flex: 1,
|
|
53
|
+
backgroundColor: Colors.overlay,
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
padding: Spacing.xxl,
|
|
57
|
+
},
|
|
58
|
+
modalContainer: {
|
|
59
|
+
width: "100%",
|
|
60
|
+
maxHeight: "70%",
|
|
61
|
+
},
|
|
62
|
+
modalContent: {
|
|
63
|
+
backgroundColor: Colors.surface,
|
|
64
|
+
borderRadius: BorderRadius.lg,
|
|
65
|
+
overflow: "hidden",
|
|
66
|
+
maxHeight: 400,
|
|
67
|
+
},
|
|
68
|
+
optionItem: {
|
|
69
|
+
flexDirection: "row",
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
justifyContent: "space-between",
|
|
72
|
+
paddingHorizontal: Spacing.lg,
|
|
73
|
+
paddingVertical: Spacing.md,
|
|
74
|
+
borderBottomWidth: 1,
|
|
75
|
+
borderBottomColor: Colors.borderLight,
|
|
76
|
+
},
|
|
77
|
+
optionItemActive: {
|
|
78
|
+
backgroundColor: Colors.primaryLight,
|
|
79
|
+
},
|
|
80
|
+
optionText: {
|
|
81
|
+
fontSize: FontSize.md,
|
|
82
|
+
color: Colors.text,
|
|
83
|
+
},
|
|
84
|
+
optionTextActive: {
|
|
85
|
+
color: Colors.primary,
|
|
86
|
+
fontWeight: "600",
|
|
87
|
+
},
|
|
88
|
+
checkMark: {
|
|
89
|
+
fontSize: FontSize.md,
|
|
90
|
+
color: Colors.primary,
|
|
91
|
+
fontWeight: "700",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
export default CustomSelect;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
|
|
3
|
+
import { Skeleton } from "../../components/ui/Skeleton";
|
|
4
|
+
import { Colors, Spacing, FontSize, BorderRadius } from "../../theme/colors";
|
|
5
|
+
const FiltersSkeleton = ({ translations }) => {
|
|
6
|
+
return (_jsx(View, { style: styles.container, children: _jsx(View, { style: styles.card, children: _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: styles.loadingText, children: translations["loading_filters"] || "Loading filters..." }), _jsx(ActivityIndicator, { size: "small", color: Colors.primary, style: styles.loader }), _jsx(Skeleton, { width: "100%", height: 16, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "80%", height: 16, style: { marginTop: Spacing.sm } }), _jsx(Skeleton, { width: "60%", height: 16, style: { marginTop: Spacing.sm } }), _jsx(Skeleton, { width: "100%", height: 40, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "100%", height: 16, style: { marginTop: Spacing.lg } }), _jsx(Skeleton, { width: "70%", height: 16, style: { marginTop: Spacing.sm } })] }) }) }));
|
|
7
|
+
};
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
container: {
|
|
10
|
+
paddingHorizontal: Spacing.lg,
|
|
11
|
+
marginBottom: Spacing.md,
|
|
12
|
+
},
|
|
13
|
+
card: {
|
|
14
|
+
backgroundColor: Colors.surface,
|
|
15
|
+
borderRadius: BorderRadius.lg,
|
|
16
|
+
padding: Spacing.lg,
|
|
17
|
+
shadowColor: Colors.black,
|
|
18
|
+
shadowOffset: { width: 0, height: 2 },
|
|
19
|
+
shadowOpacity: 0.05,
|
|
20
|
+
shadowRadius: 4,
|
|
21
|
+
elevation: 2,
|
|
22
|
+
},
|
|
23
|
+
content: {
|
|
24
|
+
alignItems: "center",
|
|
25
|
+
},
|
|
26
|
+
loadingText: {
|
|
27
|
+
fontSize: FontSize.md,
|
|
28
|
+
color: Colors.textSecondary,
|
|
29
|
+
marginBottom: Spacing.sm,
|
|
30
|
+
},
|
|
31
|
+
loader: {
|
|
32
|
+
marginBottom: Spacing.md,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
export default FiltersSkeleton;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type Country = {
|
|
3
|
+
code: string;
|
|
4
|
+
title: string;
|
|
5
|
+
titleEn?: string;
|
|
6
|
+
flag?: string;
|
|
7
|
+
};
|
|
8
|
+
type NationalityInputProps = {
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
onChange: (val: string) => void;
|
|
12
|
+
translations: Record<string, string>;
|
|
13
|
+
defaultNationality?: string | null;
|
|
14
|
+
defaultNationalities?: string[] | null;
|
|
15
|
+
lang?: "ru" | "en";
|
|
16
|
+
};
|
|
17
|
+
declare const NationalityInput: React.FC<NationalityInputProps>;
|
|
18
|
+
export default NationalityInput;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from "react";
|
|
3
|
+
import { View, Text, TextInput, TouchableOpacity, FlatList, Modal, StyleSheet, } from "react-native";
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
+
import Svg, { Circle, Path } from "react-native-svg";
|
|
6
|
+
import { SvgUri } from "react-native-svg";
|
|
7
|
+
import { useCountriesSearch } from "../hooks/useCountriesSearch";
|
|
8
|
+
import { useCountriesByCodes } from "../hooks/useCountriesByCodes";
|
|
9
|
+
import { Colors, Spacing, FontSize } from "../../theme/colors";
|
|
10
|
+
import { CloseIcon } from "../../store";
|
|
11
|
+
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: Colors.primary, strokeWidth: 2 }), _jsx(Path, { d: "M12 11L17.2857 16.0902", stroke: Colors.primary, strokeWidth: 2, strokeLinecap: "round" })] }));
|
|
13
|
+
const FLAG_BASE = "https://cdn.travel-code.com/images/remaster/flags";
|
|
14
|
+
const POPULAR_FALLBACK = [
|
|
15
|
+
{ code: "PL", title: "\u041F\u043E\u043B\u044C\u0448\u0430", titleEn: "Poland", flag: `${FLAG_BASE}/PL.svg` },
|
|
16
|
+
{ code: "LT", title: "\u041B\u0438\u0442\u0432\u0430", titleEn: "Lithuania", flag: `${FLAG_BASE}/LT.svg` },
|
|
17
|
+
{ code: "LV", title: "\u041B\u0430\u0442\u0432\u0438\u044F", titleEn: "Latvia", flag: `${FLAG_BASE}/LV.svg` },
|
|
18
|
+
{ code: "DE", title: "\u0413\u0435\u0440\u043C\u0430\u043D\u0438\u044F", titleEn: "Germany", flag: `${FLAG_BASE}/DE.svg` },
|
|
19
|
+
{ code: "TR", title: "\u0422\u0443\u0440\u0446\u0438\u044F", titleEn: "Turkey", flag: `${FLAG_BASE}/TR.svg` },
|
|
20
|
+
];
|
|
21
|
+
const NationalityInput = ({ label, value, onChange, translations, defaultNationality, defaultNationalities, lang = "ru", }) => {
|
|
22
|
+
const insets = useSafeAreaInsets();
|
|
23
|
+
const [query, setQuery] = useState("");
|
|
24
|
+
const [selectedCountry, setSelectedCountry] = useState(null);
|
|
25
|
+
const [initializedFromIP, setInitializedFromIP] = useState(false);
|
|
26
|
+
const [wasClearedManually, setWasClearedManually] = useState(false);
|
|
27
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
28
|
+
const [modalQuery, setModalQuery] = useState("");
|
|
29
|
+
const { results: apiCountries, loading: apiLoading } = useCountriesSearch(modalQuery);
|
|
30
|
+
const { countries: popularFromConfig } = useCountriesByCodes(defaultNationalities && defaultNationalities.length
|
|
31
|
+
? defaultNationalities
|
|
32
|
+
: null);
|
|
33
|
+
const { countries: ipCountry } = useCountriesByCodes(defaultNationality ? [defaultNationality] : null);
|
|
34
|
+
const getLabel = (c) => lang === "en" ? c.titleEn || c.title : c.title;
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!initializedFromIP && ipCountry && ipCountry[0] && !wasClearedManually) {
|
|
37
|
+
const c = ipCountry[0];
|
|
38
|
+
setQuery(getLabel(c));
|
|
39
|
+
setSelectedCountry(c);
|
|
40
|
+
onChange(c.code);
|
|
41
|
+
setInitializedFromIP(true);
|
|
42
|
+
}
|
|
43
|
+
}, [ipCountry, initializedFromIP, wasClearedManually]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!selectedCountry)
|
|
46
|
+
return;
|
|
47
|
+
setQuery(getLabel(selectedCountry));
|
|
48
|
+
}, [lang]);
|
|
49
|
+
const popularList = useMemo(() => {
|
|
50
|
+
const list = popularFromConfig?.length ? popularFromConfig : POPULAR_FALLBACK;
|
|
51
|
+
return [...list].sort((a, b) => getLabel(a).localeCompare(getLabel(b), lang === "en" ? "en" : "ru"));
|
|
52
|
+
}, [popularFromConfig, lang]);
|
|
53
|
+
const filteredCountries = modalQuery.trim().length < 2
|
|
54
|
+
? []
|
|
55
|
+
: apiCountries.map((c) => ({
|
|
56
|
+
code: c.code,
|
|
57
|
+
title: c.title,
|
|
58
|
+
titleEn: c.titleEn,
|
|
59
|
+
flag: `${FLAG_BASE}/${c.code}.svg`,
|
|
60
|
+
}));
|
|
61
|
+
const showPopular = !modalQuery.trim() ||
|
|
62
|
+
(modalQuery.trim().length < 2 && filteredCountries.length === 0);
|
|
63
|
+
const displayList = showPopular ? popularList : filteredCountries;
|
|
64
|
+
const handleSelect = (country) => {
|
|
65
|
+
const countryLabel = getLabel(country);
|
|
66
|
+
setQuery(countryLabel);
|
|
67
|
+
setSelectedCountry(country);
|
|
68
|
+
onChange(country.code);
|
|
69
|
+
setIsModalOpen(false);
|
|
70
|
+
setWasClearedManually(false);
|
|
71
|
+
};
|
|
72
|
+
const openModal = () => {
|
|
73
|
+
setModalQuery("");
|
|
74
|
+
setIsModalOpen(true);
|
|
75
|
+
};
|
|
76
|
+
const renderCountryItem = ({ item }) => {
|
|
77
|
+
const countryLabel = getLabel(item);
|
|
78
|
+
const isActive = query.trim().toLowerCase() === countryLabel.toLowerCase();
|
|
79
|
+
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
|
+
};
|
|
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: [
|
|
82
|
+
formStyles.inputTriggerText,
|
|
83
|
+
!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) => {
|
|
85
|
+
setModalQuery(val);
|
|
86
|
+
if (!val.trim()) {
|
|
87
|
+
setSelectedCountry(null);
|
|
88
|
+
setWasClearedManually(true);
|
|
89
|
+
onChange("");
|
|
90
|
+
}
|
|
91
|
+
}, placeholder: translations["nationality_search_placeholder"] ||
|
|
92
|
+
"Search country...", placeholderTextColor: Colors.textTertiary, autoFocus: true })] }), _jsxs(View, { style: styles.modalContent, children: [showPopular && selectedCountry && (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.sectionTitle, children: translations["nationality_selected"] || "Selected" }), renderCountryItem({ item: selectedCountry })] })), showPopular && (_jsx(Text, { style: styles.sectionTitle, children: translations["nationality_popular_title"] || "Popular countries" })), !showPopular &&
|
|
93
|
+
filteredCountries.length === 0 &&
|
|
94
|
+
modalQuery.length >= 2 && (_jsx(Text, { style: styles.noResults, children: translations["nationality_popup_no_results"] ||
|
|
95
|
+
"Nothing found" })), _jsx(FlatList, { data: displayList, renderItem: renderCountryItem, keyExtractor: (item) => item.code, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled" })] })] }) })] }));
|
|
96
|
+
};
|
|
97
|
+
const styles = StyleSheet.create({
|
|
98
|
+
inputContainer: {},
|
|
99
|
+
modalContainer: {
|
|
100
|
+
flex: 1,
|
|
101
|
+
backgroundColor: Colors.surface,
|
|
102
|
+
},
|
|
103
|
+
closeButton: {
|
|
104
|
+
alignSelf: "flex-end",
|
|
105
|
+
padding: Spacing.lg,
|
|
106
|
+
},
|
|
107
|
+
searchHeader: {
|
|
108
|
+
flexDirection: "row",
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
paddingHorizontal: Spacing.lg,
|
|
111
|
+
paddingVertical: Spacing.md,
|
|
112
|
+
borderBottomWidth: 1,
|
|
113
|
+
borderBottomColor: Colors.border,
|
|
114
|
+
gap: Spacing.md,
|
|
115
|
+
},
|
|
116
|
+
searchInput: {
|
|
117
|
+
flex: 1,
|
|
118
|
+
fontSize: FontSize.lg,
|
|
119
|
+
color: Colors.text,
|
|
120
|
+
paddingVertical: Spacing.sm,
|
|
121
|
+
},
|
|
122
|
+
modalContent: {
|
|
123
|
+
flex: 1,
|
|
124
|
+
},
|
|
125
|
+
sectionTitle: {
|
|
126
|
+
fontSize: FontSize.sm,
|
|
127
|
+
color: Colors.textTertiary,
|
|
128
|
+
fontWeight: "600",
|
|
129
|
+
textTransform: "uppercase",
|
|
130
|
+
paddingVertical: Spacing.md,
|
|
131
|
+
letterSpacing: 0.5,
|
|
132
|
+
paddingHorizontal: Spacing.lg,
|
|
133
|
+
},
|
|
134
|
+
noResults: {
|
|
135
|
+
padding: Spacing.lg,
|
|
136
|
+
fontSize: FontSize.md,
|
|
137
|
+
color: Colors.textTertiary,
|
|
138
|
+
},
|
|
139
|
+
countryItem: {
|
|
140
|
+
flexDirection: "row",
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
paddingHorizontal: Spacing.lg,
|
|
143
|
+
paddingVertical: Spacing.md,
|
|
144
|
+
borderBottomWidth: 1,
|
|
145
|
+
borderBottomColor: Colors.borderLight,
|
|
146
|
+
},
|
|
147
|
+
countryItemActive: {
|
|
148
|
+
backgroundColor: Colors.primaryLight,
|
|
149
|
+
},
|
|
150
|
+
flagContainer: {
|
|
151
|
+
width: 24,
|
|
152
|
+
height: 18,
|
|
153
|
+
marginRight: Spacing.md,
|
|
154
|
+
overflow: "hidden",
|
|
155
|
+
borderRadius: 2,
|
|
156
|
+
},
|
|
157
|
+
countryInfo: {
|
|
158
|
+
flex: 1,
|
|
159
|
+
flexDirection: "row",
|
|
160
|
+
justifyContent: "space-between",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
},
|
|
163
|
+
countryName: {
|
|
164
|
+
fontSize: FontSize.md,
|
|
165
|
+
color: Colors.text,
|
|
166
|
+
},
|
|
167
|
+
countryCode: {
|
|
168
|
+
fontSize: FontSize.sm,
|
|
169
|
+
color: Colors.textTertiary,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
export default NationalityInput;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
+
import { Colors, Spacing, FontSize, BorderRadius } from "../../theme/colors";
|
|
4
|
+
const Pagination = ({ totalPages, currentPage, onPageChange, }) => {
|
|
5
|
+
if (totalPages <= 1)
|
|
6
|
+
return null;
|
|
7
|
+
const pages = [];
|
|
8
|
+
pages.push(1);
|
|
9
|
+
if (currentPage > 4)
|
|
10
|
+
pages.push("...");
|
|
11
|
+
for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) {
|
|
12
|
+
pages.push(i);
|
|
13
|
+
}
|
|
14
|
+
if (currentPage < totalPages - 3)
|
|
15
|
+
pages.push("...");
|
|
16
|
+
if (totalPages > 1)
|
|
17
|
+
pages.push(totalPages);
|
|
18
|
+
const handlePageClick = (page) => {
|
|
19
|
+
if (page === "..." || page === currentPage)
|
|
20
|
+
return;
|
|
21
|
+
onPageChange(page);
|
|
22
|
+
};
|
|
23
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(TouchableOpacity, { style: [styles.pageButton, currentPage === 1 && styles.pageButtonDisabled], onPress: () => currentPage > 1 && onPageChange(currentPage - 1), disabled: currentPage === 1, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
24
|
+
styles.pageButtonText,
|
|
25
|
+
currentPage === 1 && styles.pageButtonTextDisabled,
|
|
26
|
+
], children: "\u2039" }) }), pages.map((p, i) => (_jsx(TouchableOpacity, { style: [
|
|
27
|
+
styles.pageButton,
|
|
28
|
+
p === currentPage && styles.pageButtonActive,
|
|
29
|
+
p === "..." && styles.pageButtonEllipsis,
|
|
30
|
+
], onPress: () => handlePageClick(p), disabled: p === "...", activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
31
|
+
styles.pageButtonText,
|
|
32
|
+
p === currentPage && styles.pageButtonTextActive,
|
|
33
|
+
p === "..." && styles.pageButtonTextEllipsis,
|
|
34
|
+
], children: p }) }, i))), _jsx(TouchableOpacity, { style: [
|
|
35
|
+
styles.pageButton,
|
|
36
|
+
currentPage === totalPages && styles.pageButtonDisabled,
|
|
37
|
+
], onPress: () => currentPage < totalPages && onPageChange(currentPage + 1), disabled: currentPage === totalPages, activeOpacity: 0.7, children: _jsx(Text, { style: [
|
|
38
|
+
styles.pageButtonText,
|
|
39
|
+
currentPage === totalPages && styles.pageButtonTextDisabled,
|
|
40
|
+
], children: "\u203A" }) })] }));
|
|
41
|
+
};
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
container: {
|
|
44
|
+
flexDirection: "row",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
gap: Spacing.xs,
|
|
48
|
+
},
|
|
49
|
+
pageButton: {
|
|
50
|
+
minWidth: 36,
|
|
51
|
+
height: 36,
|
|
52
|
+
borderRadius: BorderRadius.md,
|
|
53
|
+
backgroundColor: Colors.surface,
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
borderWidth: 1,
|
|
57
|
+
borderColor: Colors.border,
|
|
58
|
+
},
|
|
59
|
+
pageButtonActive: {
|
|
60
|
+
backgroundColor: Colors.primary,
|
|
61
|
+
borderColor: Colors.primary,
|
|
62
|
+
},
|
|
63
|
+
pageButtonDisabled: {
|
|
64
|
+
opacity: 0.4,
|
|
65
|
+
},
|
|
66
|
+
pageButtonEllipsis: {
|
|
67
|
+
borderColor: Colors.transparent,
|
|
68
|
+
},
|
|
69
|
+
pageButtonText: {
|
|
70
|
+
fontSize: FontSize.md,
|
|
71
|
+
color: Colors.text,
|
|
72
|
+
fontWeight: "500",
|
|
73
|
+
},
|
|
74
|
+
pageButtonTextActive: {
|
|
75
|
+
color: Colors.textOnPrimary,
|
|
76
|
+
},
|
|
77
|
+
pageButtonTextDisabled: {
|
|
78
|
+
color: Colors.textTertiary,
|
|
79
|
+
},
|
|
80
|
+
pageButtonTextEllipsis: {
|
|
81
|
+
color: Colors.textTertiary,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
export default Pagination;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type Props = {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onRefresh: () => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
translations: Record<string, string>;
|
|
7
|
+
type: "flight" | "hotel" | "car";
|
|
8
|
+
};
|
|
9
|
+
declare const SearchExpiredPopup: React.FC<Props>;
|
|
10
|
+
export default SearchExpiredPopup;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, TouchableOpacity, Modal, StyleSheet, } from "react-native";
|
|
3
|
+
import Svg, { Path } from "react-native-svg";
|
|
4
|
+
import { Colors, Spacing, BorderRadius, FontSize } from "../../theme/colors";
|
|
5
|
+
const TITLES = {
|
|
6
|
+
flight: "Flight results may be outdated",
|
|
7
|
+
hotel: "Hotel results may be outdated",
|
|
8
|
+
car: "Rental car results may be outdated",
|
|
9
|
+
};
|
|
10
|
+
const DESCRIPTIONS = {
|
|
11
|
+
flight: "Ticket prices change several times a day. Update your search to get exact prices.",
|
|
12
|
+
hotel: "Hotel availability and prices change frequently. Update your search to get valid offers.",
|
|
13
|
+
car: "Car rental prices change often. Update your search to get the latest deals.",
|
|
14
|
+
};
|
|
15
|
+
const SearchExpiredPopup = ({ isOpen, onRefresh, onClose, translations, type, }) => {
|
|
16
|
+
if (!isOpen)
|
|
17
|
+
return null;
|
|
18
|
+
const title = translations[`search_expired_title_${type}`] ||
|
|
19
|
+
translations["search_expired_title"] ||
|
|
20
|
+
TITLES[type];
|
|
21
|
+
const description = translations[`search_expired_description_${type}`] ||
|
|
22
|
+
translations["search_expired_description"] ||
|
|
23
|
+
DESCRIPTIONS[type];
|
|
24
|
+
const btnLater = translations["search_expired_later"] || "Later";
|
|
25
|
+
const btnRefresh = translations[`search_expired_refresh_${type}`] ||
|
|
26
|
+
translations["search_expired_refresh"] ||
|
|
27
|
+
"Refresh";
|
|
28
|
+
return (_jsx(Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: onClose, children: _jsx(View, { style: styles.overlay, children: _jsxs(View, { style: styles.popup, children: [_jsxs(Svg, { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", children: [_jsx(Path, { fillRule: "evenodd", clipRule: "evenodd", d: "M7.30864 5H7.36497C7.53622 5 7.67961 5.12976 7.69665 5.30017L7.99996 8.33333L10.1653 9.57069C10.2692 9.63004 10.3333 9.74049 10.3333 9.86011V10C10.3333 10.1406 10.2193 10.2546 10.0787 10.2546C10.0561 10.2546 10.0335 10.2516 10.0117 10.2456L6.9324 9.40582C6.77815 9.36375 6.6755 9.21808 6.68776 9.05866L6.97629 5.30777C6.98965 5.1341 7.13446 5 7.30864 5Z", fill: "#B5B5C2" }), _jsx(Path, { opacity: 0.3, fillRule: "evenodd", clipRule: "evenodd", d: "M4.92649 1.88974C5.76807 1.53139 6.69419 1.3331 7.66663 1.3331C11.5326 1.3331 14.6666 4.46711 14.6666 8.3331C14.6666 12.1991 11.5326 15.3331 7.66663 15.3331C3.80063 15.3331 0.666626 12.1991 0.666626 8.3331C0.666626 7.67652 0.757022 7.04106 0.926048 6.43847L2.20983 6.79857C2.07111 7.29314 1.99996 7.80772 1.99996 8.3331C1.99996 11.4627 4.53701 13.9998 7.66663 13.9998C10.7962 13.9998 13.3333 11.4627 13.3333 8.3331C13.3333 5.20349 10.7962 2.66643 7.66663 2.66643C7.03247 2.66643 6.41487 2.77039 5.83244 2.96941L6.62628 3.91547C6.6725 3.97055 6.69967 4.03909 6.70373 4.11087C6.71415 4.29467 6.57359 4.45212 6.38979 4.46253L3.14515 4.6464C3.11283 4.64823 3.08043 4.64535 3.04895 4.63784C2.86988 4.59513 2.75934 4.41533 2.80205 4.23626L3.55438 1.08217C3.57099 1.01257 3.60955 0.950159 3.66436 0.904165C3.80538 0.785831 4.01564 0.804226 4.13397 0.94525L4.92649 1.88974Z", fill: "#B5B5C2" })] }), _jsx(Text, { style: styles.title, children: title }), _jsx(Text, { style: styles.description, children: description }), _jsxs(View, { style: styles.buttonsRow, children: [_jsx(TouchableOpacity, { style: styles.laterButton, onPress: onClose, activeOpacity: 0.7, children: _jsx(Text, { style: styles.laterButtonText, children: btnLater }) }), _jsx(TouchableOpacity, { style: styles.refreshButton, onPress: onRefresh, activeOpacity: 0.8, children: _jsx(Text, { style: styles.refreshButtonText, children: btnRefresh }) })] })] }) }) }));
|
|
29
|
+
};
|
|
30
|
+
const styles = StyleSheet.create({
|
|
31
|
+
overlay: {
|
|
32
|
+
flex: 1,
|
|
33
|
+
backgroundColor: Colors.overlay,
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
padding: Spacing.xxl,
|
|
37
|
+
},
|
|
38
|
+
popup: {
|
|
39
|
+
backgroundColor: Colors.surface,
|
|
40
|
+
borderRadius: BorderRadius.lg,
|
|
41
|
+
padding: Spacing.xxl,
|
|
42
|
+
width: "100%",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
},
|
|
45
|
+
title: {
|
|
46
|
+
fontSize: FontSize.lg,
|
|
47
|
+
fontWeight: "600",
|
|
48
|
+
color: Colors.text,
|
|
49
|
+
textAlign: "center",
|
|
50
|
+
marginTop: Spacing.md,
|
|
51
|
+
},
|
|
52
|
+
description: {
|
|
53
|
+
fontSize: FontSize.md,
|
|
54
|
+
color: Colors.textSecondary,
|
|
55
|
+
textAlign: "center",
|
|
56
|
+
marginTop: Spacing.sm,
|
|
57
|
+
lineHeight: 20,
|
|
58
|
+
},
|
|
59
|
+
buttonsRow: {
|
|
60
|
+
flexDirection: "row",
|
|
61
|
+
gap: Spacing.md,
|
|
62
|
+
marginTop: Spacing.xl,
|
|
63
|
+
width: "100%",
|
|
64
|
+
},
|
|
65
|
+
laterButton: {
|
|
66
|
+
flex: 1,
|
|
67
|
+
paddingVertical: Spacing.md,
|
|
68
|
+
borderRadius: BorderRadius.md,
|
|
69
|
+
borderWidth: 1,
|
|
70
|
+
borderColor: Colors.border,
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
},
|
|
73
|
+
laterButtonText: {
|
|
74
|
+
fontSize: FontSize.md,
|
|
75
|
+
color: Colors.textSecondary,
|
|
76
|
+
fontWeight: "500",
|
|
77
|
+
},
|
|
78
|
+
refreshButton: {
|
|
79
|
+
flex: 1,
|
|
80
|
+
paddingVertical: Spacing.md,
|
|
81
|
+
borderRadius: BorderRadius.md,
|
|
82
|
+
backgroundColor: Colors.primary,
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
},
|
|
85
|
+
refreshButtonText: {
|
|
86
|
+
fontSize: FontSize.md,
|
|
87
|
+
color: Colors.textOnPrimary,
|
|
88
|
+
fontWeight: "600",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
export default SearchExpiredPopup;
|