fix: handle submit from summary bottom sheet

This commit is contained in:
Christel Westerberg
2024-11-11 11:00:43 +01:00
parent ee6aa8d188
commit daed74481e
7 changed files with 87 additions and 29 deletions

View File

@@ -11,7 +11,7 @@
.enter-details-layout__content { .enter-details-layout__content {
display: grid; display: grid;
gap: var(--Spacing-x3) var(--Spacing-x9); gap: var(--Spacing-x3) var(--Spacing-x9);
margin: var(--Spacing-x5) auto 0; margin: var(--Spacing-x3) var(--Spacing-x2) 0;
/* simulates padding on viewport smaller than --max-width-navigation */ /* simulates padding on viewport smaller than --max-width-navigation */
width: min( width: min(
calc(100dvw - (var(--Spacing-x2) * 2)), calc(100dvw - (var(--Spacing-x2) * 2)),
@@ -31,6 +31,7 @@
.enter-details-layout__content { .enter-details-layout__content {
grid-template-columns: 1fr 340px; grid-template-columns: 1fr 340px;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
margin: var(--Spacing-x5) auto 0;
} }
.enter-details-layout__summaryContainer { .enter-details-layout__summaryContainer {

View File

@@ -45,6 +45,8 @@ import { BreakfastPackageEnum } from "@/types/enums/breakfast"
const maxRetries = 40 const maxRetries = 40
const retryInterval = 2000 const retryInterval = 2000
export const formId = "submit-booking"
function isPaymentMethodEnum(value: string): value is PaymentMethodEnum { function isPaymentMethodEnum(value: string): value is PaymentMethodEnum {
return Object.values(PaymentMethodEnum).includes(value as PaymentMethodEnum) return Object.values(PaymentMethodEnum).includes(value as PaymentMethodEnum)
} }
@@ -59,10 +61,13 @@ export default function Payment({
const lang = useLang() const lang = useLang()
const intl = useIntl() const intl = useIntl()
const queryParams = useSearchParams() const queryParams = useSearchParams()
const { userData, roomData } = useEnterDetailsStore((state) => ({ const { userData, roomData, setIsSubmittingDisabled } = useEnterDetailsStore(
userData: state.userData, (state) => ({
roomData: state.roomData, userData: state.userData,
})) roomData: state.roomData,
setIsSubmittingDisabled: state.setIsSubmittingDisabled,
})
)
const { const {
firstName, firstName,
@@ -119,6 +124,16 @@ export default function Payment({
} }
}, [bookingStatus, router]) }, [bookingStatus, router])
useEffect(() => {
setIsSubmittingDisabled(
!methods.formState.isValid || methods.formState.isSubmitting
)
}, [
methods.formState.isValid,
methods.formState.isSubmitting,
setIsSubmittingDisabled,
])
function handleSubmit(data: PaymentFormData) { function handleSubmit(data: PaymentFormData) {
const allQueryParams = const allQueryParams =
queryParams.size > 0 ? `?${queryParams.toString()}` : "" queryParams.size > 0 ? `?${queryParams.toString()}` : ""
@@ -209,6 +224,7 @@ export default function Payment({
<form <form
className={styles.paymentContainer} className={styles.paymentContainer}
onSubmit={methods.handleSubmit(handleSubmit)} onSubmit={methods.handleSubmit(handleSubmit)}
id={formId}
> >
{mustBeGuaranteed ? ( {mustBeGuaranteed ? (
<section className={styles.section}> <section className={styles.section}>
@@ -309,15 +325,16 @@ export default function Payment({
</Caption> </Caption>
</AriaLabel> </AriaLabel>
</section> </section>
<Button <div className={styles.submitButton}>
type="submit" <Button
className={styles.submitButton} type="submit"
disabled={ disabled={
!methods.formState.isValid || methods.formState.isSubmitting !methods.formState.isValid || methods.formState.isSubmitting
} }
> >
{intl.formatMessage({ id: "Complete booking & go to payment" })} {intl.formatMessage({ id: "Complete booking" })}
</Button> </Button>
</div>
</form> </form>
</FormProvider> </FormProvider>
) )

View File

@@ -18,7 +18,7 @@
} }
.submitButton { .submitButton {
align-self: flex-start; display: none;
} }
.paymentContainer .link { .paymentContainer .link {
@@ -31,3 +31,10 @@
flex-direction: row; flex-direction: row;
gap: var(--Spacing-x-one-and-half); gap: var(--Spacing-x-one-and-half);
} }
@media screen and (min-width: 1367px) {
.submitButton {
display: flex;
align-self: flex-start;
}
}

View File

@@ -3,7 +3,6 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: var(--Spacing-x-one-and-half); gap: var(--Spacing-x-one-and-half);
padding-top: var(--Spacing-x3);
} }
.main { .main {
@@ -67,6 +66,7 @@
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.wrapper { .wrapper {
gap: var(--Spacing-x3); gap: var(--Spacing-x3);
padding-top: var(--Spacing-x3);
} }
.iconWrapper { .iconWrapper {

View File

@@ -13,8 +13,6 @@
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x5) padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x5)
var(--Spacing-x3); var(--Spacing-x3);
justify-content: space-between;
width: auto;
align-items: flex-start; align-items: flex-start;
transition: 0.5s ease-in-out; transition: 0.5s ease-in-out;
} }
@@ -24,11 +22,9 @@
border: none; border: none;
background: none; background: none;
text-align: start; text-align: start;
opacity: 1; transition: padding 0.5s ease-in-out;
transition:
opacity 0.5s ease-in-out,
padding 0.5s ease-in-out;
cursor: pointer; cursor: pointer;
white-space: nowrap;
} }
.wrapper[data-open="true"] { .wrapper[data-open="true"] {
@@ -36,15 +32,39 @@
} }
.wrapper[data-open="true"] .bottomSheet { .wrapper[data-open="true"] .bottomSheet {
grid-template-columns: 0fr 1fr; grid-template-columns: 0fr auto;
} }
.wrapper[data-open="true"] .priceDetailsButton { .wrapper[data-open="true"] .priceDetailsButton {
animation: fadeOut 0.3s ease-out;
opacity: 0; opacity: 0;
padding: 0; padding: 0;
} }
.wrapper[data-open="false"] .priceDetailsButton {
animation: fadeIn 0.8s ease-in;
opacity: 1;
}
.content, .content,
.priceDetailsButton { .priceDetailsButton {
overflow: hidden; overflow: hidden;
} }
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { PropsWithChildren, useState } from "react" import { PropsWithChildren } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details" import { useEnterDetailsStore } from "@/stores/enter-details"
@@ -9,18 +9,20 @@ import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { formId } from "../../Payment"
import styles from "./bottomSheet.module.css" import styles from "./bottomSheet.module.css"
export function SummaryBottomSheet({ children }: PropsWithChildren) { export function SummaryBottomSheet({ children }: PropsWithChildren) {
const intl = useIntl() const intl = useIntl()
const { isSummaryOpen, toggleSummaryOpen, totalPrice } = useEnterDetailsStore( const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmittingDisabled } =
(state) => ({ useEnterDetailsStore((state) => ({
isSummaryOpen: state.isSummaryOpen, isSummaryOpen: state.isSummaryOpen,
toggleSummaryOpen: state.toggleSummaryOpen, toggleSummaryOpen: state.toggleSummaryOpen,
totalPrice: state.totalPrice, totalPrice: state.totalPrice,
}) isSubmittingDisabled: state.isSubmittingDisabled,
) }))
return ( return (
<div className={styles.wrapper} data-open={isSummaryOpen}> <div className={styles.wrapper} data-open={isSummaryOpen}>
@@ -45,7 +47,13 @@ export function SummaryBottomSheet({ children }: PropsWithChildren) {
{intl.formatMessage({ id: "See details" })} {intl.formatMessage({ id: "See details" })}
</Caption> </Caption>
</button> </button>
<Button intent="primary" size="large" type="submit"> <Button
intent="primary"
size="large"
type="submit"
disabled={isSubmittingDisabled}
form={formId}
>
{intl.formatMessage({ id: "Complete booking" })} {intl.formatMessage({ id: "Complete booking" })}
</Button> </Button>
</div> </div>

View File

@@ -38,6 +38,7 @@ interface EnterDetailsState {
selectRateUrl: string selectRateUrl: string
currentStep: StepEnum currentStep: StepEnum
totalPrice: TotalPrice totalPrice: TotalPrice
isSubmittingDisabled: boolean
isSummaryOpen: boolean isSummaryOpen: boolean
isValid: Record<StepEnum, boolean> isValid: Record<StepEnum, boolean>
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
@@ -51,6 +52,7 @@ interface EnterDetailsState {
setCurrentStep: (step: StepEnum) => void setCurrentStep: (step: StepEnum) => void
toggleSummaryOpen: () => void toggleSummaryOpen: () => void
setTotalPrice: (totalPrice: TotalPrice) => void setTotalPrice: (totalPrice: TotalPrice) => void
setIsSubmittingDisabled: (isSubmittingDisabled: boolean) => void
} }
export function initEditDetailsState( export function initEditDetailsState(
@@ -143,6 +145,7 @@ export function initEditDetailsState(
euro: { price: 0, currency: "" }, euro: { price: 0, currency: "" },
}, },
isSummaryOpen: false, isSummaryOpen: false,
isSubmittingDisabled: false,
setCurrentStep: (step) => set({ currentStep: step }), setCurrentStep: (step) => set({ currentStep: step }),
navigate: (step, updatedData) => navigate: (step, updatedData) =>
set( set(
@@ -182,6 +185,8 @@ export function initEditDetailsState(
), ),
toggleSummaryOpen: () => set({ isSummaryOpen: !get().isSummaryOpen }), toggleSummaryOpen: () => set({ isSummaryOpen: !get().isSummaryOpen }),
setTotalPrice: (totalPrice) => set({ totalPrice: totalPrice }), setTotalPrice: (totalPrice) => set({ totalPrice: totalPrice }),
setIsSubmittingDisabled: (isSubmittingDisabled) =>
set({ isSubmittingDisabled }),
})) }))
} }