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,
formState: { errors },
getValues,
trigger,
} = useFormContext<BookingWidgetSchema>()
const bookingCode: BookingCodeSchema = getValues("bookingCode")
@@ -52,6 +53,12 @@ export default function BookingCode() {
function updateBookingCodeFormValue(value: string) {
setValue("bookingCode.value", value, { shouldValidate: true })
if (getValues("redemption")) {
setValue("redemption", false)
setTimeout(function () {
trigger("bookingCode.value")
}, 5000)
}
}
const closeIfOutside = useCallback(

View File

@@ -9,18 +9,21 @@ import type { PointsCardProps } from "@/types/components/hotelReservation/select
export default function HotelPointsCard({
productTypePoints,
redemptionPrice,
}: PointsCardProps) {
const intl = useIntl()
const pointsPerStay =
productTypePoints?.localPrice.pricePerStay ?? redemptionPrice
return (
<div className={styles.poinstRow}>
<Subtitle type="two" color="uiTextHighContrast">
{productTypePoints.localPrice.pointsPerStay}
{pointsPerStay}
</Subtitle>
<Caption color="uiTextHighContrast">
{intl.formatMessage({ id: "Points" })}
</Caption>
{productTypePoints.localPrice.pricePerStay ? (
{productTypePoints?.localPrice.pricePerStay ? (
<>
+
<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 { isValidClientSession } from "@/utils/clientSession"
import HotelPointsCard from "../../HotelCard/HotelPointsCard"
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
import HotelCardDialogImage from "../HotelCardDialogImage"
@@ -44,6 +45,7 @@ export default function ListingHotelCardDialog({
images,
ratings,
operaId,
redemptionPrice,
} = data
const firstImage = images[0]?.imageSizes?.small
@@ -82,14 +84,20 @@ export default function ListingHotelCardDialog({
</div>
</div>
{publicPrice || memberPrice ? (
{publicPrice || memberPrice || redemptionPrice ? (
<div className={styles.bottomContainer}>
<div className={styles.pricesContainer}>
<Caption color="uiTextHighContrast">
{intl.formatMessage({ id: "Per night from" })}
</Caption>
{redemptionPrice ? (
<Caption color="uiTextHighContrast">
{intl.formatMessage({ id: "Available rates" })}
</Caption>
) : (
<Caption color="uiTextHighContrast">
{intl.formatMessage({ id: "Per night from" })}
</Caption>
)}
<div className={styles.listingPrices}>
{publicPrice && !isUserLoggedIn && (
{publicPrice && !isUserLoggedIn && memberPrice && (
<>
<Subtitle type="two">
{publicPrice} {currency}
@@ -108,6 +116,9 @@ export default function ListingHotelCardDialog({
)}
</Subtitle>
)}
{redemptionPrice && (
<HotelPointsCard redemptionPrice={redemptionPrice} />
)}
</div>
</div>
<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 { isValidClientSession } from "@/utils/clientSession"
import HotelPointsCard from "../../HotelCard/HotelPointsCard"
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
import HotelCardDialogImage from "../HotelCardDialogImage"
@@ -41,6 +42,7 @@ export default function StandaloneHotelCardDialog({
name,
publicPrice,
memberPrice,
redemptionPrice,
currency,
amenities,
images,
@@ -85,12 +87,18 @@ export default function StandaloneHotelCardDialog({
})}
</div>
<div className={styles.pricesContainer}>
{publicPrice || memberPrice ? (
{publicPrice || memberPrice || redemptionPrice ? (
<>
<div className={styles.priceCard}>
<Caption type="bold">
{intl.formatMessage({ id: "From" })}
</Caption>
{redemptionPrice ? (
<Caption>
{intl.formatMessage({ id: "Available rates" })}
</Caption>
) : (
<Caption type="bold">
{intl.formatMessage({ id: "From" })}
</Caption>
)}
{publicPrice && !isUserLoggedIn && (
<Subtitle type="two">
{intl.formatMessage(
@@ -123,6 +131,9 @@ export default function StandaloneHotelCardDialog({
</Body>
</Subtitle>
)}
{redemptionPrice && (
<HotelPointsCard redemptionPrice={redemptionPrice} />
)}
</div>
<Button
asChild

View File

@@ -1,6 +1,7 @@
"use client"
import { useCallback, useEffect, useRef } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { useHotelsMapStore } from "@/stores/hotels-map"
@@ -17,7 +18,12 @@ import type { HotelCardDialogListingProps } from "@/types/components/hotelReserv
export default function HotelCardDialogListing({
hotels,
}: 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 observerRef = useRef<IntersectionObserver | null>(null)
const dialogRef = useRef<HTMLDivElement>(null)

View File

@@ -1,7 +1,10 @@
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
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 []
return hotels
@@ -14,11 +17,14 @@ export function getHotelPins(hotels: HotelData[]): HotelPin[] {
name: hotel.hotelData.name,
publicPrice: hotel.price?.public?.localPrice.pricePerNight ?? null,
memberPrice: hotel.price?.member?.localPrice.pricePerNight ?? null,
redemptionPrice:
hotel.price?.redemption?.localPrice.pointsPerNight ?? null,
rateType:
hotel.price?.public?.rateType ?? hotel.price?.member?.rateType ?? null,
currency:
hotel.price?.public?.localPrice.currency ||
hotel.price?.member?.localPrice.currency ||
currencyValue ||
"N/A",
images: [
hotel.hotelData.hotelContent.images,

View File

@@ -13,6 +13,7 @@ export function getSortedHotels({
const getPricePerNight = (hotel: HotelData): number =>
hotel.price?.member?.localPrice?.pricePerNight ??
hotel.price?.public?.localPrice?.pricePerNight ??
hotel.price?.redemption?.localPrice?.pointsPerNight ??
Infinity
const availableHotels = 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"
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { safeTry } from "@/utils/safeTry"
@@ -33,6 +34,7 @@ export async function SelectHotelMapContainer({
isAlternativeHotels,
}: SelectHotelMapContainerProps) {
const lang = getLang()
const intl = await getIntl()
const googleMapId = env.GOOGLE_DYNAMIC_MAP_ID
const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY
const getHotelSearchDetailsPromise = safeTry(
@@ -58,6 +60,7 @@ export async function SelectHotelMapContainer({
childrenInRoomString,
hotel: isAlternativeFor,
bookingCode,
redemption,
} = searchDetails
if (!city) return notFound()
@@ -70,6 +73,7 @@ export async function SelectHotelMapContainer({
adults: adultsInRoom[0],
children: childrenInRoomString,
bookingCode,
redemption,
})
)
: bookingCode
@@ -90,6 +94,7 @@ export async function SelectHotelMapContainer({
roomStayEndDate: selectHotelParams.toDate,
adults: adultsInRoom[0],
children: childrenInRoomString,
redemption,
})
)
@@ -97,7 +102,10 @@ export async function SelectHotelMapContainer({
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 cityCoordinates = await getCityCoordinates({
city: city.name,

View File

@@ -58,7 +58,8 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
{hotelPins.map((pin) => {
const isActiveOrHovered =
activeHotelPin === pin.name || hoveredHotelPin === pin.name
const hotelPrice = pin.memberPrice ?? pin.publicPrice
const hotelPrice =
pin.memberPrice ?? pin.publicPrice ?? pin.redemptionPrice
return (
<AdvancedMarker
key={pin.name}

View File

@@ -32,6 +32,7 @@ export type HotelPin = {
coordinates: Coordinates
publicPrice: number | null
memberPrice: number | null
redemptionPrice: number | null
rateType: string | null
currency: string
images: {

View File

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