feat: SW-2028 Points validation redemption city search

This commit is contained in:
Hrishikesh Vaipurkar
2025-03-24 16:35:09 +01:00
parent 779495017e
commit eda475d64e
12 changed files with 71 additions and 16 deletions

View File

@@ -18,6 +18,7 @@ import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery" import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getSingleDecimal } from "@/utils/numberFormatting" import { getSingleDecimal } from "@/utils/numberFormatting"
@@ -43,6 +44,7 @@ function HotelCard({
state = "default", state = "default",
type = HotelCardListingTypeEnum.PageListing, type = HotelCardListingTypeEnum.PageListing,
bookingCode = "", bookingCode = "",
userPoints,
}: HotelCardProps) { }: HotelCardProps) {
const params = useParams() const params = useParams()
const lang = params.lang as Lang const lang = params.lang as Lang
@@ -72,6 +74,15 @@ function HotelCard({
availability.productType?.member?.rateType === RateTypeEnum.Regular availability.productType?.member?.rateType === RateTypeEnum.Regular
const price = availability.productType const price = availability.productType
const userHasEnoughPoints =
userPoints !== undefined
? !!price?.redemptions?.some(
(r) => r.localPrice.pointsPerStay < userPoints
)
: false
const notEnoughPointsLabel = intl.formatMessage({ id: "Not enough points" })
return ( return (
<article <article
className={classNames} className={classNames}
@@ -207,21 +218,39 @@ function HotelCard({
))} ))}
</div> </div>
) : null} ) : null}
<Button {userPoints !== undefined && !userHasEnoughPoints ? (
asChild <Tooltip
theme="base" arrow="left"
intent="primary" position="bottom"
size="small" text={notEnoughPointsLabel}
className={styles.button}
>
<Link
href={`${selectRate(lang)}?hotel=${hotel.operaId}`}
color="none"
keepSearchParams
> >
{intl.formatMessage({ id: "See rooms" })} <Button
</Link> theme="base"
</Button> intent="primary"
size="small"
className={styles.button}
disabled
>
{notEnoughPointsLabel}
</Button>
</Tooltip>
) : (
<Button
asChild
theme="base"
intent="primary"
size="small"
className={styles.button}
>
<Link
href={`${selectRate(lang)}?hotel=${hotel.operaId}`}
color="none"
keepSearchParams
>
{intl.formatMessage({ id: "See rooms" })}
</Link>
</Button>
)}
</> </>
)} )}
</div> </div>

View File

@@ -30,6 +30,7 @@ import { RateTypeEnum } from "@/types/enums/rateType"
export default function HotelCardListing({ export default function HotelCardListing({
hotelData, hotelData,
type = HotelCardListingTypeEnum.PageListing, type = HotelCardListingTypeEnum.PageListing,
userPoints,
}: HotelCardListingProps) { }: HotelCardListingProps) {
const { data: session } = useSession() const { data: session } = useSession()
const isUserLoggedIn = isValidClientSession(session) const isUserLoggedIn = isValidClientSession(session)
@@ -121,6 +122,7 @@ export default function HotelCardListing({
} }
type={type} type={type}
bookingCode={bookingCode} bookingCode={bookingCode}
userPoints={userPoints}
/> />
</div> </div>
)) ))

View File

