feat: SW-1583 City search Map view redemption
This commit is contained in:
@@ -37,6 +37,7 @@ export default function BookingCode() {
|
|||||||
setValue,
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
getValues,
|
getValues,
|
||||||
|
trigger,
|
||||||
} = useFormContext<BookingWidgetSchema>()
|
} = useFormContext<BookingWidgetSchema>()
|
||||||
|
|
||||||
const bookingCode: BookingCodeSchema = getValues("bookingCode")
|
const bookingCode: BookingCodeSchema = getValues("bookingCode")
|
||||||
@@ -52,6 +53,12 @@ export default function BookingCode() {
|
|||||||
|
|
||||||
function updateBookingCodeFormValue(value: string) {
|
function updateBookingCodeFormValue(value: string) {
|
||||||
setValue("bookingCode.value", value, { shouldValidate: true })
|
setValue("bookingCode.value", value, { shouldValidate: true })
|
||||||
|
if (getValues("redemption")) {
|
||||||
|
setValue("redemption", false)
|
||||||
|
setTimeout(function () {
|
||||||
|
trigger("bookingCode.value")
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeIfOutside = useCallback(
|
const closeIfOutside = useCallback(
|
||||||
|
|||||||
@@ -9,18 +9,21 @@ import type { PointsCardProps } from "@/types/components/hotelReservation/select
|
|||||||
|
|
||||||
export default function HotelPointsCard({
|
export default function HotelPointsCard({
|
||||||
productTypePoints,
|
productTypePoints,
|
||||||
|
redemptionPrice,
|
||||||
}: PointsCardProps) {
|
}: PointsCardProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const pointsPerStay =
|
||||||
|
productTypePoints?.localPrice.pricePerStay ?? redemptionPrice
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.poinstRow}>
|
<div className={styles.poinstRow}>
|
||||||
<Subtitle type="two" color="uiTextHighContrast">
|
<Subtitle type="two" color="uiTextHighContrast">
|
||||||
{productTypePoints.localPrice.pointsPerStay}
|
{pointsPerStay}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">
|
||||||
{intl.formatMessage({ id: "Points" })}
|
{intl.formatMessage({ id: "Points" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
{productTypePoints.localPrice.pricePerStay ? (
|
{productTypePoints?.localPrice.pricePerStay ? (
|
||||||
<>
|
<>
|
||||||
+
|
+
|
||||||
<Subtitle type="two" color="uiTextHighContrast">
|
<Subtitle type="two" color="uiTextHighContrast">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
import { isValidClientSession } from "@/utils/clientSession"
|
||||||
|
|
||||||
|
import HotelPointsCard from "../../HotelCard/HotelPointsCard"
|
||||||
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
||||||
import HotelCardDialogImage from "../HotelCardDialogImage"
|
import HotelCardDialogImage from "../HotelCardDialogImage"
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ export default function ListingHotelCardDialog({
|
|||||||
images,
|
images,
|
||||||
ratings,
|
ratings,
|
||||||
operaId,
|
operaId,
|
||||||
|
redemptionPrice,
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
const firstImage = images[0]?.imageSizes?.small
|
const firstImage = images[0]?.imageSizes?.small
|
||||||
@@ -82,14 +84,20 @@ export default function ListingHotelCardDialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{publicPrice || memberPrice ? (
|
{publicPrice || memberPrice || redemptionPrice ? (
|
||||||
<div className={styles.bottomContainer}>
|
<div className={styles.bottomContainer}>
|
||||||
<div className={styles.pricesContainer}>
|
<div className={styles.pricesContainer}>
|
||||||
<Caption color="uiTextHighContrast">
|
{redemptionPrice ? (
|
||||||
{intl.formatMessage({ id: "Per night from" })}
|
<Caption color="uiTextHighContrast">
|
||||||
</Caption>
|
{intl.formatMessage({ id: "Available rates" })}
|
||||||
|
</Caption>
|
||||||
|
) : (
|
||||||
|
<Caption color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage({ id: "Per night from" })}
|
||||||
|
</Caption>
|
||||||
|
)}
|
||||||
<div className={styles.listingPrices}>
|
<div className={styles.listingPrices}>
|
||||||
{publicPrice && !isUserLoggedIn && (
|
{publicPrice && !isUserLoggedIn && memberPrice && (
|
||||||
<>
|
<>
|
||||||
<Subtitle type="two">
|
<Subtitle type="two">
|
||||||
{publicPrice} {currency}
|
{publicPrice} {currency}
|
||||||
@@ -108,6 +116,9 @@ export default function ListingHotelCardDialog({
|
|||||||
)}
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
)}
|
)}
|
||||||
|
{redemptionPrice && (
|
||||||
|
<HotelPointsCard redemptionPrice={redemptionPrice} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild theme="base" size="small" className={styles.button}>
|
<Button asChild theme="base" size="small" className={styles.button}>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
|||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
import { isValidClientSession } from "@/utils/clientSession"
|
||||||
|
|
||||||
|
import HotelPointsCard from "../../HotelCard/HotelPointsCard"
|
||||||
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
||||||
import HotelCardDialogImage from "../HotelCardDialogImage"
|
import HotelCardDialogImage from "../HotelCardDialogImage"
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ export default function StandaloneHotelCardDialog({
|
|||||||
name,
|
name,
|
||||||
publicPrice,
|
publicPrice,
|
||||||
memberPrice,
|
memberPrice,
|
||||||
|
redemptionPrice,
|
||||||
currency,
|
currency,
|
||||||
amenities,
|
amenities,
|
||||||
images,
|
images,
|
||||||
@@ -85,12 +87,18 @@ export default function StandaloneHotelCardDialog({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.pricesContainer}>
|
<div className={styles.pricesContainer}>
|
||||||
{publicPrice || memberPrice ? (
|
{publicPrice || memberPrice || redemptionPrice ? (
|
||||||
<>
|
<>
|
||||||
<div className={styles.priceCard}>
|
<div className={styles.priceCard}>
|
||||||
<Caption type="bold">
|
{redemptionPrice ? (
|
||||||
{intl.formatMessage({ id: "From" })}
|
<Caption>
|
||||||
</Caption>
|
{intl.formatMessage({ id: "Available rates" })}
|
||||||
|
</Caption>
|
||||||
|
) : (
|
||||||
|
<Caption type="bold">
|
||||||
|
{intl.formatMessage({ id: "From" })}
|
||||||
|
</Caption>
|
||||||
|
)}
|
||||||
{publicPrice && !isUserLoggedIn && (
|
{publicPrice && !isUserLoggedIn && (
|
||||||
<Subtitle type="two">
|
<Subtitle type="two">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
@@ -123,6 +131,9 @@ export default function StandaloneHotelCardDialog({
|
|||||||
</Body>
|
</Body>
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
)}
|
)}
|
||||||
|
{redemptionPrice && (
|
||||||
|
<HotelPointsCard redemptionPrice={redemptionPrice} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef } from "react"
|
import { useCallback, useEffect, useRef } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
import { useMediaQuery } from "usehooks-ts"
|
import { useMediaQuery } from "usehooks-ts"
|
||||||
|
|
||||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||||
@@ -17,7 +18,12 @@ import type { HotelCardDialogListingProps } from "@/types/components/hotelReserv
|
|||||||
export default function HotelCardDialogListing({
|
export default function HotelCardDialogListing({
|
||||||
hotels,
|
hotels,
|
||||||
}: HotelCardDialogListingProps) {
|
}: HotelCardDialogListingProps) {
|
||||||
const hotelsPinData = hotels ? getHotelPins(hotels) : []
|
const intl = useIntl()
|
||||||
|
const isRedemption = hotels?.find((hotel) => hotel.price?.redemption)
|
||||||
|
const currencyValue = isRedemption
|
||||||
|
? intl.formatMessage({ id: "Points" })
|
||||||
|
: undefined
|
||||||
|
const hotelsPinData = hotels ? getHotelPins(hotels, currencyValue) : []
|
||||||
const activeCardRef = useRef<HTMLDivElement | null>(null)
|
const activeCardRef = useRef<HTMLDivElement | null>(null)
|
||||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||||
const dialogRef = useRef<HTMLDivElement>(null)
|
const dialogRef = useRef<HTMLDivElement>(null)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
||||||
|
|
||||||
export function getHotelPins(hotels: HotelData[]): HotelPin[] {
|
export function getHotelPins(
|
||||||
|
hotels: HotelData[],
|
||||||
|
currencyValue?: string
|
||||||
|
): HotelPin[] {
|
||||||
if (hotels.length === 0) return []
|
if (hotels.length === 0) return []
|
||||||
|
|
||||||
return hotels
|
return hotels
|
||||||
@@ -14,11 +17,14 @@ export function getHotelPins(hotels: HotelData[]): HotelPin[] {
|
|||||||
name: hotel.hotelData.name,
|
name: hotel.hotelData.name,
|
||||||
publicPrice: hotel.price?.public?.localPrice.pricePerNight ?? null,
|
publicPrice: hotel.price?.public?.localPrice.pricePerNight ?? null,
|
||||||
memberPrice: hotel.price?.member?.localPrice.pricePerNight ?? null,
|
memberPrice: hotel.price?.member?.localPrice.pricePerNight ?? null,
|
||||||
|
redemptionPrice:
|
||||||
|
hotel.price?.redemption?.localPrice.pointsPerNight ?? null,
|
||||||
rateType:
|
rateType:
|
||||||
hotel.price?.public?.rateType ?? hotel.price?.member?.rateType ?? null,
|
hotel.price?.public?.rateType ?? hotel.price?.member?.rateType ?? null,
|
||||||
currency:
|
currency:
|
||||||
hotel.price?.public?.localPrice.currency ||
|
hotel.price?.public?.localPrice.currency ||
|
||||||
hotel.price?.member?.localPrice.currency ||
|
hotel.price?.member?.localPrice.currency ||
|
||||||
|
currencyValue ||
|
||||||
"N/A",
|
"N/A",
|
||||||
images: [
|
images: [
|
||||||
hotel.hotelData.hotelContent.images,
|
hotel.hotelData.hotelContent.images,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export function getSortedHotels({
|
|||||||
const getPricePerNight = (hotel: HotelData): number =>
|
const getPricePerNight = (hotel: HotelData): number =>
|
||||||
hotel.price?.member?.localPrice?.pricePerNight ??
|
hotel.price?.member?.localPrice?.pricePerNight ??
|
||||||
hotel.price?.public?.localPrice?.pricePerNight ??
|
hotel.price?.public?.localPrice?.pricePerNight ??
|
||||||
|
hotel.price?.redemption?.localPrice?.pointsPerNight ??
|
||||||
Infinity
|
Infinity
|
||||||
const availableHotels = hotels.filter((hotel) => !!hotel?.price)
|
const availableHotels = hotels.filter((hotel) => !!hotel?.price)
|
||||||
const unAvailableHotels = hotels.filter((hotel) => !hotel?.price)
|
const unAvailableHotels = hotels.filter((hotel) => !hotel?.price)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
|
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
|
||||||
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
|
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { safeTry } from "@/utils/safeTry"
|
import { safeTry } from "@/utils/safeTry"
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ export async function SelectHotelMapContainer({
|
|||||||
isAlternativeHotels,
|
isAlternativeHotels,
|
||||||
}: SelectHotelMapContainerProps) {
|
}: SelectHotelMapContainerProps) {
|
||||||
const lang = getLang()
|
const lang = getLang()
|
||||||
|
const intl = await getIntl()
|
||||||
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
|
||||||
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
|
||||||
const getHotelSearchDetailsPromise = safeTry(
|
const getHotelSearchDetailsPromise = safeTry(
|
||||||
@@ -58,6 +60,7 @@ export async function SelectHotelMapContainer({
|
|||||||
childrenInRoomString,
|
childrenInRoomString,
|
||||||
hotel: isAlternativeFor,
|
hotel: isAlternativeFor,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
|
redemption,
|
||||||
} = searchDetails
|
} = searchDetails
|
||||||
|
|
||||||
if (!city) return notFound()
|
if (!city) return notFound()
|
||||||
@@ -70,6 +73,7 @@ export async function SelectHotelMapContainer({
|
|||||||
adults: adultsInRoom[0],
|
adults: adultsInRoom[0],
|
||||||
children: childrenInRoomString,
|
children: childrenInRoomString,
|
||||||
bookingCode,
|
bookingCode,
|
||||||
|
redemption,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
: bookingCode
|
: bookingCode
|
||||||
@@ -90,6 +94,7 @@ export async function SelectHotelMapContainer({
|
|||||||
roomStayEndDate: selectHotelParams.toDate,
|
roomStayEndDate: selectHotelParams.toDate,
|
||||||
adults: adultsInRoom[0],
|
adults: adultsInRoom[0],
|
||||||
children: childrenInRoomString,
|
children: childrenInRoomString,
|
||||||
|
redemption,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +102,10 @@ export async function SelectHotelMapContainer({
|
|||||||
|
|
||||||
const validHotels = (hotels?.filter(Boolean) as HotelData[]) || []
|
const validHotels = (hotels?.filter(Boolean) as HotelData[]) || []
|
||||||
|
|
||||||
const hotelPins = getHotelPins(validHotels)
|
const currencyValue = redemption
|
||||||
|
? intl.formatMessage({ id: "Points" })
|
||||||
|
: undefined
|
||||||
|
const hotelPins = getHotelPins(validHotels, currencyValue)
|
||||||
const filterList = getFiltersFromHotels(validHotels)
|
const filterList = getFiltersFromHotels(validHotels)
|
||||||
const cityCoordinates = await getCityCoordinates({
|
const cityCoordinates = await getCityCoordinates({
|
||||||
city: city.name,
|
city: city.name,
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
|
|||||||
{hotelPins.map((pin) => {
|
{hotelPins.map((pin) => {
|
||||||
const isActiveOrHovered =
|
const isActiveOrHovered =
|
||||||
activeHotelPin === pin.name || hoveredHotelPin === pin.name
|
activeHotelPin === pin.name || hoveredHotelPin === pin.name
|
||||||
const hotelPrice = pin.memberPrice ?? pin.publicPrice
|
const hotelPrice =
|
||||||
|
pin.memberPrice ?? pin.publicPrice ?? pin.redemptionPrice
|
||||||
return (
|
return (
|
||||||
<AdvancedMarker
|
<AdvancedMarker
|
||||||
key={pin.name}
|
key={pin.name}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type HotelPin = {
|
|||||||
coordinates: Coordinates
|
coordinates: Coordinates
|
||||||
publicPrice: number | null
|
publicPrice: number | null
|
||||||
memberPrice: number | null
|
memberPrice: number | null
|
||||||
|
redemptionPrice: number | null
|
||||||
rateType: string | null
|
rateType: string | null
|
||||||
currency: string
|
currency: string
|
||||||
images: {
|
images: {
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ export type PriceCardProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PointsCardProps = {
|
export type PointsCardProps = {
|
||||||
productTypePoints: ProductTypePoints
|
productTypePoints?: ProductTypePoints
|
||||||
|
redemptionPrice?: number
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user