Merged in feat/SW-755-price-change-non-happy (pull request #957)
Feat/SW-755 price change non happy * fix(SW-755): dont show field error if checkbox has no children * feat(SW-755): Price change route + dialog WIP * fix(SW-755): minor refactoring * fix(SW-755): added logging to price change route * fix(SW-755): remove redundant search param logic * fix(SW-755): moved enum cast to zod instead * fix(SW-755): move prop type to types folder * fix(SW-755): Added suspense to Payment and refactored payment options hook * fix(SW-755): seperated terms and conditions copy from the checkbox label * fix(SW-755): add currency format and fixed wrong translation * fix(SW-755): change from undefined to null * fix(SW-755): added extra type safety to payment options Approved-by: Christian Andolf Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import "./enterDetailsLayout.css"
|
import "./enterDetailsLayout.css"
|
||||||
|
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getBreakfastPackages,
|
getBreakfastPackages,
|
||||||
@@ -170,16 +171,18 @@ export default async function StepPage({
|
|||||||
step={StepEnum.payment}
|
step={StepEnum.payment}
|
||||||
label={mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod}
|
label={mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod}
|
||||||
>
|
>
|
||||||
<Payment
|
<Suspense>
|
||||||
user={user}
|
<Payment
|
||||||
roomPrice={roomPrice}
|
user={user}
|
||||||
otherPaymentOptions={
|
roomPrice={roomPrice}
|
||||||
hotelData.data.attributes.merchantInformationData
|
otherPaymentOptions={
|
||||||
.alternatePaymentOptions
|
hotelData.data.attributes.merchantInformationData
|
||||||
}
|
.alternatePaymentOptions
|
||||||
savedCreditCards={savedCreditCards}
|
}
|
||||||
mustBeGuaranteed={mustBeGuaranteed}
|
savedCreditCards={savedCreditCards}
|
||||||
/>
|
mustBeGuaranteed={mustBeGuaranteed}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</SectionAccordion>
|
</SectionAccordion>
|
||||||
</section>
|
</section>
|
||||||
</StepsProvider>
|
</StepsProvider>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
import { Label as AriaLabel } from "react-aria-components"
|
|
||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -16,6 +15,7 @@ import {
|
|||||||
bookingTermsAndConditions,
|
bookingTermsAndConditions,
|
||||||
privacyPolicy,
|
privacyPolicy,
|
||||||
} from "@/constants/currentWebHrefs"
|
} from "@/constants/currentWebHrefs"
|
||||||
|
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||||
import { env } from "@/env/client"
|
import { env } from "@/env/client"
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
import { useDetailsStore } from "@/stores/details"
|
import { useDetailsStore } from "@/stores/details"
|
||||||
@@ -27,11 +27,13 @@ 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 { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions"
|
||||||
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
||||||
import { usePaymentFailedToast } from "@/hooks/booking/usePaymentFailedToast"
|
import { usePaymentFailedToast } from "@/hooks/booking/usePaymentFailedToast"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import { bedTypeMap } from "../../SelectRate/RoomSelection/utils"
|
import { bedTypeMap } from "../../SelectRate/RoomSelection/utils"
|
||||||
|
import PriceChangeDialog from "../PriceChangeDialog"
|
||||||
import GuaranteeDetails from "./GuaranteeDetails"
|
import GuaranteeDetails from "./GuaranteeDetails"
|
||||||
import PaymentOption from "./PaymentOption"
|
import PaymentOption from "./PaymentOption"
|
||||||
import { PaymentFormData, paymentSchema } from "./schema"
|
import { PaymentFormData, paymentSchema } from "./schema"
|
||||||
@@ -60,29 +62,23 @@ export default function Payment({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
||||||
|
const totalPrice = useDetailsStore((state) => state.totalPrice)
|
||||||
const setIsSubmittingDisabled = useDetailsStore(
|
const setIsSubmittingDisabled = useDetailsStore(
|
||||||
(state) => state.actions.setIsSubmittingDisabled
|
(state) => state.actions.setIsSubmittingDisabled
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
phoneNumber,
|
|
||||||
countryCode,
|
|
||||||
breakfast,
|
|
||||||
bedType,
|
|
||||||
membershipNo,
|
|
||||||
join,
|
|
||||||
dateOfBirth,
|
|
||||||
zipCode,
|
|
||||||
} = userData
|
|
||||||
const { toDate, fromDate, rooms, hotel } = booking
|
|
||||||
|
|
||||||
const [confirmationNumber, setConfirmationNumber] = useState<string>("")
|
const [confirmationNumber, setConfirmationNumber] = useState<string>("")
|
||||||
const [availablePaymentOptions, setAvailablePaymentOptions] =
|
const [isPollingForBookingStatus, setIsPollingForBookingStatus] =
|
||||||
useState(otherPaymentOptions)
|
useState(false)
|
||||||
|
|
||||||
|
const availablePaymentOptions =
|
||||||
|
useAvailablePaymentOptions(otherPaymentOptions)
|
||||||
|
const [priceChangeData, setPriceChangeData] = useState<{
|
||||||
|
oldPrice: number
|
||||||
|
newPrice: number
|
||||||
|
} | null>()
|
||||||
|
|
||||||
usePaymentFailedToast()
|
usePaymentFailedToast()
|
||||||
|
|
||||||
@@ -103,6 +99,15 @@ export default function Payment({
|
|||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
if (result?.confirmationNumber) {
|
if (result?.confirmationNumber) {
|
||||||
setConfirmationNumber(result.confirmationNumber)
|
setConfirmationNumber(result.confirmationNumber)
|
||||||
|
|
||||||
|
if (result.metadata?.priceChangedMetadata) {
|
||||||
|
setPriceChangeData({
|
||||||
|
oldPrice: roomPrice.publicPrice,
|
||||||
|
newPrice: result.metadata.priceChangedMetadata.totalPrice,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setIsPollingForBookingStatus(true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
toast.error(
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
@@ -121,25 +126,31 @@ export default function Payment({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const priceChange = trpc.booking.priceChange.useMutation({
|
||||||
|
onSuccess: (result) => {
|
||||||
|
if (result?.confirmationNumber) {
|
||||||
|
setIsPollingForBookingStatus(true)
|
||||||
|
} else {
|
||||||
|
toast.error(intl.formatMessage({ id: "payment.error.failed" }))
|
||||||
|
}
|
||||||
|
|
||||||
|
setPriceChangeData(null)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Error", error)
|
||||||
|
setPriceChangeData(null)
|
||||||
|
toast.error(intl.formatMessage({ id: "payment.error.failed" }))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const bookingStatus = useHandleBookingStatus({
|
const bookingStatus = useHandleBookingStatus({
|
||||||
confirmationNumber,
|
confirmationNumber,
|
||||||
expectedStatus: BookingStatusEnum.BookingCompleted,
|
expectedStatus: BookingStatusEnum.BookingCompleted,
|
||||||
maxRetries,
|
maxRetries,
|
||||||
retryInterval,
|
retryInterval,
|
||||||
|
enabled: isPollingForBookingStatus,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (window.ApplePaySession) {
|
|
||||||
setAvailablePaymentOptions(otherPaymentOptions)
|
|
||||||
} else {
|
|
||||||
setAvailablePaymentOptions(
|
|
||||||
otherPaymentOptions.filter(
|
|
||||||
(option) => option !== PaymentMethodEnum.applePay
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [otherPaymentOptions, setAvailablePaymentOptions])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bookingStatus?.data?.paymentUrl) {
|
if (bookingStatus?.data?.paymentUrl) {
|
||||||
router.push(bookingStatus.data.paymentUrl)
|
router.push(bookingStatus.data.paymentUrl)
|
||||||
@@ -162,76 +173,102 @@ export default function Payment({
|
|||||||
setIsSubmittingDisabled,
|
setIsSubmittingDisabled,
|
||||||
])
|
])
|
||||||
|
|
||||||
function handleSubmit(data: PaymentFormData) {
|
const handleSubmit = useCallback(
|
||||||
// set payment method to card if saved card is submitted
|
(data: PaymentFormData) => {
|
||||||
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
const {
|
||||||
? data.paymentMethod
|
firstName,
|
||||||
: PaymentMethodEnum.card
|
lastName,
|
||||||
|
email,
|
||||||
|
phoneNumber,
|
||||||
|
countryCode,
|
||||||
|
breakfast,
|
||||||
|
bedType,
|
||||||
|
membershipNo,
|
||||||
|
join,
|
||||||
|
dateOfBirth,
|
||||||
|
zipCode,
|
||||||
|
} = userData
|
||||||
|
const { toDate, fromDate, rooms, hotel } = booking
|
||||||
|
|
||||||
const savedCreditCard = savedCreditCards?.find(
|
// set payment method to card if saved card is submitted
|
||||||
(card) => card.id === data.paymentMethod
|
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
||||||
)
|
? data.paymentMethod
|
||||||
|
: PaymentMethodEnum.card
|
||||||
|
|
||||||
const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback`
|
const savedCreditCard = savedCreditCards?.find(
|
||||||
|
(card) => card.id === data.paymentMethod
|
||||||
|
)
|
||||||
|
|
||||||
initiateBooking.mutate({
|
const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback`
|
||||||
hotelId: hotel,
|
|
||||||
checkInDate: fromDate,
|
initiateBooking.mutate({
|
||||||
checkOutDate: toDate,
|
hotelId: hotel,
|
||||||
rooms: rooms.map((room) => ({
|
checkInDate: fromDate,
|
||||||
adults: room.adults,
|
checkOutDate: toDate,
|
||||||
childrenAges: room.children?.map((child) => ({
|
rooms: rooms.map((room) => ({
|
||||||
age: child.age,
|
adults: room.adults,
|
||||||
bedType: bedTypeMap[parseInt(child.bed.toString())],
|
childrenAges: room.children?.map((child) => ({
|
||||||
|
age: child.age,
|
||||||
|
bedType: bedTypeMap[parseInt(child.bed.toString())],
|
||||||
|
})),
|
||||||
|
rateCode:
|
||||||
|
user || join || membershipNo ? room.counterRateCode : room.rateCode,
|
||||||
|
roomTypeCode: bedType!.roomTypeCode, // A selection has been made in order to get to this step.
|
||||||
|
guest: {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
phoneNumber,
|
||||||
|
countryCode,
|
||||||
|
membershipNumber: membershipNo,
|
||||||
|
becomeMember: join,
|
||||||
|
dateOfBirth,
|
||||||
|
postalCode: zipCode,
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
breakfast: !!(breakfast && breakfast.code),
|
||||||
|
allergyFriendly:
|
||||||
|
room.packages?.includes(RoomPackageCodeEnum.ALLERGY_ROOM) ??
|
||||||
|
false,
|
||||||
|
petFriendly:
|
||||||
|
room.packages?.includes(RoomPackageCodeEnum.PET_ROOM) ?? false,
|
||||||
|
accessibility:
|
||||||
|
room.packages?.includes(RoomPackageCodeEnum.ACCESSIBILITY_ROOM) ??
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
smsConfirmationRequested: data.smsConfirmation,
|
||||||
|
roomPrice,
|
||||||
})),
|
})),
|
||||||
rateCode:
|
payment: {
|
||||||
user || join || membershipNo ? room.counterRateCode : room.rateCode,
|
paymentMethod,
|
||||||
roomTypeCode: bedType!.roomTypeCode, // A selection has been made in order to get to this step.
|
card: savedCreditCard
|
||||||
guest: {
|
? {
|
||||||
title: "",
|
alias: savedCreditCard.alias,
|
||||||
firstName,
|
expiryDate: savedCreditCard.expirationDate,
|
||||||
lastName,
|
cardType: savedCreditCard.cardType,
|
||||||
email,
|
}
|
||||||
phoneNumber,
|
: undefined,
|
||||||
countryCode,
|
|
||||||
membershipNumber: membershipNo,
|
|
||||||
becomeMember: join,
|
|
||||||
dateOfBirth,
|
|
||||||
postalCode: zipCode,
|
|
||||||
},
|
|
||||||
packages: {
|
|
||||||
breakfast: !!(breakfast && breakfast.code),
|
|
||||||
allergyFriendly:
|
|
||||||
room.packages?.includes(RoomPackageCodeEnum.ALLERGY_ROOM) ?? false,
|
|
||||||
petFriendly:
|
|
||||||
room.packages?.includes(RoomPackageCodeEnum.PET_ROOM) ?? false,
|
|
||||||
accessibility:
|
|
||||||
room.packages?.includes(RoomPackageCodeEnum.ACCESSIBILITY_ROOM) ??
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
smsConfirmationRequested: data.smsConfirmation,
|
|
||||||
roomPrice,
|
|
||||||
})),
|
|
||||||
payment: {
|
|
||||||
paymentMethod,
|
|
||||||
card: savedCreditCard
|
|
||||||
? {
|
|
||||||
alias: savedCreditCard.alias,
|
|
||||||
expiryDate: savedCreditCard.expirationDate,
|
|
||||||
cardType: savedCreditCard.cardType,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
|
|
||||||
success: `${paymentRedirectUrl}/success`,
|
success: `${paymentRedirectUrl}/success`,
|
||||||
error: `${paymentRedirectUrl}/error`,
|
error: `${paymentRedirectUrl}/error`,
|
||||||
cancel: `${paymentRedirectUrl}/cancel`,
|
cancel: `${paymentRedirectUrl}/cancel`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
[
|
||||||
|
userData,
|
||||||
|
booking,
|
||||||
|
roomPrice,
|
||||||
|
savedCreditCards,
|
||||||
|
lang,
|
||||||
|
user,
|
||||||
|
initiateBooking,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
initiateBooking.isPending ||
|
initiateBooking.isPending ||
|
||||||
(confirmationNumber && !bookingStatus.data?.paymentUrl)
|
(isPollingForBookingStatus && !bookingStatus.data?.paymentUrl)
|
||||||
) {
|
) {
|
||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
@@ -241,79 +278,70 @@ export default function Payment({
|
|||||||
const paymentVerb = mustBeGuaranteed ? guaranteeing : paying
|
const paymentVerb = mustBeGuaranteed ? guaranteeing : paying
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<>
|
||||||
<form
|
<FormProvider {...methods}>
|
||||||
className={styles.paymentContainer}
|
<form
|
||||||
onSubmit={methods.handleSubmit(handleSubmit)}
|
className={styles.paymentContainer}
|
||||||
id={formId}
|
onSubmit={methods.handleSubmit(handleSubmit)}
|
||||||
>
|
id={formId}
|
||||||
{mustBeGuaranteed ? (
|
>
|
||||||
|
{mustBeGuaranteed ? (
|
||||||
|
<section className={styles.section}>
|
||||||
|
<Body>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
<GuaranteeDetails />
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
{savedCreditCards?.length ? (
|
||||||
|
<section className={styles.section}>
|
||||||
|
<Body color="uiTextHighContrast" textTransform="bold">
|
||||||
|
{intl.formatMessage({ id: "MY SAVED CARDS" })}
|
||||||
|
</Body>
|
||||||
|
<div className={styles.paymentOptionContainer}>
|
||||||
|
{savedCreditCards?.map((savedCreditCard) => (
|
||||||
|
<PaymentOption
|
||||||
|
key={savedCreditCard.id}
|
||||||
|
name="paymentMethod"
|
||||||
|
value={savedCreditCard.id}
|
||||||
|
label={
|
||||||
|
PAYMENT_METHOD_TITLES[
|
||||||
|
savedCreditCard.cardType as PaymentMethodEnum
|
||||||
|
]
|
||||||
|
}
|
||||||
|
cardNumber={savedCreditCard.truncatedNumber}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<Body>
|
{savedCreditCards?.length ? (
|
||||||
{intl.formatMessage({
|
<Body color="uiTextHighContrast" textTransform="bold">
|
||||||
id: "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
|
{intl.formatMessage({ id: "OTHER PAYMENT METHODS" })}
|
||||||
})}
|
</Body>
|
||||||
</Body>
|
) : null}
|
||||||
<GuaranteeDetails />
|
|
||||||
</section>
|
|
||||||
) : null}
|
|
||||||
{savedCreditCards?.length ? (
|
|
||||||
<section className={styles.section}>
|
|
||||||
<Body color="uiTextHighContrast" textTransform="bold">
|
|
||||||
{intl.formatMessage({ id: "MY SAVED CARDS" })}
|
|
||||||
</Body>
|
|
||||||
<div className={styles.paymentOptionContainer}>
|
<div className={styles.paymentOptionContainer}>
|
||||||
{savedCreditCards?.map((savedCreditCard) => (
|
<PaymentOption
|
||||||
|
name="paymentMethod"
|
||||||
|
value={PaymentMethodEnum.card}
|
||||||
|
label={intl.formatMessage({ id: "Credit card" })}
|
||||||
|
/>
|
||||||
|
{availablePaymentOptions.map((paymentMethod) => (
|
||||||
<PaymentOption
|
<PaymentOption
|
||||||
key={savedCreditCard.id}
|
key={paymentMethod}
|
||||||
name="paymentMethod"
|
name="paymentMethod"
|
||||||
value={savedCreditCard.id}
|
value={paymentMethod}
|
||||||
label={
|
label={
|
||||||
PAYMENT_METHOD_TITLES[
|
PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum]
|
||||||
savedCreditCard.cardType as PaymentMethodEnum
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
cardNumber={savedCreditCard.truncatedNumber}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
<section className={styles.section}>
|
||||||
<section className={styles.section}>
|
|
||||||
{savedCreditCards?.length ? (
|
|
||||||
<Body color="uiTextHighContrast" textTransform="bold">
|
|
||||||
{intl.formatMessage({ id: "OTHER PAYMENT METHODS" })}
|
|
||||||
</Body>
|
|
||||||
) : null}
|
|
||||||
<div className={styles.paymentOptionContainer}>
|
|
||||||
<PaymentOption
|
|
||||||
name="paymentMethod"
|
|
||||||
value={PaymentMethodEnum.card}
|
|
||||||
label={intl.formatMessage({ id: "Credit card" })}
|
|
||||||
/>
|
|
||||||
{availablePaymentOptions.map((paymentMethod) => (
|
|
||||||
<PaymentOption
|
|
||||||
key={paymentMethod}
|
|
||||||
name="paymentMethod"
|
|
||||||
value={paymentMethod}
|
|
||||||
label={
|
|
||||||
PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className={styles.section}>
|
|
||||||
<Checkbox name="smsConfirmation">
|
|
||||||
<Caption>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "I would like to get my booking confirmation via sms",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
</Checkbox>
|
|
||||||
|
|
||||||
<AriaLabel className={styles.terms}>
|
|
||||||
<Checkbox name="termsAndConditions" />
|
|
||||||
<Caption>
|
<Caption>
|
||||||
{intl.formatMessage<React.ReactNode>(
|
{intl.formatMessage<React.ReactNode>(
|
||||||
{
|
{
|
||||||
@@ -344,19 +372,48 @@ export default function Payment({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</AriaLabel>
|
<Checkbox name="termsAndConditions">
|
||||||
</section>
|
<Caption>
|
||||||
<div className={styles.submitButton}>
|
{intl.formatMessage({
|
||||||
<Button
|
id: "I accept the terms and conditions",
|
||||||
type="submit"
|
})}
|
||||||
disabled={
|
</Caption>
|
||||||
!methods.formState.isValid || methods.formState.isSubmitting
|
</Checkbox>
|
||||||
}
|
<Checkbox name="smsConfirmation">
|
||||||
>
|
<Caption>
|
||||||
{intl.formatMessage({ id: "Complete booking" })}
|
{intl.formatMessage({
|
||||||
</Button>
|
id: "I would like to get my booking confirmation via sms",
|
||||||
</div>
|
})}
|
||||||
</form>
|
</Caption>
|
||||||
</FormProvider>
|
</Checkbox>
|
||||||
|
</section>
|
||||||
|
<div className={styles.submitButton}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={
|
||||||
|
!methods.formState.isValid || methods.formState.isSubmitting
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Complete booking" })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
{priceChangeData ? (
|
||||||
|
<PriceChangeDialog
|
||||||
|
isOpen={!!priceChangeData}
|
||||||
|
oldPrice={priceChangeData.oldPrice}
|
||||||
|
newPrice={priceChangeData.newPrice}
|
||||||
|
currency={totalPrice.local.currency}
|
||||||
|
onCancel={() => {
|
||||||
|
const allSearchParams = searchParams.size
|
||||||
|
? `?${searchParams.toString()}`
|
||||||
|
: ""
|
||||||
|
router.push(`${selectRate(lang)}${allSearchParams}`)
|
||||||
|
}}
|
||||||
|
onAccept={() => priceChange.mutate({ confirmationNumber })}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { InfoCircleIcon } from "@/components/Icons"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
|
||||||
|
import styles from "./priceChangeDialog.module.css"
|
||||||
|
|
||||||
|
import { PriceChangeDialogProps } from "@/types/components/hotelReservation/enterDetails/priceChangeDialog"
|
||||||
|
|
||||||
|
export default function PriceChangeDialog({
|
||||||
|
isOpen,
|
||||||
|
oldPrice,
|
||||||
|
newPrice,
|
||||||
|
currency,
|
||||||
|
onCancel,
|
||||||
|
onAccept,
|
||||||
|
}: PriceChangeDialogProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const title = intl.formatMessage({ id: "The price has increased" })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalOverlay
|
||||||
|
className={styles.overlay}
|
||||||
|
isOpen={isOpen}
|
||||||
|
isKeyboardDismissDisabled
|
||||||
|
>
|
||||||
|
<Modal className={styles.modal}>
|
||||||
|
<Dialog aria-label={title} className={styles.dialog}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.titleContainer}>
|
||||||
|
<InfoCircleIcon height={48} width={48} color="burgundy" />
|
||||||
|
<Title
|
||||||
|
level="h1"
|
||||||
|
as="h3"
|
||||||
|
textAlign="center"
|
||||||
|
textTransform="regular"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<Body textAlign="center">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "The price has increased since you selected your room.",
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "You can still book the room but you need to confirm that you accept the new price",
|
||||||
|
})}
|
||||||
|
<br />
|
||||||
|
<span className={styles.oldPrice}>
|
||||||
|
{intl.formatNumber(oldPrice, { style: "currency", currency })}
|
||||||
|
</span>{" "}
|
||||||
|
<strong className={styles.newPrice}>
|
||||||
|
{intl.formatNumber(newPrice, { style: "currency", currency })}
|
||||||
|
</strong>
|
||||||
|
</Body>
|
||||||
|
</header>
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
<Button intent="secondary" onClick={onCancel}>
|
||||||
|
{intl.formatMessage({ id: "Cancel" })}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onAccept}>
|
||||||
|
{intl.formatMessage({ id: "Accept new price" })}
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
|
</ModalOverlay>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
@keyframes modal-fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-up {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
height: var(--visual-viewport-height);
|
||||||
|
justify-content: center;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
&[data-entering] {
|
||||||
|
animation: modal-fade 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-exiting] {
|
||||||
|
animation: modal-fade 150ms reverse ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
&[data-entering] {
|
||||||
|
animation: slide-up 200ms;
|
||||||
|
}
|
||||||
|
&[data-exiting] {
|
||||||
|
animation: slide-up 200ms reverse ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: var(--Scandic-Brand-Pale-Peach);
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
padding: var(--Spacing-x5) var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.oldPrice {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newPrice {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
|
background: var(--UI-Input-Controls-Surface-Normal);
|
||||||
border: 1px solid var(--UI-Input-Controls-Border-Normal);
|
border: 1px solid var(--UI-Input-Controls-Border-Normal);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 200ms;
|
transition: all 200ms;
|
||||||
|
|||||||
23
hooks/booking/useAvailablePaymentOptions.ts
Normal file
23
hooks/booking/useAvailablePaymentOptions.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
import { PaymentMethodEnum } from "@/constants/booking"
|
||||||
|
|
||||||
|
export function useAvailablePaymentOptions(
|
||||||
|
otherPaymentOptions: PaymentMethodEnum[]
|
||||||
|
) {
|
||||||
|
const [availablePaymentOptions, setAvailablePaymentOptions] = useState(
|
||||||
|
otherPaymentOptions.filter(
|
||||||
|
(option) => option !== PaymentMethodEnum.applePay
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.ApplePaySession) {
|
||||||
|
setAvailablePaymentOptions(otherPaymentOptions)
|
||||||
|
}
|
||||||
|
}, [otherPaymentOptions, setAvailablePaymentOptions])
|
||||||
|
|
||||||
|
return availablePaymentOptions
|
||||||
|
}
|
||||||
@@ -10,18 +10,20 @@ export function useHandleBookingStatus({
|
|||||||
expectedStatus,
|
expectedStatus,
|
||||||
maxRetries,
|
maxRetries,
|
||||||
retryInterval,
|
retryInterval,
|
||||||
|
enabled,
|
||||||
}: {
|
}: {
|
||||||
confirmationNumber: string | null
|
confirmationNumber: string | null
|
||||||
expectedStatus: BookingStatusEnum
|
expectedStatus: BookingStatusEnum
|
||||||
maxRetries: number
|
maxRetries: number
|
||||||
retryInterval: number
|
retryInterval: number
|
||||||
|
enabled: boolean
|
||||||
}) {
|
}) {
|
||||||
const retries = useRef(0)
|
const retries = useRef(0)
|
||||||
|
|
||||||
const query = trpc.booking.status.useQuery(
|
const query = trpc.booking.status.useQuery(
|
||||||
{ confirmationNumber: confirmationNumber ?? "" },
|
{ confirmationNumber: confirmationNumber ?? "" },
|
||||||
{
|
{
|
||||||
enabled: !!confirmationNumber,
|
enabled,
|
||||||
refetchInterval: (query) => {
|
refetchInterval: (query) => {
|
||||||
retries.current = query.state.dataUpdateCount
|
retries.current = query.state.dataUpdateCount
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,6 @@ export function usePaymentFailedToast() {
|
|||||||
|
|
||||||
const queryParams = new URLSearchParams(searchParams.toString())
|
const queryParams = new URLSearchParams(searchParams.toString())
|
||||||
queryParams.delete("errorCode")
|
queryParams.delete("errorCode")
|
||||||
router.replace(`${pathname}?${queryParams.toString()}`)
|
router.push(`${pathname}?${queryParams.toString()}`)
|
||||||
}, [searchParams, router, pathname, errorCode, errorMessage])
|
}, [searchParams, pathname, errorCode, errorMessage, router])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergi",
|
"ALLG": "Allergi",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "Om hotellet",
|
"About the hotel": "Om hotellet",
|
||||||
|
"Accept new price": "Accepter ny pris",
|
||||||
"Accessibility": "Tilgængelighed",
|
"Accessibility": "Tilgængelighed",
|
||||||
"Accessible Room": "Tilgængelighedsrum",
|
"Accessible Room": "Tilgængelighedsrum",
|
||||||
"Activities": "Aktiviteter",
|
"Activities": "Aktiviteter",
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
"How do you want to sleep?": "Hvordan vil du sove?",
|
"How do you want to sleep?": "Hvordan vil du sove?",
|
||||||
"How it works": "Hvordan det virker",
|
"How it works": "Hvordan det virker",
|
||||||
"Hurry up and use them before they expire!": "Skynd dig og brug dem, før de udløber!",
|
"Hurry up and use them before they expire!": "Skynd dig og brug dem, før de udløber!",
|
||||||
|
"I accept the terms and conditions": "Jeg accepterer vilkårene",
|
||||||
"I would like to get my booking confirmation via sms": "Jeg vil gerne få min booking bekræftelse via SMS",
|
"I would like to get my booking confirmation via sms": "Jeg vil gerne få min booking bekræftelse via SMS",
|
||||||
"Image gallery": "{name} - Billedgalleri",
|
"Image gallery": "{name} - Billedgalleri",
|
||||||
"In adults bed": "i de voksnes seng",
|
"In adults bed": "i de voksnes seng",
|
||||||
@@ -358,6 +360,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
|
||||||
"Terms and conditions": "Vilkår og betingelser",
|
"Terms and conditions": "Vilkår og betingelser",
|
||||||
"Thank you": "Tak",
|
"Thank you": "Tak",
|
||||||
|
"The price has increased": "Prisen er steget",
|
||||||
|
"The price has increased since you selected your room.": "Prisen er steget, efter at du har valgt dit værelse.",
|
||||||
"Theatre": "Teater",
|
"Theatre": "Teater",
|
||||||
"There are no rooms available that match your request": "Der er ingen ledige værelser, der matcher din anmodning",
|
"There are no rooms available that match your request": "Der er ingen ledige værelser, der matcher din anmodning",
|
||||||
"There are no rooms available that match your request.": "Der er ingen værelser tilgængelige, der matcher din forespørgsel.",
|
"There are no rooms available that match your request.": "Der er ingen værelser tilgængelige, der matcher din forespørgsel.",
|
||||||
@@ -407,6 +411,7 @@
|
|||||||
"Yes, discard changes": "Ja, kasser ændringer",
|
"Yes, discard changes": "Ja, kasser ændringer",
|
||||||
"Yes, remove my card": "Ja, fjern mit kort",
|
"Yes, remove my card": "Ja, fjern mit kort",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
|
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "Du kan stadig booke værelset, men du skal bekræfte, at du accepterer den nye pris",
|
||||||
"You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.",
|
"You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver, der venter på dig!",
|
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver, der venter på dig!",
|
||||||
"You have no previous stays.": "Du har ingen tidligere ophold.",
|
"You have no previous stays.": "Du har ingen tidligere ophold.",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergie",
|
"ALLG": "Allergie",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "Über das Hotel",
|
"About the hotel": "Über das Hotel",
|
||||||
|
"Accept new price": "Neuen Preis akzeptieren",
|
||||||
"Accessibility": "Zugänglichkeit",
|
"Accessibility": "Zugänglichkeit",
|
||||||
"Accessible Room": "Barrierefreies Zimmer",
|
"Accessible Room": "Barrierefreies Zimmer",
|
||||||
"Activities": "Aktivitäten",
|
"Activities": "Aktivitäten",
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
"How do you want to sleep?": "Wie möchtest du schlafen?",
|
"How do you want to sleep?": "Wie möchtest du schlafen?",
|
||||||
"How it works": "Wie es funktioniert",
|
"How it works": "Wie es funktioniert",
|
||||||
"Hurry up and use them before they expire!": "Beeilen Sie sich und nutzen Sie sie, bevor sie ablaufen!",
|
"Hurry up and use them before they expire!": "Beeilen Sie sich und nutzen Sie sie, bevor sie ablaufen!",
|
||||||
|
"I accept the terms and conditions": "Ich akzeptiere die Geschäftsbedingungen",
|
||||||
"I would like to get my booking confirmation via sms": "Ich möchte meine Buchungsbestätigung per SMS erhalten",
|
"I would like to get my booking confirmation via sms": "Ich möchte meine Buchungsbestätigung per SMS erhalten",
|
||||||
"Image gallery": "{name} - Bildergalerie",
|
"Image gallery": "{name} - Bildergalerie",
|
||||||
"In adults bed": "Im Bett der Eltern",
|
"In adults bed": "Im Bett der Eltern",
|
||||||
@@ -358,6 +360,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
|
||||||
"Terms and conditions": "Geschäftsbedingungen",
|
"Terms and conditions": "Geschäftsbedingungen",
|
||||||
"Thank you": "Danke",
|
"Thank you": "Danke",
|
||||||
|
"The price has increased": "Der Preis ist gestiegen",
|
||||||
|
"The price has increased since you selected your room.": "Der Preis ist gestiegen, nachdem Sie Ihr Zimmer ausgewählt haben.",
|
||||||
"Theatre": "Theater",
|
"Theatre": "Theater",
|
||||||
"There are no rooms available that match your request.": "Es sind keine Zimmer verfügbar, die Ihrer Anfrage entsprechen.",
|
"There are no rooms available that match your request.": "Es sind keine Zimmer verfügbar, die Ihrer Anfrage entsprechen.",
|
||||||
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
|
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
|
||||||
@@ -406,6 +410,7 @@
|
|||||||
"Yes, discard changes": "Ja, Änderungen verwerfen",
|
"Yes, discard changes": "Ja, Änderungen verwerfen",
|
||||||
"Yes, remove my card": "Ja, meine Karte entfernen",
|
"Yes, remove my card": "Ja, meine Karte entfernen",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
|
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "Sie können das Zimmer noch buchen, aber Sie müssen bestätigen, dass Sie die neue Preis akzeptieren",
|
||||||
"You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.",
|
"You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "Es warten <b>{amount}</b> Geschenke auf Sie!",
|
"You have <b>#</b> gifts waiting for you!": "Es warten <b>{amount}</b> Geschenke auf Sie!",
|
||||||
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
|
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
|
||||||
@@ -433,7 +438,7 @@
|
|||||||
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
|
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
|
||||||
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
|
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
|
||||||
"booking.selectRoom": "Vælg værelse",
|
"booking.selectRoom": "Vælg værelse",
|
||||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
"booking.terms": "Mit der Zahlung über eine der verfügbaren Zahlungsmethoden akzeptiere ich die Buchungsbedingungen und die allgemeinen <termsLink>Geschäftsbedingungen</termsLink> und verstehe, dass Scandic meine personenbezogenen Daten im Zusammenhang mit dieser Buchung gemäß der <privacyLink>Scandic Datenschutzrichtlinie</privacyLink> verarbeitet. Ich akzeptiere, dass Scandic während meines Aufenthalts eine gültige Kreditkarte für eventuelle Rückerstattungen benötigt.",
|
||||||
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
|
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
|
||||||
"breakfast.price": "{amount} {currency}/Nacht",
|
"breakfast.price": "{amount} {currency}/Nacht",
|
||||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/Nacht",
|
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/Nacht",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergy",
|
"ALLG": "Allergy",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "About the hotel",
|
"About the hotel": "About the hotel",
|
||||||
|
"Accept new price": "Accept new price",
|
||||||
"Accessibility": "Accessibility",
|
"Accessibility": "Accessibility",
|
||||||
"Accessible Room": "Accessibility room",
|
"Accessible Room": "Accessibility room",
|
||||||
"Activities": "Activities",
|
"Activities": "Activities",
|
||||||
@@ -174,6 +175,7 @@
|
|||||||
"How do you want to sleep?": "How do you want to sleep?",
|
"How do you want to sleep?": "How do you want to sleep?",
|
||||||
"How it works": "How it works",
|
"How it works": "How it works",
|
||||||
"Hurry up and use them before they expire!": "Hurry up and use them before they expire!",
|
"Hurry up and use them before they expire!": "Hurry up and use them before they expire!",
|
||||||
|
"I accept the terms and conditions": "I accept the terms and conditions",
|
||||||
"I would like to get my booking confirmation via sms": "I would like to get my booking confirmation via sms",
|
"I would like to get my booking confirmation via sms": "I would like to get my booking confirmation via sms",
|
||||||
"Image gallery": "{name} - Image gallery",
|
"Image gallery": "{name} - Image gallery",
|
||||||
"In adults bed": "In adults bed",
|
"In adults bed": "In adults bed",
|
||||||
@@ -387,6 +389,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
|
||||||
"Terms and conditions": "Terms and conditions",
|
"Terms and conditions": "Terms and conditions",
|
||||||
"Thank you": "Thank you",
|
"Thank you": "Thank you",
|
||||||
|
"The price has increased": "The price has increased",
|
||||||
|
"The price has increased since you selected your room.": "The price has increased since you selected your room.",
|
||||||
"Theatre": "Theatre",
|
"Theatre": "Theatre",
|
||||||
"There are no rooms available that match your request.": "There are no rooms available that match your request.",
|
"There are no rooms available that match your request.": "There are no rooms available that match your request.",
|
||||||
"There are no transactions to display": "There are no transactions to display",
|
"There are no transactions to display": "There are no transactions to display",
|
||||||
@@ -437,6 +441,7 @@
|
|||||||
"Yes, discard changes": "Yes, discard changes",
|
"Yes, discard changes": "Yes, discard changes",
|
||||||
"Yes, remove my card": "Yes, remove my card",
|
"Yes, remove my card": "Yes, remove my card",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
|
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "You can still book the room but you need to confirm that you accept the new price",
|
||||||
"You canceled adding a new credit card.": "You canceled adding a new credit card.",
|
"You canceled adding a new credit card.": "You canceled adding a new credit card.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "You have <b>{amount}</b> gifts waiting for you!",
|
"You have <b>#</b> gifts waiting for you!": "You have <b>{amount}</b> gifts waiting for you!",
|
||||||
"You have no previous stays.": "You have no previous stays.",
|
"You have no previous stays.": "You have no previous stays.",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergia",
|
"ALLG": "Allergia",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "Tietoja hotellista",
|
"About the hotel": "Tietoja hotellista",
|
||||||
|
"Accept new price": "Hyväksy uusi hinta",
|
||||||
"Accessibility": "Saavutettavuus",
|
"Accessibility": "Saavutettavuus",
|
||||||
"Accessible Room": "Esteetön huone",
|
"Accessible Room": "Esteetön huone",
|
||||||
"Activities": "Aktiviteetit",
|
"Activities": "Aktiviteetit",
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
"How do you want to sleep?": "Kuinka haluat nukkua?",
|
"How do you want to sleep?": "Kuinka haluat nukkua?",
|
||||||
"How it works": "Kuinka se toimii",
|
"How it works": "Kuinka se toimii",
|
||||||
"Hurry up and use them before they expire!": "Ole nopea ja käytä ne ennen kuin ne vanhenevat!",
|
"Hurry up and use them before they expire!": "Ole nopea ja käytä ne ennen kuin ne vanhenevat!",
|
||||||
|
"I accept the terms and conditions": "Hyväksyn käyttöehdot",
|
||||||
"I would like to get my booking confirmation via sms": "Haluan saada varauksen vahvistuksen SMS-viestillä",
|
"I would like to get my booking confirmation via sms": "Haluan saada varauksen vahvistuksen SMS-viestillä",
|
||||||
"Image gallery": "{name} - Kuvagalleria",
|
"Image gallery": "{name} - Kuvagalleria",
|
||||||
"In adults bed": "Aikuisten vuoteessa",
|
"In adults bed": "Aikuisten vuoteessa",
|
||||||
@@ -359,6 +361,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
|
||||||
"Terms and conditions": "Käyttöehdot",
|
"Terms and conditions": "Käyttöehdot",
|
||||||
"Thank you": "Kiitos",
|
"Thank you": "Kiitos",
|
||||||
|
"The price has increased": "Hinta on noussut",
|
||||||
|
"The price has increased since you selected your room.": "Hinta on noussut, koska valitsit huoneen.",
|
||||||
"Theatre": "Teatteri",
|
"Theatre": "Teatteri",
|
||||||
"There are no rooms available that match your request.": "Ei huoneita saatavilla, jotka vastaavat pyyntöäsi.",
|
"There are no rooms available that match your request.": "Ei huoneita saatavilla, jotka vastaavat pyyntöäsi.",
|
||||||
"There are no transactions to display": "Näytettäviä tapahtumia ei ole",
|
"There are no transactions to display": "Näytettäviä tapahtumia ei ole",
|
||||||
@@ -407,6 +411,7 @@
|
|||||||
"Yes, discard changes": "Kyllä, hylkää muutokset",
|
"Yes, discard changes": "Kyllä, hylkää muutokset",
|
||||||
"Yes, remove my card": "Kyllä, poista korttini",
|
"Yes, remove my card": "Kyllä, poista korttini",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
|
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "Voit vielä bookea huoneen, mutta sinun on vahvistettava, että hyväksyt uuden hinnan",
|
||||||
"You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.",
|
"You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "Sinulla on <b>{amount}</b> lahjaa odottamassa sinua!",
|
"You have <b>#</b> gifts waiting for you!": "Sinulla on <b>{amount}</b> lahjaa odottamassa sinua!",
|
||||||
"You have no previous stays.": "Sinulla ei ole aiempia majoituksia.",
|
"You have no previous stays.": "Sinulla ei ole aiempia majoituksia.",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergi",
|
"ALLG": "Allergi",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "Om hotellet",
|
"About the hotel": "Om hotellet",
|
||||||
|
"Accept new price": "Aksepterer ny pris",
|
||||||
"Accessibility": "Tilgjengelighet",
|
"Accessibility": "Tilgjengelighet",
|
||||||
"Accessible Room": "Tilgjengelighetsrom",
|
"Accessible Room": "Tilgjengelighetsrom",
|
||||||
"Activities": "Aktiviteter",
|
"Activities": "Aktiviteter",
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"How do you want to sleep?": "Hvordan vil du sove?",
|
"How do you want to sleep?": "Hvordan vil du sove?",
|
||||||
"How it works": "Hvordan det fungerer",
|
"How it works": "Hvordan det fungerer",
|
||||||
"Hurry up and use them before they expire!": "Skynd deg og bruk dem før de utløper!",
|
"Hurry up and use them before they expire!": "Skynd deg og bruk dem før de utløper!",
|
||||||
|
"I accept the terms and conditions": "Jeg aksepterer vilkårene",
|
||||||
"Image gallery": "{name} - Bildegalleri",
|
"Image gallery": "{name} - Bildegalleri",
|
||||||
"In adults bed": "i voksnes seng",
|
"In adults bed": "i voksnes seng",
|
||||||
"In crib": "i sprinkelseng",
|
"In crib": "i sprinkelseng",
|
||||||
@@ -356,6 +358,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
|
||||||
"Terms and conditions": "Vilkår og betingelser",
|
"Terms and conditions": "Vilkår og betingelser",
|
||||||
"Thank you": "Takk",
|
"Thank you": "Takk",
|
||||||
|
"The price has increased": "Prisen er steget",
|
||||||
|
"The price has increased since you selected your room.": "Prisen er steget, etter at du har valgt rommet.",
|
||||||
"Theatre": "Teater",
|
"Theatre": "Teater",
|
||||||
"There are no rooms available that match your request.": "Det er ingen rom tilgjengelige som matcher din forespørsel.",
|
"There are no rooms available that match your request.": "Det er ingen rom tilgjengelige som matcher din forespørsel.",
|
||||||
"There are no transactions to display": "Det er ingen transaksjoner å vise",
|
"There are no transactions to display": "Det er ingen transaksjoner å vise",
|
||||||
@@ -404,6 +408,7 @@
|
|||||||
"Yes, discard changes": "Ja, forkast endringer",
|
"Yes, discard changes": "Ja, forkast endringer",
|
||||||
"Yes, remove my card": "Ja, fjern kortet mitt",
|
"Yes, remove my card": "Ja, fjern kortet mitt",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
|
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "Du kan fortsatt booke rommet, men du må bekrefte at du aksepterer den nye prisen",
|
||||||
"You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.",
|
"You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver som venter på deg!",
|
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver som venter på deg!",
|
||||||
"You have no previous stays.": "Du har ingen tidligere opphold.",
|
"You have no previous stays.": "Du har ingen tidligere opphold.",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"ALLG": "Allergi",
|
"ALLG": "Allergi",
|
||||||
"About meetings & conferences": "About meetings & conferences",
|
"About meetings & conferences": "About meetings & conferences",
|
||||||
"About the hotel": "Om hotellet",
|
"About the hotel": "Om hotellet",
|
||||||
|
"Accept new price": "Accepter ny pris",
|
||||||
"Accessibility": "Tillgänglighet",
|
"Accessibility": "Tillgänglighet",
|
||||||
"Accessible Room": "Tillgänglighetsrum",
|
"Accessible Room": "Tillgänglighetsrum",
|
||||||
"Activities": "Aktiviteter",
|
"Activities": "Aktiviteter",
|
||||||
@@ -161,6 +162,7 @@
|
|||||||
"How do you want to sleep?": "Hur vill du sova?",
|
"How do you want to sleep?": "Hur vill du sova?",
|
||||||
"How it works": "Hur det fungerar",
|
"How it works": "Hur det fungerar",
|
||||||
"Hurry up and use them before they expire!": "Skynda dig och använd dem innan de går ut!",
|
"Hurry up and use them before they expire!": "Skynda dig och använd dem innan de går ut!",
|
||||||
|
"I accept the terms and conditions": "Jag accepterar villkoren",
|
||||||
"Image gallery": "{name} - Bildgalleri",
|
"Image gallery": "{name} - Bildgalleri",
|
||||||
"In adults bed": "I vuxens säng",
|
"In adults bed": "I vuxens säng",
|
||||||
"In crib": "I spjälsäng",
|
"In crib": "I spjälsäng",
|
||||||
@@ -356,6 +358,8 @@
|
|||||||
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
|
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
|
||||||
"Terms and conditions": "Allmänna villkor",
|
"Terms and conditions": "Allmänna villkor",
|
||||||
"Thank you": "Tack",
|
"Thank you": "Tack",
|
||||||
|
"The price has increased": "Priset har ökat",
|
||||||
|
"The price has increased since you selected your room.": "Priset har ökat sedan du valde ditt rum.",
|
||||||
"Theatre": "Teater",
|
"Theatre": "Teater",
|
||||||
"There are no rooms available that match your request.": "Det finns inga rum tillgängliga som matchar din begäran.",
|
"There are no rooms available that match your request.": "Det finns inga rum tillgängliga som matchar din begäran.",
|
||||||
"There are no transactions to display": "Det finns inga transaktioner att visa",
|
"There are no transactions to display": "Det finns inga transaktioner att visa",
|
||||||
@@ -404,6 +408,7 @@
|
|||||||
"Yes, discard changes": "Ja, ignorera ändringar",
|
"Yes, discard changes": "Ja, ignorera ändringar",
|
||||||
"Yes, remove my card": "Ja, ta bort mitt kort",
|
"Yes, remove my card": "Ja, ta bort mitt kort",
|
||||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
|
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
|
||||||
|
"You can still book the room but you need to confirm that you accept the new price": "Du kan fortsatt boka rummet men du måste bekräfta att du accepterar det nya priset",
|
||||||
"You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.",
|
"You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.",
|
||||||
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> presenter som väntar på dig!",
|
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> presenter som väntar på dig!",
|
||||||
"You have no previous stays.": "Du har inga tidigare vistelser.",
|
"You have no previous stays.": "Du har inga tidigare vistelser.",
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ export namespace endpoints {
|
|||||||
export function status(confirmationNumber: string) {
|
export function status(confirmationNumber: string) {
|
||||||
return `${bookings}/${confirmationNumber}/status`
|
return `${bookings}/${confirmationNumber}/status`
|
||||||
}
|
}
|
||||||
|
export function priceChange(confirmationNumber: string) {
|
||||||
|
return `${bookings}/${confirmationNumber}/priceChange`
|
||||||
|
}
|
||||||
|
|
||||||
export const enum Stays {
|
export const enum Stays {
|
||||||
future = `${base.path.booking}/${version}/${base.enitity.Stays}/future`,
|
future = `${base.path.booking}/${version}/${base.enitity.Stays}/future`,
|
||||||
|
|||||||
@@ -34,13 +34,7 @@ export async function get(
|
|||||||
) {
|
) {
|
||||||
const url = new URL(env.API_BASEURL)
|
const url = new URL(env.API_BASEURL)
|
||||||
url.pathname = endpoint
|
url.pathname = endpoint
|
||||||
const searchParams = new URLSearchParams(params)
|
url.search = new URLSearchParams(params).toString()
|
||||||
if (searchParams.size) {
|
|
||||||
searchParams.forEach((value, key) => {
|
|
||||||
url.searchParams.append(key, value)
|
|
||||||
})
|
|
||||||
url.searchParams.sort()
|
|
||||||
}
|
|
||||||
return wrappedFetch(
|
return wrappedFetch(
|
||||||
url,
|
url,
|
||||||
merge.all([defaultOptions, { method: "GET" }, options])
|
merge.all([defaultOptions, { method: "GET" }, options])
|
||||||
@@ -55,13 +49,7 @@ export async function patch(
|
|||||||
const { body, ...requestOptions } = options
|
const { body, ...requestOptions } = options
|
||||||
const url = new URL(env.API_BASEURL)
|
const url = new URL(env.API_BASEURL)
|
||||||
url.pathname = endpoint
|
url.pathname = endpoint
|
||||||
const searchParams = new URLSearchParams(params)
|
url.search = new URLSearchParams(params).toString()
|
||||||
if (searchParams.size) {
|
|
||||||
searchParams.forEach((value, key) => {
|
|
||||||
url.searchParams.set(key, value)
|
|
||||||
})
|
|
||||||
url.searchParams.sort()
|
|
||||||
}
|
|
||||||
return wrappedFetch(
|
return wrappedFetch(
|
||||||
url,
|
url,
|
||||||
merge.all([
|
merge.all([
|
||||||
@@ -80,13 +68,7 @@ export async function post(
|
|||||||
const { body, ...requestOptions } = options
|
const { body, ...requestOptions } = options
|
||||||
const url = new URL(env.API_BASEURL)
|
const url = new URL(env.API_BASEURL)
|
||||||
url.pathname = endpoint
|
url.pathname = endpoint
|
||||||
const searchParams = new URLSearchParams(params)
|
url.search = new URLSearchParams(params).toString()
|
||||||
if (searchParams.size) {
|
|
||||||
searchParams.forEach((value, key) => {
|
|
||||||
url.searchParams.set(key, value)
|
|
||||||
})
|
|
||||||
url.searchParams.sort()
|
|
||||||
}
|
|
||||||
return wrappedFetch(
|
return wrappedFetch(
|
||||||
url,
|
url,
|
||||||
merge.all([
|
merge.all([
|
||||||
@@ -97,6 +79,25 @@ export async function post(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function put(
|
||||||
|
endpoint: Endpoint | `${Endpoint}/${string}`,
|
||||||
|
options: RequestOptionsWithJSONBody,
|
||||||
|
params = {}
|
||||||
|
) {
|
||||||
|
const { body, ...requestOptions } = options
|
||||||
|
const url = new URL(env.API_BASEURL)
|
||||||
|
url.pathname = endpoint
|
||||||
|
url.search = new URLSearchParams(params).toString()
|
||||||
|
return wrappedFetch(
|
||||||
|
url,
|
||||||
|
merge.all([
|
||||||
|
defaultOptions,
|
||||||
|
{ body: JSON.stringify(body), method: "PUT" },
|
||||||
|
requestOptions,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function remove(
|
export async function remove(
|
||||||
endpoint: Endpoint | `${Endpoint}/${string}`,
|
endpoint: Endpoint | `${Endpoint}/${string}`,
|
||||||
options: RequestOptionsWithOutBody,
|
options: RequestOptionsWithOutBody,
|
||||||
@@ -104,13 +105,7 @@ export async function remove(
|
|||||||
) {
|
) {
|
||||||
const url = new URL(env.API_BASEURL)
|
const url = new URL(env.API_BASEURL)
|
||||||
url.pathname = endpoint
|
url.pathname = endpoint
|
||||||
const searchParams = new URLSearchParams(params)
|
url.search = new URLSearchParams(params).toString()
|
||||||
if (searchParams.size) {
|
|
||||||
searchParams.forEach((value, key) => {
|
|
||||||
url.searchParams.set(key, value)
|
|
||||||
})
|
|
||||||
url.searchParams.sort()
|
|
||||||
}
|
|
||||||
return wrappedFetch(
|
return wrappedFetch(
|
||||||
url,
|
url,
|
||||||
merge.all([defaultOptions, { method: "DELETE" }, options])
|
merge.all([defaultOptions, { method: "DELETE" }, options])
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ export const createBookingInput = z.object({
|
|||||||
payment: paymentSchema,
|
payment: paymentSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const priceChangeInput = z.object({
|
||||||
|
confirmationNumber: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
const confirmationNumberInput = z.object({
|
const confirmationNumberInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
confirmationNumber: z.string(),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { router, safeProtectedServiceProcedure } from "@/server/trpc"
|
|||||||
|
|
||||||
import { getMembership } from "@/utils/user"
|
import { getMembership } from "@/utils/user"
|
||||||
|
|
||||||
import { createBookingInput } from "./input"
|
import { createBookingInput, priceChangeInput } from "./input"
|
||||||
import { createBookingSchema } from "./output"
|
import { createBookingSchema } from "./output"
|
||||||
|
|
||||||
import type { Session } from "next-auth"
|
import type { Session } from "next-auth"
|
||||||
@@ -20,6 +20,14 @@ const createBookingFailCounter = meter.createCounter(
|
|||||||
"trpc.bookings.create-fail"
|
"trpc.bookings.create-fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const priceChangeCounter = meter.createCounter("trpc.bookings.price-change")
|
||||||
|
const priceChangeSuccessCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.price-change-success"
|
||||||
|
)
|
||||||
|
const priceChangeFailCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.price-change-fail"
|
||||||
|
)
|
||||||
|
|
||||||
async function getMembershipNumber(
|
async function getMembershipNumber(
|
||||||
session: Session | null
|
session: Session | null
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
@@ -122,6 +130,71 @@ export const bookingMutationRouter = router({
|
|||||||
query: loggingAttributes,
|
query: loggingAttributes,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return verifiedData.data
|
||||||
|
}),
|
||||||
|
priceChange: safeProtectedServiceProcedure
|
||||||
|
.input(priceChangeInput)
|
||||||
|
.mutation(async function ({ ctx, input }) {
|
||||||
|
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
|
const { confirmationNumber } = input
|
||||||
|
|
||||||
|
priceChangeCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResponse = await api.put(
|
||||||
|
api.endpoints.v1.Booking.priceChange(confirmationNumber),
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
body: input,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
const text = await apiResponse.text()
|
||||||
|
priceChangeFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
|
status: apiResponse.status,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.priceChange error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
error: text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiJson = await apiResponse.json()
|
||||||
|
const verifiedData = createBookingSchema.safeParse(apiJson)
|
||||||
|
if (!verifiedData.success) {
|
||||||
|
priceChangeFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "validation_error",
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.priceChange validation error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
error: verifiedData.error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
priceChangeSuccessCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
return verifiedData.data
|
return verifiedData.data
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ export const createBookingSchema = z
|
|||||||
errorMessage: z.string().nullable().optional(),
|
errorMessage: z.string().nullable().optional(),
|
||||||
priceChangedMetadata: z
|
priceChangedMetadata: z
|
||||||
.object({
|
.object({
|
||||||
roomPrice: z.number().nullable().optional(),
|
roomPrice: z.number(),
|
||||||
totalPrice: z.number().nullable().optional(),
|
totalPrice: z.number(),
|
||||||
})
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { ChildBedTypeEnum } from "@/constants/booking"
|
import { ChildBedTypeEnum, PaymentMethodEnum } from "@/constants/booking"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { toLang } from "@/server/utils"
|
import { toLang } from "@/server/utils"
|
||||||
|
|
||||||
@@ -376,6 +376,7 @@ const merchantInformationSchema = z.object({
|
|||||||
return Object.entries(val)
|
return Object.entries(val)
|
||||||
.filter(([_, enabled]) => enabled)
|
.filter(([_, enabled]) => enabled)
|
||||||
.map(([key]) => key)
|
.map(([key]) => key)
|
||||||
|
.filter((key): key is PaymentMethodEnum => !!key)
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type PriceChangeDialogProps = {
|
||||||
|
isOpen: boolean
|
||||||
|
oldPrice: number
|
||||||
|
newPrice: number
|
||||||
|
currency: string
|
||||||
|
onCancel: () => void
|
||||||
|
onAccept: () => void
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { PaymentMethodEnum } from "@/constants/booking"
|
||||||
|
|
||||||
import { CreditCard, SafeUser } from "@/types/user"
|
import { CreditCard, SafeUser } from "@/types/user"
|
||||||
|
|
||||||
export interface SectionProps {
|
export interface SectionProps {
|
||||||
@@ -30,7 +32,7 @@ export interface DetailsProps extends SectionProps {}
|
|||||||
export interface PaymentProps {
|
export interface PaymentProps {
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
roomPrice: { publicPrice: number; memberPrice: number | undefined }
|
roomPrice: { publicPrice: number; memberPrice: number | undefined }
|
||||||
otherPaymentOptions: string[]
|
otherPaymentOptions: PaymentMethodEnum[]
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
mustBeGuaranteed: boolean
|
mustBeGuaranteed: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user