import JsonURL from '@jsonurl/jsonurl'; import { BookingPackageDetailsRequest, BookingPackageItem, BookingPackagePax, BookingPackageRequest, BookingPackageRequestRoom, BookingPackageSearchRequest } from '@qite/tide-client/build/types'; import { addDays, addMonths, addYears, format, formatISO } from 'date-fns'; import { isEmpty, now, omit } from 'lodash'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { ApiSettingsState } from '../../shared/types'; import { getTranslations } from '../../shared/utils/localization-util'; import { getDateAsDateFromParams, getRoomsFromParams } from '../../shared/utils/query-string-util'; import SettingsContext from '../settings-context'; import { DateRange, ProductRoom } from '../types'; import packageApi from '../utils/api'; import { formatPriceByMode } from '../utils/price'; import Dates from './dates'; import Footer from './footer'; import Header from './header'; import ListView from './list-view'; import Rooms from './rooms'; import { DATE_FORMAT } from '../constants'; interface ProductProps { productCode: string; productName: string; duration?: number; rating?: number; } const Product: React.FC = ({ productCode, productName, duration, rating }) => { const { apiKey, apiUrl, officeId, agentId, catalogueId, includeFlights, language, basePath, priceMode, addProductToQuery, isOffer, displayMode = 'calendar', searchType = 0 } = useContext(SettingsContext); const translations = getTranslations(language); const [loaded, setLoaded] = useState(false); const [isLoading, setIsLoading] = useState(false); const [roomsIsDisabled, setRoomsIsDisabled] = useState(true); const [price, setPrice] = useState(); const [hasFlight, setHasFlight] = useState(false); const [hasTransfer, setHasTransfer] = useState(false); const [rooms, setRooms] = useState([{ adults: 2, children: 0, childAges: [] }]); const [searchResponse, setSearchResponse] = useState([]); const [detailResponse, setDetailResponse] = useState(); const [dateRange, setDateRange] = useState(); const [packageProductName, setPackageProductName] = useState(productName); const [currencyCode, setCurrencyCode] = useState(''); const [searchResults, setSearchResults] = useState([]); const skipNextFetchRef = useRef(false); const fetchPackage = async (signal: AbortSignal) => { if (displayMode === 'list' && searchType !== 1) { console.error('The combination of searchType and displayMode is not supported.' + " Please set searchType to 1 when using displayMode 'list'."); return; } if (loaded && productCode && dateRange?.fromDate && dateRange?.toDate && rooms) { const apiSettingsState = apiKey && apiUrl ? ({ apiKey: apiKey, apiUrl: apiUrl } as ApiSettingsState) : undefined; const startDate = formatISO(dateRange?.fromDate, { representation: 'date' }) + 'T00:00:00Z'; const endDate = formatISO(dateRange?.toDate, { representation: 'date' }) + 'T00:00:00Z'; const requestRooms = rooms.map((room, i) => { const requestRoom = { index: i, pax: [] } as BookingPackageRequestRoom; for (var a = 0; a < room.adults; a++) { requestRoom.pax.push({ age: 30 } as BookingPackagePax); } for (var c = 0; c < room.children; c++) { requestRoom.pax.push({ age: room.childAges[c] } as BookingPackagePax); } return requestRoom; }); const detailsRequest: BookingPackageRequest = { officeId: officeId, agentId: agentId, payload: { searchType: 0, catalogueId: catalogueId, productCode: productCode, fromDate: startDate, toDate: endDate, includeFlights: includeFlights, rooms: requestRooms } }; const searchRequest: BookingPackageRequest = { officeId: officeId, agentId: agentId, payload: { searchType: 1, useExactDates: false, earliestFromOffset: 0, latestToOffset: 0, catalogueIds: [catalogueId], productCodes: [productCode], fromDate: new Date(now()).toISOString(), toDate: addYears(new Date(now()), 1).toISOString(), includeFlights: includeFlights, rooms: requestRooms } }; setIsLoading(true); if (displayMode === 'calendar' && searchType === 0) { setDetailResponse(await packageApi.fetchDetails(detailsRequest, signal, language, apiSettingsState)); } if (searchType === 1) { setSearchResponse(await packageApi.fetchSearch(searchRequest, signal, apiSettingsState)); } setIsLoading(false); } }; const handleBookClick = () => { const params: Record = {}; if (rooms) { const serialized = JsonURL.stringify( rooms.map((room) => omit(room, ['children'])), { AQF: true } ); if (serialized) { params['rooms'] = serialized; } } if (dateRange?.fromDate) { params['startDate'] = format(dateRange.fromDate, DATE_FORMAT); } if (dateRange?.toDate) { params['endDate'] = format(dateRange.toDate, DATE_FORMAT); } params['catalogueId'] = catalogueId.toString(); if (addProductToQuery) { params['productCode'] = productCode; params['productName'] = encodeURI(packageProductName)!; } const path = window.location.pathname; const paramString = Object.keys(params) .map((key) => `${key}=${params[key]}`) .join('&'); window.location.href = basePath.startsWith('/') ? `${window.location.protocol}//${window.location.host}${basePath}?${paramString}` : `${path}${path.endsWith('/') ? '' : '/'}${basePath}?${paramString}`; }; const handleRoomChange = (rooms: ProductRoom[]) => { setRooms(rooms); }; const handleDateChange = (value: DateRange) => { if (searchType === 1) { skipNextFetchRef.current = true; } setDateRange(value); if (value.fromDate && value.toDate && searchType === 1 && searchResults.length > 0) { const from = format(value.fromDate, DATE_FORMAT); const to = format(value.toDate, DATE_FORMAT); const selectedItem = searchResults.find((item) => { const itemFrom = format(new Date(item.fromDate), DATE_FORMAT); const itemTo = format(new Date(item.toDate), DATE_FORMAT); return itemFrom === from && itemTo === to; }); if (selectedItem) { setPrice(selectedItem.price); setPackageProductName(selectedItem.name); } } }; const handleDateSelect = (selectedItem: BookingPackageItem) => { if (searchType === 1) { skipNextFetchRef.current = true; } const fromDate = new Date(selectedItem.fromDate); const toDate = new Date(selectedItem.toDate); setDateRange({ fromDate, toDate }); setPrice(selectedItem.price); setPackageProductName(selectedItem.name); }; useEffect(() => { if (searchResponse && !isEmpty(searchResponse)) { setSearchResults(searchResponse); const selectedItem = searchResponse.find( (item) => new Date(item.fromDate).getTime() === dateRange?.fromDate?.getTime() && new Date(item.toDate).getTime() === dateRange?.toDate?.getTime() ); if (selectedItem) { setPrice(selectedItem.price); setPackageProductName(selectedItem.name); setCurrencyCode(selectedItem.currencyCode); } else { setPrice(searchResponse[0].price); setPackageProductName(searchResponse[0].name); setCurrencyCode(searchResponse[0].currencyCode); } } else { setSearchResults([]); setIsLoading(false); setPackageProductName(translations.PRODUCT.NOT_AVAILABLE); } }, [searchResponse]); useEffect(() => { if (detailResponse && !detailResponse.errorCode && detailResponse.payload) { const selectedOption = detailResponse.payload.options.find((x: any) => x.isSelected); if (selectedOption) { const hasFlight = selectedOption.includedServiceTypes.some((x: any) => x === 7); const hasTranfer = selectedOption.includedServiceTypes.some((x: any) => x === 13); setPrice(selectedOption.price); setHasFlight(hasFlight); setHasTransfer(hasTranfer); setCurrencyCode(detailResponse.payload.currencyCode); setPackageProductName(selectedOption.name); } } else if (detailResponse !== undefined) { setPrice(undefined); setHasFlight(false); setHasTransfer(false); setCurrencyCode(''); } }, [detailResponse]); useEffect(() => { if (searchType === 1 && skipNextFetchRef.current) { skipNextFetchRef.current = false; return; } const controller = new AbortController(); const { signal } = controller; (async () => { fetchPackage(signal); })(); return () => { controller.abort(); }; }, [dateRange?.fromDate?.valueOf(), dateRange?.toDate?.valueOf(), JSON.stringify(rooms)]); useEffect(() => { const params = new URLSearchParams(location.search); const rooms = getRoomsFromParams(params, 'rooms'); const from = getDateAsDateFromParams(params, 'from') || getDateAsDateFromParams(params, 'startDate'); const to = getDateAsDateFromParams(params, 'to') || getDateAsDateFromParams(params, 'endDate'); if (rooms) { setRooms(rooms); } if (from && duration) { const durationTo = new Date(Date.UTC(from.getFullYear(), from.getMonth(), from.getDate() + duration)); setDateRange({ fromDate: from, toDate: durationTo }); } else if (from && to) { setDateRange({ fromDate: from, toDate: to }); } else if (duration) { const now = new Date(); const durationFrom = new Date(Date.UTC(now.getFullYear(), now.getMonth() + 3, now.getDate())); const durationTo = new Date(Date.UTC(durationFrom.getFullYear(), durationFrom.getMonth(), durationFrom.getDate() + duration)); setDateRange({ fromDate: durationFrom, toDate: durationTo }); } else { const now = new Date(); const startDate = addMonths(now, 1); const endDate = addDays(startDate, 7); setDateRange({ fromDate: startDate, toDate: endDate }); } setLoaded(true); }, [location]); useEffect(() => { if (searchType === 1 && searchResults.length > 0) { const first = searchResults[0]; setDateRange({ fromDate: new Date(first.fromDate), toDate: new Date(first.toDate) }); setPackageProductName(first.name); setPrice(first.price); setCurrencyCode(first.currencyCode); skipNextFetchRef.current = true; } }, [searchResults, searchType]); const personCount = rooms.reduce((s, r) => s + r.adults + r.children, 0); const durationCount = 0; const priceText = formatPriceByMode( price, priceMode, personCount, durationCount, translations.PRODUCT.PER_PERSON, translations.PRODUCT.PER_NIGHT, translations.PRODUCT.PER_PERSON_PER_NIGHT, currencyCode ); return (
{displayMode === 'calendar' && ( ({ fromDate: new Date(item.fromDate), toDate: new Date(item.toDate) }))} /> )} {displayMode === 'list' && }
); }; export default Product;