feat: SW-1583 City search Map view redemption

This commit is contained in:
Hrishikesh Vaipurkar
2025-03-05 19:08:54 +01:00
parent f6db5f2732
commit 23eaa772ea
11 changed files with 72 additions and 16 deletions

View File

@@ -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(

View File

@@ -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">

View File

@@ -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}>

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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,

View File

@@ -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}

View File

@@ -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: {

View File

@@ -9,5 +9,6 @@ export type PriceCardProps = {
} }
export type PointsCardProps = { export type PointsCardProps = {
productTypePoints: ProductTypePoints productTypePoints?: ProductTypePoints
redemptionPrice?: number
} }