From 87402a209203be57b882b004f6b857e53590ae94 Mon Sep 17 00:00:00 2001 From: Anton Gunnarsson Date: Mon, 1 Sep 2025 08:37:00 +0000 Subject: [PATCH] Merged in feat/sw-2873-move-selecthotel-to-booking-flow (pull request #2727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(SW-2873): Move select-hotel to booking flow * crude setup of select-hotel in partner-sas * wip * Fix linting * restructure tracking files * Remove dependency on trpc in tracking hooks * Move pageview tracking to common * Fix some lint and import issues * Add AlternativeHotelsPage * Add SelectHotelMapPage * Add AlternativeHotelsMapPage * remove next dependency in tracking store * Remove dependency on react in tracking hooks * move isSameBooking to booking-flow * Inject searchParamsComparator into tracking store * Move useTrackHardNavigation to common * Move useTrackSoftNavigation to common * Add TrackingSDK to partner-sas * call serverclient in layout * Remove unused css * Update types * Move HotelPin type * Fix todos * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Merge branch 'master' into feat/sw-2873-move-selecthotel-to-booking-flow * Fix component Approved-by: Joakim Jäderberg --- .../alternative-hotels/map/page.tsx | 28 ++ .../(standard)/alternative-hotels/page.tsx | 26 ++ .../(standard)/select-hotel/map/page.tsx | 24 ++ .../(standard)/select-hotel/page.tsx | 26 +- apps/partner-sas/app/[lang]/layout.tsx | 52 ++- apps/partner-sas/app/[lang]/page.tsx | 5 - apps/partner-sas/app/utils/tracking.ts | 4 + apps/partner-sas/components/TrackingSDK.tsx | 55 +++ apps/partner-sas/hooks/useLang.ts | 17 + apps/partner-sas/types/params.ts | 19 + .../(standard)/alternative-hotels/loading.tsx | 2 +- .../alternative-hotels/map/page.tsx | 35 +- .../(standard)/alternative-hotels/page.tsx | 129 +------ .../(standard)/details/page.tsx | 4 +- .../hotelreservation/(standard)/page.tsx | 9 +- .../(standard)/select-hotel/loading.tsx | 2 +- .../(standard)/select-hotel/map/page.tsx | 35 +- .../(standard)/select-hotel/page.tsx | 109 +----- .../hotelreservation/my-stay/tracking.tsx | 8 +- apps/scandic-web/app/[lang]/(live)/layout.tsx | 14 +- .../hotelreservation/my-stay/tracking.tsx | 8 +- apps/scandic-web/auth.ts | 2 +- .../components/BookingFlowProviders.tsx | 30 ++ .../DestinationPage/Tracking/index.tsx | 2 +- .../ContentType/HotelMapPage/Client.tsx | 3 +- .../components/ContentType/HotelPage/utils.ts | 10 +- .../ContentType/StaticPages/staticPage.ts | 2 +- .../components/Current/Tracking.tsx | 2 +- .../components/Forms/Signup/index.tsx | 2 +- .../BookingConfirmation/Tracking/tracking.ts | 16 +- .../EnterDetails/Details/Multiroom/index.tsx | 2 +- .../EnterDetails/Details/RoomOne/index.tsx | 2 +- .../EnterDetails/Header/index.tsx | 2 +- .../EnterDetails/Tracking/tracking.ts | 12 +- .../MyStay/Receipt/tracking.tsx | 8 +- .../MyStay/Rooms/MultiRoom/Room.tsx | 2 +- .../Rooms/SingleRoom/Details/Terms/Terms.tsx | 2 +- .../MyStay/utils/mapRoomDetails.ts | 2 +- .../SelectHotelMapContainer.tsx | 114 ------ .../RateSummary/DesktopSummary.tsx | 2 +- .../SelectedRoomPanel/index.tsx | 2 +- .../Rooms/RoomsList/RoomsListSkeleton.tsx | 2 +- .../RoomsContainer/RoomsContainerSkeleton.tsx | 2 +- .../SelectRate/Tracking/tracking.ts | 18 +- .../HotelReservation/SelectRate/index.tsx | 7 +- .../components/LoginButton/index.tsx | 3 +- apps/scandic-web/components/RouteChange.tsx | 10 +- .../components/TrackingSDK/hooks.ts | 345 ------------------ .../components/TrackingSDK/index.tsx | 39 +- .../components/TrackingSDK/useFormTracking.ts | 78 ++++ apps/scandic-web/constants/booking.ts | 2 - .../SelectRate/isRateSelected.test.ts | 2 +- .../contexts/SelectRate/isRateSelected.ts | 2 +- .../lib/trpc/memoizedRequests/index.ts | 98 +---- .../providers/EnterDetailsProvider.tsx | 6 +- .../stores/enter-details/helpers.ts | 27 -- .../selectHotel/availabilityInput.ts | 18 - .../selectHotel/filterAndSortModal.ts | 6 - .../selectHotel/filterCheckbox.ts | 7 - .../selectHotel/hotelCardListingProps.ts | 25 -- .../selectHotel/hotelCardProps.ts | 11 - .../hotelReservation/selectHotel/hotelData.ts | 11 + .../hotelReservation/selectHotel/map.ts | 30 -- .../selectHotel/noAvailabilityAlert.ts | 10 - .../hotelReservation/selectRate/selectRate.ts | 2 +- apps/scandic-web/utils/tracking/booking.ts | 2 +- apps/scandic-web/utils/tracking/index.ts | 6 +- apps/scandic-web/utils/tracking/navigation.ts | 2 +- apps/scandic-web/utils/tracking/payment.ts | 2 +- packages/booking-flow/env/server.ts | 9 +- .../booking-flow/lib/bookingFlowContext.tsx | 23 ++ .../components/AlternativeHotelsPageTitle.tsx | 21 ++ .../components/BookingFlowContextProvider.tsx | 17 + .../FnFNotAllowedAlert.module.css | 0 .../components/FnFNotAllowedAlert/index.tsx | 10 +- .../hotelCardDialogListing.module.css | 0 .../HotelCardDialogListing/index.tsx | 14 +- .../HotelCardDialogListing/utils.ts | 38 +- .../hotelCardListing.module.css | 0 .../components}/HotelCardListing/index.tsx | 43 ++- .../lib/components}/HotelCardListing/utils.ts | 5 +- .../hotelSidePeek.module.css | 0 .../HotelSidePeekContent/index.tsx | 15 +- .../HotelDetailsSidePeek/index.tsx | 12 +- .../ListingHotelCardDialog/index.tsx | 16 +- .../listingHotelCardDialog.module.css | 0 .../lib}/components/MapContainer/index.tsx | 0 .../MapContainer/mapModal.module.css | 0 .../RoomCardSkeleton.module.css | 0 .../RoomCardSkeleton/RoomCardSkeleton.tsx | 0 .../filterAndSortModal.module.css | 0 .../Filters/FilterAndSortModal/index.tsx | 23 +- .../FilterCheckbox/filterCheckbox.module.css | 0 .../FilterContent/FilterCheckbox/index.tsx | 8 +- .../FilterContent/filterContent.module.css | 0 .../Filters/FilterContent/index.tsx | 6 +- .../HotelFilter/hotelFilter.module.css | 0 .../SelectHotel/Filters/HotelFilter/index.tsx | 26 +- .../components}/SelectHotel/Filters/index.ts | 0 .../SelectHotel/HotelCount/index.tsx | 2 +- .../SelectHotel/HotelSorter/index.tsx | 28 +- .../MapWithButtonWrapper/index.tsx | 28 ++ .../mapWithButtonWrapper.module.css | 16 + .../MobileMapButtonContainer/index.tsx | 5 +- .../mobileMapButtonContainer.module.css | 0 .../SelectHotel/NoAvailabilityAlert.tsx | 26 +- .../HotelListing/hotelListing.module.css | 0 .../SelectHotelMap/HotelListing/index.tsx | 17 +- .../SelectHotelMapContent/index.tsx | 58 +-- .../selectHotelMapContent.module.css | 0 .../SelectHotelMapContent/utils.ts | 4 +- .../SelectHotelMapSkeleton.module.css | 0 .../SelectHotelMap/SelectHotelMapSkeleton.tsx | 6 +- .../SelectHotel/SelectHotelMap/index.tsx | 30 +- .../SelectHotel/SelectHotelSkeleton.tsx | 0 .../lib/components}/SelectHotel/helpers.ts | 42 ++- .../lib/components}/SelectHotel/index.tsx | 45 ++- .../SelectHotel/selectHotel.module.css | 0 .../booking-flow/lib/components/StaticMap.tsx | 22 ++ .../lib}/hooks/useInitializeFiltersFromUrl.ts | 2 +- .../booking-flow/lib/hooks/useIsLoggedIn.ts | 7 + .../lib/misc/getHotelSearchDetails.ts | 41 +-- .../booking-flow/lib/misc/imageGallery.ts | 16 + .../lib/misc/selectHotelTracking.ts | 21 +- .../booking-flow/lib/misc/sortOrder.ts | 9 - .../lib/pages/AlternativeHotelsMapPage.tsx | 134 +++++++ .../lib/pages/AlternativeHotelsPage.tsx | 137 +++++++ .../lib/pages/SelectHotelMapPage.tsx | 136 +++++++ .../lib/pages/SelectHotelPage.tsx | 120 ++++++ .../booking-flow/lib}/stores/hotel-filters.ts | 0 .../booking-flow/lib}/stores/hotels-map.ts | 0 packages/booking-flow/lib/trackingContext.tsx | 1 + .../booking-flow/lib/trpc/memoizedRequests.ts | 11 + .../memoizedRequests/getCityCoordinates.ts | 12 + .../lib/trpc/memoizedRequests/getLocations.ts | 12 + packages/booking-flow/lib/types.ts | 12 + .../booking-flow/lib/utils/isSameBooking.ts | 68 ++++ packages/booking-flow/package.json | 34 +- packages/common/constants/familyAndFriends.ts | 1 + .../types => common/constants}/loginType.ts | 0 .../lib/enums => common/constants}/rate.ts | 0 packages/common/package.json | 56 +-- .../common}/stores/router-transition.ts | 0 .../common}/stores/tracking.ts | 44 +-- .../common}/tracking/pageview.ts | 4 +- .../common/tracking/types.ts | 11 +- .../common/tracking/useTrackHardNavigation.ts | 166 +++++++++ .../common/tracking/useTrackSoftNavigation.ts | 106 ++++++ .../common}/utils/promiseWithTimeout.ts | 0 packages/trpc/jwt.d.ts | 2 +- packages/trpc/lib/routers/hotels/output.ts | 2 +- packages/trpc/lib/routers/hotels/query.ts | 2 +- .../schemas/roomAvailability/product.ts | 3 +- packages/trpc/lib/routers/types.ts | 3 +- packages/trpc/lib/routers/user/query.ts | 3 +- packages/trpc/lib/types/room.ts | 3 +- yarn.lock | 5 +- 157 files changed, 2026 insertions(+), 1376 deletions(-) create mode 100644 apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx create mode 100644 apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx create mode 100644 apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx create mode 100644 apps/partner-sas/components/TrackingSDK.tsx create mode 100644 apps/partner-sas/hooks/useLang.ts create mode 100644 apps/partner-sas/types/params.ts create mode 100644 apps/scandic-web/components/BookingFlowProviders.tsx delete mode 100644 apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx delete mode 100644 apps/scandic-web/components/TrackingSDK/hooks.ts create mode 100644 apps/scandic-web/components/TrackingSDK/useFormTracking.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/availabilityInput.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/filterAndSortModal.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/filterCheckbox.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardProps.ts create mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/hotelData.ts delete mode 100644 apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts create mode 100644 packages/booking-flow/lib/bookingFlowContext.tsx create mode 100644 packages/booking-flow/lib/components/AlternativeHotelsPageTitle.tsx create mode 100644 packages/booking-flow/lib/components/BookingFlowContextProvider.tsx rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/FnFNotAllowedAlert/FnFNotAllowedAlert.module.css (100%) rename apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.tsx => packages/booking-flow/lib/components/FnFNotAllowedAlert/index.tsx (78%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardDialogListing/hotelCardDialogListing.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardDialogListing/index.tsx (91%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardDialogListing/utils.ts (65%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardListing/hotelCardListing.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardListing/index.tsx (91%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/HotelCardListing/utils.ts (91%) rename {apps/scandic-web/components/SidePeeks => packages/booking-flow/lib/components}/HotelDetailsSidePeek/HotelSidePeekContent/hotelSidePeek.module.css (100%) rename {apps/scandic-web/components/SidePeeks => packages/booking-flow/lib/components}/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx (80%) rename {apps/scandic-web/components/SidePeeks => packages/booking-flow/lib/components}/HotelDetailsSidePeek/index.tsx (89%) rename {apps/scandic-web/components/HotelReservation/HotelCardDialog => packages/booking-flow/lib/components}/ListingHotelCardDialog/index.tsx (91%) rename {apps/scandic-web/components/HotelReservation/HotelCardDialog => packages/booking-flow/lib/components}/ListingHotelCardDialog/listingHotelCardDialog.module.css (100%) rename {apps/scandic-web => packages/booking-flow/lib}/components/MapContainer/index.tsx (100%) rename {apps/scandic-web => packages/booking-flow/lib}/components/MapContainer/mapModal.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/RoomCardSkeleton/RoomCardSkeleton.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/RoomCardSkeleton/RoomCardSkeleton.tsx (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterAndSortModal/filterAndSortModal.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterAndSortModal/index.tsx (94%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterContent/FilterCheckbox/filterCheckbox.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx (87%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterContent/filterContent.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/FilterContent/index.tsx (94%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/HotelFilter/hotelFilter.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/HotelFilter/index.tsx (78%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/Filters/index.ts (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/HotelCount/index.tsx (88%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/HotelSorter/index.tsx (82%) create mode 100644 packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/index.tsx create mode 100644 packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/mapWithButtonWrapper.module.css rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/MobileMapButtonContainer/index.tsx (90%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/MobileMapButtonContainer/mobileMapButtonContainer.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/NoAvailabilityAlert.tsx (78%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/HotelListing/index.tsx (63%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx (86%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/SelectHotelMapContent/selectHotelMapContent.module.css (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts (80%) rename apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.module.css => packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.module.css (100%) rename apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.tsx => packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.tsx (72%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelMap/index.tsx (55%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/SelectHotelSkeleton.tsx (100%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/helpers.ts (91%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/index.tsx (75%) rename {apps/scandic-web/components/HotelReservation => packages/booking-flow/lib/components}/SelectHotel/selectHotel.module.css (100%) create mode 100644 packages/booking-flow/lib/components/StaticMap.tsx rename {apps/scandic-web => packages/booking-flow/lib}/hooks/useInitializeFiltersFromUrl.ts (88%) create mode 100644 packages/booking-flow/lib/hooks/useIsLoggedIn.ts rename apps/scandic-web/utils/hotelSearchDetails.ts => packages/booking-flow/lib/misc/getHotelSearchDetails.ts (61%) create mode 100644 packages/booking-flow/lib/misc/imageGallery.ts rename apps/scandic-web/components/HotelReservation/SelectHotel/tracking.ts => packages/booking-flow/lib/misc/selectHotelTracking.ts (89%) rename apps/scandic-web/types/components/hotelReservation/selectHotel/hotelSorter.ts => packages/booking-flow/lib/misc/sortOrder.ts (52%) create mode 100644 packages/booking-flow/lib/pages/AlternativeHotelsMapPage.tsx create mode 100644 packages/booking-flow/lib/pages/AlternativeHotelsPage.tsx create mode 100644 packages/booking-flow/lib/pages/SelectHotelMapPage.tsx create mode 100644 packages/booking-flow/lib/pages/SelectHotelPage.tsx rename {apps/scandic-web => packages/booking-flow/lib}/stores/hotel-filters.ts (100%) rename {apps/scandic-web => packages/booking-flow/lib}/stores/hotels-map.ts (100%) create mode 100644 packages/booking-flow/lib/trpc/memoizedRequests/getCityCoordinates.ts create mode 100644 packages/booking-flow/lib/trpc/memoizedRequests/getLocations.ts create mode 100644 packages/booking-flow/lib/utils/isSameBooking.ts create mode 100644 packages/common/constants/familyAndFriends.ts rename packages/{trpc/lib/types => common/constants}/loginType.ts (100%) rename packages/{trpc/lib/enums => common/constants}/rate.ts (100%) rename {apps/scandic-web => packages/common}/stores/router-transition.ts (100%) rename {apps/scandic-web => packages/common}/stores/tracking.ts (64%) rename {apps/scandic-web/utils => packages/common}/tracking/pageview.ts (86%) rename apps/scandic-web/types/components/tracking.ts => packages/common/tracking/types.ts (94%) create mode 100644 packages/common/tracking/useTrackHardNavigation.ts create mode 100644 packages/common/tracking/useTrackSoftNavigation.ts rename {apps/scandic-web => packages/common}/utils/promiseWithTimeout.ts (100%) diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx new file mode 100644 index 000000000..b2419ae87 --- /dev/null +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/map/page.tsx @@ -0,0 +1,28 @@ +import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" + +import type { LangParams, PageArgs } from "@/types/params" + +export default async function AlternativeHotelsMapPage( + props: PageArgs +) { + const searchParams = await props.searchParams + const lang = await getLang() + + return ( +
+ ( + + )} + /> +
+ ) +} diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx new file mode 100644 index 000000000..ca51ac659 --- /dev/null +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/alternative-hotels/page.tsx @@ -0,0 +1,26 @@ +import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" + +import { type LangParams, type PageArgs } from "@/types/params" + +export default async function AlternativeHotelsPage( + props: PageArgs +) { + const searchParams = await props.searchParams + const lang = await getLang() + + return ( + ( + + )} + /> + ) +} diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx new file mode 100644 index 000000000..0a632b843 --- /dev/null +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/map/page.tsx @@ -0,0 +1,24 @@ +import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" + +import type { LangParams, PageArgs } from "@/types/params" + +export default async function SelectHotelMapPage(props: PageArgs) { + const searchParams = await props.searchParams + const lang = await getLang() + + return ( + ( + + )} + /> + ) +} diff --git a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx index a6d92296f..725117572 100644 --- a/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx +++ b/apps/partner-sas/app/[lang]/hotelreservation/(standard)/select-hotel/page.tsx @@ -1,4 +1,24 @@ -export default async function SelectHotelPage() { - // eslint-disable-next-line formatjs/no-literal-string-in-jsx - return
select-hotel
+import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" + +import type { LangParams, PageArgs } from "@/types/params" + +export default async function SelectHotelPage(props: PageArgs) { + const searchParams = await props.searchParams + const lang = await getLang() + + return ( + ( + + )} + /> + ) } diff --git a/apps/partner-sas/app/[lang]/layout.tsx b/apps/partner-sas/app/[lang]/layout.tsx index e797fdc6d..117d1dc1d 100644 --- a/apps/partner-sas/app/[lang]/layout.tsx +++ b/apps/partner-sas/app/[lang]/layout.tsx @@ -4,10 +4,13 @@ import "@scandic-hotels/design-system/normalize.css" import "@scandic-hotels/design-system/design-system-new-deprecated.css" import "../../globals.css" +import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider" import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider" import { Lang } from "@scandic-hotels/common/constants/language" import { TrpcProvider } from "@scandic-hotels/trpc/Provider" +import { serverClient } from "@/lib/trpc" + import { getMessages } from "@/i18n" import ClientIntlProvider from "@/i18n/Provider" import { setLang } from "@/i18n/serverContext" @@ -15,6 +18,7 @@ import { setLang } from "@/i18n/serverContext" import { trackAccordionItemOpen, trackBookingSearchClick, + trackGenericEvent, trackOpenSidePeek, } from "../utils/tracking" @@ -43,6 +47,10 @@ export default async function RootLayout(props: RootLayoutProps) { setLang(lang) const messages = await getMessages(lang) + // TODO we need this import right now to ensure configureServerClient is called, + // but check where we do this + const _caller = await serverClient() + return ( {/* TODO */} @@ -55,28 +63,36 @@ export default async function RootLayout(props: RootLayoutProps) { > {/* TODO handle onError */} - -
- {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} -

SAS

-
-
{children}
-
+
+ {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} +

SAS

+
+
{children}
+ +
diff --git a/apps/partner-sas/app/[lang]/page.tsx b/apps/partner-sas/app/[lang]/page.tsx index c42416e12..2aca0259f 100644 --- a/apps/partner-sas/app/[lang]/page.tsx +++ b/apps/partner-sas/app/[lang]/page.tsx @@ -1,8 +1,6 @@ import { BookingWidget } from "@scandic-hotels/booking-flow/BookingWidget" import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/utils/url" -import { serverClient } from "@/lib/trpc" - import { getLang } from "@/i18n/serverContext" import type { Lang } from "@scandic-hotels/common/constants/language" @@ -14,9 +12,6 @@ type SearchParams = { export default async function Home(props: SearchParams<{ lang: Lang }>) { const searchParams = await props.searchParams - // TODO we need this import right now to ensure configureServerClient is called, - // but we should ensure it's called in a layout instead. - const _caller = await serverClient() const lang = await getLang() const booking = parseBookingWidgetSearchParams(searchParams) diff --git a/apps/partner-sas/app/utils/tracking.ts b/apps/partner-sas/app/utils/tracking.ts index 13faaa5bb..1c400d06b 100644 --- a/apps/partner-sas/app/utils/tracking.ts +++ b/apps/partner-sas/app/utils/tracking.ts @@ -22,3 +22,7 @@ export function trackOpenSidePeek(input: { }) { console.warn("TODO: Implement trackOpenSidePeek", { input }) } + +export function trackGenericEvent(data: any) { + console.warn("TODO: Implement trackGenericEvent", { data }) +} diff --git a/apps/partner-sas/components/TrackingSDK.tsx b/apps/partner-sas/components/TrackingSDK.tsx new file mode 100644 index 000000000..4cd48ab47 --- /dev/null +++ b/apps/partner-sas/components/TrackingSDK.tsx @@ -0,0 +1,55 @@ +"use client" + +import { usePathname } from "next/navigation" + +import { useTrackHardNavigation } from "@scandic-hotels/common/tracking/useTrackHardNavigation" +import { useTrackSoftNavigation } from "@scandic-hotels/common/tracking/useTrackSoftNavigation" +import { trpc } from "@scandic-hotels/trpc/client" + +import useLang from "@/hooks/useLang" + +import type { + TrackingSDKAncillaries, + TrackingSDKHotelInfo, + TrackingSDKPageData, + TrackingSDKPaymentInfo, +} from "@scandic-hotels/common/tracking/types" + +export default function TrackingSDK({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, +}: { + pageData: TrackingSDKPageData + hotelInfo?: TrackingSDKHotelInfo + paymentInfo?: TrackingSDKPaymentInfo + ancillaries?: TrackingSDKAncillaries +}) { + const lang = useLang() + const pathName = usePathname() + const { data, isError } = trpc.user.userTrackingInfo.useQuery({ + lang, + }) + + const userData = isError ? ({ loginStatus: "Error" } as const) : data + + useTrackHardNavigation({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, + }) + useTrackSoftNavigation({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, + }) + + return null +} diff --git a/apps/partner-sas/hooks/useLang.ts b/apps/partner-sas/hooks/useLang.ts new file mode 100644 index 000000000..e3b9367ba --- /dev/null +++ b/apps/partner-sas/hooks/useLang.ts @@ -0,0 +1,17 @@ +"use client" +import { useParams } from "next/navigation" + +import { Lang } from "@scandic-hotels/common/constants/language" +import { languageSchema } from "@scandic-hotels/common/utils/languages" + +/** + * A hook to get the current lang from the URL + */ +export default function useLang() { + const { lang } = useParams<{ + lang: Lang + }>() + + const parsedLang = languageSchema.safeParse(lang) + return parsedLang.success ? parsedLang.data : Lang.en +} diff --git a/apps/partner-sas/types/params.ts b/apps/partner-sas/types/params.ts new file mode 100644 index 000000000..dafa9af91 --- /dev/null +++ b/apps/partner-sas/types/params.ts @@ -0,0 +1,19 @@ +import type { Lang } from "@scandic-hotels/common/constants/language" + +type NextSearchParams = { [key: string]: string | string[] | undefined } + +type SearchParams = { + searchParams: Promise +} + +type Params

= { + params: Promise

+} + +export type LangParams = { + lang: Lang +} + +export type LayoutArgs

= P extends undefined ? {} : Params

+ +export type PageArgs

= LayoutArgs

& SearchParams diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/loading.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/loading.tsx index d1dafb91b..db72f1b3a 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/loading.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/loading.tsx @@ -1,4 +1,4 @@ -import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton" +import { SelectHotelSkeleton } from "@scandic-hotels/booking-flow/components/SelectHotel" export default function AlternativeHotelsLoading() { return diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx index 76d833824..deb30b8e8 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/map/page.tsx @@ -1,35 +1,30 @@ -import { notFound } from "next/navigation" -import { Suspense } from "react" +import { AlternativeHotelsMapPage as AlternativeHotelsMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsMapPage" -import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url" - -import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer" -import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton" -import { MapContainer } from "@/components/MapContainer" +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" import styles from "./page.module.css" import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" -export default async function SelectHotelMapPage( +export default async function AlternativeHotelsMapPage( props: PageArgs ) { const searchParams = await props.searchParams - - const booking = parseSelectHotelSearchParams(searchParams) - - if (!booking) return notFound() + const lang = await getLang() return (

- - } - > - - - + ( + + )} + />
) } diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx index 9310f1a3e..ed7d4c2b4 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/alternative-hotels/page.tsx @@ -1,20 +1,7 @@ -import stringify from "json-stable-stringify-without-jsonify" -import { cookies } from "next/headers" -import { notFound } from "next/navigation" -import { Suspense } from "react" +import { AlternativeHotelsPage as AlternativeHotelsPagePrimitive } from "@scandic-hotels/booking-flow/pages/AlternativeHotelsPage" -import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url" -import { alternativeHotelsMap } from "@scandic-hotels/common/constants/routes/hotelReservation" - -import { FamilyAndFriendsCodes } from "@/constants/booking" - -import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert" -import SelectHotel from "@/components/HotelReservation/SelectHotel" -import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers" -import { getSelectHotelTracking } from "@/components/HotelReservation/SelectHotel/tracking" import TrackingSDK from "@/components/TrackingSDK" -import { getIntl } from "@/i18n" -import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" +import { getLang } from "@/i18n/serverContext" import { type LangParams, @@ -26,112 +13,18 @@ export default async function AlternativeHotelsPage( props: PageArgs ) { const searchParams = await props.searchParams - const params = await props.params + const lang = await getLang() - const booking = parseSelectHotelSearchParams(searchParams) - - if (!booking) return notFound() - - const searchDetails = await getHotelSearchDetails(booking, true) - - if (!searchDetails || !searchDetails.hotel || !searchDetails.city) { - return notFound() - } - - if ( - booking.bookingCode && - FamilyAndFriendsCodes.includes(booking.bookingCode) - ) { - const cookieStore = await cookies() - const isInvalidFNF = cookieStore.get("sc")?.value !== "1" - - if (isInvalidFNF) { - return - } - } - - // TODO: This needs to be refactored into its - // own functions - const hotels = await getHotels({ - fromDate: booking.fromDate, - toDate: booking.toDate, - rooms: booking.rooms, - isAlternativeFor: searchDetails.hotel, - bookingCode: booking.bookingCode, - city: searchDetails.city, - redemption: !!searchDetails.redemption, - }) - - const arrivalDate = new Date(booking.fromDate) - const departureDate = new Date(booking.toDate) - - const isRedemptionAvailability = searchDetails.redemption - ? hotels.some( - (hotel) => hotel.availability.productType?.redemptions?.length - ) - : false - - const isBookingCodeRateAvailable = booking.bookingCode - ? hotels.some( - (hotel) => - hotel.availability.bookingCode && - hotel.availability.status === "Available" - ) - : false - - const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ - lang: params.lang, - pageId: searchDetails.hotel ? "alternative-hotels" : "select-hotel", - pageName: searchDetails.hotel - ? "hotelreservation|alternative-hotels" - : "hotelreservation|select-hotel", - siteSections: searchDetails.hotel - ? "hotelreservation|alternative-hotels" - : "hotelreservation|select-hotel", - arrivalDate, - departureDate, - rooms: booking.rooms, - hotelsResult: hotels?.length ?? 0, - searchTerm: searchDetails.hotel - ? booking.hotelId - : searchDetails.cityIdentifier, - country: hotels?.[0]?.hotel.address.country, - hotelCity: hotels?.[0]?.hotel.address.city, - bookingCode: booking.bookingCode, - isBookingCodeRateAvailable, - isRedemption: searchDetails.redemption, - isRedemptionAvailable: isRedemptionAvailability, - }) - - const mapHref = alternativeHotelsMap(params.lang) - - const intl = await getIntl() - const title = intl.formatMessage( - { - defaultMessage: "Alternatives for {value}", - }, - { - value: searchDetails.hotel.name, - } - ) - const suspenseKey = stringify(searchParams) return ( - <> - - + ( - - + )} + /> ) } diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index 821c8cd52..6dd1ebf56 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -2,9 +2,10 @@ import { cookies } from "next/headers" import { notFound } from "next/navigation" import { Suspense } from "react" +import FnFNotAllowedAlert from "@scandic-hotels/booking-flow/components/FnFNotAllowedAlert" import { parseDetailsSearchParams } from "@scandic-hotels/booking-flow/utils/url" +import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends" -import { FamilyAndFriendsCodes } from "@/constants/booking" import { getBreakfastPackages, getHotel, @@ -19,7 +20,6 @@ import RoomOne from "@/components/HotelReservation/EnterDetails/Room/One" import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop" import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile" import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking" -import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert" import RoomProvider from "@/providers/Details/RoomProvider" import EnterDetailsProvider from "@/providers/EnterDetailsProvider" diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx index 4d5dbf099..8ac40de70 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/page.tsx @@ -1,11 +1,12 @@ +import { + TrackingChannelEnum, + type TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" + import TrackingSDK from "@/components/TrackingSDK" import styles from "./page.module.css" -import { - TrackingChannelEnum, - type TrackingSDKPageData, -} from "@/types/components/tracking" import type { LangParams, PageArgs } from "@/types/params" export default async function HotelReservationPage( diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/loading.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/loading.tsx index 5a17cab56..e06c13975 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/loading.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/loading.tsx @@ -1,4 +1,4 @@ -import { SelectHotelSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelSkeleton" +import { SelectHotelSkeleton } from "@scandic-hotels/booking-flow/components/SelectHotel" export default function SelectHotelLoading() { return diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx index 331d11752..7008c7226 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/map/page.tsx @@ -1,12 +1,7 @@ -import stringify from "json-stable-stringify-without-jsonify" -import { notFound } from "next/navigation" -import { Suspense } from "react" +import { SelectHotelMapPage as SelectHotelMapPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelMapPage" -import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url" - -import { SelectHotelMapContainer } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer" -import { SelectHotelMapContainerSkeleton } from "@/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton" -import { MapContainer } from "@/components/MapContainer" +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" import styles from "./page.module.css" @@ -16,22 +11,20 @@ export default async function SelectHotelMapPage( props: PageArgs ) { const searchParams = await props.searchParams - const suspenseKey = stringify(searchParams) - - const booking = parseSelectHotelSearchParams(searchParams) - - if (!booking) return notFound() + const lang = await getLang() return (
- - } - > - - - + ( + + )} + />
) } diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index 5f138feac..391b9038f 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -1,19 +1,7 @@ -import stringify from "json-stable-stringify-without-jsonify" -import { cookies } from "next/headers" -import { notFound } from "next/navigation" -import { Suspense } from "react" +import { SelectHotelPage as SelectHotelPagePrimitive } from "@scandic-hotels/booking-flow/pages/SelectHotelPage" -import { parseSelectHotelSearchParams } from "@scandic-hotels/booking-flow/utils/url" -import { selectHotelMap } from "@scandic-hotels/common/constants/routes/hotelReservation" - -import { FamilyAndFriendsCodes } from "@/constants/booking" - -import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert" -import SelectHotel from "@/components/HotelReservation/SelectHotel" -import { getHotels } from "@/components/HotelReservation/SelectHotel/helpers" -import { getSelectHotelTracking } from "@/components/HotelReservation/SelectHotel/tracking" import TrackingSDK from "@/components/TrackingSDK" -import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" +import { getLang } from "@/i18n/serverContext" import type { LangParams, NextSearchParams, PageArgs } from "@/types/params" @@ -21,93 +9,18 @@ export default async function SelectHotelPage( props: PageArgs ) { const searchParams = await props.searchParams - const params = await props.params + const lang = await getLang() - const booking = parseSelectHotelSearchParams(searchParams) - - if (!booking) return notFound() - - const searchDetails = await getHotelSearchDetails(booking) - - if (!searchDetails || !searchDetails.city) return notFound() - - if ( - booking.bookingCode && - FamilyAndFriendsCodes.includes(booking.bookingCode) - ) { - const cookieStore = await cookies() - const isInvalidFNF = cookieStore.get("sc")?.value !== "1" - - if (isInvalidFNF) { - return - } - } - - const { city, redemption } = searchDetails - - const hotels = await getHotels({ - fromDate: booking.fromDate, - toDate: booking.toDate, - rooms: booking.rooms, - isAlternativeFor: null, - bookingCode: booking.bookingCode, - city: city, - redemption: !!redemption, - }) - - const isRedemptionAvailability = redemption - ? hotels.some( - (hotel) => hotel.availability.productType?.redemptions?.length - ) - : false - - const isBookingCodeRateAvailable = booking.bookingCode - ? hotels.some( - (hotel) => - hotel.availability.bookingCode && - hotel.availability.status === "Available" - ) - : false - - const arrivalDate = new Date(booking.fromDate) - const departureDate = new Date(booking.toDate) - - const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ - rooms: booking.rooms, - lang: params.lang, - pageId: "select-hotel", - pageName: "hotelreservation|select-hotel", - siteSections: "hotelreservation|select-hotel", - arrivalDate, - departureDate, - hotelsResult: hotels?.length ?? 0, - searchTerm: booking.hotelId, - country: hotels?.[0]?.hotel.address.country, - hotelCity: hotels?.[0]?.hotel.address.city, - bookingCode: booking.bookingCode, - isBookingCodeRateAvailable, - isRedemption: redemption, - isRedemptionAvailable: isRedemptionAvailability, - }) - - const mapHref = selectHotelMap(params.lang) - const suspenseKey = stringify(searchParams) return ( - <> - - + ( - - + )} + /> ) } diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/tracking.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/tracking.tsx index 757acd92e..052186404 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/tracking.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/tracking.tsx @@ -1,10 +1,10 @@ -import TrackingSDK from "@/components/TrackingSDK" -import { getLang } from "@/i18n/serverContext" - import { TrackingChannelEnum, type TrackingSDKPageData, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" export default async function Tracking() { const lang = await getLang() diff --git a/apps/scandic-web/app/[lang]/(live)/layout.tsx b/apps/scandic-web/app/[lang]/(live)/layout.tsx index 33b47d497..44da328ef 100644 --- a/apps/scandic-web/app/[lang]/(live)/layout.tsx +++ b/apps/scandic-web/app/[lang]/(live)/layout.tsx @@ -9,13 +9,13 @@ import Script from "next/script" import { SessionProvider } from "next-auth/react" import { NuqsAdapter } from "nuqs/adapters/next/app" -import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider" import { Lang } from "@scandic-hotels/common/constants/language" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import TrpcProvider from "@/lib/trpc/Provider" import { SessionRefresher } from "@/components/Auth/TokenRefresher" +import { BookingFlowProviders } from "@/components/BookingFlowProviders" import CookieBotConsent from "@/components/CookieBot" import Footer from "@/components/Footer" import Header from "@/components/Header" @@ -30,8 +30,6 @@ import { FontPreload } from "@/fonts/font-preloading" import { getMessages } from "@/i18n" import ClientIntlProvider from "@/i18n/Provider" import { setLang } from "@/i18n/serverContext" -import { trackAccordionClick, trackOpenSidePeekEvent } from "@/utils/tracking" -import { trackBookingSearchClick } from "@/utils/tracking/booking" import type { LangParams, LayoutArgs } from "@/types/params" @@ -72,13 +70,7 @@ export default async function RootLayout( - +
@@ -91,7 +83,7 @@ export default async function RootLayout( - + diff --git a/apps/scandic-web/app/[lang]/webview/(views)/hotelreservation/my-stay/tracking.tsx b/apps/scandic-web/app/[lang]/webview/(views)/hotelreservation/my-stay/tracking.tsx index 757acd92e..052186404 100644 --- a/apps/scandic-web/app/[lang]/webview/(views)/hotelreservation/my-stay/tracking.tsx +++ b/apps/scandic-web/app/[lang]/webview/(views)/hotelreservation/my-stay/tracking.tsx @@ -1,10 +1,10 @@ -import TrackingSDK from "@/components/TrackingSDK" -import { getLang } from "@/i18n/serverContext" - import { TrackingChannelEnum, type TrackingSDKPageData, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" export default async function Tracking() { const lang = await getLang() diff --git a/apps/scandic-web/auth.ts b/apps/scandic-web/auth.ts index 8b9966939..f575276e1 100644 --- a/apps/scandic-web/auth.ts +++ b/apps/scandic-web/auth.ts @@ -1,7 +1,7 @@ import NextAuth, { type NextAuthConfig, type User } from "next-auth" +import { LoginTypeEnum } from "@scandic-hotels/common/constants/loginType" import { logger } from "@scandic-hotels/common/logger" -import { LoginTypeEnum } from "@scandic-hotels/trpc/types/loginType" import { PRE_REFRESH_TIME_IN_SECONDS } from "@/constants/auth" import { env } from "@/env/server" diff --git a/apps/scandic-web/components/BookingFlowProviders.tsx b/apps/scandic-web/components/BookingFlowProviders.tsx new file mode 100644 index 000000000..5c400bbc4 --- /dev/null +++ b/apps/scandic-web/components/BookingFlowProviders.tsx @@ -0,0 +1,30 @@ +"use client" + +import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider" +import { BookingFlowTrackingProvider } from "@scandic-hotels/booking-flow/BookingFlowTrackingProvider" +import { trackEvent } from "@scandic-hotels/common/tracking/base" + +import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" +import { trackAccordionClick, trackOpenSidePeekEvent } from "@/utils/tracking" +import { trackBookingSearchClick } from "@/utils/tracking/booking" + +import type { ReactNode } from "react" + +export function BookingFlowProviders({ children }: { children: ReactNode }) { + const isLoggedIn = useIsUserLoggedIn() + + return ( + + + {children} + + + ) +} diff --git a/apps/scandic-web/components/ContentType/DestinationPage/Tracking/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/Tracking/index.tsx index 4c0e91519..df9926709 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/Tracking/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/Tracking/index.tsx @@ -6,7 +6,7 @@ import { useEffect } from "react" import TrackingSDK from "@/components/TrackingSDK" import { trackOpenMapView } from "@/utils/tracking/destinationPage" -import type { TrackingSDKPageData } from "@/types/components/tracking" +import type { TrackingSDKPageData } from "@scandic-hotels/common/tracking/types" interface DestinationTrackingProps { pageData: TrackingSDKPageData diff --git a/apps/scandic-web/components/ContentType/HotelMapPage/Client.tsx b/apps/scandic-web/components/ContentType/HotelMapPage/Client.tsx index 879cd9c8a..d1f4af596 100644 --- a/apps/scandic-web/components/ContentType/HotelMapPage/Client.tsx +++ b/apps/scandic-web/components/ContentType/HotelMapPage/Client.tsx @@ -10,13 +10,12 @@ import { } from "react" import { useIntl } from "react-intl" +import { useHotelsMapStore } from "@scandic-hotels/booking-flow/stores/hotels-map" import { debounce } from "@scandic-hotels/common/utils/debounce" import { Button } from "@scandic-hotels/design-system/Button" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { InteractiveMap } from "@scandic-hotels/design-system/Map/InteractiveMap" -import { useHotelsMapStore } from "@/stores/hotels-map" - import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" import useLang from "@/hooks/useLang" diff --git a/apps/scandic-web/components/ContentType/HotelPage/utils.ts b/apps/scandic-web/components/ContentType/HotelPage/utils.ts index 57f6668f6..e548bf92c 100644 --- a/apps/scandic-web/components/ContentType/HotelPage/utils.ts +++ b/apps/scandic-web/components/ContentType/HotelPage/utils.ts @@ -1,4 +1,9 @@ import { logger } from "@scandic-hotels/common/logger" +import { + TrackingChannelEnum, + type TrackingSDKHotelInfo, + type TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" import { PointOfInterestGroupEnum } from "@scandic-hotels/trpc/enums/pointOfInterest" import type { Lang } from "@scandic-hotels/common/constants/language" @@ -15,11 +20,6 @@ import type { HotelPageSectionHeadings, HotelPageSections, } from "@/types/components/hotelPage/sections" -import { - TrackingChannelEnum, - type TrackingSDKHotelInfo, - type TrackingSDKPageData, -} from "@/types/components/tracking" import { HotelHashValues } from "@/types/enums/hotelPage" export function getRoomNameAsParam(roomName: string) { diff --git a/apps/scandic-web/components/ContentType/StaticPages/staticPage.ts b/apps/scandic-web/components/ContentType/StaticPages/staticPage.ts index 2e6da2840..fd920f360 100644 --- a/apps/scandic-web/components/ContentType/StaticPages/staticPage.ts +++ b/apps/scandic-web/components/ContentType/StaticPages/staticPage.ts @@ -1,8 +1,8 @@ +import type { TrackingSDKPageData } from "@scandic-hotels/common/tracking/types" import type { CollectionPage } from "@scandic-hotels/trpc/types/collectionPage" import type { ContentPage } from "@scandic-hotels/trpc/types/contentPage" import type { VariantProps } from "class-variance-authority" -import type { TrackingSDKPageData } from "@/types/components/tracking" import type { staticPageVariants } from "./variants" export interface StaticPageProps diff --git a/apps/scandic-web/components/Current/Tracking.tsx b/apps/scandic-web/components/Current/Tracking.tsx index 415ded0c4..070ccca13 100644 --- a/apps/scandic-web/components/Current/Tracking.tsx +++ b/apps/scandic-web/components/Current/Tracking.tsx @@ -9,7 +9,7 @@ import type { SiteSectionObject, TrackingData, TrackingProps, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" function createPageObject(trackingData: TrackingData) { const englishSegments = trackingData.englishUrl diff --git a/apps/scandic-web/components/Forms/Signup/index.tsx b/apps/scandic-web/components/Forms/Signup/index.tsx index 617eff294..6a8179a90 100644 --- a/apps/scandic-web/components/Forms/Signup/index.tsx +++ b/apps/scandic-web/components/Forms/Signup/index.tsx @@ -30,7 +30,7 @@ import { import Input from "@/components/TempDesignSystem/Form/Input" import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput" -import { useFormTracking } from "@/components/TrackingSDK/hooks" +import { useFormTracking } from "@/components/TrackingSDK/useFormTracking" import useLang from "@/hooks/useLang" import { getFormattedCountryList } from "@/utils/countries" import { getErrorMessage } from "@/utils/getErrorMessage" diff --git a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Tracking/tracking.ts b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Tracking/tracking.ts index 5e9157677..8b92890cc 100644 --- a/apps/scandic-web/components/HotelReservation/BookingConfirmation/Tracking/tracking.ts +++ b/apps/scandic-web/components/HotelReservation/BookingConfirmation/Tracking/tracking.ts @@ -2,8 +2,15 @@ import { createHash } from "crypto" import { differenceInCalendarDays, format, isWeekend } from "date-fns" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" +import { RateEnum } from "@scandic-hotels/common/constants/rate" +import { + TrackingChannelEnum, + type TrackingSDKAncillaries, + type TrackingSDKHotelInfo, + type TrackingSDKPageData, + type TrackingSDKPaymentInfo, +} from "@scandic-hotels/common/tracking/types" import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import { CancellationRuleEnum } from "@/constants/booking" @@ -17,13 +24,6 @@ import type { Lang } from "@scandic-hotels/common/constants/language" import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation" import type { RateDefinition } from "@scandic-hotels/trpc/types/roomAvailability" -import { - TrackingChannelEnum, - type TrackingSDKAncillaries, - type TrackingSDKHotelInfo, - type TrackingSDKPageData, - type TrackingSDKPaymentInfo, -} from "@/types/components/tracking" import type { Room } from "@/types/stores/booking-confirmation" function getRate(cancellationRule: RateDefinition["cancellationRule"] | null) { diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx index 5b79eed45..a2bd21955 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx @@ -13,7 +13,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details" import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests" import Input from "@/components/TempDesignSystem/Form/Input" -import { useFormTracking } from "@/components/TrackingSDK/hooks" +import { useFormTracking } from "@/components/TrackingSDK/useFormTracking" import { useRoomContext } from "@/contexts/Details/Room" import useLang from "@/hooks/useLang" import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing" diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx index f504d5e29..008c5db99 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx @@ -13,7 +13,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details" import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests" import Input from "@/components/TempDesignSystem/Form/Input" -import { useFormTracking } from "@/components/TrackingSDK/hooks" +import { useFormTracking } from "@/components/TrackingSDK/useFormTracking" import { useRoomContext } from "@/contexts/Details/Room" import useLang from "@/hooks/useLang" import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing" diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Header/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Header/index.tsx index 0a285e6a5..cd6e91405 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Header/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Header/index.tsx @@ -1,8 +1,8 @@ +import { HotelDetailsSidePeek } from "@scandic-hotels/booking-flow/components/HotelDetailsSidePeek" import Image from "@scandic-hotels/design-system/Image" import Title from "@scandic-hotels/design-system/Title" import { Typography } from "@scandic-hotels/design-system/Typography" -import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek" import { getIntl } from "@/i18n" import styles from "./header.module.css" diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Tracking/tracking.ts b/apps/scandic-web/components/HotelReservation/EnterDetails/Tracking/tracking.ts index b9f4b8444..1c9b833bf 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Tracking/tracking.ts +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Tracking/tracking.ts @@ -1,6 +1,12 @@ import { differenceInCalendarDays, format, isWeekend } from "date-fns" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" +import { + TrackingChannelEnum, + type TrackingSDKAncillaries, + type TrackingSDKHotelInfo, + type TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" import { PackageTypeEnum } from "@scandic-hotels/trpc/enums/packages" @@ -22,12 +28,6 @@ import type { DetailsBooking, RoomRate, } from "@/types/components/hotelReservation/enterDetails/details" -import { - TrackingChannelEnum, - type TrackingSDKAncillaries, - type TrackingSDKHotelInfo, - type TrackingSDKPageData, -} from "@/types/components/tracking" import type { RoomState } from "@/types/stores/enter-details" export function getTracking( diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/tracking.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/tracking.tsx index 8cd0b222f..9d6b26451 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/tracking.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/tracking.tsx @@ -1,10 +1,10 @@ -import TrackingSDK from "@/components/TrackingSDK" -import { getLang } from "@/i18n/serverContext" - import { TrackingChannelEnum, type TrackingSDKPageData, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" + +import TrackingSDK from "@/components/TrackingSDK" +import { getLang } from "@/i18n/serverContext" export default async function Tracking() { const lang = await getLang() diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/Room.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/Room.tsx index 211372e94..415ffe0ad 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/Room.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/MultiRoom/Room.tsx @@ -2,6 +2,7 @@ import { useIntl } from "react-intl" import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { dt } from "@scandic-hotels/common/dt" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import { Divider } from "@scandic-hotels/design-system/Divider" @@ -11,7 +12,6 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Image from "@scandic-hotels/design-system/Image" import Modal from "@scandic-hotels/design-system/Modal" import { Typography } from "@scandic-hotels/design-system/Typography" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" import { CancellationRuleEnum } from "@/constants/booking" diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/Details/Terms/Terms.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/Details/Terms/Terms.tsx index d0017ec43..3ddb4ba82 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/Details/Terms/Terms.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Rooms/SingleRoom/Details/Terms/Terms.tsx @@ -1,10 +1,10 @@ import { useIntl } from "react-intl" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Modal from "@scandic-hotels/design-system/Modal" import { Typography } from "@scandic-hotels/design-system/Typography" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" import { CancellationRuleEnum } from "@/constants/booking" import { useMyStayStore } from "@/stores/my-stay" diff --git a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts index 3e6bd896b..9f4d1c02b 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts +++ b/apps/scandic-web/components/HotelReservation/MyStay/utils/mapRoomDetails.ts @@ -10,7 +10,7 @@ import { convertToChildType } from "../../utils/convertToChildType" import { getPriceType } from "../../utils/getPriceType" import { formatChildBedPreferences } from "../utils" -import type { RateEnum } from "@scandic-hotels/trpc/enums/rate" +import type { RateEnum } from "@scandic-hotels/common/constants/rate" import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation" import type { Room } from "@scandic-hotels/trpc/types/hotel" diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx b/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx deleted file mode 100644 index f1f3427ef..000000000 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainer.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { notFound } from "next/navigation" - -import { safeTry } from "@scandic-hotels/common/utils/safeTry" - -import { env } from "@/env/server" -import { getCityCoordinates } from "@/lib/trpc/memoizedRequests" - -import TrackingSDK from "@/components/TrackingSDK" -import { getLang } from "@/i18n/serverContext" -import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" - -import { getHotelPins } from "../../HotelCardDialogListing/utils" -import { getFiltersFromHotels, getHotels } from "../helpers" -import { getSelectHotelTracking } from "../tracking" -import SelectHotelMap from "." - -import type { SelectHotelMapContainerProps } from "@/types/components/hotelReservation/selectHotel/map" - -export async function SelectHotelMapContainer({ - booking, - isAlternativeHotels, -}: SelectHotelMapContainerProps) { - const lang = await getLang() - const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID - const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY - const getHotelSearchDetailsPromise = safeTry( - getHotelSearchDetails(booking, isAlternativeHotels) - ) - - const [searchDetails] = await getHotelSearchDetailsPromise - - if (!searchDetails) { - return notFound() - } - - const { - city, - cityIdentifier, - hotel: isAlternativeFor, - redemption, - } = searchDetails - - if (!city) { - return notFound() - } - - const hotels = await getHotels({ - fromDate: booking.fromDate, - toDate: booking.toDate, - rooms: booking.rooms, - isAlternativeFor, - bookingCode: booking.bookingCode, - city, - redemption: !!redemption, - }) - - const hotelPins = getHotelPins(hotels) - const filterList = getFiltersFromHotels(hotels) - const cityCoordinates = await getCityCoordinates({ - city: city.name, - hotel: { address: hotels?.[0]?.hotel?.address.streetAddress }, - }) - - const arrivalDate = new Date(booking.fromDate) - const departureDate = new Date(booking.toDate) - const isRedemptionAvailability = redemption - ? hotels.some( - (hotel) => hotel.availability.productType?.redemptions?.length - ) - : false - - const isBookingCodeRateAvailable = booking.bookingCode - ? hotels?.some((hotel) => hotel.availability.bookingCode) - : false - - const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ - lang, - pageId: isAlternativeFor ? "alternative-hotels" : "select-hotel", - pageName: isAlternativeHotels - ? "hotelreservation|alternative-hotels|mapview" - : "hotelreservation|select-hotel|mapview", - siteSections: isAlternativeHotels - ? "hotelreservation|altervative-hotels|mapview" - : "hotelreservation|select-hotel|mapview", - arrivalDate, - departureDate, - rooms: booking.rooms, - hotelsResult: hotels.length, - searchTerm: isAlternativeFor ? booking.hotelId : cityIdentifier, - country: hotels?.[0]?.hotel.address.country, - hotelCity: hotels?.[0]?.hotel.address.city, - bookingCode: booking.bookingCode, - isBookingCodeRateAvailable, - isRedemption: redemption, - isRedemptionAvailable: isRedemptionAvailability, - }) - - return ( - <> - - - - ) -} diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx index 00a4d28bd..6aaef4b33 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/DesktopSummary.tsx @@ -1,12 +1,12 @@ import { useIntl } from "react-intl" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting" import Body from "@scandic-hotels/design-system/Body" import Caption from "@scandic-hotels/design-system/Caption" import Footnote from "@scandic-hotels/design-system/Footnote" import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" import Subtitle from "@scandic-hotels/design-system/Subtitle" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx index d3eaa07b8..6e32002cc 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/MultiRoomWrapper/SelectedRoomPanel/index.tsx @@ -2,6 +2,7 @@ import { useIntl } from "react-intl" import { CurrencyEnum } from "@scandic-hotels/common/constants/currency" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { logger } from "@scandic-hotels/common/logger" import Body from "@scandic-hotels/design-system/Body" import Caption from "@scandic-hotels/design-system/Caption" @@ -10,7 +11,6 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Image from "@scandic-hotels/design-system/Image" import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" import Subtitle from "@scandic-hotels/design-system/Subtitle" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext" import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx index c90ac9037..96f69752b 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/Rooms/RoomsList/RoomsListSkeleton.tsx @@ -1,4 +1,4 @@ -import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" +import { RoomCardSkeleton } from "@scandic-hotels/booking-flow/components/RoomCardSkeleton" import styles from "./roomsListSkeleton.module.css" diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton.tsx index f14ca0e06..012e90f0b 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton.tsx @@ -1,4 +1,4 @@ -import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" +import { RoomCardSkeleton } from "@scandic-hotels/booking-flow/components/RoomCardSkeleton" import styles from "./RoomsContainerSkeleton.module.css" diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/Tracking/tracking.ts b/apps/scandic-web/components/HotelReservation/SelectRate/Tracking/tracking.ts index 01c535cb9..928cc7a79 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/Tracking/tracking.ts +++ b/apps/scandic-web/components/HotelReservation/SelectRate/Tracking/tracking.ts @@ -1,17 +1,19 @@ import { differenceInCalendarDays, format, isWeekend } from "date-fns" -import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" -import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" - -import type { Lang } from "@scandic-hotels/common/constants/language" - -import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate" import { TrackingChannelEnum, type TrackingSDKHotelInfo, type TrackingSDKPageData, -} from "@/types/components/tracking" -import type { ChildrenInRoom } from "@/utils/hotelSearchDetails" +} from "@scandic-hotels/common/tracking/types" +import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" +import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { Child } from "@scandic-hotels/trpc/types/child" + +import type { Room } from "@/types/components/hotelReservation/selectRate/selectRate" + +type ChildrenInRoom = (Child[] | null)[] | null type SelectRateTrackingInput = { lang: Lang diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx index 3088c26ff..dc9fd434e 100644 --- a/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx +++ b/apps/scandic-web/components/HotelReservation/SelectRate/index.tsx @@ -1,16 +1,15 @@ import { cookies } from "next/headers" +import FnFNotAllowedAlert from "@scandic-hotels/booking-flow/components/FnFNotAllowedAlert" +import { HotelDetailsSidePeek } from "@scandic-hotels/booking-flow/components/HotelDetailsSidePeek" +import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends" import { dt } from "@scandic-hotels/common/dt" import { HotelInfoCard } from "@scandic-hotels/design-system/HotelInfoCard" -import { FamilyAndFriendsCodes } from "@/constants/booking" - import { RoomsContainer } from "@/components/HotelReservation/SelectRate/RoomsContainer" -import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek" import { getIntl } from "@/i18n" import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" -import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert" import { hasOverlappingDates } from "../utils" import AvailabilityError from "./AvailabilityError" import Tracking from "./Tracking" diff --git a/apps/scandic-web/components/LoginButton/index.tsx b/apps/scandic-web/components/LoginButton/index.tsx index 94be62517..d611edbd8 100644 --- a/apps/scandic-web/components/LoginButton/index.tsx +++ b/apps/scandic-web/components/LoginButton/index.tsx @@ -8,10 +8,9 @@ import useLang from "@/hooks/useLang" import { useLazyPathname } from "@/hooks/useLazyPathname" import { trackLoginClick } from "@/utils/tracking" +import type { TrackingPosition } from "@scandic-hotels/common/tracking/types" import type { PropsWithChildren } from "react" -import type { TrackingPosition } from "@/types/components/tracking" - export default function LoginButton({ position, trackingId, diff --git a/apps/scandic-web/components/RouteChange.tsx b/apps/scandic-web/components/RouteChange.tsx index 74b9a3a41..7bd2fc45d 100644 --- a/apps/scandic-web/components/RouteChange.tsx +++ b/apps/scandic-web/components/RouteChange.tsx @@ -3,8 +3,9 @@ import { usePathname, useSearchParams } from "next/navigation" import { startTransition, useEffect } from "react" -import useRouterTransitionStore from "@/stores/router-transition" -import useTrackingStore from "@/stores/tracking" +import { isSameBookingWidgetParams } from "@scandic-hotels/booking-flow/utils/isSameBooking" +import useRouterTransitionStore from "@scandic-hotels/common/stores/router-transition" +import useTrackingStore from "@scandic-hotels/common/stores/tracking" import useLang from "@/hooks/useLang" import { trackPageViewStart } from "@/utils/tracking" @@ -34,7 +35,10 @@ export default function RouteChange() { } updateRouteInfo(pathName, currentLang, searchParams) - if (hasPathOrLangChanged() || hasBookingFlowParamsChanged()) { + if ( + hasPathOrLangChanged() || + hasBookingFlowParamsChanged(isSameBookingWidgetParams) + ) { setInitialPageLoadTime(Date.now()) trackPageViewStart() startTransition(() => { diff --git a/apps/scandic-web/components/TrackingSDK/hooks.ts b/apps/scandic-web/components/TrackingSDK/hooks.ts deleted file mode 100644 index d9c986afc..000000000 --- a/apps/scandic-web/components/TrackingSDK/hooks.ts +++ /dev/null @@ -1,345 +0,0 @@ -"use client" - -import { usePathname } from "next/navigation" -import { - startTransition, - useCallback, - useEffect, - useRef, - useState, -} from "react" -import { - type Control, - type FieldValues, - useFormState, - type UseFromSubscribe, -} from "react-hook-form" - -import { useSessionId } from "@scandic-hotels/common/hooks/useSessionId" -import { logger } from "@scandic-hotels/common/logger" -import { trpc } from "@scandic-hotels/trpc/client" - -import useRouterTransitionStore from "@/stores/router-transition" -import useTrackingStore from "@/stores/tracking" - -import useLang from "@/hooks/useLang" -import { promiseWithTimeout } from "@/utils/promiseWithTimeout" -import { createSDKPageObject, trackPageView } from "@/utils/tracking" -import { - type FormType, - trackFormAbandonment, - trackFormCompletion, - trackFormInputStarted, -} from "@/utils/tracking/form" - -import type { - TrackingSDKProps, - TrackingSDKUserData, -} from "@/types/components/tracking" - -enum TransitionStatusEnum { - NotRun = "NotRun", - Running = "Running", - Done = "Done", -} - -type TransitionStatus = keyof typeof TransitionStatusEnum - -let hasTrackedHardNavigation = false -export const useTrackHardNavigation = ({ - pageData, - hotelInfo, - paymentInfo, - ancillaries, -}: TrackingSDKProps) => { - const lang = useLang() - const { - data: userTrackingData, - isPending, - isError, - } = trpc.user.userTrackingInfo.useQuery({ lang }) - - const sessionId = useSessionId() - const pathName = usePathname() - - useEffect(() => { - if (isPending) { - return - } - - const userData: TrackingSDKUserData = isError - ? { loginStatus: "Error" } - : userTrackingData - - if (hasTrackedHardNavigation) { - return - } - - hasTrackedHardNavigation = true - - const track = () => { - trackPerformance({ - pathName, - sessionId, - paymentInfo, - hotelInfo, - userData, - pageData, - ancillaries, - }) - } - - if (document.readyState === "complete") { - track() - return - } - - window.addEventListener("load", track) - return () => window.removeEventListener("load", track) - }, [ - isError, - pathName, - hotelInfo, - userTrackingData, - pageData, - sessionId, - paymentInfo, - isPending, - ancillaries, - ]) -} - -export const useTrackSoftNavigation = ({ - pageData, - hotelInfo, - paymentInfo, - ancillaries, -}: TrackingSDKProps) => { - const lang = useLang() - const { - data: userTrackingData, - isPending, - isError, - } = trpc.user.userTrackingInfo.useQuery({ lang }) - - const [status, setStatus] = useState( - TransitionStatusEnum.NotRun - ) - const { getPageLoadTime } = useTrackingStore() - - const sessionId = useSessionId() - const pathName = usePathname() - const { isTransitioning, stopRouterTransition } = useRouterTransitionStore() - - const previousPathname = useRef(null) - - useEffect(() => { - if (isPending) { - return - } - - if (isTransitioning && status === TransitionStatusEnum.NotRun) { - startTransition(() => { - setStatus(TransitionStatusEnum.Running) - }) - return - } - - if (isTransitioning && status === TransitionStatusEnum.Running) { - setStatus(TransitionStatusEnum.Done) - stopRouterTransition() - return - } - - if (!isTransitioning && status === TransitionStatusEnum.Done) { - const pageLoadTime = getPageLoadTime() - const trackingData = { - ...pageData, - sessionId, - pathName, - pageLoadTime: pageLoadTime, - } - const pageObject = createSDKPageObject(trackingData) - const userData: TrackingSDKUserData = isError - ? { loginStatus: "Error" } - : userTrackingData - - trackPageView({ - event: "pageView", - pageInfo: pageObject, - userInfo: userData, - hotelInfo: hotelInfo, - paymentInfo, - ancillaries, - }) - - setStatus(TransitionStatusEnum.NotRun) // Reset status - previousPathname.current = pathName // Update for next render - } - }, [ - isError, - isPending, - isTransitioning, - status, - stopRouterTransition, - pageData, - pathName, - hotelInfo, - getPageLoadTime, - sessionId, - paymentInfo, - userTrackingData, - ancillaries, - ]) -} - -const trackPerformance = async ({ - pathName, - sessionId, - paymentInfo, - hotelInfo, - userData, - pageData, - ancillaries, -}: { - pathName: string - sessionId: string | null - paymentInfo: TrackingSDKProps["paymentInfo"] - hotelInfo: TrackingSDKProps["hotelInfo"] - userData: TrackingSDKUserData - pageData: TrackingSDKProps["pageData"] - ancillaries: TrackingSDKProps["ancillaries"] -}) => { - let pageLoadTime: number | undefined = undefined - let lcpTime: number | undefined = undefined - - try { - pageLoadTime = await promiseWithTimeout(getPageLoadTimeEntry(), 3000) - } catch (error) { - logger.error("Error obtaining pageLoadTime:", error) - } - - try { - lcpTime = await promiseWithTimeout(getLCPTimeEntry(), 3000) - } catch (error) { - logger.error("Error obtaining lcpTime:", error) - } - - const trackingData = { - ...pageData, - sessionId, - pathName, - pageLoadTime, - lcpTime, - } - const pageObject = createSDKPageObject(trackingData) - - trackPageView({ - event: "pageView", - pageInfo: pageObject, - userInfo: userData, - hotelInfo, - paymentInfo, - ancillaries, - }) -} - -const getLCPTimeEntry = () => { - return new Promise((resolve) => { - const observer = new PerformanceObserver((entries) => { - const lastEntry = entries.getEntries().at(-1) - if (lastEntry) { - observer.disconnect() - resolve(lastEntry.startTime / 1000) - } - }) - - const lcpSupported = PerformanceObserver.supportedEntryTypes?.includes( - "largest-contentful-paint" - ) - - if (lcpSupported) { - observer.observe({ - type: "largest-contentful-paint", - buffered: true, - }) - } else { - resolve(undefined) - } - }) -} - -const getPageLoadTimeEntry = () => { - return new Promise((resolve) => { - const observer = new PerformanceObserver((entries) => { - const navEntry = entries.getEntriesByType("navigation")[0] - if (navEntry) { - observer.disconnect() - resolve(navEntry.duration / 1000) - } - }) - observer.observe({ type: "navigation", buffered: true }) - }) -} - -export function useFormTracking( - formType: FormType, - subscribe: UseFromSubscribe, - control: Control, - nameSuffix: string = "" -) { - const [formStarted, setFormStarted] = useState(false) - const lastAccessedField = useRef(undefined) - const formState = useFormState({ control }) - - useEffect(() => { - const unsubscribe = subscribe({ - formState: { dirtyFields: true }, - callback: (data) => { - if ("name" in data) { - lastAccessedField.current = data.name as string - } - - if (!formStarted) { - trackFormInputStarted(formType, nameSuffix) - setFormStarted(true) - } - }, - }) - return () => unsubscribe() - }, [subscribe, formType, nameSuffix, formStarted]) - - useEffect(() => { - if (!formStarted || !lastAccessedField.current || formState.isValid) return - - const lastField = lastAccessedField.current - - function handleBeforeUnload() { - trackFormAbandonment(formType, lastField, nameSuffix) - } - - function handleVisibilityChange() { - if (document.visibilityState === "hidden") { - trackFormAbandonment(formType, lastField, nameSuffix) - } - } - - window.addEventListener("beforeunload", handleBeforeUnload) - window.addEventListener("visibilitychange", handleVisibilityChange) - - return () => { - window.removeEventListener("beforeunload", handleBeforeUnload) - window.removeEventListener("visibilitychange", handleVisibilityChange) - } - }, [formStarted, formType, nameSuffix, formState.isValid]) - - const trackFormSubmit = useCallback(() => { - if (formState.isValid) { - trackFormCompletion(formType, nameSuffix) - } - }, [formType, nameSuffix, formState.isValid]) - - return { - trackFormSubmit, - } -} diff --git a/apps/scandic-web/components/TrackingSDK/index.tsx b/apps/scandic-web/components/TrackingSDK/index.tsx index 2374a9af8..4cd48ab47 100644 --- a/apps/scandic-web/components/TrackingSDK/index.tsx +++ b/apps/scandic-web/components/TrackingSDK/index.tsx @@ -1,16 +1,19 @@ "use client" -import { - useTrackHardNavigation, - useTrackSoftNavigation, -} from "@/components/TrackingSDK/hooks" +import { usePathname } from "next/navigation" + +import { useTrackHardNavigation } from "@scandic-hotels/common/tracking/useTrackHardNavigation" +import { useTrackSoftNavigation } from "@scandic-hotels/common/tracking/useTrackSoftNavigation" +import { trpc } from "@scandic-hotels/trpc/client" + +import useLang from "@/hooks/useLang" import type { TrackingSDKAncillaries, TrackingSDKHotelInfo, TrackingSDKPageData, TrackingSDKPaymentInfo, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" export default function TrackingSDK({ pageData, @@ -23,8 +26,30 @@ export default function TrackingSDK({ paymentInfo?: TrackingSDKPaymentInfo ancillaries?: TrackingSDKAncillaries }) { - useTrackHardNavigation({ pageData, hotelInfo, paymentInfo, ancillaries }) - useTrackSoftNavigation({ pageData, hotelInfo, paymentInfo, ancillaries }) + const lang = useLang() + const pathName = usePathname() + const { data, isError } = trpc.user.userTrackingInfo.useQuery({ + lang, + }) + + const userData = isError ? ({ loginStatus: "Error" } as const) : data + + useTrackHardNavigation({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, + }) + useTrackSoftNavigation({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, + }) return null } diff --git a/apps/scandic-web/components/TrackingSDK/useFormTracking.ts b/apps/scandic-web/components/TrackingSDK/useFormTracking.ts new file mode 100644 index 000000000..c76a27ead --- /dev/null +++ b/apps/scandic-web/components/TrackingSDK/useFormTracking.ts @@ -0,0 +1,78 @@ +"use client" + +import { useCallback, useEffect, useRef, useState } from "react" +import { + type Control, + type FieldValues, + useFormState, + type UseFromSubscribe, +} from "react-hook-form" + +import { + type FormType, + trackFormAbandonment, + trackFormCompletion, + trackFormInputStarted, +} from "@/utils/tracking/form" + +export function useFormTracking( + formType: FormType, + subscribe: UseFromSubscribe, + control: Control, + nameSuffix: string = "" +) { + const [formStarted, setFormStarted] = useState(false) + const lastAccessedField = useRef(undefined) + const formState = useFormState({ control }) + + useEffect(() => { + const unsubscribe = subscribe({ + formState: { dirtyFields: true }, + callback: (data) => { + if ("name" in data) { + lastAccessedField.current = data.name as string + } + + if (!formStarted) { + trackFormInputStarted(formType, nameSuffix) + setFormStarted(true) + } + }, + }) + return () => unsubscribe() + }, [subscribe, formType, nameSuffix, formStarted]) + + useEffect(() => { + if (!formStarted || !lastAccessedField.current || formState.isValid) return + + const lastField = lastAccessedField.current + + function handleBeforeUnload() { + trackFormAbandonment(formType, lastField, nameSuffix) + } + + function handleVisibilityChange() { + if (document.visibilityState === "hidden") { + trackFormAbandonment(formType, lastField, nameSuffix) + } + } + + window.addEventListener("beforeunload", handleBeforeUnload) + window.addEventListener("visibilitychange", handleVisibilityChange) + + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload) + window.removeEventListener("visibilitychange", handleVisibilityChange) + } + }, [formStarted, formType, nameSuffix, formState.isValid]) + + const trackFormSubmit = useCallback(() => { + if (formState.isValid) { + trackFormCompletion(formType, nameSuffix) + } + }, [formType, nameSuffix, formState.isValid]) + + return { + trackFormSubmit, + } +} diff --git a/apps/scandic-web/constants/booking.ts b/apps/scandic-web/constants/booking.ts index c14afadbf..98623048a 100644 --- a/apps/scandic-web/constants/booking.ts +++ b/apps/scandic-web/constants/booking.ts @@ -1,7 +1,5 @@ import type { PaymentMethodEnum } from "@scandic-hotels/common/constants/paymentMethod" -export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"] - export const SEARCHTYPE = "searchtype" export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError" diff --git a/apps/scandic-web/contexts/SelectRate/isRateSelected.test.ts b/apps/scandic-web/contexts/SelectRate/isRateSelected.test.ts index ad702967c..6b252f8cc 100644 --- a/apps/scandic-web/contexts/SelectRate/isRateSelected.test.ts +++ b/apps/scandic-web/contexts/SelectRate/isRateSelected.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" -import { RateEnum } from "@scandic-hotels/trpc/enums/rate" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { isRateSelected } from "./isRateSelected" diff --git a/apps/scandic-web/contexts/SelectRate/isRateSelected.ts b/apps/scandic-web/contexts/SelectRate/isRateSelected.ts index 2a2839784..d45c52bd4 100644 --- a/apps/scandic-web/contexts/SelectRate/isRateSelected.ts +++ b/apps/scandic-web/contexts/SelectRate/isRateSelected.ts @@ -1,4 +1,4 @@ -import type { RateEnum } from "@scandic-hotels/trpc/enums/rate" +import type { RateEnum } from "@scandic-hotels/common/constants/rate" import type { Rate } from "./types" diff --git a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts index ec2ca9696..5f354fec6 100644 --- a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts +++ b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts @@ -1,8 +1,5 @@ import { redirect } from "next/navigation" -import { isDefined } from "@scandic-hotels/common/utils/isDefined" - -import { getLang } from "@/i18n/serverContext" import { cache } from "@/utils/cache" import { serverClient } from "../server" @@ -12,22 +9,13 @@ import type { GetHotelsByCSFilterInput } from "@scandic-hotels/trpc/routers/hote import type { GetSavedPaymentCardsInput } from "@scandic-hotels/trpc/routers/user/input" import type { RoomsAvailabilityExtendedInputSchema } from "@scandic-hotels/trpc/types/availability" import type { Country } from "@scandic-hotels/trpc/types/country" -import type { - CityCoordinatesInput, - HotelInput, -} from "@scandic-hotels/trpc/types/hotel" +import type { HotelInput } from "@scandic-hotels/trpc/types/hotel" import type { AncillaryPackagesInput, BreackfastPackagesInput, PackagesInput, } from "@scandic-hotels/trpc/types/packages" -export const getLocations = cache(async function getMemoizedLocations() { - const lang = await getLang() - const caller = await serverClient() - return caller.hotel.locations.get({ lang }) -}) - export const getProfile = cache(async function getMemoizedProfile() { const caller = await serverClient() return caller.user.get() @@ -187,13 +175,6 @@ export const getLinkedReservations = cache( } ) -export const getCityCoordinates = cache( - async function getMemoizedCityCoordinates(input: CityCoordinatesInput) { - const caller = await serverClient() - return caller.hotel.map.city(input) - } -) - export const getCurrentRewards = cache( async function getMemoizedCurrentRewards() { const caller = await serverClient() @@ -294,83 +275,6 @@ export const getJobylonFeed = cache(async function getMemoizedJobylonFeed() { return caller.partner.jobylon.feed.get() }) -export const getJumpToData = cache(async function getMemoizedJumpToData() { - const lang = await getLang() - const caller = await serverClient() - const [locationsResults, urlsResults] = await Promise.allSettled([ - getLocations(), - caller.hotel.locations.urls({ lang }), - ]) - - if ( - locationsResults.status === "fulfilled" && - urlsResults.status === "fulfilled" - ) { - const locations = locationsResults.value - const urls = urlsResults.value - - if (!locations || !urls) { - return null - } - - return locations - .map((location) => { - const { id, name, type } = location - - const isCity = type === "cities" - const isHotel = type === "hotels" - - let url: string | undefined - - if (isCity) { - url = urls.cities.find( - (c) => - c.city && - location.cityIdentifier && - c.city === location.cityIdentifier - )?.url - } else if (isHotel) { - url = urls.hotels.find( - (h) => h.hotelId && location.id && h.hotelId === location.id - )?.url - } - - if (!url) { - return null - } - - let description = "" - if (isCity) { - description = location.country - } else if (isHotel) { - description = location.relationships.city.name - } - - const rankingNames: string[] = [location.name] - if (isCity) { - if (location.cityIdentifier) { - rankingNames.push(location.cityIdentifier) - } - } - - const rankingKeywords = location.keyWords || [] - - return { - id, - displayName: name, - type, - description, - url, - rankingNames: rankingNames.map((v) => v.toLowerCase()), - rankingKeywords: rankingKeywords.map((v) => v.toLowerCase()), - } - }) - .filter(isDefined) - } - - return null -}) - export const getSelectedRoomsAvailabilityEnterDetails = cache( async function getMemoizedSelectedRoomsAvailability( input: RoomsAvailabilityExtendedInputSchema diff --git a/apps/scandic-web/providers/EnterDetailsProvider.tsx b/apps/scandic-web/providers/EnterDetailsProvider.tsx index 04dd12c14..884127dbb 100644 --- a/apps/scandic-web/providers/EnterDetailsProvider.tsx +++ b/apps/scandic-web/providers/EnterDetailsProvider.tsx @@ -2,11 +2,11 @@ import deepmerge from "deepmerge" import { useEffect, useRef, useState } from "react" +import { isSameBooking } from "@scandic-hotels/booking-flow/utils/isSameBooking" import { dt } from "@scandic-hotels/common/dt" import { createDetailsStore } from "@/stores/enter-details" import { - checkIsSameBooking as checkIsSameBooking, clearSessionStorage, getTotalPrice, readFromSessionStorage, @@ -89,8 +89,8 @@ export default function EnterDetailsProvider({ setHasInitializedStore(true) return } - const isSameBooking = checkIsSameBooking(storedValues.booking, booking) - if (!isSameBooking) { + + if (!isSameBooking(storedValues.booking, booking)) { clearSessionStorage() setHasInitializedStore(true) return diff --git a/apps/scandic-web/stores/enter-details/helpers.ts b/apps/scandic-web/stores/enter-details/helpers.ts index a551c27e0..873e69712 100644 --- a/apps/scandic-web/stores/enter-details/helpers.ts +++ b/apps/scandic-web/stores/enter-details/helpers.ts @@ -48,33 +48,6 @@ export function extractGuestFromUser(user: NonNullable) { } } -export function checkIsSameBooking( - prev: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string }, - next: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string } -) { - const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev - - const prevRoomsWithoutRateCodes = prevRooms?.map( - ({ adults, childrenInRoom }) => ({ adults, childrenInRoom }) - ) - const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next - - const nextRoomsWithoutRateCodes = nextRooms?.map( - ({ adults, childrenInRoom }) => ({ adults, childrenInRoom }) - ) - - return isEqual( - { - ...prevBooking, - rooms: prevRoomsWithoutRateCodes, - }, - { - ...nextBooking, - rooms: nextRoomsWithoutRateCodes, - } - ) -} - export function add(...nums: (number | string | undefined)[]) { return nums.reduce((total: number, num) => { if (typeof num === "undefined") { diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/availabilityInput.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/availabilityInput.ts deleted file mode 100644 index 714fec93e..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/availabilityInput.ts +++ /dev/null @@ -1,18 +0,0 @@ -export type AvailabilityInput = { - cityId: string - roomStayStartDate: string - roomStayEndDate: string - adults: number - children?: string - bookingCode?: string - redemption?: boolean -} - -export type AlternativeHotelsAvailabilityInput = { - roomStayStartDate: string - roomStayEndDate: string - adults: number - children?: string - bookingCode?: string - redemption?: boolean -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/filterAndSortModal.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/filterAndSortModal.ts deleted file mode 100644 index 362d543dc..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/filterAndSortModal.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { CategorizedHotelFilters } from "./hotelFilters" - -export type FilterAndSortModalProps = { - filters: CategorizedHotelFilters - setShowSkeleton?: (showSkeleton: boolean) => void -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/filterCheckbox.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/filterCheckbox.ts deleted file mode 100644 index 23670912b..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/filterCheckbox.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type FilterCheckboxProps = { - name: string - id: string - isDisabled?: boolean - isSelected: boolean - onChange: (filterId: string) => void -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts deleted file mode 100644 index 926f7acbc..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ProductType } from "@scandic-hotels/trpc/types/availability" -import type { Hotel } from "@scandic-hotels/trpc/types/hotel" - -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" - -export enum HotelCardListingTypeEnum { - MapListing = "mapListing", - PageListing = "pageListing", -} - -export type HotelData = { - hotelData: Hotel - price: ProductType -} - -export type HotelCardListingProps = { - hotelData: HotelResponse[] - unfilteredHotelCount: number - type?: HotelCardListingTypeEnum - isAlternative?: boolean -} - -export interface NullableHotelData extends Omit { - hotelData: HotelData["hotelData"] | null -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardProps.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardProps.ts deleted file mode 100644 index d5ec1dcf1..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelCardProps.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" -import type { HotelCardListingTypeEnum } from "./hotelCardListingProps" - -export type HotelCardProps = { - hotelData: HotelResponse - isUserLoggedIn: boolean - type?: HotelCardListingTypeEnum - state?: "default" | "active" - bookingCode?: string | null - isAlternative?: boolean -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelData.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelData.ts new file mode 100644 index 000000000..2bcf33267 --- /dev/null +++ b/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelData.ts @@ -0,0 +1,11 @@ +import type { ProductType } from "@scandic-hotels/trpc/types/availability" +import type { Hotel } from "@scandic-hotels/trpc/types/hotel" + +export type HotelData = { + hotelData: Hotel + price: ProductType +} + +export interface NullableHotelData extends Omit { + hotelData: HotelData["hotelData"] | null +} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts index f3636e518..584bb4693 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectHotel/map.ts @@ -4,26 +4,6 @@ import type { Amenities } from "@scandic-hotels/trpc/types/hotel" import type { z } from "zod" import type { Coordinates } from "@/types/components/maps/coordinates" -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" -import type { CategorizedHotelFilters } from "./hotelFilters" -import type { SelectHotelBooking } from "./selectHotel" - -export interface HotelListingProps { - hotels: HotelResponse[] - unfilteredHotelCount: number -} - -export interface SelectHotelMapProps { - apiKey: string - hotelPins: HotelPin[] - mapId: string - hotels: HotelResponse[] - filterList: CategorizedHotelFilters - cityCoordinates: Coordinates - bookingCode: string | undefined - isBookingCodeRateAvailable?: boolean - isAlternativeHotels?: boolean -} type ImageSizes = z.infer["imageSizes"] type ImageMetaData = z.infer["metaData"] @@ -56,13 +36,3 @@ export interface HotelCardDialogProps { data: HotelPin handleClose: (event: { stopPropagation: () => void }) => void } - -export interface HotelCardDialogListingProps { - hotels: HotelResponse[] - unfilteredHotelCount: number -} - -export type SelectHotelMapContainerProps = { - booking: SelectHotelBooking - isAlternativeHotels?: boolean -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts b/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts deleted file mode 100644 index 6019fde02..000000000 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/noAvailabilityAlert.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Hotel } from "@scandic-hotels/trpc/types/hotel" - -export type NoAvailabilityAlertProp = { - hotelsLength: number - bookingCode?: string - isAllUnavailable: boolean - isAlternative?: boolean - isBookingCodeRateNotAvailable?: boolean - operaId: Hotel["operaId"] -} diff --git a/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts b/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts index 53f61d149..d4b993634 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts +++ b/apps/scandic-web/types/components/hotelReservation/selectRate/selectRate.ts @@ -1,5 +1,5 @@ import type { BookingSearchType } from "@scandic-hotels/booking-flow/searchType" -import type { RateEnum } from "@scandic-hotels/trpc/enums/rate" +import type { RateEnum } from "@scandic-hotels/common/constants/rate" import type { Child } from "@scandic-hotels/trpc/types/child" import type { PackageEnum, Packages } from "@scandic-hotels/trpc/types/packages" import type { diff --git a/apps/scandic-web/utils/tracking/booking.ts b/apps/scandic-web/utils/tracking/booking.ts index 94e6e83bf..8d2c081bc 100644 --- a/apps/scandic-web/utils/tracking/booking.ts +++ b/apps/scandic-web/utils/tracking/booking.ts @@ -3,7 +3,7 @@ import { trackEvent } from "@scandic-hotels/common/tracking/base" import type { BreakfastPackages } from "@/types/components/hotelReservation/breakfast" -import type { LowestRoomPriceEvent } from "@/types/components/tracking" +import type { LowestRoomPriceEvent } from "@scandic-hotels/common/tracking/types" export function trackLowestRoomPrice(event: LowestRoomPriceEvent) { trackEvent({ diff --git a/apps/scandic-web/utils/tracking/index.ts b/apps/scandic-web/utils/tracking/index.ts index fc7783523..5b344c075 100644 --- a/apps/scandic-web/utils/tracking/index.ts +++ b/apps/scandic-web/utils/tracking/index.ts @@ -11,10 +11,10 @@ export { trackLoginClick, trackSocialMediaClick, } from "./navigation" +export { trackPaymentEvent, trackUpdatePaymentMethod } from "./payment" +export { trackClick } from "@scandic-hotels/common/tracking/base" export { createSDKPageObject, trackPageView, trackPageViewStart, -} from "./pageview" -export { trackPaymentEvent, trackUpdatePaymentMethod } from "./payment" -export { trackClick } from "@scandic-hotels/common/tracking/base" +} from "@scandic-hotels/common/tracking/pageview" diff --git a/apps/scandic-web/utils/tracking/navigation.ts b/apps/scandic-web/utils/tracking/navigation.ts index 00472dd40..27addb9ba 100644 --- a/apps/scandic-web/utils/tracking/navigation.ts +++ b/apps/scandic-web/utils/tracking/navigation.ts @@ -1,6 +1,6 @@ import { trackEvent } from "@scandic-hotels/common/tracking/base" -import type { TrackingPosition } from "@/types/components/tracking" +import type { TrackingPosition } from "@scandic-hotels/common/tracking/types" export function trackFooterClick(group: string, name: string) { trackEvent({ diff --git a/apps/scandic-web/utils/tracking/payment.ts b/apps/scandic-web/utils/tracking/payment.ts index cccefff36..7f94b65e3 100644 --- a/apps/scandic-web/utils/tracking/payment.ts +++ b/apps/scandic-web/utils/tracking/payment.ts @@ -3,7 +3,7 @@ import { trackEvent } from "@scandic-hotels/common/tracking/base" import type { PaymentEvent, PaymentFailEvent, -} from "@/types/components/tracking" +} from "@scandic-hotels/common/tracking/types" function isPaymentFailEvent(event: PaymentEvent): event is PaymentFailEvent { return "errorMessage" in event diff --git a/packages/booking-flow/env/server.ts b/packages/booking-flow/env/server.ts index a4f0982d8..c82a5c942 100644 --- a/packages/booking-flow/env/server.ts +++ b/packages/booking-flow/env/server.ts @@ -9,10 +9,15 @@ export const env = createEnv({ */ isServer: typeof window === "undefined" || "Deno" in window, server: { - FOO: z.string().optional(), + GOOGLE_STATIC_MAP_KEY: z.string(), + GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string(), + GOOGLE_DYNAMIC_MAP_ID: z.string(), }, emptyStringAsUndefined: true, runtimeEnv: { - FOO: process.env.FOO, + GOOGLE_STATIC_MAP_KEY: process.env.GOOGLE_STATIC_MAP_KEY, + GOOGLE_STATIC_MAP_SIGNATURE_SECRET: + process.env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET, + GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, }, }) diff --git a/packages/booking-flow/lib/bookingFlowContext.tsx b/packages/booking-flow/lib/bookingFlowContext.tsx new file mode 100644 index 000000000..fdb3576a7 --- /dev/null +++ b/packages/booking-flow/lib/bookingFlowContext.tsx @@ -0,0 +1,23 @@ +"use client" + +import { createContext, useContext } from "react" + +export type BookingFlowContextData = { + isLoggedIn: boolean +} + +export const BookingFlowContext = createContext< + BookingFlowContextData | undefined +>(undefined) + +export const useBookingFlowContext = (): BookingFlowContextData => { + const context = useContext(BookingFlowContext) + + if (!context) { + throw new Error( + "useBookingFlowContext must be used within a BookingFlowContextProvider. Did you forget to use the provider in the consuming app?" + ) + } + + return context +} diff --git a/packages/booking-flow/lib/components/AlternativeHotelsPageTitle.tsx b/packages/booking-flow/lib/components/AlternativeHotelsPageTitle.tsx new file mode 100644 index 000000000..23ee3a59e --- /dev/null +++ b/packages/booking-flow/lib/components/AlternativeHotelsPageTitle.tsx @@ -0,0 +1,21 @@ +"use client" + +import { useIntl } from "react-intl" + +export function AlternativeHotelsPageTitle({ + hotelName, +}: { + hotelName: string +}) { + const intl = useIntl() + const title = intl.formatMessage( + { + defaultMessage: "Alternatives for {value}", + }, + { + value: hotelName, + } + ) + + return <>{title} +} diff --git a/packages/booking-flow/lib/components/BookingFlowContextProvider.tsx b/packages/booking-flow/lib/components/BookingFlowContextProvider.tsx new file mode 100644 index 000000000..7e3010ebb --- /dev/null +++ b/packages/booking-flow/lib/components/BookingFlowContextProvider.tsx @@ -0,0 +1,17 @@ +"use client" + +import { + BookingFlowContext, + type BookingFlowContextData, +} from "../bookingFlowContext" + +import type { ReactNode } from "react" + +export function BookingFlowContextProvider(props: { + children: ReactNode + data: BookingFlowContextData +}) { + return ( + {props.children} + ) +} diff --git a/apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.module.css b/packages/booking-flow/lib/components/FnFNotAllowedAlert/FnFNotAllowedAlert.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.module.css rename to packages/booking-flow/lib/components/FnFNotAllowedAlert/FnFNotAllowedAlert.module.css diff --git a/apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.tsx b/packages/booking-flow/lib/components/FnFNotAllowedAlert/index.tsx similarity index 78% rename from apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.tsx rename to packages/booking-flow/lib/components/FnFNotAllowedAlert/index.tsx index 08755bbe1..eb779cf71 100644 --- a/apps/scandic-web/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert.tsx +++ b/packages/booking-flow/lib/components/FnFNotAllowedAlert/index.tsx @@ -1,12 +1,14 @@ +"use client" + +import { useIntl } from "react-intl" + import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert" import { Alert } from "@scandic-hotels/design-system/Alert" -import { getIntl } from "@/i18n" - import styles from "./FnFNotAllowedAlert.module.css" -export default async function FnFNotAllowedAlert() { - const intl = await getIntl() +export default function FnFNotAllowedAlert() { + const intl = useIntl() return (
diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css b/packages/booking-flow/lib/components/HotelCardDialogListing/hotelCardDialogListing.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/HotelCardDialogListing/hotelCardDialogListing.module.css rename to packages/booking-flow/lib/components/HotelCardDialogListing/hotelCardDialogListing.module.css diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx b/packages/booking-flow/lib/components/HotelCardDialogListing/index.tsx similarity index 91% rename from apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx rename to packages/booking-flow/lib/components/HotelCardDialogListing/index.tsx index 1bc53c05d..83a7d5b2c 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/index.tsx +++ b/packages/booking-flow/lib/components/HotelCardDialogListing/index.tsx @@ -3,15 +3,19 @@ import { useCallback, useEffect, useRef } from "react" import { useIntl } from "react-intl" -import { useHotelFilterStore } from "@/stores/hotel-filters" -import { useHotelsMapStore } from "@/stores/hotels-map" - -import ListingHotelCardDialog from "../HotelCardDialog/ListingHotelCardDialog" +import { useHotelFilterStore } from "../../stores/hotel-filters" +import { useHotelsMapStore } from "../../stores/hotels-map" +import ListingHotelCardDialog from "../ListingHotelCardDialog" import { getHotelPins } from "./utils" import styles from "./hotelCardDialogListing.module.css" -import type { HotelCardDialogListingProps } from "@/types/components/hotelReservation/selectHotel/map" +import type { HotelResponse } from "../SelectHotel/helpers" + +interface HotelCardDialogListingProps { + hotels: HotelResponse[] + unfilteredHotelCount: number +} export default function HotelCardDialogListing({ hotels, diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts b/packages/booking-flow/lib/components/HotelCardDialogListing/utils.ts similarity index 65% rename from apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts rename to packages/booking-flow/lib/components/HotelCardDialogListing/utils.ts index 5cb1e2768..48b7a13a1 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialogListing/utils.ts +++ b/packages/booking-flow/lib/components/HotelCardDialogListing/utils.ts @@ -1,5 +1,39 @@ -import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" +import type { imageSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/image" +import type { ProductTypeCheque } from "@scandic-hotels/trpc/types/availability" +import type { Amenities } from "@scandic-hotels/trpc/types/hotel" +import type { z } from "zod" + +import type { HotelResponse } from "../SelectHotel/helpers" + +type ImageSizes = z.infer["imageSizes"] +type ImageMetaData = z.infer["metaData"] + +interface Coordinates { + lat: number + lng: number +} + +export type HotelPin = { + bookingCode?: string | null + name: string + coordinates: Coordinates + chequePrice: ProductTypeCheque["localPrice"] | null + publicPrice: number | null + memberPrice: number | null + redemptionPrice: number | null + voucherPrice: number | null + rateType: string | null + currency: string + images: { + imageSizes: ImageSizes + metaData: ImageMetaData + }[] + amenities: Amenities + ratings: number | null + operaId: string + facilityIds: number[] + hasEnoughPoints: boolean +} export function getHotelPins( hotels: HotelResponse[], diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/hotelCardListing.module.css b/packages/booking-flow/lib/components/HotelCardListing/hotelCardListing.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/HotelCardListing/hotelCardListing.module.css rename to packages/booking-flow/lib/components/HotelCardListing/hotelCardListing.module.css diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx b/packages/booking-flow/lib/components/HotelCardListing/index.tsx similarity index 91% rename from apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx rename to packages/booking-flow/lib/components/HotelCardListing/index.tsx index 6beac9c76..3b0fd5c21 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/index.tsx +++ b/packages/booking-flow/lib/components/HotelCardListing/index.tsx @@ -1,14 +1,9 @@ "use client" import { useRouter, useSearchParams } from "next/navigation" -import { useSession } from "next-auth/react" import { useEffect, useMemo, useRef } from "react" import { useIntl } from "react-intl" -import { - BookingCodeFilterEnum, - useBookingCodeFilterStore, -} from "@scandic-hotels/booking-flow/stores/bookingCode-filter" import { alternativeHotelsMap, selectHotelMap, @@ -17,14 +12,16 @@ import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop" import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton" import { HotelCard } from "@scandic-hotels/design-system/HotelCard" -import { useHotelFilterStore } from "@/stores/hotel-filters" -import { useHotelsMapStore } from "@/stores/hotels-map" - -import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek" -import useLang from "@/hooks/useLang" -import { isValidClientSession } from "@/utils/clientSession" -import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" - +import { useIsLoggedIn } from "../../hooks/useIsLoggedIn" +import useLang from "../../hooks/useLang" +import { mapApiImagesToGalleryImages } from "../../misc/imageGallery" +import { + BookingCodeFilterEnum, + useBookingCodeFilterStore, +} from "../../stores/bookingCode-filter" +import { useHotelFilterStore } from "../../stores/hotel-filters" +import { useHotelsMapStore } from "../../stores/hotels-map" +import { HotelDetailsSidePeek } from "../HotelDetailsSidePeek" import { DEFAULT_SORT } from "../SelectHotel/HotelSorter" import { getSortedHotels } from "./utils" @@ -32,10 +29,19 @@ import styles from "./hotelCardListing.module.css" import type { HotelType } from "@scandic-hotels/common/constants/hotelType" -import { - type HotelCardListingProps, - HotelCardListingTypeEnum, -} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" +import type { HotelResponse } from "../SelectHotel/helpers" + +export enum HotelCardListingTypeEnum { + MapListing = "mapListing", + PageListing = "pageListing", +} + +type HotelCardListingProps = { + hotelData: HotelResponse[] + unfilteredHotelCount: number + type?: HotelCardListingTypeEnum + isAlternative?: boolean +} export default function HotelCardListing({ hotelData, @@ -46,8 +52,7 @@ export default function HotelCardListing({ const router = useRouter() const lang = useLang() const intl = useIntl() - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) + const isUserLoggedIn = useIsLoggedIn() const searchParams = useSearchParams() const activeFilters = useHotelFilterStore((state) => state.activeFilters) const setResultCount = useHotelFilterStore((state) => state.setResultCount) diff --git a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts b/packages/booking-flow/lib/components/HotelCardListing/utils.ts similarity index 91% rename from apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts rename to packages/booking-flow/lib/components/HotelCardListing/utils.ts index dda074ead..a1c6e2a03 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardListing/utils.ts +++ b/packages/booking-flow/lib/components/HotelCardListing/utils.ts @@ -1,5 +1,6 @@ -import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter" -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" +import { SortOrder } from "../../misc/sortOrder" + +import type { HotelResponse } from "../SelectHotel/helpers" function getPricePerNight(hotel: HotelResponse): number { return ( diff --git a/apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/HotelSidePeekContent/hotelSidePeek.module.css b/packages/booking-flow/lib/components/HotelDetailsSidePeek/HotelSidePeekContent/hotelSidePeek.module.css similarity index 100% rename from apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/HotelSidePeekContent/hotelSidePeek.module.css rename to packages/booking-flow/lib/components/HotelDetailsSidePeek/HotelSidePeekContent/hotelSidePeek.module.css diff --git a/apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx b/packages/booking-flow/lib/components/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx similarity index 80% rename from apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx rename to packages/booking-flow/lib/components/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx index 207ea3f18..e22f6a5ec 100644 --- a/apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx +++ b/packages/booking-flow/lib/components/HotelDetailsSidePeek/HotelSidePeekContent/index.tsx @@ -1,17 +1,17 @@ import { useIntl } from "react-intl" -import AdditionalAmenities from "@scandic-hotels/booking-flow/components/AdditionalAmenities" -import Contact from "@scandic-hotels/booking-flow/components/Contact" -import BreakfastAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/BreakfastAccordionItem" -import CheckInCheckOutAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/CheckInCheckOutAccordionItem" -import ParkingAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/ParkingAccordionItem" import Accordion from "@scandic-hotels/design-system/Accordion" import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem" import ButtonLink from "@scandic-hotels/design-system/ButtonLink" import { IconName } from "@scandic-hotels/design-system/Icons/iconName" import { Typography } from "@scandic-hotels/design-system/Typography" -import { trackAccordionClick } from "@/utils/tracking" +import { useTrackingContext } from "../../../trackingContext" +import AdditionalAmenities from "../../AdditionalAmenities" +import Contact from "../../Contact" +import BreakfastAccordionItem from "../../SidePeekAccordions/BreakfastAccordionItem" +import CheckInCheckOutAccordionItem from "../../SidePeekAccordions/CheckInCheckOutAccordionItem" +import ParkingAccordionItem from "../../SidePeekAccordions/ParkingAccordionItem" import styles from "./hotelSidePeek.module.css" @@ -82,6 +82,7 @@ function AccessibilityAccordionItem({ elevatorPitch, }: AccessibilityAccordionItemProps) { const intl = useIntl() + const tracking = useTrackingContext() if (!elevatorPitch) { return null @@ -95,7 +96,7 @@ function AccessibilityAccordionItem({ iconName={IconName.Accessibility} className={styles.accordionItem} variant="sidepeek" - onOpen={() => trackAccordionClick("amenities:accessibility")} + onOpen={() => tracking.trackAccordionItemOpen("amenities:accessibility")} >
diff --git a/apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/index.tsx b/packages/booking-flow/lib/components/HotelDetailsSidePeek/index.tsx similarity index 89% rename from apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/index.tsx rename to packages/booking-flow/lib/components/HotelDetailsSidePeek/index.tsx index f07ce1149..f72d98e55 100644 --- a/apps/scandic-web/components/SidePeeks/HotelDetailsSidePeek/index.tsx +++ b/packages/booking-flow/lib/components/HotelDetailsSidePeek/index.tsx @@ -6,8 +6,7 @@ import { Button } from "@scandic-hotels/design-system/Button" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import SidePeekSelfControlled from "@scandic-hotels/design-system/SidePeekSelfControlled" -import { trackOpenSidePeekEvent } from "@/utils/tracking" - +import { useTrackingContext } from "../../trackingContext" import { HotelSidePeekContent } from "./HotelSidePeekContent" import type { @@ -16,7 +15,9 @@ import type { Restaurant, } from "@scandic-hotels/trpc/types/hotel" -import { SidePeekEnum } from "@/types/sidepeek" +enum SidePeekEnum { + hotelDetails = "hotel-detail-side-peek", +} interface HotelDetailsSidePeekProps { hotel: Hotel & { url: string | null } @@ -48,7 +49,7 @@ const buttonPropsMap: Record< }, } -export default function HotelDetailsSidePeek({ +export function HotelDetailsSidePeek({ hotel, restaurants, additionalHotelData, @@ -56,6 +57,7 @@ export default function HotelDetailsSidePeek({ wrapping = true, buttonVariant, }: HotelDetailsSidePeekProps) { + const tracking = useTrackingContext() const buttonProps = buttonPropsMap[buttonVariant] return ( @@ -64,7 +66,7 @@ export default function HotelDetailsSidePeek({ {...buttonProps} wrapping={wrapping} onPress={() => - trackOpenSidePeekEvent({ + tracking.trackOpenSidePeek({ name: SidePeekEnum.hotelDetails, hotelId: hotel.operaId, includePathname: true, diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx b/packages/booking-flow/lib/components/ListingHotelCardDialog/index.tsx similarity index 91% rename from apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx rename to packages/booking-flow/lib/components/ListingHotelCardDialog/index.tsx index f9dcd6b76..00718bfe6 100644 --- a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/index.tsx +++ b/packages/booking-flow/lib/components/ListingHotelCardDialog/index.tsx @@ -1,6 +1,5 @@ "use client" -import { useSession } from "next-auth/react" import { useState } from "react" import { useIntl } from "react-intl" @@ -17,12 +16,12 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton import Subtitle from "@scandic-hotels/design-system/Subtitle" import { Typography } from "@scandic-hotels/design-system/Typography" -import useLang from "@/hooks/useLang" -import { isValidClientSession } from "@/utils/clientSession" +import { useIsLoggedIn } from "../../hooks/useIsLoggedIn" +import useLang from "../../hooks/useLang" import styles from "./listingHotelCardDialog.module.css" -import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" +import type { HotelPin } from "../HotelCardDialogListing/utils" interface ListingHotelCardProps { data: HotelPin @@ -38,8 +37,7 @@ export default function ListingHotelCardDialog({ const [imageError, setImageError] = useState(false) - const { data: session } = useSession() - const isUserLoggedIn = isValidClientSession(session) + const isUserLoggedIn = useIsLoggedIn() const { bookingCode, name, @@ -130,7 +128,6 @@ export default function ListingHotelCardDialog({ {publicPrice} {currency} - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {memberPrice && /} ) : ( @@ -169,8 +166,7 @@ export default function ListingHotelCardDialog({ } )} {chequePrice.additionalPricePerStay > 0 - ? // eslint-disable-next-line formatjs/no-literal-string-in-jsx - " + " + + ? " + " + intl.formatMessage( { defaultMessage: "{price} {currency}", @@ -182,7 +178,6 @@ export default function ListingHotelCardDialog({ ) : null} - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} / {intl.formatMessage({ @@ -204,7 +199,6 @@ export default function ListingHotelCardDialog({ } )} - {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} / {intl.formatMessage({ diff --git a/apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/listingHotelCardDialog.module.css b/packages/booking-flow/lib/components/ListingHotelCardDialog/listingHotelCardDialog.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/HotelCardDialog/ListingHotelCardDialog/listingHotelCardDialog.module.css rename to packages/booking-flow/lib/components/ListingHotelCardDialog/listingHotelCardDialog.module.css diff --git a/apps/scandic-web/components/MapContainer/index.tsx b/packages/booking-flow/lib/components/MapContainer/index.tsx similarity index 100% rename from apps/scandic-web/components/MapContainer/index.tsx rename to packages/booking-flow/lib/components/MapContainer/index.tsx diff --git a/apps/scandic-web/components/MapContainer/mapModal.module.css b/packages/booking-flow/lib/components/MapContainer/mapModal.module.css similarity index 100% rename from apps/scandic-web/components/MapContainer/mapModal.module.css rename to packages/booking-flow/lib/components/MapContainer/mapModal.module.css diff --git a/apps/scandic-web/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton.module.css b/packages/booking-flow/lib/components/RoomCardSkeleton/RoomCardSkeleton.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton.module.css rename to packages/booking-flow/lib/components/RoomCardSkeleton/RoomCardSkeleton.module.css diff --git a/apps/scandic-web/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton.tsx b/packages/booking-flow/lib/components/RoomCardSkeleton/RoomCardSkeleton.tsx similarity index 100% rename from apps/scandic-web/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton.tsx rename to packages/booking-flow/lib/components/RoomCardSkeleton/RoomCardSkeleton.tsx diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterAndSortModal/filterAndSortModal.module.css b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterAndSortModal/filterAndSortModal.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterAndSortModal/filterAndSortModal.module.css rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterAndSortModal/filterAndSortModal.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterAndSortModal/index.tsx b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterAndSortModal/index.tsx similarity index 94% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterAndSortModal/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterAndSortModal/index.tsx index 4fee57313..42294ada9 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterAndSortModal/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterAndSortModal/index.tsx @@ -20,20 +20,25 @@ import { IconButton } from "@scandic-hotels/design-system/IconButton" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useHotelFilterStore } from "@/stores/hotel-filters" - -import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl" - +import useInitializeFiltersFromUrl from "../../../../hooks/useInitializeFiltersFromUrl" +import { SortOrder } from "../../../../misc/sortOrder" +import { useHotelFilterStore } from "../../../../stores/hotel-filters" import { DEFAULT_SORT } from "../../HotelSorter" import FilterContent from "../FilterContent" import styles from "./filterAndSortModal.module.css" -import type { FilterAndSortModalProps } from "@/types/components/hotelReservation/selectHotel/filterAndSortModal" -import { - type SortItem, - SortOrder, -} from "@/types/components/hotelReservation/selectHotel/hotelSorter" +import type { CategorizedHotelFilters } from "../../../../types" + +type SortItem = { + label: string + value: string +} + +type FilterAndSortModalProps = { + filters: CategorizedHotelFilters + setShowSkeleton?: (showSkeleton: boolean) => void +} export default function FilterAndSortModal({ filters, diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/FilterCheckbox/filterCheckbox.module.css b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/FilterCheckbox/filterCheckbox.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/FilterCheckbox/filterCheckbox.module.css rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/FilterCheckbox/filterCheckbox.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx similarity index 87% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx index 5938348b1..ebba98dc0 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/FilterCheckbox/index.tsx @@ -7,7 +7,13 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import styles from "./filterCheckbox.module.css" -import type { FilterCheckboxProps } from "@/types/components/hotelReservation/selectHotel/filterCheckbox" +type FilterCheckboxProps = { + name: string + id: string + isDisabled?: boolean + isSelected: boolean + onChange: (filterId: string) => void +} export default function FilterCheckbox({ isSelected, diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/filterContent.module.css b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/filterContent.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/filterContent.module.css rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/filterContent.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/index.tsx b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/index.tsx similarity index 94% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/index.tsx index 3b1c86730..f0f808e27 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/FilterContent/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/Filters/FilterContent/index.tsx @@ -8,10 +8,7 @@ import FilterCheckbox from "./FilterCheckbox" import styles from "./filterContent.module.css" -import type { - CategorizedHotelFilters, - HotelFilter, -} from "@/types/components/hotelReservation/selectHotel/hotelFilters" +import type { CategorizedHotelFilters, HotelFilter } from "../../../../types" interface FilterContentProps { filters: CategorizedHotelFilters @@ -83,7 +80,6 @@ export default function FilterContent({ isDisabled={isDisabled} /> {!isDisabled && ( - // eslint-disable-next-line formatjs/no-literal-string-in-jsx {`(${combinedFiltersCount > 0 ? combinedFiltersCount : filterCount})`} )} diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/HotelFilter/hotelFilter.module.css b/packages/booking-flow/lib/components/SelectHotel/Filters/HotelFilter/hotelFilter.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/HotelFilter/hotelFilter.module.css rename to packages/booking-flow/lib/components/SelectHotel/Filters/HotelFilter/hotelFilter.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/HotelFilter/index.tsx b/packages/booking-flow/lib/components/SelectHotel/Filters/HotelFilter/index.tsx similarity index 78% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/HotelFilter/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/Filters/HotelFilter/index.tsx index 4c7a55a2a..4e4260c26 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/HotelFilter/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/Filters/HotelFilter/index.tsx @@ -3,17 +3,20 @@ import { usePathname, useSearchParams } from "next/navigation" import { useCallback, useEffect } from "react" -import { trackEvent } from "@scandic-hotels/common/tracking/base" - -import { useHotelFilterStore } from "@/stores/hotel-filters" - -import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl" - +import useInitializeFiltersFromUrl from "../../../../hooks/useInitializeFiltersFromUrl" +import { useHotelFilterStore } from "../../../../stores/hotel-filters" +import { useTrackingContext } from "../../../../trackingContext" import FilterContent from "../FilterContent" -import type { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters" +import type { CategorizedHotelFilters } from "../../../../types" + +type HotelFiltersProps = { + filters: CategorizedHotelFilters + className?: string +} export default function HotelFilter({ className, filters }: HotelFiltersProps) { + const tracking = useTrackingContext() const searchParams = useSearchParams() const pathname = usePathname() useInitializeFiltersFromUrl() @@ -41,13 +44,18 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) { .map((id) => surroundingsMap.get(id)) .join(",") - trackEvent({ + tracking.trackGenericEvent({ event: "filterUsed", filter: { filtersUsed: `Filters values - hotelfacilities:${hotelFacilitiesFilter}|hotelsurroundings:${hotelSurroundingsFilter}`, }, }) - }, [activeFilters, filters.facilityFilters, filters.surroundingsFilters]) + }, [ + tracking, + activeFilters, + filters.facilityFilters, + filters.surroundingsFilters, + ]) // Update the URL when the filters changes useEffect(() => { diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/Filters/index.ts b/packages/booking-flow/lib/components/SelectHotel/Filters/index.ts similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/Filters/index.ts rename to packages/booking-flow/lib/components/SelectHotel/Filters/index.ts diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/HotelCount/index.tsx b/packages/booking-flow/lib/components/SelectHotel/HotelCount/index.tsx similarity index 88% rename from apps/scandic-web/components/HotelReservation/SelectHotel/HotelCount/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/HotelCount/index.tsx index da30f5040..c93ae4727 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/HotelCount/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/HotelCount/index.tsx @@ -4,7 +4,7 @@ import { useIntl } from "react-intl" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useHotelFilterStore } from "@/stores/hotel-filters" +import { useHotelFilterStore } from "../../../stores/hotel-filters" export default function HotelCount() { const intl = useIntl() diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/HotelSorter/index.tsx b/packages/booking-flow/lib/components/SelectHotel/HotelSorter/index.tsx similarity index 82% rename from apps/scandic-web/components/HotelReservation/SelectHotel/HotelSorter/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/HotelSorter/index.tsx index c3c19d5ad..697cc1aea 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/HotelSorter/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/HotelSorter/index.tsx @@ -4,18 +4,30 @@ import { usePathname, useSearchParams } from "next/navigation" import { useCallback } from "react" import { useIntl } from "react-intl" -import { trackEvent } from "@scandic-hotels/common/tracking/base" import DeprecatedSelect from "@scandic-hotels/design-system/DeprecatedSelect" -import { - type HotelSorterProps, - type SortItem, - SortOrder, -} from "@/types/components/hotelReservation/selectHotel/hotelSorter" +import { useTrackingContext } from "../../../trackingContext" + +const enum SortOrder { + Distance = "distance", + Name = "name", + Price = "price", + TripAdvisorRating = "tripadvisor", +} + +type SortItem = { + label: string + value: string +} export const DEFAULT_SORT = SortOrder.Distance +type HotelSorterProps = { + discreet?: boolean +} + export default function HotelSorter({ discreet }: HotelSorterProps) { + const tracking = useTrackingContext() const searchParams = useSearchParams() const pathname = usePathname() const intl = useIntl() @@ -29,7 +41,7 @@ export default function HotelSorter({ discreet }: HotelSorterProps) { const newSearchParams = new URLSearchParams(searchParams) newSearchParams.set("sort", newSort) - trackEvent({ + tracking.trackGenericEvent({ event: "sortOptionClick", filter: { sortOptions: newSort, @@ -41,7 +53,7 @@ export default function HotelSorter({ discreet }: HotelSorterProps) { `${pathname}?${newSearchParams.toString()}` ) }, - [pathname, searchParams] + [tracking, pathname, searchParams] ) const sortItems: SortItem[] = [ { diff --git a/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/index.tsx b/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/index.tsx new file mode 100644 index 000000000..43c6f7f44 --- /dev/null +++ b/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/index.tsx @@ -0,0 +1,28 @@ +"use client" +import { useIntl } from "react-intl" + +import { FakeButton } from "@scandic-hotels/design-system/FakeButton" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" + +import styles from "./mapWithButtonWrapper.module.css" + +export function MapWithButtonWrapper({ children }: React.PropsWithChildren) { + const intl = useIntl() + return ( +
+ {children} + + + {intl.formatMessage({ + defaultMessage: "See on map", + })} + +
+ ) +} diff --git a/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/mapWithButtonWrapper.module.css b/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/mapWithButtonWrapper.module.css new file mode 100644 index 000000000..fbed5ce8e --- /dev/null +++ b/packages/booking-flow/lib/components/SelectHotel/MapWithButtonWrapper/mapWithButtonWrapper.module.css @@ -0,0 +1,16 @@ +.container { + display: flex; + position: relative; + border-radius: var(--Corner-radius-md); + overflow: hidden; + flex-direction: column; + + align-items: center; +} + +.button { + position: absolute; + bottom: var(--Space-x2); + right: var(--Space-x2); + box-shadow: 0px 0px 8px 1px rgba(0, 0, 0, 0.1); +} diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/MobileMapButtonContainer/index.tsx b/packages/booking-flow/lib/components/SelectHotel/MobileMapButtonContainer/index.tsx similarity index 90% rename from apps/scandic-web/components/HotelReservation/SelectHotel/MobileMapButtonContainer/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/MobileMapButtonContainer/index.tsx index 436be3024..964f1778c 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/MobileMapButtonContainer/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/MobileMapButtonContainer/index.tsx @@ -10,13 +10,12 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import Link from "@scandic-hotels/design-system/Link" import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" -import useLang from "@/hooks/useLang" - +import useLang from "../../../hooks/useLang" import FilterAndSortModal from "../Filters/FilterAndSortModal" import styles from "./mobileMapButtonContainer.module.css" -import type { CategorizedHotelFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters" +import type { CategorizedHotelFilters } from "../../../types" export default function MobileMapButtonContainer({ filters, diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/MobileMapButtonContainer/mobileMapButtonContainer.module.css b/packages/booking-flow/lib/components/SelectHotel/MobileMapButtonContainer/mobileMapButtonContainer.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/MobileMapButtonContainer/mobileMapButtonContainer.module.css rename to packages/booking-flow/lib/components/SelectHotel/MobileMapButtonContainer/mobileMapButtonContainer.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/NoAvailabilityAlert.tsx b/packages/booking-flow/lib/components/SelectHotel/NoAvailabilityAlert.tsx similarity index 78% rename from apps/scandic-web/components/HotelReservation/SelectHotel/NoAvailabilityAlert.tsx rename to packages/booking-flow/lib/components/SelectHotel/NoAvailabilityAlert.tsx index c99228027..97cc4ee5d 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/NoAvailabilityAlert.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/NoAvailabilityAlert.tsx @@ -1,22 +1,34 @@ +"use client" + +import { useIntl } from "react-intl" + import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert" import { alternativeHotels } from "@scandic-hotels/common/constants/routes/hotelReservation" import { Alert } from "@scandic-hotels/design-system/Alert" -import { getIntl } from "@/i18n" -import { getLang } from "@/i18n/serverContext" +import useLang from "../../hooks/useLang" -import type { NoAvailabilityAlertProp } from "@/types/components/hotelReservation/selectHotel/noAvailabilityAlert" +import type { Hotel } from "@scandic-hotels/trpc/types/hotel" -export default async function NoAvailabilityAlert({ +type NoAvailabilityAlertProps = { + hotelsLength: number + bookingCode?: string + isAllUnavailable: boolean + isAlternative?: boolean + isBookingCodeRateNotAvailable?: boolean + operaId: Hotel["operaId"] +} + +export default function NoAvailabilityAlert({ hotelsLength, bookingCode, isAllUnavailable, isAlternative, isBookingCodeRateNotAvailable, operaId, -}: NoAvailabilityAlertProp) { - const intl = await getIntl() - const lang = await getLang() +}: NoAvailabilityAlertProps) { + const intl = useIntl() + const lang = useLang() if (bookingCode && isBookingCodeRateNotAvailable && hotelsLength > 0) { const bookingCodeText = intl.formatMessage( diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/HotelListing/hotelListing.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/HotelListing/index.tsx similarity index 63% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/HotelListing/index.tsx index 35e21be9e..deda4c6b6 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/HotelListing/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/HotelListing/index.tsx @@ -2,15 +2,20 @@ import { useMediaQuery } from "usehooks-ts" -import { useHotelsMapStore } from "@/stores/hotels-map" - -import HotelCardDialogListing from "@/components/HotelReservation/HotelCardDialogListing" -import HotelCardListing from "@/components/HotelReservation/HotelCardListing" +import { useHotelsMapStore } from "../../../../stores/hotels-map" +import HotelCardDialogListing from "../../../HotelCardDialogListing" +import HotelCardListing, { + HotelCardListingTypeEnum, +} from "../../../HotelCardListing" import styles from "./hotelListing.module.css" -import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" -import type { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map" +import type { HotelResponse } from "../../helpers" + +interface HotelListingProps { + hotels: HotelResponse[] + unfilteredHotelCount: number +} export default function HotelListing({ hotels, diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx similarity index 86% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx index 166dbe749..65fa1b646 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/index.tsx @@ -5,17 +5,11 @@ import { useCallback, useMemo, useRef, useState } from "react" import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" -import BookingCodeFilter from "@scandic-hotels/booking-flow/BookingCodeFilter" -import { - BookingCodeFilterEnum, - useBookingCodeFilterStore, -} from "@scandic-hotels/booking-flow/stores/bookingCode-filter" import { alternativeHotels, selectHotel, } from "@scandic-hotels/common/constants/routes/hotelReservation" import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop" -import { trackEvent } from "@scandic-hotels/common/tracking/base" import { debounce } from "@scandic-hotels/common/utils/debounce" import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton" import { Button } from "@scandic-hotels/design-system/Button" @@ -24,39 +18,57 @@ import Link from "@scandic-hotels/design-system/Link" import { InteractiveMap } from "@scandic-hotels/design-system/Map/InteractiveMap" import { Typography } from "@scandic-hotels/design-system/Typography" -import { useHotelFilterStore } from "@/stores/hotel-filters" -import { useHotelsMapStore } from "@/stores/hotels-map" - -import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" -import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn" -import useLang from "@/hooks/useLang" -import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" - +import { useIsLoggedIn } from "../../../../hooks/useIsLoggedIn" +import useLang from "../../../../hooks/useLang" +import { mapApiImagesToGalleryImages } from "../../../../misc/imageGallery" +import { + BookingCodeFilterEnum, + useBookingCodeFilterStore, +} from "../../../../stores/bookingCode-filter" +import { useHotelFilterStore } from "../../../../stores/hotel-filters" +import { useHotelsMapStore } from "../../../../stores/hotels-map" +import { useTrackingContext } from "../../../../trackingContext" +import BookingCodeFilter from "../../../BookingCodeFilter" +import { getHotelPins } from "../../../HotelCardDialogListing/utils" +import { RoomCardSkeleton } from "../../../RoomCardSkeleton/RoomCardSkeleton" import FilterAndSortModal from "../../Filters/FilterAndSortModal" +import { type HotelResponse } from "../../helpers" import HotelListing from "../HotelListing" import { getVisibleHotels } from "./utils" import styles from "./selectHotelMapContent.module.css" -import type { SelectHotelMapProps } from "@/types/components/hotelReservation/selectHotel/map" -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" +import type { CategorizedHotelFilters } from "../../../../types" const SKELETON_LOAD_DELAY = 750 +interface SelectHotelMapContentProps { + mapId: string + hotels: HotelResponse[] + cityCoordinates: { + lat: number + lng: number + } + bookingCode: string | undefined + isBookingCodeRateAvailable?: boolean + isAlternativeHotels?: boolean + filterList: CategorizedHotelFilters +} + export function SelectHotelMapContent({ - hotelPins, cityCoordinates, mapId, hotels, - filterList, bookingCode, isBookingCodeRateAvailable, isAlternativeHotels, -}: Omit) { + filterList, +}: SelectHotelMapContentProps) { const lang = useLang() const intl = useIntl() const map = useMap() - const isUserLoggedIn = useIsUserLoggedIn() + const isUserLoggedIn = useIsLoggedIn() + const tracking = useTrackingContext() const isAboveMobile = useMediaQuery("(min-width: 900px)") const [visibleHotels, setVisibleHotels] = useState([]) @@ -75,6 +87,8 @@ export function SelectHotelMapContent({ (state) => state.activeCodeFilter ) + const hotelPins = getHotelPins(hotels) + const coordinates = useMemo(() => { if (hotelMapStore.activeHotel) { const hotel = hotels.find( @@ -269,7 +283,7 @@ export function SelectHotelMapContent({ return } - trackEvent({ + tracking.trackGenericEvent({ event: "hotelClickMap", map: { action: "hotel click - map", @@ -282,7 +296,7 @@ export function SelectHotelMapContent({ hotelMapStore.activate(args.hotelName) }} onClickHotel={(hotelId) => { - trackEvent({ + tracking.trackGenericEvent({ event: "hotelClickMap", map: { action: "hotel click - map", diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/selectHotelMapContent.module.css b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/selectHotelMapContent.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/selectHotelMapContent.module.css rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/selectHotelMapContent.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts similarity index 80% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts index 2ee2d324e..3fe858457 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts +++ b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapContent/utils.ts @@ -1,5 +1,5 @@ -import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" -import type { HotelResponse } from "@/components/HotelReservation/SelectHotel/helpers" +import type { HotelPin } from "../../../HotelCardDialogListing/utils" +import type { HotelResponse } from "../../helpers" export function getVisibleHotelPins( map: google.maps.Map | null, diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.module.css b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.module.css similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.module.css rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.module.css diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.tsx b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.tsx similarity index 72% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.tsx rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.tsx index 2df814c2a..53ea68b07 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/SelectHotelMapContainerSkeleton.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/SelectHotelMapSkeleton.tsx @@ -1,14 +1,14 @@ import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer" -import { RoomCardSkeleton } from "@/components/HotelReservation/RoomCardSkeleton/RoomCardSkeleton" +import { RoomCardSkeleton } from "../../RoomCardSkeleton/RoomCardSkeleton" -import styles from "./SelectHotelMapContainerSkeleton.module.css" +import styles from "./SelectHotelMapSkeleton.module.css" type Props = { count?: number } -export function SelectHotelMapContainerSkeleton({ count = 2 }: Props) { +export function SelectHotelMapSkeleton({ count = 2 }: Props) { return (
diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/index.tsx similarity index 55% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/index.tsx index 4ca01d3b2..115e10a38 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/SelectHotelMap/index.tsx @@ -1,33 +1,49 @@ "use client" - import { APIProvider } from "@vis.gl/react-google-maps" +import { type HotelResponse } from "../helpers" import { SelectHotelMapContent } from "./SelectHotelMapContent" -import type { SelectHotelMapProps } from "@/types/components/hotelReservation/selectHotel/map" +import type { CategorizedHotelFilters } from "../../../types" -export default function SelectHotelMap({ +export { SelectHotelMapSkeleton } from "./SelectHotelMapSkeleton" + +interface Coordinates { + lat: number + lng: number +} + +interface SelectHotelMapProps { + apiKey: string + mapId: string + hotels: HotelResponse[] + cityCoordinates: Coordinates + bookingCode: string | undefined + isBookingCodeRateAvailable?: boolean + isAlternativeHotels?: boolean + filterList: CategorizedHotelFilters +} + +export function SelectHotelMap({ apiKey, - hotelPins, mapId, hotels, - filterList, cityCoordinates, bookingCode, isBookingCodeRateAvailable, isAlternativeHotels, + filterList, }: SelectHotelMapProps) { return ( ) diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelSkeleton.tsx b/packages/booking-flow/lib/components/SelectHotel/SelectHotelSkeleton.tsx similarity index 100% rename from apps/scandic-web/components/HotelReservation/SelectHotel/SelectHotelSkeleton.tsx rename to packages/booking-flow/lib/components/SelectHotel/SelectHotelSkeleton.tsx diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/helpers.ts b/packages/booking-flow/lib/components/SelectHotel/helpers.ts similarity index 91% rename from apps/scandic-web/components/HotelReservation/SelectHotel/helpers.ts rename to packages/booking-flow/lib/components/SelectHotel/helpers.ts index 8ce2e02e8..95292c45f 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/helpers.ts +++ b/packages/booking-flow/lib/components/SelectHotel/helpers.ts @@ -2,11 +2,10 @@ import { dt } from "@scandic-hotels/common/dt" import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel" import { generateChildrenString } from "@scandic-hotels/trpc/routers/hotels/helpers" -import { getHotel } from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" - -import { getLang } from "@/i18n/serverContext" +import { serverClient } from "../../trpc" +import { getHotel } from "../../trpc/memoizedRequests" +import type { Lang } from "@scandic-hotels/common/constants/language" import type { HotelsAvailabilityItem } from "@scandic-hotels/trpc/types/availability" import type { Child } from "@scandic-hotels/trpc/types/child" import type { @@ -19,14 +18,26 @@ import type { Location, } from "@scandic-hotels/trpc/types/locations" -import type { - AlternativeHotelsAvailabilityInput, - AvailabilityInput, -} from "@/types/components/hotelReservation/selectHotel/availabilityInput" -import type { - CategorizedHotelFilters, - HotelFilter, -} from "@/types/components/hotelReservation/selectHotel/hotelFilters" +import type { CategorizedHotelFilters, HotelFilter } from "../../types" + +type AvailabilityInput = { + cityId: string + roomStayStartDate: string + roomStayEndDate: string + adults: number + children?: string + bookingCode?: string + redemption?: boolean +} + +type AlternativeHotelsAvailabilityInput = { + roomStayStartDate: string + roomStayEndDate: string + adults: number + children?: string + bookingCode?: string + redemption?: boolean +} interface AvailabilityResponse { availability: HotelsAvailabilityItem[] @@ -43,8 +54,7 @@ export interface HotelResponse { type Result = AvailabilityResponse | null type SettledResult = PromiseSettledResult[] -async function enhanceHotels(hotels: HotelsAvailabilityItem[]) { - const language = await getLang() +async function enhanceHotels(hotels: HotelsAvailabilityItem[], language: Lang) { return await Promise.allSettled( hotels.map(async (availability) => { const hotelData = await getHotel({ @@ -179,6 +189,7 @@ type GetHotelsInput = { bookingCode: string | undefined city: Location redemption: boolean + lang: Lang } export async function getHotels({ @@ -189,6 +200,7 @@ export async function getHotels({ bookingCode, city, redemption, + lang, }: GetHotelsInput) { let availableHotelsResponse: SettledResult = [] @@ -255,7 +267,7 @@ export async function getHotels({ if (!availableHotels.length) { return [] } - const hotelsResponse = await enhanceHotels(availableHotels) + const hotelsResponse = await enhanceHotels(availableHotels, lang) const hotels = getFulfilledResponses(hotelsResponse) return hotels diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx b/packages/booking-flow/lib/components/SelectHotel/index.tsx similarity index 75% rename from apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx rename to packages/booking-flow/lib/components/SelectHotel/index.tsx index b0587ce94..eda80811d 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/index.tsx +++ b/packages/booking-flow/lib/components/SelectHotel/index.tsx @@ -1,21 +1,28 @@ -import BookingCodeFilter from "@scandic-hotels/booking-flow/BookingCodeFilter" +import { + alternativeHotelsMap, + selectHotelMap, +} from "@scandic-hotels/common/constants/routes/hotelReservation" import Link from "@scandic-hotels/design-system/Link" import { Typography } from "@scandic-hotels/design-system/Typography" -import HotelCardListing from "@/components/HotelReservation/HotelCardListing" -import HotelFilter from "@/components/HotelReservation/SelectHotel/Filters/HotelFilter" -import HotelCount from "@/components/HotelReservation/SelectHotel/HotelCount" -import HotelSorter from "@/components/HotelReservation/SelectHotel/HotelSorter" -import MobileMapButtonContainer from "@/components/HotelReservation/SelectHotel/MobileMapButtonContainer" -import NoAvailabilityAlert from "@/components/HotelReservation/SelectHotel/NoAvailabilityAlert" -import { MapWithButtonWrapper } from "@/components/Maps/MapWithButtonWrapper" -import StaticMap from "@/components/Maps/StaticMap" - +import BookingCodeFilter from "../BookingCodeFilter" +import HotelCardListing from "../HotelCardListing" +import { StaticMap } from "../StaticMap" +import HotelFilter from "./Filters/HotelFilter" import { getFiltersFromHotels, type HotelResponse } from "./helpers" +import HotelCount from "./HotelCount" +import HotelSorter from "./HotelSorter" +import { MapWithButtonWrapper } from "./MapWithButtonWrapper" +import MobileMapButtonContainer from "./MobileMapButtonContainer" +import NoAvailabilityAlert from "./NoAvailabilityAlert" import styles from "./selectHotel.module.css" +import type { Lang } from "@scandic-hotels/common/constants/language" import type { Location } from "@scandic-hotels/trpc/types/locations" +import type { ReactNode } from "react" + +export { SelectHotelSkeleton } from "./SelectHotelSkeleton" interface SelectHotelProps { isAlternative?: boolean @@ -23,18 +30,18 @@ interface SelectHotelProps { city: Location hotels: HotelResponse[] isBookingCodeRateAvailable?: boolean - mapHref: string - title: string + title: ReactNode + lang: Lang } -export default async function SelectHotel({ +export async function SelectHotel({ bookingCode, city, hotels, isAlternative = false, isBookingCodeRateAvailable = false, - mapHref, title, + lang, }: SelectHotelProps) { const isAllUnavailable = hotels.every( (hotel) => hotel.availability.status !== "Available" @@ -75,7 +82,15 @@ export default async function SelectHotel({ {showBookingCodeFilter ? : null}
{hotels.length ? ( - + , + "googleMapKey" | "googleMapSecret" +> +export async function StaticMap(props: Props) { + const key = env.GOOGLE_STATIC_MAP_KEY + const secret = env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET + + return ( + + ) +} diff --git a/apps/scandic-web/hooks/useInitializeFiltersFromUrl.ts b/packages/booking-flow/lib/hooks/useInitializeFiltersFromUrl.ts similarity index 88% rename from apps/scandic-web/hooks/useInitializeFiltersFromUrl.ts rename to packages/booking-flow/lib/hooks/useInitializeFiltersFromUrl.ts index 6ff791064..c71662ebd 100644 --- a/apps/scandic-web/hooks/useInitializeFiltersFromUrl.ts +++ b/packages/booking-flow/lib/hooks/useInitializeFiltersFromUrl.ts @@ -1,7 +1,7 @@ import { useSearchParams } from "next/navigation" import { useEffect } from "react" -import { useHotelFilterStore } from "@/stores/hotel-filters" +import { useHotelFilterStore } from "../stores/hotel-filters" export default function useInitializeFiltersFromUrl() { const searchParams = useSearchParams() diff --git a/packages/booking-flow/lib/hooks/useIsLoggedIn.ts b/packages/booking-flow/lib/hooks/useIsLoggedIn.ts new file mode 100644 index 000000000..81cdebb6c --- /dev/null +++ b/packages/booking-flow/lib/hooks/useIsLoggedIn.ts @@ -0,0 +1,7 @@ +import { useBookingFlowContext } from "../bookingFlowContext" + +export function useIsLoggedIn() { + const data = useBookingFlowContext() + + return data.isLoggedIn +} diff --git a/apps/scandic-web/utils/hotelSearchDetails.ts b/packages/booking-flow/lib/misc/getHotelSearchDetails.ts similarity index 61% rename from apps/scandic-web/utils/hotelSearchDetails.ts rename to packages/booking-flow/lib/misc/getHotelSearchDetails.ts index 3ea5f43a5..299e80a06 100644 --- a/apps/scandic-web/utils/hotelSearchDetails.ts +++ b/packages/booking-flow/lib/misc/getHotelSearchDetails.ts @@ -1,5 +1,3 @@ -import { notFound } from "next/navigation" - import { safeTry } from "@scandic-hotels/common/utils/safeTry" import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking" import { @@ -8,12 +6,12 @@ import { type Location, } from "@scandic-hotels/trpc/types/locations" -import { getLocations } from "@/lib/trpc/memoizedRequests" +import { getLocations } from "../trpc/memoizedRequests/getLocations" -import type { BookingSearchType } from "@scandic-hotels/booking-flow/searchType" +import type { Lang } from "@scandic-hotels/common/constants/language" import type { Child } from "@scandic-hotels/trpc/types/child" -export type ChildrenInRoom = (Child[] | null)[] | null +import type { BookingSearchType } from "./searchType" interface HotelSearchDetails { city: Location | null @@ -22,19 +20,18 @@ interface HotelSearchDetails { redemption?: boolean } -export async function getHotelSearchDetails( - params: { - hotelId?: string - city?: string - rooms?: { - adults: number - childrenInRoom?: Child[] - }[] - searchType?: BookingSearchType - }, +export async function getHotelSearchDetails(params: { + hotelId?: string + city?: string + rooms?: { + adults: number + childrenInRoom?: Child[] + }[] + searchType?: BookingSearchType isAlternativeHotels?: boolean -): Promise { - const [locations, error] = await safeTry(getLocations()) + lang: Lang +}): Promise { + const [locations, error] = await safeTry(getLocations(params.lang)) if (!locations || error) { return null } @@ -48,11 +45,11 @@ export async function getHotelSearchDetails( ) as HotelLocation | undefined) ?? null) : null - if (isAlternativeHotels && !hotel) { - return notFound() + if (params.isAlternativeHotels && !hotel) { + return null } - const cityIdentifier = isAlternativeHotels + const cityIdentifier = params.isAlternativeHotels ? hotel?.relationships.city.cityIdentifier : params.city @@ -65,8 +62,8 @@ export async function getHotelSearchDetails( ) ?? null) : null - if (!city && !hotel) return notFound() - if (isAlternativeHotels && (!city || !hotel)) return notFound() + if (!city && !hotel) return null + if (params.isAlternativeHotels && (!city || !hotel)) return null return { city, diff --git a/packages/booking-flow/lib/misc/imageGallery.ts b/packages/booking-flow/lib/misc/imageGallery.ts new file mode 100644 index 000000000..dbfdf3d76 --- /dev/null +++ b/packages/booking-flow/lib/misc/imageGallery.ts @@ -0,0 +1,16 @@ +import type { ApiImage } from "@scandic-hotels/trpc/types/hotel" + +export function mapApiImagesToGalleryImages(apiImages: ApiImage[]) { + return apiImages.map((apiImage) => { + return { + src: apiImage.imageSizes.medium, + alt: + apiImage.metaData.altText || + apiImage.metaData.altText_En || + apiImage.metaData.title || + apiImage.metaData.title_En, + caption: apiImage.metaData.title || apiImage.metaData.title_En, + smallSrc: apiImage.imageSizes.small, + } + }) +} diff --git a/apps/scandic-web/components/HotelReservation/SelectHotel/tracking.ts b/packages/booking-flow/lib/misc/selectHotelTracking.ts similarity index 89% rename from apps/scandic-web/components/HotelReservation/SelectHotel/tracking.ts rename to packages/booking-flow/lib/misc/selectHotelTracking.ts index e26706985..6fb44fa2f 100644 --- a/apps/scandic-web/components/HotelReservation/SelectHotel/tracking.ts +++ b/packages/booking-flow/lib/misc/selectHotelTracking.ts @@ -1,16 +1,18 @@ import { differenceInCalendarDays, format, isWeekend } from "date-fns" -import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" - -import type { Lang } from "@scandic-hotels/common/constants/language" - -import type { SelectHotelBooking } from "@/types/components/hotelReservation/selectHotel/selectHotel" import { TrackingChannelEnum, type TrackingSDKHotelInfo, type TrackingSDKPageData, -} from "@/types/components/tracking" -import type { ChildrenInRoom } from "@/utils/hotelSearchDetails" +} from "@scandic-hotels/common/tracking/types" +import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { Child } from "@scandic-hotels/trpc/types/child" + +import type { SelectHotelBooking } from "../utils/url" + +type ChildrenInRoom = (Child[] | null)[] | null type SelectHotelTrackingInput = { lang: Lang @@ -46,7 +48,10 @@ export function getSelectHotelTracking({ isBookingCodeRateAvailable = false, isRedemption = false, isRedemptionAvailable = false, -}: SelectHotelTrackingInput) { +}: SelectHotelTrackingInput): { + hotelsTrackingData: TrackingSDKHotelInfo + pageTrackingData: TrackingSDKPageData +} { const pageTrackingData: TrackingSDKPageData = { channel: TrackingChannelEnum["hotelreservation"], domainLanguage: lang, diff --git a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelSorter.ts b/packages/booking-flow/lib/misc/sortOrder.ts similarity index 52% rename from apps/scandic-web/types/components/hotelReservation/selectHotel/hotelSorter.ts rename to packages/booking-flow/lib/misc/sortOrder.ts index 05df13785..460ec592e 100644 --- a/apps/scandic-web/types/components/hotelReservation/selectHotel/hotelSorter.ts +++ b/packages/booking-flow/lib/misc/sortOrder.ts @@ -4,12 +4,3 @@ export const enum SortOrder { Price = "price", TripAdvisorRating = "tripadvisor", } - -export type SortItem = { - label: string - value: string -} - -export type HotelSorterProps = { - discreet?: boolean -} diff --git a/packages/booking-flow/lib/pages/AlternativeHotelsMapPage.tsx b/packages/booking-flow/lib/pages/AlternativeHotelsMapPage.tsx new file mode 100644 index 000000000..6ff011793 --- /dev/null +++ b/packages/booking-flow/lib/pages/AlternativeHotelsMapPage.tsx @@ -0,0 +1,134 @@ +import { notFound } from "next/navigation" +import { Suspense } from "react" + +import { safeTry } from "@scandic-hotels/common/utils/safeTry" + +import { env } from "../../env/server" +import { MapContainer } from "../components/MapContainer" +import { + getFiltersFromHotels, + getHotels, +} from "../components/SelectHotel/helpers" +import { + SelectHotelMap, + SelectHotelMapSkeleton, +} from "../components/SelectHotel/SelectHotelMap" +import { getHotelSearchDetails } from "../misc/getHotelSearchDetails" +import { getSelectHotelTracking } from "../misc/selectHotelTracking" +import { getCityCoordinates } from "../trpc/memoizedRequests/getCityCoordinates" +import { parseSelectHotelSearchParams } from "../utils/url" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { + TrackingSDKHotelInfo, + TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" + +import type { NextSearchParams } from "../types" + +export async function AlternativeHotelsMapPage({ + lang, + searchParams, + renderTracking, +}: { + lang: Lang + searchParams: NextSearchParams + renderTracking: (trackingProps: { + hotelsTrackingData: TrackingSDKHotelInfo + pageTrackingData: TrackingSDKPageData + }) => React.ReactNode +}) { + const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID + const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY + + const booking = parseSelectHotelSearchParams(searchParams) + + if (!booking) return notFound() + + const getHotelSearchDetailsPromise = safeTry( + getHotelSearchDetails({ ...booking, lang, isAlternativeHotels: true }) + ) + + const [searchDetails] = await getHotelSearchDetailsPromise + + if (!searchDetails) { + return notFound() + } + + const { + city, + cityIdentifier, + hotel: isAlternativeFor, + redemption, + } = searchDetails + + if (!city) { + return notFound() + } + + const hotels = await getHotels({ + fromDate: booking.fromDate, + toDate: booking.toDate, + rooms: booking.rooms, + isAlternativeFor, + bookingCode: booking.bookingCode, + city, + redemption: !!redemption, + lang, + }) + + const cityCoordinates = await getCityCoordinates({ + city: city.name, + hotel: { address: hotels?.[0]?.hotel?.address.streetAddress }, + }) + + const arrivalDate = new Date(booking.fromDate) + const departureDate = new Date(booking.toDate) + const isRedemptionAvailability = redemption + ? hotels.some( + (hotel) => hotel.availability.productType?.redemptions?.length + ) + : false + + const isBookingCodeRateAvailable = booking.bookingCode + ? hotels?.some((hotel) => hotel.availability.bookingCode) + : false + + const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ + lang, + pageId: isAlternativeFor ? "alternative-hotels" : "select-hotel", + pageName: "hotelreservation|alternative-hotels|mapview", + siteSections: "hotelreservation|altervative-hotels|mapview", + arrivalDate, + departureDate, + rooms: booking.rooms, + hotelsResult: hotels.length, + searchTerm: isAlternativeFor ? booking.hotelId : cityIdentifier, + country: hotels?.[0]?.hotel.address.country, + hotelCity: hotels?.[0]?.hotel.address.city, + bookingCode: booking.bookingCode, + isBookingCodeRateAvailable, + isRedemption: redemption, + isRedemptionAvailable: isRedemptionAvailability, + }) + + const filterList = getFiltersFromHotels(hotels) + + return ( + + }> + + {renderTracking({ hotelsTrackingData, pageTrackingData })} + + + ) +} diff --git a/packages/booking-flow/lib/pages/AlternativeHotelsPage.tsx b/packages/booking-flow/lib/pages/AlternativeHotelsPage.tsx new file mode 100644 index 000000000..e060345ff --- /dev/null +++ b/packages/booking-flow/lib/pages/AlternativeHotelsPage.tsx @@ -0,0 +1,137 @@ +import stringify from "json-stable-stringify-without-jsonify" +import { cookies } from "next/headers" +import { notFound } from "next/navigation" +import { Suspense } from "react" + +import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends" + +import { AlternativeHotelsPageTitle } from "../components/AlternativeHotelsPageTitle" +import FnFNotAllowedAlert from "../components/FnFNotAllowedAlert" +import { SelectHotel } from "../components/SelectHotel" +import { getHotels } from "../components/SelectHotel/helpers" +import { getHotelSearchDetails } from "../misc/getHotelSearchDetails" +import { getSelectHotelTracking } from "../misc/selectHotelTracking" +import { parseSelectHotelSearchParams } from "../utils/url" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { + TrackingSDKHotelInfo, + TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" + +import type { NextSearchParams } from "../types" + +export async function AlternativeHotelsPage({ + lang, + searchParams, + renderTracking, +}: { + lang: Lang + searchParams: NextSearchParams + renderTracking: (trackingProps: { + hotelsTrackingData: TrackingSDKHotelInfo + pageTrackingData: TrackingSDKPageData + }) => React.ReactNode +}) { + const booking = parseSelectHotelSearchParams(searchParams) + + if (!booking) return notFound() + + const searchDetails = await getHotelSearchDetails({ + ...booking, + lang, + isAlternativeHotels: true, + }) + + if (!searchDetails || !searchDetails.hotel || !searchDetails.city) { + return notFound() + } + + // TODO move logic to function to reuse + if ( + booking.bookingCode && + FamilyAndFriendsCodes.includes(booking.bookingCode) + ) { + const cookieStore = await cookies() + const isInvalidFNF = cookieStore.get("sc")?.value !== "1" + + if (isInvalidFNF) { + return + } + } + + // TODO: This needs to be refactored into its + // own functions + const hotels = await getHotels({ + fromDate: booking.fromDate, + toDate: booking.toDate, + rooms: booking.rooms, + isAlternativeFor: searchDetails.hotel, + bookingCode: booking.bookingCode, + city: searchDetails.city, + redemption: !!searchDetails.redemption, + lang, + }) + + const arrivalDate = new Date(booking.fromDate) + const departureDate = new Date(booking.toDate) + + const isRedemptionAvailability = searchDetails.redemption + ? hotels.some( + (hotel) => hotel.availability.productType?.redemptions?.length + ) + : false + + const isBookingCodeRateAvailable = booking.bookingCode + ? hotels.some( + (hotel) => + hotel.availability.bookingCode && + hotel.availability.status === "Available" + ) + : false + + const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ + lang, + pageId: searchDetails.hotel ? "alternative-hotels" : "select-hotel", + pageName: searchDetails.hotel + ? "hotelreservation|alternative-hotels" + : "hotelreservation|select-hotel", + siteSections: searchDetails.hotel + ? "hotelreservation|alternative-hotels" + : "hotelreservation|select-hotel", + arrivalDate, + departureDate, + rooms: booking.rooms, + hotelsResult: hotels?.length ?? 0, + searchTerm: searchDetails.hotel + ? booking.hotelId + : searchDetails.cityIdentifier, + country: hotels?.[0]?.hotel.address.country, + hotelCity: hotels?.[0]?.hotel.address.city, + bookingCode: booking.bookingCode, + isBookingCodeRateAvailable, + isRedemption: searchDetails.redemption, + isRedemptionAvailable: isRedemptionAvailability, + }) + + const suspenseKey = stringify(searchParams) + + return ( + <> + + } + lang={lang} + /> + + {renderTracking({ hotelsTrackingData, pageTrackingData })} + + + ) +} diff --git a/packages/booking-flow/lib/pages/SelectHotelMapPage.tsx b/packages/booking-flow/lib/pages/SelectHotelMapPage.tsx new file mode 100644 index 000000000..d5cd1484d --- /dev/null +++ b/packages/booking-flow/lib/pages/SelectHotelMapPage.tsx @@ -0,0 +1,136 @@ +import stringify from "json-stable-stringify-without-jsonify" +import { notFound } from "next/navigation" +import { Suspense } from "react" + +import { safeTry } from "@scandic-hotels/common/utils/safeTry" + +import { env } from "../../env/server" +import { MapContainer } from "../components/MapContainer" +import { + getFiltersFromHotels, + getHotels, +} from "../components/SelectHotel/helpers" +import { + SelectHotelMap, + SelectHotelMapSkeleton, +} from "../components/SelectHotel/SelectHotelMap" +import { getHotelSearchDetails } from "../misc/getHotelSearchDetails" +import { getSelectHotelTracking } from "../misc/selectHotelTracking" +import { getCityCoordinates } from "../trpc/memoizedRequests/getCityCoordinates" +import { parseSelectHotelSearchParams } from "../utils/url" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { + TrackingSDKHotelInfo, + TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" + +import type { NextSearchParams } from "../types" + +export async function SelectHotelMapPage({ + lang, + searchParams, + renderTracking, +}: { + lang: Lang + searchParams: NextSearchParams + renderTracking: (trackingProps: { + hotelsTrackingData: TrackingSDKHotelInfo + pageTrackingData: TrackingSDKPageData + }) => React.ReactNode +}) { + const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID + const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY + + const booking = parseSelectHotelSearchParams(searchParams) + + if (!booking) return notFound() + + const getHotelSearchDetailsPromise = safeTry( + getHotelSearchDetails({ ...booking, lang }) + ) + + const [searchDetails] = await getHotelSearchDetailsPromise + + if (!searchDetails) { + return notFound() + } + + const { + city, + cityIdentifier, + hotel: isAlternativeFor, + redemption, + } = searchDetails + + if (!city) { + return notFound() + } + + const hotels = await getHotels({ + fromDate: booking.fromDate, + toDate: booking.toDate, + rooms: booking.rooms, + isAlternativeFor, + bookingCode: booking.bookingCode, + city, + redemption: !!redemption, + lang, + }) + + const cityCoordinates = await getCityCoordinates({ + city: city.name, + hotel: { address: hotels?.[0]?.hotel?.address.streetAddress }, + }) + + const arrivalDate = new Date(booking.fromDate) + const departureDate = new Date(booking.toDate) + const isRedemptionAvailability = redemption + ? hotels.some( + (hotel) => hotel.availability.productType?.redemptions?.length + ) + : false + + const isBookingCodeRateAvailable = booking.bookingCode + ? hotels?.some((hotel) => hotel.availability.bookingCode) + : false + + const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ + lang, + pageId: isAlternativeFor ? "alternative-hotels" : "select-hotel", + pageName: "hotelreservation|select-hotel|mapview", + siteSections: "hotelreservation|select-hotel|mapview", + arrivalDate, + departureDate, + rooms: booking.rooms, + hotelsResult: hotels.length, + searchTerm: isAlternativeFor ? booking.hotelId : cityIdentifier, + country: hotels?.[0]?.hotel.address.country, + hotelCity: hotels?.[0]?.hotel.address.city, + bookingCode: booking.bookingCode, + isBookingCodeRateAvailable, + isRedemption: redemption, + isRedemptionAvailable: isRedemptionAvailability, + }) + + const filterList = getFiltersFromHotels(hotels) + + const suspenseKey = stringify(searchParams) + + return ( + + }> + + {renderTracking({ hotelsTrackingData, pageTrackingData })} + + + ) +} diff --git a/packages/booking-flow/lib/pages/SelectHotelPage.tsx b/packages/booking-flow/lib/pages/SelectHotelPage.tsx new file mode 100644 index 000000000..0c0787435 --- /dev/null +++ b/packages/booking-flow/lib/pages/SelectHotelPage.tsx @@ -0,0 +1,120 @@ +import stringify from "json-stable-stringify-without-jsonify" +import { cookies } from "next/headers" +import { notFound } from "next/navigation" +import { Suspense } from "react" + +import { FamilyAndFriendsCodes } from "@scandic-hotels/common/constants/familyAndFriends" + +import FnFNotAllowedAlert from "../components/FnFNotAllowedAlert" +import { SelectHotel } from "../components/SelectHotel" +import { getHotels } from "../components/SelectHotel/helpers" +import { getHotelSearchDetails } from "../misc/getHotelSearchDetails" +import { getSelectHotelTracking } from "../misc/selectHotelTracking" +import { parseSelectHotelSearchParams } from "../utils/url" + +import type { Lang } from "@scandic-hotels/common/constants/language" +import type { + TrackingSDKHotelInfo, + TrackingSDKPageData, +} from "@scandic-hotels/common/tracking/types" + +import type { NextSearchParams } from "../types" + +export async function SelectHotelPage({ + lang, + searchParams, + renderTracking, +}: { + lang: Lang + searchParams: NextSearchParams + renderTracking: (trackingProps: { + hotelsTrackingData: TrackingSDKHotelInfo + pageTrackingData: TrackingSDKPageData + }) => React.ReactNode +}) { + const booking = parseSelectHotelSearchParams(searchParams) + + if (!booking) return notFound() + + const searchDetails = await getHotelSearchDetails({ ...booking, lang }) + + if (!searchDetails || !searchDetails.city) return notFound() + + if ( + booking.bookingCode && + FamilyAndFriendsCodes.includes(booking.bookingCode) + ) { + const cookieStore = await cookies() + const isInvalidFNF = cookieStore.get("sc")?.value !== "1" + + if (isInvalidFNF) { + return + } + } + + const { city, redemption } = searchDetails + + const hotels = await getHotels({ + fromDate: booking.fromDate, + toDate: booking.toDate, + rooms: booking.rooms, + isAlternativeFor: null, + bookingCode: booking.bookingCode, + city: city, + redemption: !!redemption, + lang, + }) + + const isRedemptionAvailability = redemption + ? hotels.some( + (hotel) => hotel.availability.productType?.redemptions?.length + ) + : false + + const isBookingCodeRateAvailable = booking.bookingCode + ? hotels.some( + (hotel) => + hotel.availability.bookingCode && + hotel.availability.status === "Available" + ) + : false + + const arrivalDate = new Date(booking.fromDate) + const departureDate = new Date(booking.toDate) + + const { hotelsTrackingData, pageTrackingData } = getSelectHotelTracking({ + rooms: booking.rooms, + lang: lang, + pageId: "select-hotel", + pageName: "hotelreservation|select-hotel", + siteSections: "hotelreservation|select-hotel", + arrivalDate, + departureDate, + hotelsResult: hotels?.length ?? 0, + searchTerm: booking.hotelId, + country: hotels?.[0]?.hotel.address.country, + hotelCity: hotels?.[0]?.hotel.address.city, + bookingCode: booking.bookingCode, + isBookingCodeRateAvailable, + isRedemption: redemption, + isRedemptionAvailable: isRedemptionAvailability, + }) + + const suspenseKey = stringify(searchParams) + + return ( + <> + + + {renderTracking({ hotelsTrackingData, pageTrackingData })} + + + ) +} diff --git a/apps/scandic-web/stores/hotel-filters.ts b/packages/booking-flow/lib/stores/hotel-filters.ts similarity index 100% rename from apps/scandic-web/stores/hotel-filters.ts rename to packages/booking-flow/lib/stores/hotel-filters.ts diff --git a/apps/scandic-web/stores/hotels-map.ts b/packages/booking-flow/lib/stores/hotels-map.ts similarity index 100% rename from apps/scandic-web/stores/hotels-map.ts rename to packages/booking-flow/lib/stores/hotels-map.ts diff --git a/packages/booking-flow/lib/trackingContext.tsx b/packages/booking-flow/lib/trackingContext.tsx index 5d660ed18..63698f6b7 100644 --- a/packages/booking-flow/lib/trackingContext.tsx +++ b/packages/booking-flow/lib/trackingContext.tsx @@ -14,6 +14,7 @@ export type TrackingFunctions = { includePathname?: boolean roomTypeCode?: string | null }) => void + trackGenericEvent(data: any): void } export const TrackingContext = createContext( diff --git a/packages/booking-flow/lib/trpc/memoizedRequests.ts b/packages/booking-flow/lib/trpc/memoizedRequests.ts index 327a38f36..4b955ac3d 100644 --- a/packages/booking-flow/lib/trpc/memoizedRequests.ts +++ b/packages/booking-flow/lib/trpc/memoizedRequests.ts @@ -3,6 +3,7 @@ import { cache } from "react" import { serverClient } from "../trpc" import type { Lang } from "@scandic-hotels/common/constants/language" +import type { HotelInput } from "@scandic-hotels/trpc/types/hotel" export const getSiteConfig = cache(async function getMemoizedSiteConfig( lang: Lang @@ -46,3 +47,13 @@ export const getPageSettingsBookingCode = cache( return pageSettings?.page.settings.booking_code ?? "" } ) + +export const getHotel = cache(async function getMemoizedHotelData( + input: HotelInput +) { + if (!input.isCardOnlyPayment) { + input.isCardOnlyPayment = false + } + const caller = await serverClient() + return caller.hotel.get(input) +}) diff --git a/packages/booking-flow/lib/trpc/memoizedRequests/getCityCoordinates.ts b/packages/booking-flow/lib/trpc/memoizedRequests/getCityCoordinates.ts new file mode 100644 index 000000000..74c8d2257 --- /dev/null +++ b/packages/booking-flow/lib/trpc/memoizedRequests/getCityCoordinates.ts @@ -0,0 +1,12 @@ +import { cache } from "react" + +import { serverClient } from "../../trpc" + +import type { CityCoordinatesInput } from "@scandic-hotels/trpc/types/hotel" + +export const getCityCoordinates = cache( + async function getMemoizedCityCoordinates(input: CityCoordinatesInput) { + const caller = await serverClient() + return caller.hotel.map.city(input) + } +) diff --git a/packages/booking-flow/lib/trpc/memoizedRequests/getLocations.ts b/packages/booking-flow/lib/trpc/memoizedRequests/getLocations.ts new file mode 100644 index 000000000..94c8f3242 --- /dev/null +++ b/packages/booking-flow/lib/trpc/memoizedRequests/getLocations.ts @@ -0,0 +1,12 @@ +import { cache } from "react" + +import { serverClient } from "../../trpc" + +import type { Lang } from "@scandic-hotels/common/constants/language" + +export const getLocations = cache(async function getMemoizedLocations( + lang: Lang +) { + const caller = await serverClient() + return caller.hotel.locations.get({ lang }) +}) diff --git a/packages/booking-flow/lib/types.ts b/packages/booking-flow/lib/types.ts index 476cf64da..c03c87e1a 100644 --- a/packages/booking-flow/lib/types.ts +++ b/packages/booking-flow/lib/types.ts @@ -1 +1,13 @@ +import type { Hotel } from "@scandic-hotels/trpc/types/hotel" + export type NextSearchParams = { [key: string]: string | string[] | undefined } + +export type HotelFilter = Hotel["detailedFacilities"][number] & { + hotelId: string + hotelIds: string[] +} + +export type CategorizedHotelFilters = { + facilityFilters: HotelFilter[] + surroundingsFilters: HotelFilter[] +} diff --git a/packages/booking-flow/lib/utils/isSameBooking.ts b/packages/booking-flow/lib/utils/isSameBooking.ts new file mode 100644 index 000000000..60cdbd810 --- /dev/null +++ b/packages/booking-flow/lib/utils/isSameBooking.ts @@ -0,0 +1,68 @@ +import isEqual from "fast-deep-equal" + +import { + parseBookingWidgetSearchParams, + searchParamsToRecord, + type SelectRateBooking, +} from "./url" + +import type { BookingWidgetSearchData } from "../components/BookingWidget" + +/** + * Parses and compares booking widget search parameters + * @param param0 + * @returns true if the searches are the same + */ +export function isSameBookingWidgetParams({ + previousParams, + currentParams, +}: { + previousParams: URLSearchParams + currentParams: URLSearchParams +}) { + const previousParamsObject = parseBookingWidgetSearchParams( + searchParamsToRecord(previousParams) + ) + const currentParamsObject = parseBookingWidgetSearchParams( + searchParamsToRecord(currentParams) + ) + + if (!previousParamsObject && !currentParamsObject) return false + if (!previousParamsObject || !currentParamsObject) return true + + const isSame = isSameBooking(previousParamsObject, currentParamsObject) + return !isSame +} + +/** + * Compares if two sets of select-rate searches are the same + * @param prev + * @param next + * @returns + */ +export function isSameBooking( + prev: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string }, + next: (SelectRateBooking | BookingWidgetSearchData) & { errorCode?: string } +) { + const { rooms: prevRooms, errorCode: prevErrorCode, ...prevBooking } = prev + + const prevRoomsWithoutRateCodes = prevRooms?.map( + ({ adults, childrenInRoom }) => ({ adults, childrenInRoom }) + ) + const { rooms: nextRooms, errorCode: nextErrorCode, ...nextBooking } = next + + const nextRoomsWithoutRateCodes = nextRooms?.map( + ({ adults, childrenInRoom }) => ({ adults, childrenInRoom }) + ) + + return isEqual( + { + ...prevBooking, + rooms: prevRoomsWithoutRateCodes, + }, + { + ...nextBooking, + rooms: nextRoomsWithoutRateCodes, + } + ) +} diff --git a/packages/booking-flow/package.json b/packages/booking-flow/package.json index cc4975e8b..bfbd6c0ce 100644 --- a/packages/booking-flow/package.json +++ b/packages/booking-flow/package.json @@ -11,35 +11,47 @@ "test:watch": "vitest" }, "exports": { + "./bedTypeIcons": "./lib/misc/bedTypeIcons.ts", "./BookingCodeFilter": "./lib/components/BookingCodeFilter/index.tsx", + "./BookingFlowContextProvider": "./lib/components/BookingFlowContextProvider.tsx", + "./BookingFlowTrackingProvider": "./lib/components/BookingFlowTrackingProvider.tsx", "./BookingWidget": "./lib/components/BookingWidget/index.tsx", + "./BookingWidget/BookingWidgetForm/FormContent/Search": "./lib/components/BookingWidget/BookingWidgetForm/FormContent/Search/index.tsx", "./BookingWidget/FloatingBookingWidget": "./lib/components/BookingWidget/FloatingBookingWidget/index.tsx", "./BookingWidget/Skeleton": "./lib/components/BookingWidget/Skeleton.tsx", - "./BookingWidget/BookingWidgetForm/FormContent/Search": "./lib/components/BookingWidget/BookingWidgetForm/FormContent/Search/index.tsx", - "./BookingFlowTrackingProvider": "./lib/components/BookingFlowTrackingProvider.tsx", - "./utils/url": "./lib/utils/url.ts", - "./hooks/useSearchHistory": "./lib/hooks/useSearchHistory.ts", - "./searchType": "./lib/misc/searchType.ts", - "./bedTypeIcons": "./lib/misc/bedTypeIcons.ts", - "./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts", - "./components/TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx", - "./components/Contact": "./lib/components/Contact/index.tsx", "./components/AdditionalAmenities": "./lib/components/AdditionalAmenities/index.tsx", + "./components/Contact": "./lib/components/Contact/index.tsx", + "./components/FnFNotAllowedAlert": "./lib/components/FnFNotAllowedAlert/index.tsx", + "./components/HotelDetailsSidePeek": "./lib/components/HotelDetailsSidePeek/index.tsx", "./components/HotelReservationSidePeek": "./lib/components/HotelReservationSidePeek/index.tsx", - "./components/RoomSidePeekContent": "./lib/components/RoomSidePeek/RoomSidePeekContent/index.tsx", "./components/OpenSidePeekButton": "./lib/components/OpenSidePeekButton/index.tsx", + "./components/RoomCardSkeleton": "./lib/components/RoomCardSkeleton/RoomCardSkeleton.tsx", + "./components/RoomSidePeekContent": "./lib/components/RoomSidePeek/RoomSidePeekContent/index.tsx", + "./components/SelectHotel": "./lib/components/SelectHotel/index.tsx", + "./components/SelectHotelMap": "./lib/components/SelectHotel/SelectHotelMap/index.tsx", "./components/SidePeekAccordions/BreakfastAccordionItem": "./lib/components/SidePeekAccordions/BreakfastAccordionItem.tsx", "./components/SidePeekAccordions/CheckInCheckOutAccordionItem": "./lib/components/SidePeekAccordions/CheckInCheckOutAccordionItem.tsx", - "./components/SidePeekAccordions/ParkingAccordionItem": "./lib/components/SidePeekAccordions/ParkingAccordionItem.tsx" + "./components/SidePeekAccordions/ParkingAccordionItem": "./lib/components/SidePeekAccordions/ParkingAccordionItem.tsx", + "./components/TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx", + "./hooks/useSearchHistory": "./lib/hooks/useSearchHistory.ts", + "./pages/*": "./lib/pages/*.tsx", + "./searchType": "./lib/misc/searchType.ts", + "./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts", + "./stores/hotels-map": "./lib/stores/hotels-map.ts", + "./utils/isSameBooking": "./lib/utils/isSameBooking.ts", + "./utils/url": "./lib/utils/url.ts" }, "dependencies": { "@hookform/resolvers": "^5.0.1", "@scandic-hotels/common": "workspace:*", "@scandic-hotels/design-system": "workspace:*", "@scandic-hotels/trpc": "workspace:*", + "@vis.gl/react-google-maps": "^1.5.2", "class-variance-authority": "^0.7.1", "date-fns": "^4.1.0", "downshift": "^9.0.9", + "fast-deep-equal": "^3.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", "motion": "^12.10.0", "react-aria-components": "^1.8.0", "react-day-picker": "^9.6.7", diff --git a/packages/common/constants/familyAndFriends.ts b/packages/common/constants/familyAndFriends.ts new file mode 100644 index 000000000..d7d4390bd --- /dev/null +++ b/packages/common/constants/familyAndFriends.ts @@ -0,0 +1 @@ +export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"] diff --git a/packages/trpc/lib/types/loginType.ts b/packages/common/constants/loginType.ts similarity index 100% rename from packages/trpc/lib/types/loginType.ts rename to packages/common/constants/loginType.ts diff --git a/packages/trpc/lib/enums/rate.ts b/packages/common/constants/rate.ts similarity index 100% rename from packages/trpc/lib/enums/rate.ts rename to packages/common/constants/rate.ts diff --git a/packages/common/package.json b/packages/common/package.json index 644f61a6f..d213a28df 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -11,40 +11,48 @@ "lint": "eslint . --max-warnings 0 && tsc --noEmit" }, "exports": { - "./global.d.ts": "./global.d.ts", - "./dataCache": "./dataCache/index.ts", - "./telemetry": "./telemetry/index.ts", - "./tokenManager": "./tokenManager/index.ts", - "./tracking/base": "./tracking/base.ts", - "./dt": "./dt/dt.ts", - "./logger": "./logger/index.ts", - "./logger/*": "./logger/*.ts", - "./utils/isEdge": "./utils/isEdge.ts", - "./utils/safeTry": "./utils/safeTry.ts", - "./utils/url": "./utils/url.ts", - "./utils/languages": "./utils/languages.ts", - "./utils/chunk": "./utils/chunk.ts", - "./utils/isDefined": "./utils/isDefined.ts", - "./utils/maskValue": "./utils/maskValue.ts", - "./utils/dateFormatting": "./utils/dateFormatting.ts", - "./utils/numberFormatting": "./utils/numberFormatting.ts", - "./utils/rangeArray": "./utils/rangeArray.ts", - "./utils/zod/*": "./utils/zod/*.ts", - "./utils/debounce": "./utils/debounce.ts", - "./utils/isValidJson": "./utils/isValidJson.ts", - "./hooks/*": "./hooks/*.ts", - "./stores/*": "./stores/*.ts", "./constants/alert": "./constants/alert.ts", "./constants/currency": "./constants/currency.ts", "./constants/dateFormats": "./constants/dateFormats.ts", "./constants/facilities": "./constants/facilities.ts", + "./constants/familyAndFriends": "./constants/familyAndFriends.ts", "./constants/hotelType": "./constants/hotelType.ts", "./constants/language": "./constants/language.ts", + "./constants/loginType": "./constants/loginType.ts", "./constants/membershipLevels": "./constants/membershipLevels.ts", "./constants/paymentMethod": "./constants/paymentMethod.ts", + "./constants/rate": "./constants/rate.ts", "./constants/rateType": "./constants/rateType.ts", "./constants/routes/*": "./constants/routes/*.ts", - "./constants/signatureHotels": "./constants/signatureHotels.ts" + "./constants/signatureHotels": "./constants/signatureHotels.ts", + "./dataCache": "./dataCache/index.ts", + "./dt": "./dt/dt.ts", + "./global.d.ts": "./global.d.ts", + "./hooks/*": "./hooks/*.ts", + "./logger": "./logger/index.ts", + "./logger/*": "./logger/*.ts", + "./stores/*": "./stores/*.ts", + "./telemetry": "./telemetry/index.ts", + "./tokenManager": "./tokenManager/index.ts", + "./tracking/base": "./tracking/base.ts", + "./tracking/pageview": "./tracking/pageview.ts", + "./tracking/types": "./tracking/types.ts", + "./tracking/useTrackHardNavigation": "./tracking/useTrackHardNavigation.ts", + "./tracking/useTrackSoftNavigation": "./tracking/useTrackSoftNavigation.ts", + "./utils/chunk": "./utils/chunk.ts", + "./utils/dateFormatting": "./utils/dateFormatting.ts", + "./utils/debounce": "./utils/debounce.ts", + "./utils/isDefined": "./utils/isDefined.ts", + "./utils/isEdge": "./utils/isEdge.ts", + "./utils/isValidJson": "./utils/isValidJson.ts", + "./utils/languages": "./utils/languages.ts", + "./utils/maskValue": "./utils/maskValue.ts", + "./utils/numberFormatting": "./utils/numberFormatting.ts", + "./utils/rangeArray": "./utils/rangeArray.ts", + "./utils/safeTry": "./utils/safeTry.ts", + "./utils/url": "./utils/url.ts", + "./utils/promiseWithTimeout": "./utils/promiseWithTimeout.ts", + "./utils/zod/*": "./utils/zod/*.ts" }, "dependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/apps/scandic-web/stores/router-transition.ts b/packages/common/stores/router-transition.ts similarity index 100% rename from apps/scandic-web/stores/router-transition.ts rename to packages/common/stores/router-transition.ts diff --git a/apps/scandic-web/stores/tracking.ts b/packages/common/stores/tracking.ts similarity index 64% rename from apps/scandic-web/stores/tracking.ts rename to packages/common/stores/tracking.ts index af351c23a..0bc3628e5 100644 --- a/apps/scandic-web/stores/tracking.ts +++ b/packages/common/stores/tracking.ts @@ -2,32 +2,24 @@ import { create } from "zustand" -import { - parseBookingWidgetSearchParams, - searchParamsToRecord, -} from "@scandic-hotels/booking-flow/utils/url" - -import { checkIsSameBooking } from "./enter-details/helpers" - -import type { ReadonlyURLSearchParams } from "next/navigation" - interface TrackingStoreState { initialStartTime: number setInitialPageLoadTime: (time: number) => void getPageLoadTime: () => number - currentParams: ReadonlyURLSearchParams | null - previousParams: ReadonlyURLSearchParams | null + currentParams: URLSearchParams | null + previousParams: URLSearchParams | null currentPath: string | null previousPath: string | null currentLang: string | null previousLang: string | null - updateRouteInfo: ( - path: string, - lang: string, - params: ReadonlyURLSearchParams - ) => void + updateRouteInfo: (path: string, lang: string, params: URLSearchParams) => void hasPathOrLangChanged: () => boolean - hasBookingFlowParamsChanged: () => boolean + hasBookingFlowParamsChanged: ( + searchParamsComparator: (input: { + previousParams: URLSearchParams + currentParams: URLSearchParams + }) => boolean + ) => boolean } const useTrackingStore = create((set, get) => ({ @@ -74,7 +66,7 @@ const useTrackingStore = create((set, get) => ({ return currentPath !== previousPath || currentLang !== previousLang }, - hasBookingFlowParamsChanged: () => { + hasBookingFlowParamsChanged: (searchParamsComparator) => { const { currentPath, currentParams, previousParams } = get() if (!previousParams || !currentParams) return false @@ -82,21 +74,7 @@ const useTrackingStore = create((set, get) => ({ if (!currentPath?.match(/^\/(da|de|en|fi|no|sv)\/(hotelreservation)/)) return false - const previousParamsObject = parseBookingWidgetSearchParams( - searchParamsToRecord(previousParams) - ) - const currentParamsObject = parseBookingWidgetSearchParams( - searchParamsToRecord(currentParams) - ) - - if (!previousParamsObject && !currentParamsObject) return false - if (!previousParamsObject || !currentParamsObject) return true - - const isSameBooking = checkIsSameBooking( - previousParamsObject, - currentParamsObject - ) - return !isSameBooking + return searchParamsComparator({ previousParams, currentParams }) }, })) diff --git a/apps/scandic-web/utils/tracking/pageview.ts b/packages/common/tracking/pageview.ts similarity index 86% rename from apps/scandic-web/utils/tracking/pageview.ts rename to packages/common/tracking/pageview.ts index a7ff3965b..af2e0e5c1 100644 --- a/apps/scandic-web/utils/tracking/pageview.ts +++ b/packages/common/tracking/pageview.ts @@ -1,6 +1,6 @@ -import { trackEvent } from "@scandic-hotels/common/tracking/base" +import { trackEvent } from "./base" -import type { TrackingSDKData } from "@/types/components/tracking" +import type { TrackingSDKData } from "./types" function convertSlashToPipe(url: string) { const formattedUrl = url.startsWith("/") ? url.slice(1) : url diff --git a/apps/scandic-web/types/components/tracking.ts b/packages/common/tracking/types.ts similarity index 94% rename from apps/scandic-web/types/components/tracking.ts rename to packages/common/tracking/types.ts index 5cb32c182..f734791c8 100644 --- a/apps/scandic-web/types/components/tracking.ts +++ b/packages/common/tracking/types.ts @@ -1,7 +1,7 @@ import type { Lang } from "@scandic-hotels/common/constants/language" +import type { LoginType } from "@scandic-hotels/common/constants/loginType" import type { MembershipLevel } from "@scandic-hotels/common/constants/membershipLevels" -import type { RateEnum } from "@scandic-hotels/trpc/enums/rate" -import type { LoginType } from "@scandic-hotels/trpc/types/loginType" +import type { RateEnum } from "@scandic-hotels/common/constants/rate" export enum TrackingChannelEnum { "scandic-friends" = "scandic-friends", @@ -122,13 +122,6 @@ export type TrackingSDKPaymentInfo = { status?: string } -export type TrackingSDKProps = { - pageData: TrackingSDKPageData - hotelInfo?: TrackingSDKHotelInfo - paymentInfo?: TrackingSDKPaymentInfo - ancillaries?: TrackingSDKAncillaries -} - export type TrackingSDKData = TrackingSDKPageData & { pathName: string } diff --git a/packages/common/tracking/useTrackHardNavigation.ts b/packages/common/tracking/useTrackHardNavigation.ts new file mode 100644 index 000000000..21b62ae22 --- /dev/null +++ b/packages/common/tracking/useTrackHardNavigation.ts @@ -0,0 +1,166 @@ +"use client" + +import { useEffect } from "react" + +import { useSessionId } from "../hooks/useSessionId" +import { logger } from "../logger" +import { createSDKPageObject, trackPageView } from "../tracking/pageview" +import { promiseWithTimeout } from "../utils/promiseWithTimeout" + +import type { + TrackingSDKAncillaries, + TrackingSDKHotelInfo, + TrackingSDKPageData, + TrackingSDKPaymentInfo, + TrackingSDKUserData, +} from "../tracking/types" + +type TrackingSDKProps = { + pageData: TrackingSDKPageData + hotelInfo?: TrackingSDKHotelInfo + paymentInfo?: TrackingSDKPaymentInfo + ancillaries?: TrackingSDKAncillaries + userData: TrackingSDKUserData | undefined + pathName: string +} + +let hasTrackedHardNavigation = false +export const useTrackHardNavigation = ({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, +}: TrackingSDKProps) => { + const sessionId = useSessionId() + + useEffect(() => { + if (!userData) { + return + } + + if (hasTrackedHardNavigation) { + return + } + + hasTrackedHardNavigation = true + + const track = () => { + trackPerformance({ + pathName, + sessionId, + paymentInfo, + hotelInfo, + userData, + pageData, + ancillaries, + }) + } + + if (document.readyState === "complete") { + track() + return + } + + window.addEventListener("load", track) + return () => window.removeEventListener("load", track) + }, [ + pathName, + hotelInfo, + pageData, + sessionId, + paymentInfo, + userData, + ancillaries, + ]) +} + +const trackPerformance = async ({ + pathName, + sessionId, + paymentInfo, + hotelInfo, + userData, + pageData, + ancillaries, +}: { + pathName: string + sessionId: string | null + paymentInfo: TrackingSDKProps["paymentInfo"] + hotelInfo: TrackingSDKProps["hotelInfo"] + userData: TrackingSDKUserData + pageData: TrackingSDKProps["pageData"] + ancillaries: TrackingSDKProps["ancillaries"] +}) => { + let pageLoadTime: number | undefined = undefined + let lcpTime: number | undefined = undefined + + try { + pageLoadTime = await promiseWithTimeout(getPageLoadTimeEntry(), 3000) + } catch (error) { + logger.error("Error obtaining pageLoadTime:", error) + } + + try { + lcpTime = await promiseWithTimeout(getLCPTimeEntry(), 3000) + } catch (error) { + logger.error("Error obtaining lcpTime:", error) + } + + const trackingData = { + ...pageData, + sessionId, + pathName, + pageLoadTime, + lcpTime, + } + const pageObject = createSDKPageObject(trackingData) + + trackPageView({ + event: "pageView", + pageInfo: pageObject, + userInfo: userData, + hotelInfo, + paymentInfo, + ancillaries, + }) +} + +const getLCPTimeEntry = () => { + return new Promise((resolve) => { + const observer = new PerformanceObserver((entries) => { + const lastEntry = entries.getEntries().at(-1) + if (lastEntry) { + observer.disconnect() + resolve(lastEntry.startTime / 1000) + } + }) + + const lcpSupported = PerformanceObserver.supportedEntryTypes?.includes( + "largest-contentful-paint" + ) + + if (lcpSupported) { + observer.observe({ + type: "largest-contentful-paint", + buffered: true, + }) + } else { + resolve(undefined) + } + }) +} + +const getPageLoadTimeEntry = () => { + return new Promise((resolve) => { + const observer = new PerformanceObserver((entries) => { + const navEntry = entries.getEntriesByType("navigation")[0] + if (navEntry) { + observer.disconnect() + resolve(navEntry.duration / 1000) + } + }) + observer.observe({ type: "navigation", buffered: true }) + }) +} diff --git a/packages/common/tracking/useTrackSoftNavigation.ts b/packages/common/tracking/useTrackSoftNavigation.ts new file mode 100644 index 000000000..94f5508c4 --- /dev/null +++ b/packages/common/tracking/useTrackSoftNavigation.ts @@ -0,0 +1,106 @@ +"use client" + +import { startTransition, useEffect, useRef, useState } from "react" + +import { useSessionId } from "../hooks/useSessionId" +import useRouterTransitionStore from "../stores/router-transition" +import useTrackingStore from "../stores/tracking" +import { createSDKPageObject, trackPageView } from "./pageview" + +import type { + TrackingSDKAncillaries, + TrackingSDKHotelInfo, + TrackingSDKPageData, + TrackingSDKPaymentInfo, + TrackingSDKUserData, +} from "./types" + +type TrackingSDKProps = { + pageData: TrackingSDKPageData + hotelInfo?: TrackingSDKHotelInfo + paymentInfo?: TrackingSDKPaymentInfo + ancillaries?: TrackingSDKAncillaries + userData: TrackingSDKUserData | undefined + pathName: string +} + +enum TransitionStatusEnum { + NotRun = "NotRun", + Running = "Running", + Done = "Done", +} + +type TransitionStatus = keyof typeof TransitionStatusEnum + +export const useTrackSoftNavigation = ({ + pageData, + hotelInfo, + paymentInfo, + ancillaries, + userData, + pathName, +}: TrackingSDKProps) => { + const [status, setStatus] = useState( + TransitionStatusEnum.NotRun + ) + const { getPageLoadTime } = useTrackingStore() + + const sessionId = useSessionId() + const { isTransitioning, stopRouterTransition } = useRouterTransitionStore() + + const previousPathname = useRef(null) + + useEffect(() => { + if (!userData) { + return + } + + if (isTransitioning && status === TransitionStatusEnum.NotRun) { + startTransition(() => { + setStatus(TransitionStatusEnum.Running) + }) + return + } + + if (isTransitioning && status === TransitionStatusEnum.Running) { + setStatus(TransitionStatusEnum.Done) + stopRouterTransition() + return + } + + if (!isTransitioning && status === TransitionStatusEnum.Done) { + const pageLoadTime = getPageLoadTime() + const trackingData = { + ...pageData, + sessionId, + pathName, + pageLoadTime: pageLoadTime, + } + const pageObject = createSDKPageObject(trackingData) + + trackPageView({ + event: "pageView", + pageInfo: pageObject, + userInfo: userData, + hotelInfo: hotelInfo, + paymentInfo, + ancillaries, + }) + + setStatus(TransitionStatusEnum.NotRun) // Reset status + previousPathname.current = pathName // Update for next render + } + }, [ + isTransitioning, + status, + stopRouterTransition, + pageData, + pathName, + hotelInfo, + getPageLoadTime, + sessionId, + paymentInfo, + userData, + ancillaries, + ]) +} diff --git a/apps/scandic-web/utils/promiseWithTimeout.ts b/packages/common/utils/promiseWithTimeout.ts similarity index 100% rename from apps/scandic-web/utils/promiseWithTimeout.ts rename to packages/common/utils/promiseWithTimeout.ts diff --git a/packages/trpc/jwt.d.ts b/packages/trpc/jwt.d.ts index a78f6c021..4b55c1178 100644 --- a/packages/trpc/jwt.d.ts +++ b/packages/trpc/jwt.d.ts @@ -1,7 +1,7 @@ +import type { LoginType } from "@scandic-hotels/common/constants/loginType" import type { DefaultJWT } from "next-auth/jwt" import type { RefreshTokenError } from "./lib/types/authError" -import type { LoginType } from "./lib/types/loginType" // Module augmentation // https://authjs.dev/getting-started/typescript#popular-interfaces-to-augment diff --git a/packages/trpc/lib/routers/hotels/output.ts b/packages/trpc/lib/routers/hotels/output.ts index f412ef2e9..52df0890b 100644 --- a/packages/trpc/lib/routers/hotels/output.ts +++ b/packages/trpc/lib/routers/hotels/output.ts @@ -1,11 +1,11 @@ import { z } from "zod" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { logger } from "@scandic-hotels/common/logger" import { toLang } from "@scandic-hotels/common/utils/languages" import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/stringValidator" -import { RateEnum } from "../../enums/rate" import { RoomPackageCodeEnum } from "../../enums/roomFilter" import { AvailabilityEnum } from "../../enums/selectHotel" import { diff --git a/packages/trpc/lib/routers/hotels/query.ts b/packages/trpc/lib/routers/hotels/query.ts index a74b87acb..3021169d0 100644 --- a/packages/trpc/lib/routers/hotels/query.ts +++ b/packages/trpc/lib/routers/hotels/query.ts @@ -1,4 +1,5 @@ import { Lang } from "@scandic-hotels/common/constants/language" +import { RateEnum } from "@scandic-hotels/common/constants/rate" import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType" import { getCacheClient } from "@scandic-hotels/common/dataCache" import { dt } from "@scandic-hotels/common/dt" @@ -10,7 +11,6 @@ import { router } from "../.." import * as api from "../../api" import { SEARCH_TYPE_REDEMPTION } from "../../constants/booking" import { BreakfastPackageEnum } from "../../enums/breakfast" -import { RateEnum } from "../../enums/rate" import { AvailabilityEnum } from "../../enums/selectHotel" import { badRequestError, unauthorizedError } from "../../errors" import { diff --git a/packages/trpc/lib/routers/hotels/schemas/roomAvailability/product.ts b/packages/trpc/lib/routers/hotels/schemas/roomAvailability/product.ts index 825bdd41c..94914040f 100644 --- a/packages/trpc/lib/routers/hotels/schemas/roomAvailability/product.ts +++ b/packages/trpc/lib/routers/hotels/schemas/roomAvailability/product.ts @@ -1,6 +1,7 @@ import { z } from "zod" -import { RateEnum } from "../../../../enums/rate" +import { RateEnum } from "@scandic-hotels/common/constants/rate" + import { productTypeCorporateChequeSchema, productTypePointsSchema, diff --git a/packages/trpc/lib/routers/types.ts b/packages/trpc/lib/routers/types.ts index 9f2d97c6a..1642d7a78 100644 --- a/packages/trpc/lib/routers/types.ts +++ b/packages/trpc/lib/routers/types.ts @@ -1,8 +1,7 @@ import type { Lang } from "@scandic-hotels/common/constants/language" +import type { LoginType } from "@scandic-hotels/common/constants/loginType" import type { MembershipLevel } from "@scandic-hotels/common/constants/membershipLevels" -import type { LoginType } from "../types/loginType" - export type TrackingPageData = { pageId: string createDate?: string diff --git a/packages/trpc/lib/routers/user/query.ts b/packages/trpc/lib/routers/user/query.ts index efba14f76..4c5c5ba67 100644 --- a/packages/trpc/lib/routers/user/query.ts +++ b/packages/trpc/lib/routers/user/query.ts @@ -30,7 +30,8 @@ import { updateStaysBookingUrl, } from "./utils" -import type { LoginType } from "../../types/loginType" +import type { LoginType } from "@scandic-hotels/common/constants/loginType" + import type { TrackingUserData } from "../types" export const userQueryRouter = router({ diff --git a/packages/trpc/lib/types/room.ts b/packages/trpc/lib/types/room.ts index 4330005e0..f4cdd9b32 100644 --- a/packages/trpc/lib/types/room.ts +++ b/packages/trpc/lib/types/room.ts @@ -1,4 +1,5 @@ -import type { RateEnum } from "../enums/rate" +import type { RateEnum } from "@scandic-hotels/common/constants/rate" + import type { BedTypeSelection } from "./bedTypeSelection" import type { Package } from "./packages" import type { Product } from "./roomAvailability" diff --git a/yarn.lock b/yarn.lock index 809b99588..418a82ca7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6114,6 +6114,7 @@ __metadata: "@types/react": "npm:19.1.0" "@typescript-eslint/eslint-plugin": "npm:^8.32.0" "@typescript-eslint/parser": "npm:^8.32.0" + "@vis.gl/react-google-maps": "npm:^1.5.2" class-variance-authority: "npm:^0.7.1" date-fns: "npm:^4.1.0" dotenv: "npm:^16.5.0" @@ -6121,6 +6122,8 @@ __metadata: eslint: "npm:^9" eslint-plugin-import: "npm:^2.31.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" + fast-deep-equal: "npm:^3.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" motion: "npm:^12.10.0" react-aria-components: "npm:^1.8.0" react-day-picker: "npm:^9.6.7" @@ -12153,7 +12156,7 @@ __metadata: languageName: node linkType: hard -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": +"fast-deep-equal@npm:^3.1.0, fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0