From b33381d1b4646fba6a0c749daa692c2ac9376770 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Mon, 21 Oct 2024 10:39:19 +0000 Subject: [PATCH] Merged in feat/SW-588-payment-saved-card (pull request #697) feat(SW-588): Added saved card to payment step * feat(SW-588): Added saved card to payment step * feat(SW-588): Add proper label for saved card * feat(SW-588): fix from PR feedback * feat(SW-588): Add preloading of data * feat(SW-588): remove onChange logic for PaymentOption * feat(SW-588): moved payment files to correct folder * feat(SW-588): moved preload to layout * fix: remove unused prop Approved-by: Simon.Emanuelsson --- .../hotelreservation/[step]/layout.tsx | 19 +- .../(public)/hotelreservation/[step]/page.tsx | 25 ++- .../Payment/PaymentOption/index.tsx | 49 ++++++ .../PaymentOption/paymentOption.module.css | 0 .../Payment/PaymentOption/paymentOption.ts | 6 +- .../Payment/index.tsx | 166 ++++++++++++------ .../Payment/payment.module.css | 8 +- .../Payment/schema.ts | 4 +- .../EnterDetails/SectionAccordion/index.tsx | 2 +- .../Payment/PaymentOption/index.tsx | 43 ----- constants/booking.ts | 27 +++ i18n/dictionaries/da.json | 74 ++++---- i18n/dictionaries/de.json | 74 ++++---- i18n/dictionaries/en.json | 70 ++++---- i18n/dictionaries/fi.json | 74 ++++---- i18n/dictionaries/no.json | 72 ++++---- i18n/dictionaries/sv.json | 74 ++++---- lib/trpc/memoizedRequests/index.ts | 16 ++ server/routers/user/output.ts | 9 +- server/routers/user/query.ts | 99 ++++++----- .../hotelReservation/selectRate/section.ts | 6 +- 21 files changed, 536 insertions(+), 381 deletions(-) create mode 100644 components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx rename components/HotelReservation/{SelectRate => EnterDetails}/Payment/PaymentOption/paymentOption.module.css (100%) rename components/HotelReservation/{SelectRate => EnterDetails}/Payment/PaymentOption/paymentOption.ts (65%) rename components/HotelReservation/{SelectRate => EnterDetails}/Payment/index.tsx (55%) rename components/HotelReservation/{SelectRate => EnterDetails}/Payment/payment.module.css (80%) rename components/HotelReservation/{SelectRate => EnterDetails}/Payment/schema.ts (74%) delete mode 100644 components/HotelReservation/SelectRate/Payment/PaymentOption/index.tsx diff --git a/app/[lang]/(live)/(public)/hotelreservation/[step]/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/[step]/layout.tsx index 5f62c69c9..0e8edd50e 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[step]/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/[step]/layout.tsx @@ -1,6 +1,10 @@ import { redirect } from "next/navigation" -import { serverClient } from "@/lib/trpc/server" +import { + getCreditCardsSafely, + getHotelData, + getProfileSafely, +} from "@/lib/trpc/memoizedRequests" import EnterDetailsProvider from "@/components/HotelReservation/EnterDetails/Provider" import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom" @@ -14,15 +18,20 @@ import styles from "./layout.module.css" import { StepEnum } from "@/types/components/enterDetails/step" import type { LangParams, LayoutArgs } from "@/types/params" +function preload(id: string, lang: string) { + void getHotelData(id, lang) + void getProfileSafely() + void getCreditCardsSafely() +} + export default async function StepLayout({ children, params, }: React.PropsWithChildren>) { setLang(params.lang) - const hotel = await serverClient().hotel.hotelData.get({ - hotelId: "811", - language: params.lang, - }) + preload("811", params.lang) + + const hotel = await getHotelData("811", params.lang) if (!hotel?.data) { redirect(`/${params.lang}`) diff --git a/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx index 5f2666c8e..8e8d3c891 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx @@ -1,14 +1,17 @@ import { notFound } from "next/navigation" -import { getProfileSafely } from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" +import { + getCreditCardsSafely, + getHotelData, + getProfileSafely, +} from "@/lib/trpc/memoizedRequests" import BedType from "@/components/HotelReservation/EnterDetails/BedType" import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast" import Details from "@/components/HotelReservation/EnterDetails/Details" import HistoryStateManager from "@/components/HotelReservation/EnterDetails/HistoryStateManager" +import Payment from "@/components/HotelReservation/EnterDetails/Payment" import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion" -import Payment from "@/components/HotelReservation/SelectRate/Payment" import { getIntl } from "@/i18n" import { StepEnum } from "@/types/components/enterDetails/step" @@ -25,12 +28,9 @@ export default async function StepPage({ const intl = await getIntl() - const hotel = await serverClient().hotel.hotelData.get({ - hotelId: "811", - language: lang, - }) - + const hotel = await getHotelData("811", lang) const user = await getProfileSafely() + const savedCreditCards = await getCreditCardsSafely() if (!isValidStep(step) || !hotel) { return notFound() @@ -65,7 +65,14 @@ export default async function StepPage({ step={StepEnum.payment} label={intl.formatMessage({ id: "Select payment method" })} > - + ) diff --git a/components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx b/components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx new file mode 100644 index 000000000..c076a074b --- /dev/null +++ b/components/HotelReservation/EnterDetails/Payment/PaymentOption/index.tsx @@ -0,0 +1,49 @@ +import Image from "next/image" +import { useFormContext } from "react-hook-form" + +import { PAYMENT_METHOD_ICONS, PaymentMethodEnum } from "@/constants/booking" + +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import { PaymentOptionProps } from "./paymentOption" + +import styles from "./paymentOption.module.css" + +export default function PaymentOption({ + name, + value, + label, + cardNumber, + registerOptions = {}, +}: PaymentOptionProps) { + const { register } = useFormContext() + + return ( + + ) +} diff --git a/components/HotelReservation/SelectRate/Payment/PaymentOption/paymentOption.module.css b/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.module.css similarity index 100% rename from components/HotelReservation/SelectRate/Payment/PaymentOption/paymentOption.module.css rename to components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.module.css diff --git a/components/HotelReservation/SelectRate/Payment/PaymentOption/paymentOption.ts b/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.ts similarity index 65% rename from components/HotelReservation/SelectRate/Payment/PaymentOption/paymentOption.ts rename to components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.ts index 5d77f6560..151a18860 100644 --- a/components/HotelReservation/SelectRate/Payment/PaymentOption/paymentOption.ts +++ b/components/HotelReservation/EnterDetails/Payment/PaymentOption/paymentOption.ts @@ -1,10 +1,10 @@ import { RegisterOptions } from "react-hook-form" -import { PaymentMethodEnum } from "@/constants/booking" - export interface PaymentOptionProps { name: string - value: PaymentMethodEnum + value: string label: string + cardNumber?: string registerOptions?: RegisterOptions + onChange?: () => void } diff --git a/components/HotelReservation/SelectRate/Payment/index.tsx b/components/HotelReservation/EnterDetails/Payment/index.tsx similarity index 55% rename from components/HotelReservation/SelectRate/Payment/index.tsx rename to components/HotelReservation/EnterDetails/Payment/index.tsx index b1bddd84c..3d582e7e3 100644 --- a/components/HotelReservation/SelectRate/Payment/index.tsx +++ b/components/HotelReservation/EnterDetails/Payment/index.tsx @@ -23,6 +23,7 @@ import LoadingSpinner from "@/components/LoadingSpinner" import Button from "@/components/TempDesignSystem/Button" import Checkbox from "@/components/TempDesignSystem/Checkbox" import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import { toast } from "@/components/TempDesignSystem/Toasts" import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" @@ -38,7 +39,15 @@ import { PaymentProps } from "@/types/components/hotelReservation/selectRate/sec const maxRetries = 40 const retryInterval = 2000 -export default function Payment({ hotel }: PaymentProps) { +function isPaymentMethodEnum(value: string): value is PaymentMethodEnum { + return Object.values(PaymentMethodEnum).includes(value as PaymentMethodEnum) +} + +export default function Payment({ + hotelId, + otherPaymentOptions, + savedCreditCards, +}: PaymentProps) { const router = useRouter() const lang = useLang() const intl = useIntl() @@ -46,7 +55,9 @@ export default function Payment({ hotel }: PaymentProps) { const methods = useForm({ defaultValues: { - paymentMethod: PaymentMethodEnum.card, + paymentMethod: savedCreditCards?.length + ? savedCreditCards[0].id + : PaymentMethodEnum.card, smsConfirmation: false, termsAndConditions: false, }, @@ -87,8 +98,17 @@ export default function Payment({ hotel }: PaymentProps) { }, [confirmationNumber, bookingStatus, router]) function handleSubmit(data: PaymentFormData) { + // set payment method to card if saved card is submitted + const paymentMethod = isPaymentMethodEnum(data.paymentMethod) + ? data.paymentMethod + : PaymentMethodEnum.card + + const savedCreditCard = savedCreditCards?.find( + (card) => card.id === data.paymentMethod + ) + initiateBooking.mutate({ - hotelId: hotel.operaId, + hotelId: hotelId, checkInDate: "2024-12-10", checkOutDate: "2024-12-11", rooms: [ @@ -116,7 +136,14 @@ export default function Payment({ hotel }: PaymentProps) { }, ], payment: { - paymentMethod: data.paymentMethod, + paymentMethod, + card: savedCreditCard + ? { + alias: savedCreditCard.alias, + expiryDate: savedCreditCard.expirationDate, + cardType: savedCreditCard.cardType, + } + : undefined, cardHolder: { email: "test.user@scandichotels.com", name: "Test User", @@ -143,65 +170,94 @@ export default function Payment({ hotel }: PaymentProps) { className={styles.paymentContainer} onSubmit={methods.handleSubmit(handleSubmit)} > -
- - {hotel.merchantInformationData.alternatePaymentOptions.map( - (paymentMethod) => ( + {savedCreditCards?.length ? ( +
+ + {intl.formatMessage({ id: "MY SAVED CARDS" })} + +
+ {savedCreditCards?.map((savedCreditCard) => ( + + ))} +
+
+ ) : null} +
+ {savedCreditCards?.length ? ( + + {intl.formatMessage({ id: "OTHER PAYMENT METHODS" })} + + ) : null} +
+ + {otherPaymentOptions.map((paymentMethod) => ( - ) - )} -
- - - {intl.formatMessage({ - id: "I would like to get my booking confirmation via sms", - })} - - + ))} +
+ +
+ + + {intl.formatMessage({ + id: "I would like to get my booking confirmation via sms", + })} + + - - - - {intl.formatMessage( - { - id: "booking.terms", - }, - { - termsLink: (str) => ( - - {str} - - ), - privacyLink: (str) => ( - - {str} - - ), - } - )} - - + + + + {intl.formatMessage( + { + id: "booking.terms", + }, + { + termsLink: (str) => ( + + {str} + + ), + privacyLink: (str) => ( + + {str} + + ), + } + )} + + +