@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
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TravelHub. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary and confidential.
|
|
6
|
+
|
|
7
|
+
Permission is granted solely to authorized licensees to use this package
|
|
8
|
+
in their applications, subject to a valid license agreement with TravelHub.
|
|
9
|
+
|
|
10
|
+
The following actions are strictly prohibited without prior written consent:
|
|
11
|
+
- Copying, modifying, or distributing this software
|
|
12
|
+
- Reverse engineering, decompiling, or disassembling
|
|
13
|
+
- Sublicensing, selling, or transferring to third parties
|
|
14
|
+
- Using this software without a valid API token issued by TravelHub
|
|
15
|
+
|
|
16
|
+
This software is provided "as is" without warranty of any kind.
|
|
17
|
+
|
|
18
|
+
For licensing inquiries: support@travel-code.com
|
package/dist/App.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type AppProps = {
|
|
3
|
+
lang: string;
|
|
4
|
+
currency: string;
|
|
5
|
+
onFlightSelect?: (flight: any) => void;
|
|
6
|
+
onHotelSelect?: (hotel: any) => void;
|
|
7
|
+
onCarSelect?: (car: any) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const App: React.FC<AppProps>;
|
|
10
|
+
export default App;
|
package/dist/App.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useEffect, useMemo } from "react";
|
|
3
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
4
|
+
import { useWidget } from "./context/WidgetContext";
|
|
5
|
+
import { Navbar } from "./modules/navbar/Navbar";
|
|
6
|
+
import { defaultTranslations } from "./locales/widgetTranslations";
|
|
7
|
+
import FlightSearchForm from "./modules/search-form/flight/FlightSearchForm";
|
|
8
|
+
import FlightResults from "./modules/search-form/flight/flight-results/FlightResults";
|
|
9
|
+
import { FlightFilters } from "./modules/search-form/flight/flight-filters";
|
|
10
|
+
import FlightsSkeleton from "./modules/search-form/flight/flight-results/FlightsSkeleton";
|
|
11
|
+
import ProvidersLoader from "./modules/search-form/flight/flight-results/ProvidersLoader";
|
|
12
|
+
import { useFlightSearch } from "./modules/hooks/useFlightSearch";
|
|
13
|
+
import HotelSearchForm from "./modules/search-form/hotel/HotelSearchForm";
|
|
14
|
+
import HotelResults from "./modules/search-form/hotel/HotelResults";
|
|
15
|
+
import { useHotelSearchSSE } from "./modules/hooks/useHotelSearchSSE";
|
|
16
|
+
import CarSearchForm from "./modules/search-form/cars/CarSearchForm";
|
|
17
|
+
import CarResults from "./modules/search-form/cars/CarResults";
|
|
18
|
+
import { useCarOffers } from "./modules/hooks/useCarOffers";
|
|
19
|
+
import { useCarFilters } from "./modules/search-form/cars/car-filters/useCarFilters";
|
|
20
|
+
import { useSearchExpiration } from "./modules/hooks/useSearchExpiration";
|
|
21
|
+
import { Colors, Spacing, FontSize } from "./theme/colors";
|
|
22
|
+
const ITEMS_PER_PAGE_CAR = 10;
|
|
23
|
+
const App = ({ lang, currency, onFlightSelect, onHotelSelect, onCarSelect }) => {
|
|
24
|
+
const { config, colors } = useWidget();
|
|
25
|
+
const isPreview = config?.mode === "preview";
|
|
26
|
+
const appLang = (config?.form?.language || lang || "en").toLowerCase();
|
|
27
|
+
const appCurrency = config?.form?.currency || currency || "USD";
|
|
28
|
+
const [translations, setTranslations] = useState(defaultTranslations[appLang] || defaultTranslations.en);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setTranslations(defaultTranslations[appLang] || defaultTranslations.en);
|
|
31
|
+
}, [appLang]);
|
|
32
|
+
const [activeTab, setActiveTab] = useState("");
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const tabMap = {
|
|
35
|
+
flights: "avia",
|
|
36
|
+
hotels: "hotel",
|
|
37
|
+
cars: "car",
|
|
38
|
+
};
|
|
39
|
+
const allTabs = ["flights", "hotels", "cars"];
|
|
40
|
+
const visible = { ...config?.tabs_visibility, car: false };
|
|
41
|
+
const firstVisible = allTabs.find((t) => visible[tabMap[t]] !== false);
|
|
42
|
+
if (firstVisible && activeTab !== firstVisible) {
|
|
43
|
+
setActiveTab(firstVisible);
|
|
44
|
+
}
|
|
45
|
+
}, [config]);
|
|
46
|
+
const defaultTexts = {
|
|
47
|
+
flight: "Find the Cheapest Flights",
|
|
48
|
+
hotel: "Book Your Ideal Hotel",
|
|
49
|
+
car: "Search and rent cars",
|
|
50
|
+
};
|
|
51
|
+
const getBannerText = () => {
|
|
52
|
+
const t = config?.texts || {};
|
|
53
|
+
if (activeTab === "flights")
|
|
54
|
+
return t.banner_flight ?? defaultTexts.flight;
|
|
55
|
+
if (activeTab === "hotels")
|
|
56
|
+
return t.banner_hotel ?? defaultTexts.hotel;
|
|
57
|
+
if (activeTab === "cars")
|
|
58
|
+
return t.banner_car ?? defaultTexts.car;
|
|
59
|
+
return "";
|
|
60
|
+
};
|
|
61
|
+
const showHeadings = config?.texts?.headings_visible !== false;
|
|
62
|
+
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
|
+
const [hasFlightSearched, setHasFlightSearched] = useState(false);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (typeof flightStatus === "string" && flightStatus.startsWith("success")) {
|
|
66
|
+
setHasFlightSearched(true);
|
|
67
|
+
}
|
|
68
|
+
}, [flightStatus]);
|
|
69
|
+
const flightExpiration = useSearchExpiration(30);
|
|
70
|
+
const hasFlightFilters = airlinesData && Object.keys(airlinesData).length > 0;
|
|
71
|
+
const isFlightEmptyAfterSearch = hasFlightSearched && !flightLoading && !flightIsFiltering && flightResults.length === 0 && flightTotal === 0 && !hasFlightFilters;
|
|
72
|
+
const isFlightEmptyAfterFilters = hasFlightSearched && !flightLoading && !flightIsFiltering && flightResults.length === 0 && flightTotal === 0 && hasFlightFilters;
|
|
73
|
+
const { offers: hotelOffers, allHotels: hotelAllHotels, loading: hotelLoading, isStreaming: hotelIsStreaming, isPageLoading: hotelIsPageLoading, error: hotelError, total: hotelTotal, offset: hotelOffset, limit: hotelLimit, streamCompleted: hotelStreamCompleted, startSearch: startHotelSearch, filterSearch: filterHotelSearch, fetchPage: fetchHotelPage, reset: resetHotels, } = useHotelSearchSSE();
|
|
74
|
+
const [hasHotelSearched, setHasHotelSearched] = useState(false);
|
|
75
|
+
const [hotelSearchParams, setHotelSearchParams] = useState(null);
|
|
76
|
+
const hotelExpiration = useSearchExpiration(30);
|
|
77
|
+
const { offers: carOffers, loading: carLoading, error: carError, fetchOffers: fetchCarOffers, reset: resetCars, } = useCarOffers();
|
|
78
|
+
const [hasCarSearched, setHasCarSearched] = useState(false);
|
|
79
|
+
const [carDays, setCarDays] = useState(1);
|
|
80
|
+
const [carSearchParams, setCarSearchParams] = useState(null);
|
|
81
|
+
const [carCurrentPage, setCarCurrentPage] = useState(1);
|
|
82
|
+
const carExpiration = useSearchExpiration(30);
|
|
83
|
+
const { filters: carFiltersState, filteredOffers: carFilteredOffers, resetFilters: resetCarFilters, handleRangeChange: handleCarRangeChange, toggle: toggleCarFilter, UI_MIN_PRICE: carUiMinPrice, UI_MAX_PRICE: carUiMaxPrice, setFilters: setCarFilters, } = useCarFilters(carOffers);
|
|
84
|
+
const [carHasAppliedFilters, setCarHasAppliedFilters] = useState(false);
|
|
85
|
+
const carIsFiltering = carHasAppliedFilters && carFilteredOffers.length !== carOffers.length;
|
|
86
|
+
const carPaginatedOffers = useMemo(() => {
|
|
87
|
+
const start = (carCurrentPage - 1) * ITEMS_PER_PAGE_CAR;
|
|
88
|
+
return carFilteredOffers.slice(start, start + ITEMS_PER_PAGE_CAR);
|
|
89
|
+
}, [carFilteredOffers, carCurrentPage]);
|
|
90
|
+
const carTotalPages = Math.ceil(carFilteredOffers.length / ITEMS_PER_PAGE_CAR);
|
|
91
|
+
const prevTabRef = React.useRef(activeTab);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const prev = prevTabRef.current;
|
|
94
|
+
prevTabRef.current = activeTab;
|
|
95
|
+
if (prev === activeTab)
|
|
96
|
+
return;
|
|
97
|
+
if (prev === "flights") {
|
|
98
|
+
resetFlights();
|
|
99
|
+
setHasFlightSearched(false);
|
|
100
|
+
flightExpiration.reset();
|
|
101
|
+
}
|
|
102
|
+
else if (prev === "hotels") {
|
|
103
|
+
resetHotels();
|
|
104
|
+
setHasHotelSearched(false);
|
|
105
|
+
setHotelSearchParams(null);
|
|
106
|
+
hotelExpiration.reset();
|
|
107
|
+
}
|
|
108
|
+
else if (prev === "cars") {
|
|
109
|
+
resetCars();
|
|
110
|
+
setHasCarSearched(false);
|
|
111
|
+
setCarSearchParams(null);
|
|
112
|
+
resetCarFilters();
|
|
113
|
+
setCarCurrentPage(1);
|
|
114
|
+
carExpiration.reset();
|
|
115
|
+
}
|
|
116
|
+
}, [activeTab]);
|
|
117
|
+
const handleHotelSearch = async (params) => {
|
|
118
|
+
const enriched = { ...params, currency: appCurrency };
|
|
119
|
+
setHotelSearchParams(enriched);
|
|
120
|
+
setHasHotelSearched(true);
|
|
121
|
+
startHotelSearch(enriched);
|
|
122
|
+
};
|
|
123
|
+
const handleCarSearch = async (params) => {
|
|
124
|
+
setHasCarSearched(true);
|
|
125
|
+
await fetchCarOffers(params);
|
|
126
|
+
};
|
|
127
|
+
const bannerStyle = [
|
|
128
|
+
styles.bannerContainer,
|
|
129
|
+
{ backgroundColor: colors.banner || Colors.primary },
|
|
130
|
+
];
|
|
131
|
+
const bannerTextStyle = [
|
|
132
|
+
styles.bannerText,
|
|
133
|
+
{ color: colors.textHeadings || Colors.white },
|
|
134
|
+
];
|
|
135
|
+
if (activeTab === "hotels") {
|
|
136
|
+
const hotelHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: getBannerText() }) })), _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 })] }));
|
|
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 }) }));
|
|
138
|
+
}
|
|
139
|
+
if (activeTab === "cars") {
|
|
140
|
+
const carHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: getBannerText() }) })), _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
|
+
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
|
+
}
|
|
143
|
+
const flightHeader = (_jsxs(_Fragment, { children: [showHeadings && (_jsx(View, { style: bannerStyle, children: _jsx(Text, { style: bannerTextStyle, children: getBannerText() }) })), _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
|
+
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
|
+
};
|
|
146
|
+
const styles = StyleSheet.create({
|
|
147
|
+
container: {
|
|
148
|
+
flex: 1,
|
|
149
|
+
backgroundColor: Colors.background,
|
|
150
|
+
},
|
|
151
|
+
bannerContainer: {
|
|
152
|
+
backgroundColor: Colors.bannerBg,
|
|
153
|
+
paddingHorizontal: Spacing.lg,
|
|
154
|
+
paddingVertical: Spacing.xxxl,
|
|
155
|
+
paddingTop: 40,
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
},
|
|
158
|
+
bannerText: {
|
|
159
|
+
fontSize: FontSize.xxl,
|
|
160
|
+
fontWeight: '600',
|
|
161
|
+
color: Colors.bannerText,
|
|
162
|
+
textTransform: 'uppercase',
|
|
163
|
+
textAlign: 'center',
|
|
164
|
+
letterSpacing: -0.2,
|
|
165
|
+
},
|
|
166
|
+
totalCount: {
|
|
167
|
+
paddingHorizontal: Spacing.lg,
|
|
168
|
+
paddingVertical: Spacing.md,
|
|
169
|
+
},
|
|
170
|
+
totalCountText: {
|
|
171
|
+
fontSize: FontSize.xxl,
|
|
172
|
+
color: Colors.black,
|
|
173
|
+
fontWeight: "700",
|
|
174
|
+
marginBottom: Spacing.lg,
|
|
175
|
+
},
|
|
176
|
+
emptyState: {
|
|
177
|
+
padding: Spacing.xxl,
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
},
|
|
180
|
+
emptyTitle: {
|
|
181
|
+
fontSize: FontSize.xl,
|
|
182
|
+
fontWeight: '600',
|
|
183
|
+
color: Colors.text,
|
|
184
|
+
marginBottom: Spacing.sm,
|
|
185
|
+
},
|
|
186
|
+
emptyHint: {
|
|
187
|
+
fontSize: FontSize.md,
|
|
188
|
+
color: Colors.textSecondary,
|
|
189
|
+
textAlign: 'center',
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
export default App;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type TravelHubWidgetProps = {
|
|
3
|
+
token: string;
|
|
4
|
+
lang?: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
config?: any;
|
|
7
|
+
onFlightSelect?: (flight: any) => void;
|
|
8
|
+
onHotelSelect?: (hotel: any) => void;
|
|
9
|
+
onCarSelect?: (car: any) => void;
|
|
10
|
+
style?: any;
|
|
11
|
+
};
|
|
12
|
+
export declare const TravelHubWidget: React.FC<TravelHubWidgetProps>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
|
4
|
+
import { Provider } from 'react-redux';
|
|
5
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
6
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
7
|
+
import store from './store';
|
|
8
|
+
import { WidgetProvider } from './context/WidgetContext';
|
|
9
|
+
import { setGlobalToken } from './utils/getToken';
|
|
10
|
+
import App from './App';
|
|
11
|
+
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
12
|
+
const CONFIG_URL = 'https://api.travel-code.com/v1/user/widget-config';
|
|
13
|
+
export const TravelHubWidget = ({ token, lang = 'en', currency = 'USD', config: configProp, onFlightSelect, onHotelSelect, onCarSelect, style, }) => {
|
|
14
|
+
const [remoteConfig, setRemoteConfig] = useState(null);
|
|
15
|
+
const [configLoading, setConfigLoading] = useState(true);
|
|
16
|
+
setGlobalToken(token);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
fetch(CONFIG_URL, {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${token}`,
|
|
22
|
+
Accept: 'application/json',
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.then((res) => {
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
throw new Error(`Config fetch failed: ${res.status}`);
|
|
28
|
+
return res.json();
|
|
29
|
+
})
|
|
30
|
+
.then((data) => {
|
|
31
|
+
setRemoteConfig(data);
|
|
32
|
+
})
|
|
33
|
+
.catch(() => { })
|
|
34
|
+
.finally(() => {
|
|
35
|
+
setConfigLoading(false);
|
|
36
|
+
});
|
|
37
|
+
}, [token]);
|
|
38
|
+
const mergedConfig = remoteConfig
|
|
39
|
+
? { ...configProp, ...remoteConfig }
|
|
40
|
+
: configProp || {};
|
|
41
|
+
if (configLoading) {
|
|
42
|
+
return (_jsx(GestureHandlerRootView, { style: styles.root, children: _jsx(View, { style: [styles.container, styles.loadingContainer, style], children: _jsx(ActivityIndicator, { size: "large", color: "#306DDE" }) }) }));
|
|
43
|
+
}
|
|
44
|
+
return (_jsx(GestureHandlerRootView, { style: styles.root, children: _jsx(Provider, { store: store, children: _jsx(WidgetProvider, { token: token, initialConfig: mergedConfig, lang: lang, currency: currency, children: _jsx(SafeAreaView, { style: [styles.container, style], children: _jsx(ErrorBoundary, { children: _jsx(App, { lang: lang, currency: currency, onFlightSelect: onFlightSelect, onHotelSelect: onHotelSelect, onCarSelect: onCarSelect }) }) }) }) }) }));
|
|
45
|
+
};
|
|
46
|
+
const styles = StyleSheet.create({
|
|
47
|
+
root: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
},
|
|
50
|
+
container: {
|
|
51
|
+
flex: 1,
|
|
52
|
+
backgroundColor: '#FFFFFF',
|
|
53
|
+
},
|
|
54
|
+
loadingContainer: {
|
|
55
|
+
justifyContent: 'center',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode } from "react";
|
|
2
|
+
type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
fallbackMessage?: string;
|
|
5
|
+
onReset?: () => void;
|
|
6
|
+
};
|
|
7
|
+
type State = {
|
|
8
|
+
hasError: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
};
|
|
11
|
+
export declare class ErrorBoundary extends Component<Props, State> {
|
|
12
|
+
state: State;
|
|
13
|
+
static getDerivedStateFromError(error: Error): State;
|
|
14
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
15
|
+
handleReset: () => void;
|
|
16
|
+
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Component } from "react";
|
|
3
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
4
|
+
import { Colors, Spacing, FontSize, BorderRadius } from "../theme/colors";
|
|
5
|
+
export class ErrorBoundary extends Component {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.state = { hasError: false, error: null };
|
|
9
|
+
this.handleReset = () => {
|
|
10
|
+
this.setState({ hasError: false, error: null });
|
|
11
|
+
this.props.onReset?.();
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
static getDerivedStateFromError(error) {
|
|
15
|
+
return { hasError: true, error };
|
|
16
|
+
}
|
|
17
|
+
componentDidCatch(error, info) {
|
|
18
|
+
console.error("ErrorBoundary caught:", error, info.componentStack);
|
|
19
|
+
}
|
|
20
|
+
render() {
|
|
21
|
+
if (this.state.hasError) {
|
|
22
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(Text, { style: styles.icon, children: "\u26A0\uFE0F" }), _jsx(Text, { style: styles.title, children: this.props.fallbackMessage || "Something went wrong" }), _jsx(Text, { style: styles.message, children: this.state.error?.message || "An unexpected error occurred" }), _jsx(TouchableOpacity, { style: styles.button, onPress: this.handleReset, activeOpacity: 0.7, children: _jsx(Text, { style: styles.buttonText, children: "Try again" }) })] }));
|
|
23
|
+
}
|
|
24
|
+
return this.props.children;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const styles = StyleSheet.create({
|
|
28
|
+
container: {
|
|
29
|
+
flex: 1,
|
|
30
|
+
justifyContent: "center",
|
|
31
|
+
alignItems: "center",
|
|
32
|
+
padding: Spacing.xxl,
|
|
33
|
+
backgroundColor: Colors.background,
|
|
34
|
+
},
|
|
35
|
+
icon: {
|
|
36
|
+
fontSize: 48,
|
|
37
|
+
marginBottom: Spacing.lg,
|
|
38
|
+
},
|
|
39
|
+
title: {
|
|
40
|
+
fontSize: FontSize.xl,
|
|
41
|
+
fontWeight: "600",
|
|
42
|
+
color: Colors.text,
|
|
43
|
+
marginBottom: Spacing.sm,
|
|
44
|
+
textAlign: "center",
|
|
45
|
+
},
|
|
46
|
+
message: {
|
|
47
|
+
fontSize: FontSize.md,
|
|
48
|
+
color: Colors.textSecondary,
|
|
49
|
+
textAlign: "center",
|
|
50
|
+
marginBottom: Spacing.xl,
|
|
51
|
+
},
|
|
52
|
+
button: {
|
|
53
|
+
backgroundColor: Colors.primary,
|
|
54
|
+
paddingHorizontal: Spacing.xxl,
|
|
55
|
+
paddingVertical: Spacing.md,
|
|
56
|
+
borderRadius: BorderRadius.md,
|
|
57
|
+
},
|
|
58
|
+
buttonText: {
|
|
59
|
+
color: Colors.white,
|
|
60
|
+
fontSize: FontSize.md,
|
|
61
|
+
fontWeight: "600",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewStyle } from 'react-native';
|
|
3
|
+
export interface ModalProps {
|
|
4
|
+
visible: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
title?: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
fullScreen?: boolean;
|
|
9
|
+
contentStyle?: ViewStyle;
|
|
10
|
+
}
|
|
11
|
+
export declare function Modal({ visible, onClose, title, children, fullScreen, contentStyle, }: ModalProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Modal as RNModal, View, Text, TouchableOpacity, StyleSheet, ScrollView, } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
+
import { Colors, Spacing, BorderRadius, FontSize } from '../../theme/colors';
|
|
5
|
+
import { CloseIcon } from '../../store';
|
|
6
|
+
export function Modal({ visible, onClose, title, children, fullScreen = false, contentStyle, }) {
|
|
7
|
+
const insets = useSafeAreaInsets();
|
|
8
|
+
return (_jsx(RNModal, { visible: visible, animationType: fullScreen ? 'slide' : 'fade', transparent: !fullScreen, onRequestClose: onClose, children: fullScreen ? (_jsxs(View, { style: [styles.fullScreenContainer, { paddingTop: insets.top }], children: [_jsx(TouchableOpacity, { style: styles.closeButtonTopRight, onPress: onClose, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) }), title ? (_jsx(View, { style: styles.header, children: _jsx(Text, { style: styles.title, children: title }) })) : null, _jsx(ScrollView, { style: styles.scrollView, contentContainerStyle: [styles.scrollContent, contentStyle], keyboardShouldPersistTaps: "handled", children: children })] })) : (_jsx(View, { style: styles.overlay, children: _jsxs(View, { style: styles.dialog, children: [_jsxs(View, { style: styles.dialogHeader, children: [title ? _jsx(Text, { style: styles.title, children: title }) : _jsx(View, {}), _jsx(TouchableOpacity, { onPress: onClose, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: _jsx(CloseIcon, { size: 16 }) })] }), _jsx(ScrollView, { style: styles.dialogScrollView, contentContainerStyle: [styles.scrollContent, contentStyle], keyboardShouldPersistTaps: "handled", children: children })] }) })) }));
|
|
9
|
+
}
|
|
10
|
+
const styles = StyleSheet.create({
|
|
11
|
+
overlay: {
|
|
12
|
+
flex: 1,
|
|
13
|
+
backgroundColor: Colors.overlay,
|
|
14
|
+
justifyContent: 'center',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
padding: Spacing.xxl,
|
|
17
|
+
},
|
|
18
|
+
dialog: {
|
|
19
|
+
backgroundColor: Colors.surface,
|
|
20
|
+
borderRadius: BorderRadius.xl,
|
|
21
|
+
width: '100%',
|
|
22
|
+
maxHeight: '80%',
|
|
23
|
+
shadowColor: Colors.black,
|
|
24
|
+
shadowOffset: { width: 0, height: 4 },
|
|
25
|
+
shadowOpacity: 0.15,
|
|
26
|
+
shadowRadius: 12,
|
|
27
|
+
elevation: 8,
|
|
28
|
+
},
|
|
29
|
+
fullScreenContainer: {
|
|
30
|
+
flex: 1,
|
|
31
|
+
backgroundColor: Colors.surface,
|
|
32
|
+
},
|
|
33
|
+
closeButtonTopRight: {
|
|
34
|
+
alignSelf: 'flex-end',
|
|
35
|
+
padding: Spacing.lg,
|
|
36
|
+
},
|
|
37
|
+
header: {
|
|
38
|
+
paddingHorizontal: Spacing.lg,
|
|
39
|
+
paddingBottom: Spacing.md,
|
|
40
|
+
borderBottomWidth: 1,
|
|
41
|
+
borderBottomColor: Colors.border,
|
|
42
|
+
},
|
|
43
|
+
dialogHeader: {
|
|
44
|
+
flexDirection: 'row',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
justifyContent: 'space-between',
|
|
47
|
+
paddingHorizontal: Spacing.lg,
|
|
48
|
+
paddingVertical: Spacing.md,
|
|
49
|
+
borderBottomWidth: 1,
|
|
50
|
+
borderBottomColor: Colors.border,
|
|
51
|
+
},
|
|
52
|
+
title: {
|
|
53
|
+
fontSize: FontSize.lg,
|
|
54
|
+
fontWeight: '700',
|
|
55
|
+
color: Colors.text,
|
|
56
|
+
},
|
|
57
|
+
scrollView: {
|
|
58
|
+
flex: 1,
|
|
59
|
+
},
|
|
60
|
+
dialogScrollView: {
|
|
61
|
+
flexGrow: 0,
|
|
62
|
+
},
|
|
63
|
+
scrollContent: {
|
|
64
|
+
padding: Spacing.lg,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ViewStyle, DimensionValue } from 'react-native';
|
|
2
|
+
export interface SkeletonProps {
|
|
3
|
+
width: DimensionValue;
|
|
4
|
+
height: DimensionValue;
|
|
5
|
+
borderRadius?: number;
|
|
6
|
+
style?: ViewStyle;
|
|
7
|
+
}
|
|
8
|
+
export declare function Skeleton({ width, height, borderRadius, style, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Animated, StyleSheet } from 'react-native';
|
|
4
|
+
import { Colors, BorderRadius } from '../../theme/colors';
|
|
5
|
+
export function Skeleton({ width, height, borderRadius = BorderRadius.md, style, }) {
|
|
6
|
+
const opacity = useRef(new Animated.Value(0.4)).current;
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const animation = Animated.loop(Animated.sequence([
|
|
9
|
+
Animated.timing(opacity, {
|
|
10
|
+
toValue: 1,
|
|
11
|
+
duration: 800,
|
|
12
|
+
useNativeDriver: true,
|
|
13
|
+
}),
|
|
14
|
+
Animated.timing(opacity, {
|
|
15
|
+
toValue: 0.4,
|
|
16
|
+
duration: 800,
|
|
17
|
+
useNativeDriver: true,
|
|
18
|
+
}),
|
|
19
|
+
]));
|
|
20
|
+
animation.start();
|
|
21
|
+
return () => animation.stop();
|
|
22
|
+
}, [opacity]);
|
|
23
|
+
return (_jsx(Animated.View, { style: [
|
|
24
|
+
styles.skeleton,
|
|
25
|
+
{ width, height, borderRadius, opacity },
|
|
26
|
+
style,
|
|
27
|
+
] }));
|
|
28
|
+
}
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
skeleton: {
|
|
31
|
+
backgroundColor: Colors.skeleton,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline' | 'success';
|
|
4
|
+
export interface BadgeProps {
|
|
5
|
+
variant?: BadgeVariant;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
style?: ViewStyle;
|
|
8
|
+
textStyle?: TextStyle;
|
|
9
|
+
}
|
|
10
|
+
export declare function Badge({ variant, children, style, textStyle }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { Colors, Spacing, BorderRadius, FontSize } from '../../theme/colors';
|
|
4
|
+
const getVariantStyles = (variant) => {
|
|
5
|
+
switch (variant) {
|
|
6
|
+
case 'default':
|
|
7
|
+
return {
|
|
8
|
+
container: { backgroundColor: Colors.primary },
|
|
9
|
+
text: { color: Colors.textOnPrimary },
|
|
10
|
+
};
|
|
11
|
+
case 'secondary':
|
|
12
|
+
return {
|
|
13
|
+
container: { backgroundColor: Colors.primaryLight },
|
|
14
|
+
text: { color: Colors.primary },
|
|
15
|
+
};
|
|
16
|
+
case 'destructive':
|
|
17
|
+
return {
|
|
18
|
+
container: { backgroundColor: Colors.error },
|
|
19
|
+
text: { color: Colors.textOnPrimary },
|
|
20
|
+
};
|
|
21
|
+
case 'outline':
|
|
22
|
+
return {
|
|
23
|
+
container: {
|
|
24
|
+
backgroundColor: Colors.transparent,
|
|
25
|
+
borderWidth: 1,
|
|
26
|
+
borderColor: Colors.border,
|
|
27
|
+
},
|
|
28
|
+
text: { color: Colors.text },
|
|
29
|
+
};
|
|
30
|
+
case 'success':
|
|
31
|
+
return {
|
|
32
|
+
container: { backgroundColor: Colors.success },
|
|
33
|
+
text: { color: Colors.textOnPrimary },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export function Badge({ variant = 'default', children, style, textStyle }) {
|
|
38
|
+
const variantStyles = getVariantStyles(variant);
|
|
39
|
+
return (_jsx(View, { style: [styles.base, variantStyles.container, style], children: typeof children === 'string' ? (_jsx(Text, { style: [styles.text, variantStyles.text, textStyle], children: children })) : (children) }));
|
|
40
|
+
}
|
|
41
|
+
const styles = StyleSheet.create({
|
|
42
|
+
base: {
|
|
43
|
+
alignSelf: 'flex-start',
|
|
44
|
+
flexDirection: 'row',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
paddingHorizontal: Spacing.sm + 2,
|
|
47
|
+
paddingVertical: 2,
|
|
48
|
+
borderRadius: BorderRadius.full,
|
|
49
|
+
},
|
|
50
|
+
text: {
|
|
51
|
+
fontSize: FontSize.xs,
|
|
52
|
+
fontWeight: '600',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewStyle, TextStyle, TouchableOpacityProps } from 'react-native';
|
|
3
|
+
type ButtonVariant = 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive';
|
|
4
|
+
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface ButtonProps extends Omit<TouchableOpacityProps, 'style'> {
|
|
6
|
+
variant?: ButtonVariant;
|
|
7
|
+
size?: ButtonSize;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
style?: ViewStyle;
|
|
11
|
+
textStyle?: TextStyle;
|
|
12
|
+
}
|
|
13
|
+
export declare function Button({ variant, size, children, loading, disabled, style, textStyle, ...rest }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|