feat(SW-1255): Added loading state to submit button in enter details
This commit is contained in:
committed by
Simon Emanuelsson
parent
89468bc37f
commit
f56a1ece0f
@@ -8,6 +8,7 @@ import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
|
||||
import {
|
||||
BOOKING_CONFIRMATION_NUMBER,
|
||||
@@ -25,8 +26,6 @@ import { trpc } from "@/lib/trpc/client"
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import PaymentOption from "@/components/HotelReservation/PaymentOption"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions"
|
||||
@@ -76,14 +75,18 @@ export default function PaymentClient({
|
||||
|
||||
const [showBookingAlert, setShowBookingAlert] = useState(false)
|
||||
|
||||
const { booking, rooms, totalPrice, preSubmitCallbacks } =
|
||||
const { booking, rooms, totalPrice, isSubmitting, preSubmitCallbacks, setIsSubmitting } =
|
||||
useEnterDetailsStore((state) => ({
|
||||
booking: state.booking,
|
||||
rooms: state.rooms,
|
||||
totalPrice: state.totalPrice,
|
||||
preSubmitCallbacks: state.preSubmitCallbacks,
|
||||
isSubmitting: state.isSubmitting,
|
||||
setIsSubmitting: state.actions.setIsSubmitting,
|
||||
}))
|
||||
|
||||
const allRoomsComplete = rooms.every((r) => r.isComplete)
|
||||
|
||||
const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => {
|
||||
if (idx === 0 && isUserLoggedIn && room.memberMustBeGuaranteed) {
|
||||
return true
|
||||
@@ -202,6 +205,7 @@ export default function PaymentClient({
|
||||
const handlePaymentError = useCallback(
|
||||
(errorMessage: string) => {
|
||||
setShowBookingAlert(true)
|
||||
setIsSubmitting(false)
|
||||
|
||||
const currentPaymentMethod = methods.getValues("paymentMethod")
|
||||
const smsEnable = methods.getValues("smsConfirmation")
|
||||
@@ -243,6 +247,7 @@ export default function PaymentClient({
|
||||
hotelId,
|
||||
bookingMustBeGuaranteed,
|
||||
hasOnlyFlexRates,
|
||||
setIsSubmitting,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -284,6 +289,8 @@ export default function PaymentClient({
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(data: PaymentFormData) => {
|
||||
setIsSubmitting(true)
|
||||
|
||||
Object.values(preSubmitCallbacks).forEach((callback) => {
|
||||
callback()
|
||||
})
|
||||
@@ -321,24 +328,24 @@ export default function PaymentClient({
|
||||
const guarantee = data.guarantee
|
||||
const useSavedCard = savedCreditCard
|
||||
? {
|
||||
card: {
|
||||
alias: savedCreditCard.alias,
|
||||
expiryDate: savedCreditCard.expirationDate,
|
||||
cardType: savedCreditCard.cardType,
|
||||
},
|
||||
}
|
||||
card: {
|
||||
alias: savedCreditCard.alias,
|
||||
expiryDate: savedCreditCard.expirationDate,
|
||||
cardType: savedCreditCard.cardType,
|
||||
},
|
||||
}
|
||||
: {}
|
||||
|
||||
const shouldUsePayment =
|
||||
guarantee || bookingMustBeGuaranteed || !hasOnlyFlexRates
|
||||
const payment = shouldUsePayment
|
||||
? {
|
||||
paymentMethod: paymentMethod,
|
||||
...useSavedCard,
|
||||
success: `${paymentRedirectUrl}/success`,
|
||||
error: `${paymentRedirectUrl}/error`,
|
||||
cancel: `${paymentRedirectUrl}/cancel`,
|
||||
}
|
||||
paymentMethod: paymentMethod,
|
||||
...useSavedCard,
|
||||
success: `${paymentRedirectUrl}/success`,
|
||||
error: `${paymentRedirectUrl}/error`,
|
||||
cancel: `${paymentRedirectUrl}/cancel`,
|
||||
}
|
||||
: undefined
|
||||
|
||||
if (guarantee || (bookingMustBeGuaranteed && hasOnlyFlexRates)) {
|
||||
@@ -457,18 +464,10 @@ export default function PaymentClient({
|
||||
preSubmitCallbacks,
|
||||
isUserLoggedIn,
|
||||
getTopOffset,
|
||||
setIsSubmitting,
|
||||
]
|
||||
)
|
||||
|
||||
if (
|
||||
initiateBooking.isPending ||
|
||||
(isPollingForBookingStatus &&
|
||||
!bookingStatus.data?.paymentUrl &&
|
||||
!bookingStatus.isTimeout)
|
||||
) {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
const paymentGuarantee = intl.formatMessage({
|
||||
defaultMessage: "Payment Guarantee",
|
||||
})
|
||||
@@ -480,7 +479,9 @@ export default function PaymentClient({
|
||||
})
|
||||
|
||||
return (
|
||||
<section className={styles.paymentSection}>
|
||||
<section
|
||||
className={`${styles.paymentSection} ${allRoomsComplete && !isSubmitting ? "" : styles.disabled}`}
|
||||
>
|
||||
<header>
|
||||
<Title level="h2" as="h4">
|
||||
{hasOnlyFlexRates && bookingMustBeGuaranteed
|
||||
@@ -551,7 +552,7 @@ export default function PaymentClient({
|
||||
value={savedCreditCard.id}
|
||||
label={
|
||||
PAYMENT_METHOD_TITLES[
|
||||
savedCreditCard.cardType as PaymentMethodEnum
|
||||
savedCreditCard.cardType as PaymentMethodEnum
|
||||
]
|
||||
}
|
||||
cardNumber={savedCreditCard.truncatedNumber}
|
||||
@@ -580,7 +581,7 @@ export default function PaymentClient({
|
||||
value={paymentMethod}
|
||||
label={
|
||||
PAYMENT_METHOD_TITLES[
|
||||
paymentMethod as PaymentMethodEnum
|
||||
paymentMethod as PaymentMethodEnum
|
||||
]
|
||||
}
|
||||
/>
|
||||
@@ -601,11 +602,12 @@ export default function PaymentClient({
|
||||
)}
|
||||
<div className={styles.submitButton}>
|
||||
<Button
|
||||
intent="primary"
|
||||
theme="base"
|
||||
size="small"
|
||||
type="submit"
|
||||
disabled={methods.formState.isSubmitting}
|
||||
isDisabled={
|
||||
!methods.formState.isValid || methods.formState.isSubmitting
|
||||
}
|
||||
isPending={isSubmitting}
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Complete booking",
|
||||
|
||||
@@ -4,10 +4,11 @@ import { useSearchParams } from "next/navigation"
|
||||
import { type PropsWithChildren, useEffect, useRef } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import { formId } from "@/components/HotelReservation/EnterDetails/Payment/PaymentClient"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
@@ -20,13 +21,19 @@ export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
||||
const searchParams = useSearchParams()
|
||||
const errorCode = searchParams.get("errorCode")
|
||||
|
||||
const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmittingDisabled } =
|
||||
useEnterDetailsStore((state) => ({
|
||||
isSummaryOpen: state.isSummaryOpen,
|
||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||
totalPrice: state.totalPrice,
|
||||
isSubmittingDisabled: state.isSubmittingDisabled,
|
||||
}))
|
||||
const {
|
||||
isSummaryOpen,
|
||||
toggleSummaryOpen,
|
||||
totalPrice,
|
||||
isSubmittingDisabled,
|
||||
isSubmitting,
|
||||
} = useEnterDetailsStore((state) => ({
|
||||
isSummaryOpen: state.isSummaryOpen,
|
||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||
totalPrice: state.totalPrice,
|
||||
isSubmittingDisabled: state.isSubmittingDisabled,
|
||||
isSubmitting: state.isSubmitting,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if (isSummaryOpen) {
|
||||
@@ -82,11 +89,12 @@ export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
||||
</Caption>
|
||||
</button>
|
||||
<Button
|
||||
intent="primary"
|
||||
theme="base"
|
||||
size="large"
|
||||
variant="Primary"
|
||||
size="Large"
|
||||
type="submit"
|
||||
disabled={isSubmittingDisabled}
|
||||
isDisabled={isSubmittingDisabled}
|
||||
isPending={isSubmitting}
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
form={formId}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
|
||||
@@ -143,6 +143,7 @@ export function createDetailsStore(
|
||||
breakfastPackages,
|
||||
canProceedToPayment: false,
|
||||
isSubmittingDisabled: false,
|
||||
isSubmitting: false,
|
||||
isSummaryOpen: false,
|
||||
lastRoom: initialState.booking.rooms.length - 1,
|
||||
rooms: initialState.rooms.map((room, idx) => {
|
||||
@@ -365,6 +366,13 @@ export function createDetailsStore(
|
||||
})
|
||||
)
|
||||
},
|
||||
setIsSubmitting(isSubmitting) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.isSubmitting = isSubmitting
|
||||
})
|
||||
)
|
||||
},
|
||||
setTotalPrice(totalPrice) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
|
||||
@@ -84,6 +84,7 @@ export type InitialState = {
|
||||
export interface DetailsState {
|
||||
actions: {
|
||||
setIsSubmittingDisabled: (isSubmittingDisabled: boolean) => void
|
||||
setIsSubmitting: (isSubmitting: boolean) => void
|
||||
setTotalPrice: (totalPrice: Price) => void
|
||||
toggleSummaryOpen: () => void
|
||||
updateSeachParamString: (searchParamString: string) => void
|
||||
@@ -93,6 +94,7 @@ export interface DetailsState {
|
||||
breakfastPackages: BreakfastPackages
|
||||
canProceedToPayment: boolean
|
||||
isSubmittingDisabled: boolean
|
||||
isSubmitting: boolean
|
||||
isSummaryOpen: boolean
|
||||
lastRoom: number
|
||||
rooms: RoomState[]
|
||||
|
||||
Reference in New Issue
Block a user