diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx
index 06bf48ce6..83828369f 100644
--- a/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Summary/UI/index.tsx
@@ -262,8 +262,7 @@ export default function SummaryUI({
{formatPrice(
intl,
0,
- room.roomPrice.perStay.local.additionalPriceCurrency ??
- room.roomPrice.perStay.local.currency
+ room.roomPrice.perStay.local.currency
)}
diff --git a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx
index d730dd7e5..ddf9cd558 100644
--- a/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/HotelCard/index.tsx
@@ -73,7 +73,9 @@ function HotelCard({
availability.productType?.member?.rateType === RateTypeEnum.Regular
const price = availability.productType
- const userHasEnoughPoints = price?.redemptions?.some((r) => r.hasEnoughPoints)
+ const hasInsufficientPoints = !price?.redemptions?.some(
+ (r) => r.hasEnoughPoints
+ )
const notEnoughPointsLabel = intl.formatMessage({ id: "Not enough points" })
return (
@@ -211,7 +213,7 @@ function HotelCard({
))}
) : null}
- {price?.redemptions?.length && !userHasEnoughPoints ? (
+ {price?.redemptions?.length && hasInsufficientPoints ? (
p.type === "Breakfast"
)
const breakfastTotalPriceInMoney = breakfastPackages
- .filter((p) => p.currency !== "Points")
+ .filter((p) => p.currency !== CurrencyEnum.POINTS)
.reduce((acc, curr) => acc + curr.totalPrice, 0)
const breakfastTotalPriceInPoints = breakfastPackages
- .filter((p) => p.currency === "Points")
+ .filter((p) => p.currency === CurrencyEnum.POINTS)
.reduce((acc, curr) => acc + curr.totalPrice, 0)
const breakfastCount = breakfastPackages.reduce(
@@ -138,7 +139,7 @@ export default async function Specification({
-
- {ancillary.currency !== "Points"
+ {ancillary.currency !== CurrencyEnum.POINTS
? intl.formatMessage({ id: "Price including VAT" })
: intl.formatMessage({ id: "Price" })}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/Total/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/Total/index.tsx
index 78109caf5..3060d3021 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/Total/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/Total/index.tsx
@@ -6,6 +6,7 @@ import { getIntl } from "@/i18n"
import styles from "./total.module.css"
import type { TotalProps } from "@/types/components/hotelReservation/myStay/receipt"
+import { CurrencyEnum } from "@/types/enums/currency"
export default async function Total({ booking, currency }: TotalProps) {
const intl = await getIntl()
@@ -14,7 +15,7 @@ export default async function Total({ booking, currency }: TotalProps) {
const totalPriceInMoneyExclVat = booking.totalPriceExVat
const totalVat = booking.vatAmount
const totalPriceInPoints = booking.ancillaries
- .filter((a) => a.currency === "Points")
+ .filter((a) => a.currency === CurrencyEnum.POINTS)
.reduce((acc, curr) => acc + curr.totalPrice, 0)
const moneyString =
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx
index 2f3798db2..47f28cd6c 100644
--- a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx
+++ b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx
@@ -26,6 +26,8 @@ import Total from "./Total"
import styles from "./receipt.module.css"
+import { CurrencyEnum } from "@/types/enums/currency"
+
export async function Receipt({ refId }: { refId: string }) {
const value = decrypt(refId)
if (!value) {
@@ -52,10 +54,12 @@ export async function Receipt({ refId }: { refId: string }) {
})
const currency =
- booking.currencyCode !== "Points"
+ booking.currencyCode !== CurrencyEnum.POINTS
? booking.currencyCode
- : (booking.ancillaries.find((a) => a.currency !== "Points")?.currency ??
- booking.packages.find((p) => p.currency !== "Points")?.currency)
+ : (booking.ancillaries.find((a) => a.currency !== CurrencyEnum.POINTS)
+ ?.currency ??
+ booking.packages.find((p) => p.currency !== CurrencyEnum.POINTS)
+ ?.currency)
return (
diff --git a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts
index 4849477b9..190ae82d5 100644
--- a/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts
+++ b/apps/scandic-web/components/HotelReservation/SelectRate/RoomsContainer/RateSummary/utils.ts
@@ -83,7 +83,7 @@ export function calculateTotalPrice(
},
{
local: {
- currency: "",
+ currency: CurrencyEnum.Unknown,
price: 0,
regularPrice: undefined,
},
@@ -103,7 +103,7 @@ export function calculateRedemptionTotalPrice(
additionalPriceCurrency: redemption.localPrice.currency
? redemption.localPrice.currency
: undefined,
- currency: "PTS",
+ currency: CurrencyEnum.POINTS,
price: redemption.localPrice.pointsPerStay,
},
}
diff --git a/apps/scandic-web/server/routers/booking/output.ts b/apps/scandic-web/server/routers/booking/output.ts
index b9f827ce5..d490e5c5f 100644
--- a/apps/scandic-web/server/routers/booking/output.ts
+++ b/apps/scandic-web/server/routers/booking/output.ts
@@ -9,6 +9,8 @@ import {
nullableStringValidator,
} from "@/utils/zod/stringValidator"
+import { CurrencyEnum } from "@/types/enums/currency"
+
const guestSchema = z.object({
email: nullableStringEmailValidator,
firstName: nullableStringValidator,
@@ -97,7 +99,7 @@ export const packageSchema = z
unitPrice: z.number(),
totalPrice: z.number().nullish(),
totalUnit: z.number().int().nullish(),
- currency: z.string().default(""),
+ currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
points: nullableIntValidator,
}),
comment: z.string().nullish(),
@@ -218,7 +220,7 @@ export const bookingConfirmationSchema = z
computedReservationStatus: z.string().nullable().default(""),
confirmationNumber: nullableStringValidator,
createDateTime: z.date({ coerce: true }),
- currencyCode: z.string(),
+ currencyCode: z.nativeEnum(CurrencyEnum),
guest: guestSchema,
linkedReservations: nullableArrayObjectValidator(
linkedReservationSchema
diff --git a/apps/scandic-web/server/routers/hotels/query.ts b/apps/scandic-web/server/routers/hotels/query.ts
index 5ba49a66b..5a59c1bb1 100644
--- a/apps/scandic-web/server/routers/hotels/query.ts
+++ b/apps/scandic-web/server/routers/hotels/query.ts
@@ -208,12 +208,12 @@ export const getHotel = cache(
}
)
-export const getHotelsAvailabilityByCity = async (
+async function getHotelsAvailabilityByCity(
input: HotelsAvailabilityInputSchema,
apiLang: string,
token: string, // Either service token or user access token in case of redemption search
session?: Session
-) => {
+) {
const {
cityId,
roomStayStartDate,
@@ -223,131 +223,124 @@ export const getHotelsAvailabilityByCity = async (
bookingCode,
redemption,
} = input
- const cacheClient = await getCacheClient()
- return await cacheClient.cacheOrGet(
- `${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}:${redemption ? "isRedemption" : ""}`,
- async () => {
- const params: Record = {
- roomStayStartDate,
- roomStayEndDate,
- adults,
- ...(children && { children }),
- ...(bookingCode && { bookingCode }),
- ...(redemption ? { isRedemption: "true" } : {}),
- language: apiLang,
- }
- metrics.hotelsAvailability.counter.add(1, {
- cityId,
- roomStayStartDate,
- roomStayEndDate,
- adults,
- children,
- bookingCode,
- redemption,
- })
- console.info(
- "api.hotels.hotelsAvailability start",
- JSON.stringify({ query: { cityId, params } })
- )
- const apiResponse = await api.get(
- api.endpoints.v1.Availability.city(cityId),
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- },
- params
- )
- if (!apiResponse.ok) {
- const text = await apiResponse.text()
- metrics.hotelsAvailability.fail.add(1, {
- cityId,
- roomStayStartDate,
- roomStayEndDate,
- adults,
- children,
- bookingCode,
- error_type: "http_error",
- error: JSON.stringify({
- status: apiResponse.status,
- statusText: apiResponse.statusText,
- text,
- }),
- })
- console.error(
- "api.hotels.hotelsAvailability error",
- JSON.stringify({
- query: { cityId, params },
- error: {
- status: apiResponse.status,
- statusText: apiResponse.statusText,
- text,
- },
- })
- )
- throw new Error("Failed to fetch hotels availability by city")
- }
-
- const apiJson = await apiResponse.json()
- const validateAvailabilityData =
- hotelsAvailabilitySchema.safeParse(apiJson)
- if (!validateAvailabilityData.success) {
- metrics.hotelsAvailability.fail.add(1, {
- cityId,
- roomStayStartDate,
- roomStayEndDate,
- adults,
- children,
- bookingCode,
- redemption,
- error_type: "validation_error",
- error: JSON.stringify(validateAvailabilityData.error),
- })
- console.error(
- "api.hotels.hotelsAvailability validation error",
- JSON.stringify({
- query: { cityId, params },
- error: validateAvailabilityData.error,
- })
- )
- throw badRequestError()
- }
- metrics.hotelsAvailability.success.add(1, {
- cityId,
- roomStayStartDate,
- roomStayEndDate,
- adults,
- children,
- bookingCode,
- redemption,
- })
- console.info(
- "api.hotels.hotelsAvailability success",
- JSON.stringify({
- query: { cityId, params: params },
- })
- )
- if (redemption && session) {
- const verifiedUser = await getVerifiedUser({ session })
- if (!verifiedUser?.error) {
- const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
- validateAvailabilityData.data.data.forEach((data) => {
- data.attributes.productType?.redemptions?.forEach((r) => {
- r.hasEnoughPoints = userPoints >= r.localPrice.pointsPerStay
- })
- })
- }
- }
-
- return {
- availability: validateAvailabilityData.data.data.flatMap(
- (hotels) => hotels.attributes
- ),
- }
- },
- env.CACHE_TIME_CITY_SEARCH
+ const params: Record = {
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ ...(children && { children }),
+ ...(bookingCode && { bookingCode }),
+ ...(redemption ? { isRedemption: "true" } : {}),
+ language: apiLang,
+ }
+ metrics.hotelsAvailability.counter.add(1, {
+ cityId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ bookingCode,
+ redemption,
+ })
+ console.info(
+ "api.hotels.hotelsAvailability start",
+ JSON.stringify({ query: { cityId, params } })
)
+ const apiResponse = await api.get(
+ api.endpoints.v1.Availability.city(cityId),
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ params
+ )
+ if (!apiResponse.ok) {
+ const text = await apiResponse.text()
+ metrics.hotelsAvailability.fail.add(1, {
+ cityId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ bookingCode,
+ error_type: "http_error",
+ error: JSON.stringify({
+ status: apiResponse.status,
+ statusText: apiResponse.statusText,
+ text,
+ }),
+ })
+ console.error(
+ "api.hotels.hotelsAvailability error",
+ JSON.stringify({
+ query: { cityId, params },
+ error: {
+ status: apiResponse.status,
+ statusText: apiResponse.statusText,
+ text,
+ },
+ })
+ )
+
+ throw new Error("Failed to fetch hotels availability by city")
+ }
+
+ const apiJson = await apiResponse.json()
+ const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson)
+ if (!validateAvailabilityData.success) {
+ metrics.hotelsAvailability.fail.add(1, {
+ cityId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ bookingCode,
+ redemption,
+ error_type: "validation_error",
+ error: JSON.stringify(validateAvailabilityData.error),
+ })
+ console.error(
+ "api.hotels.hotelsAvailability validation error",
+ JSON.stringify({
+ query: { cityId, params },
+ error: validateAvailabilityData.error,
+ })
+ )
+ throw badRequestError()
+ }
+ metrics.hotelsAvailability.success.add(1, {
+ cityId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ bookingCode,
+ redemption,
+ })
+ console.info(
+ "api.hotels.hotelsAvailability success",
+ JSON.stringify({
+ query: { cityId, params: params },
+ })
+ )
+ if (redemption && session) {
+ const verifiedUser = await getVerifiedUser({ session })
+ if (!verifiedUser?.error) {
+ const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
+ validateAvailabilityData.data.data.forEach((data) => {
+ data.attributes.productType?.redemptions?.forEach((r) => {
+ r.hasEnoughPoints = userPoints >= r.localPrice.pointsPerStay
+ })
+ })
+ }
+ }
+
+ return {
+ availability: validateAvailabilityData.data.data.flatMap(
+ (hotels) => hotels.attributes
+ ),
+ }
}
export const getHotelsAvailabilityByHotelIds = async (
@@ -492,7 +485,23 @@ export const hotelQueryRouter = router({
.query(async ({ input, ctx }) => {
const { lang } = ctx
const apiLang = toApiLang(lang)
- return getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken)
+ const {
+ cityId,
+ roomStayStartDate,
+ roomStayEndDate,
+ adults,
+ children,
+ bookingCode,
+ } = input
+ const cacheClient = await getCacheClient()
+
+ return await cacheClient.cacheOrGet(
+ `${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
+ async () => {
+ return getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken)
+ },
+ env.CACHE_TIME_CITY_SEARCH
+ )
}),
hotelsByCityWithRedemption: protectedProcedure
.input(hotelsAvailabilityInputSchema)
@@ -503,7 +512,7 @@ export const hotelQueryRouter = router({
input,
apiLang,
ctx.session.token.access_token,
- ctx.session,
+ ctx.session
)
}),
hotelsByHotelIds: serviceProcedure
@@ -519,12 +528,16 @@ export const hotelQueryRouter = router({
.use(async ({ ctx, input, next }) => {
if (input.redemption) {
if (ctx.session?.token.access_token) {
- return next({
- ctx: {
- token: ctx.session.token.access_token,
- },
- input,
- })
+ const verifiedUser = await getVerifiedUser({ session: ctx.session })
+ if (!verifiedUser?.error) {
+ return next({
+ ctx: {
+ token: ctx.session.token.access_token,
+ userPoints: verifiedUser?.data.membership?.currentPoints ?? 0,
+ },
+ input,
+ })
+ }
}
throw unauthorizedError()
}
@@ -622,16 +635,15 @@ export const hotelQueryRouter = router({
)?.mustBeGuaranteed
}
- if (redemption && ctx.session) {
- const verifiedUser = await getVerifiedUser({ session: ctx.session })
- if (!verifiedUser?.error) {
- const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
- validateAvailabilityData.data.roomConfigurations.forEach((data) => {
- data.redemptions?.forEach(r => {
- r.redemption.hasEnoughPoints = userPoints >= r.redemption.localPrice.pointsPerStay
+ if (redemption) {
+ validateAvailabilityData.data.roomConfigurations.forEach(
+ (data) => {
+ data.redemptions?.forEach((r) => {
+ r.redemption.hasEnoughPoints =
+ ctx.userPoints >= r.redemption.localPrice.pointsPerStay
})
- })
- }
+ }
+ )
}
return validateAvailabilityData.data
diff --git a/apps/scandic-web/server/routers/hotels/schemas/packages.ts b/apps/scandic-web/server/routers/hotels/schemas/packages.ts
index fc2bd1860..3e1820d52 100644
--- a/apps/scandic-web/server/routers/hotels/schemas/packages.ts
+++ b/apps/scandic-web/server/routers/hotels/schemas/packages.ts
@@ -4,18 +4,19 @@ import { imageSizesSchema } from "./image"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
+import { CurrencyEnum } from "@/types/enums/currency"
import { PackageTypeEnum } from "@/types/enums/packages"
// TODO: Remove optional and default when the API change has been deployed
export const packagePriceSchema = z
.object({
- currency: z.string().default("N/A"),
+ currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
price: z.number(),
totalPrice: z.number(),
})
.optional()
.default({
- currency: "N/A",
+ currency: CurrencyEnum.Unknown,
price: 0,
totalPrice: 0,
})
diff --git a/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts b/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
index f81eba050..caf709091 100644
--- a/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
+++ b/apps/scandic-web/stores/my-stay/myStayRoomDetailsStore.ts
@@ -5,6 +5,7 @@ import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDet
import type { RoomPrice } from "@/types/components/hotelReservation/enterDetails/details"
import type { PriceType } from "@/types/components/hotelReservation/myStay/myStay"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
+import { CurrencyEnum } from "@/types/enums/currency"
import type { Packages } from "@/types/requests/packages"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
@@ -73,7 +74,7 @@ export const useMyStayRoomDetailsStore = create(
bookingCode: null,
cheques: 0,
vouchers: 0,
- currencyCode: "",
+ currencyCode: CurrencyEnum.Unknown,
guest: {
email: "",
firstName: "",
@@ -97,21 +98,21 @@ export const useMyStayRoomDetailsStore = create(
perNight: {
requested: {
price: 0,
- currency: "",
+ currency: CurrencyEnum.Unknown,
},
local: {
price: 0,
- currency: "",
+ currency: CurrencyEnum.Unknown,
},
},
perStay: {
requested: {
price: 0,
- currency: "",
+ currency: CurrencyEnum.Unknown,
},
local: {
price: 0,
- currency: "",
+ currency: CurrencyEnum.Unknown,
},
},
},
diff --git a/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts b/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
index aeb824e34..1f6ca5594 100644
--- a/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
+++ b/apps/scandic-web/stores/my-stay/myStayTotalPrice.ts
@@ -1,9 +1,11 @@
import { create } from "zustand"
+import { CurrencyEnum } from "@/types/enums/currency"
+
interface RoomPrice {
id: string
totalPrice: number
- currencyCode: string
+ currencyCode: CurrencyEnum
isMainBooking?: boolean
roomPoints: number
}
@@ -11,7 +13,7 @@ interface RoomPrice {
interface MyStayTotalPriceState {
rooms: RoomPrice[]
totalPrice: number | null
- currencyCode: string
+ currencyCode: CurrencyEnum
totalPoints: number
actions: {
// Add a single room price
@@ -26,7 +28,7 @@ export const useMyStayTotalPriceStore = create(
totalPoints: 0,
totalCheques: 0,
totalVouchers: 0,
- currencyCode: "",
+ currencyCode: CurrencyEnum.Unknown,
actions: {
addRoomPrice: (room) => {
set((state) => {
@@ -44,7 +46,7 @@ export const useMyStayTotalPriceStore = create(
// Get currency from main booking or first room
const mainRoom = newRooms.find((r) => r.isMainBooking) || newRooms[0]
- const currencyCode = mainRoom?.currencyCode || ""
+ const currencyCode = mainRoom?.currencyCode ?? CurrencyEnum.Unknown
// Calculate total (only same currency for now)
const total = newRooms.reduce((sum, r) => {
diff --git a/apps/scandic-web/types/components/hotelReservation/price.ts b/apps/scandic-web/types/components/hotelReservation/price.ts
index d03abe2f4..bcdc91d15 100644
--- a/apps/scandic-web/types/components/hotelReservation/price.ts
+++ b/apps/scandic-web/types/components/hotelReservation/price.ts
@@ -3,7 +3,7 @@ import { z } from "zod"
import { CurrencyEnum } from "@/types/enums/currency"
interface TPrice {
- currency: string
+ currency: CurrencyEnum
price: number
regularPrice?: number
additionalPrice?: number
|