@@ -246,6 +246,14 @@ export async function getHotels(
return hotels return hotels
} }
export async function getUserPoints() {
const membershipCard = await serverClient().user.safeMembershipLevel()
if (!membershipCard) {
return undefined
}
return membershipCard.currentPoints
}
const hotelSurroundingsFilterNames = [ const hotelSurroundingsFilterNames = [
"Hotel surroundings", "Hotel surroundings",
"Hotel omgivelser", "Hotel omgivelser",

View File

@@ -23,7 +23,7 @@ import { convertObjToSearchParams } from "@/utils/url"
import HotelCardListing from "../HotelCardListing" import HotelCardListing from "../HotelCardListing"
import BookingCodeFilter from "./BookingCodeFilter" import BookingCodeFilter from "./BookingCodeFilter"
import { getFiltersFromHotels, getHotels } from "./helpers" import { getFiltersFromHotels, getHotels, getUserPoints } from "./helpers"
import HotelCount from "./HotelCount" import HotelCount from "./HotelCount"
import HotelFilter from "./HotelFilter" import HotelFilter from "./HotelFilter"
import HotelSorter from "./HotelSorter" import HotelSorter from "./HotelSorter"
@@ -76,6 +76,11 @@ export default async function SelectHotel({
!!redemption !!redemption
) )
let userPoints
if (redemption) {
userPoints = await getUserPoints()
}
const arrivalDate = new Date(selectHotelParams.fromDate) const arrivalDate = new Date(selectHotelParams.fromDate)
const departureDate = new Date(selectHotelParams.toDate) const departureDate = new Date(selectHotelParams.toDate)
@@ -247,7 +252,10 @@ export default async function SelectHotel({
isAllUnavailable={isAllUnavailable} isAllUnavailable={isAllUnavailable}
operaId={hotels?.[0]?.hotel.operaId} operaId={hotels?.[0]?.hotel.operaId}
/> />
<HotelCardListing hotelData={hotels} /> <HotelCardListing
hotelData={hotels}
userPoints={userPoints ? userPoints : undefined}
/>
</div> </div>
</main> </main>
<Suspense key={`${suspenseKey}-tracking`} fallback={null}> <Suspense key={`${suspenseKey}-tracking`} fallback={null}>

View File

@@ -539,6 +539,7 @@
"Non refundable": "Ikke-refunderbart", "Non refundable": "Ikke-refunderbart",
"Non-refundable": "Ikke-refunderbart", "Non-refundable": "Ikke-refunderbart",
"Nordic Swan Ecolabel": "Svanemærket", "Nordic Swan Ecolabel": "Svanemærket",
"Not enough points": "Ikke nok point",
"Not found": "Ikke fundet", "Not found": "Ikke fundet",
"Not included": "Ikke inkluderet", "Not included": "Ikke inkluderet",
"Number of charging points for electric cars: {number}": "Antal ladepunkter til elbiler: {number}", "Number of charging points for electric cars: {number}": "Antal ladepunkter til elbiler: {number}",

View File

@@ -540,6 +540,7 @@
"Non refundable": "Nicht erstattungsfähig", "Non refundable": "Nicht erstattungsfähig",
"Non-refundable": "Nicht erstattungsfähig", "Non-refundable": "Nicht erstattungsfähig",
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel", "Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
"Not enough points": "Nicht genügend Punkte",
"Not found": "Nicht gefunden", "Not found": "Nicht gefunden",
"Not included": "Nicht inbegriffen", "Not included": "Nicht inbegriffen",
"Number of charging points for electric cars: {number}": "Anzahl der Ladestationen für Elektroautos: {number}", "Number of charging points for electric cars: {number}": "Anzahl der Ladestationen für Elektroautos: {number}",

View File

@@ -538,6 +538,7 @@
"Non refundable": "Non refundable", "Non refundable": "Non refundable",
"Non-refundable": "Non-refundable", "Non-refundable": "Non-refundable",
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel", "Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
"Not enough points": "Not enough points",
"Not found": "Not found", "Not found": "Not found",
"Not included": "Not included", "Not included": "Not included",
"Number of charging points for electric cars: {number}": "Number of charging points for electric cars: {number}", "Number of charging points for electric cars: {number}": "Number of charging points for electric cars: {number}",

View File

@@ -539,6 +539,7 @@
"Non refundable": "Ei palautettavissa", "Non refundable": "Ei palautettavissa",
"Non-refundable": "Ei palautettavissa", "Non-refundable": "Ei palautettavissa",
"Nordic Swan Ecolabel": "Ympäristömerkki Miljömärkt", "Nordic Swan Ecolabel": "Ympäristömerkki Miljömärkt",
"Not enough points": "Pisteet eivät riitä",
"Not found": "Ei löydetty", "Not found": "Ei löydetty",
"Not included": "Ei sisälly", "Not included": "Ei sisälly",
"Number of charging points for electric cars: {number}": "Sähköautojen latauspisteiden määrä: {number}", "Number of charging points for electric cars: {number}": "Sähköautojen latauspisteiden määrä: {number}",

View File

@@ -538,6 +538,7 @@
"Non refundable": "Ikke-refunderbart", "Non refundable": "Ikke-refunderbart",
"Non-refundable": "Ikke-refunderbart", "Non-refundable": "Ikke-refunderbart",
"Nordic Swan Ecolabel": "Svanemerket", "Nordic Swan Ecolabel": "Svanemerket",
"Not enough points": "Ikke nok poeng",
"Not found": "Ikke funnet", "Not found": "Ikke funnet",
"Not included": "Ikke inkludert", "Not included": "Ikke inkludert",
"Number of charging points for electric cars: {number}": "Antall ladepunkter for elbiler: {number}", "Number of charging points for electric cars: {number}": "Antall ladepunkter for elbiler: {number}",

View File

@@ -538,6 +538,7 @@
"Non refundable": "Ej återbetalningsbar", "Non refundable": "Ej återbetalningsbar",
"Non-refundable": "Ej återbetalningsbar", "Non-refundable": "Ej återbetalningsbar",
"Nordic Swan Ecolabel": "Svanenmärkt", "Nordic Swan Ecolabel": "Svanenmärkt",
"Not enough points": "Inte tillräckligt antal poäng",
"Not found": "Hittades inte", "Not found": "Hittades inte",
"Not included": "Ej inkluderat", "Not included": "Ej inkluderat",
"Number of charging points for electric cars: {number}": "Antal laddplatser för elbilar: {number}", "Number of charging points for electric cars: {number}": "Antal laddplatser för elbilar: {number}",

View File

@@ -15,6 +15,7 @@ export type HotelData = {
export type HotelCardListingProps = { export type HotelCardListingProps = {
hotelData: HotelResponse[] hotelData: HotelResponse[]
type?: HotelCardListingTypeEnum type?: HotelCardListingTypeEnum
userPoints?: number
} }
export interface NullableHotelData extends Omit<HotelData, "hotelData"> { export interface NullableHotelData extends Omit<HotelData, "hotelData"> {

View File

@@ -7,4 +7,5 @@ export type HotelCardProps = {
type?: HotelCardListingTypeEnum type?: HotelCardListingTypeEnum
state?: "default" | "active" state?: "default" | "active"
bookingCode?: string | null bookingCode?: string | null
userPoints?: number
} }