Merged in chore/move-enter-details (pull request #2778)

Chore/move enter details

Approved-by: Anton Gunnarsson
This commit is contained in:
Joakim Jäderberg
2025-09-11 07:16:24 +00:00
parent 15711cb3a4
commit 7dee6d5083
238 changed files with 1656 additions and 1602 deletions

View File

@@ -0,0 +1,94 @@
.wrapper {
display: grid;
grid-template-rows: 0fr auto;
transition: all 0.5s ease-in-out;
border-top: 1px solid var(--Base-Border-Subtle);
background: var(--Base-Surface-Primary-light-Normal);
align-content: end;
}
.bottomSheet {
display: grid;
grid-template-columns: 1fr 1fr;
padding: var(--Space-x2) var(--Space-x3) var(--Space-x5);
align-items: flex-start;
transition: all 0.5s ease-in-out;
width: 100vw;
}
.priceDetailsButton {
border-width: 0;
background-color: transparent;
text-align: start;
cursor: pointer;
padding: 0;
display: grid;
overflow: hidden;
transition: all 0.3s ease-in-out;
}
.wrapper[data-open="true"] {
grid-template-rows: 1fr 7.5em;
position: relative;
z-index: var(--default-modal-z-index);
}
.wrapper[data-open="true"] .bottomSheet {
grid-template-columns: 0fr auto;
}
.wrapper[data-open="true"] .priceDetailsButton {
animation: fadeOut 0.3s ease-out;
opacity: 0;
padding: 0;
}
.wrapper[data-open="false"] .priceDetailsButton {
animation: fadeIn 0.8s ease-in;
opacity: 1;
}
.content {
max-height: 50dvh;
overflow-y: auto;
}
.summaryAccordion {
background-color: var(--Main-Grey-White);
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
border-style: solid;
border-width: 1px;
border-bottom: none;
z-index: 10;
}
.priceLabel {
color: var(--Text-Default);
}
.price {
color: var(--Text-Default);
&.discounted {
color: var(--Text-Accent-Primary);
}
}
.strikeThroughRate {
text-decoration: line-through;
color: var(--Text-Secondary);
}
.seeDetails {
margin-top: var(--Space-x15);
display: flex;
gap: var(--Space-x1);
align-items: center;
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
}
@media screen and (min-width: 1367px) {
.bottomSheet {
padding: var(--Space-x2) 0 var(--Space-x7);
}
}

View File

@@ -0,0 +1,149 @@
"use client"
import { cx } from "class-variance-authority"
import { useSearchParams } from "next/navigation"
import { type PropsWithChildren, useEffect, useRef } from "react"
import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useEnterDetailsStore } from "../../../../../stores/enter-details"
import { isBookingCodeRate } from "../../../../SelectRate/RoomsContainer/RateSummary/utils"
import { formId } from "../../../Payment/PaymentClient"
import styles from "./bottomSheet.module.css"
interface SummaryBottomSheetProps
extends PropsWithChildren<{
isUserLoggedIn: boolean
}> {}
export default function SummaryBottomSheet({
children,
isUserLoggedIn,
}: SummaryBottomSheetProps) {
const intl = useIntl()
const scrollY = useRef(0)
const searchParams = useSearchParams()
const errorCode = searchParams.get("errorCode")
const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmitting, rooms } =
useEnterDetailsStore((state) => ({
isSummaryOpen: state.isSummaryOpen,
toggleSummaryOpen: state.actions.toggleSummaryOpen,
totalPrice: state.totalPrice,
isSubmitting: state.isSubmitting,
rooms: state.rooms,
}))
useEffect(() => {
if (isSummaryOpen) {
scrollY.current = window.scrollY
document.body.style.position = "fixed"
document.body.style.top = `-${scrollY.current}px`
} else {
document.body.style.position = ""
document.body.style.top = ""
if (!errorCode) {
window.scrollTo({
top: scrollY.current,
left: 0,
behavior: "instant",
})
}
}
return () => {
document.body.style.position = ""
document.body.style.top = ""
}
}, [isSummaryOpen, errorCode])
const containsBookingCodeRate = rooms.find(
(r) => r && isBookingCodeRate(r.room.roomRate)
)
const showDiscounted = containsBookingCodeRate || isUserLoggedIn
return (
<div className={styles.wrapper} data-open={isSummaryOpen}>
<div className={styles.content}>{children}</div>
<div className={styles.bottomSheet}>
<ButtonRAC
data-open={isSummaryOpen}
onPress={toggleSummaryOpen}
className={styles.priceDetailsButton}
>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span className={styles.priceLabel}>
{intl.formatMessage({
defaultMessage: "Total price",
})}
</span>
</Typography>
<Typography variant="Title/Subtitle/lg">
<span
className={cx(styles.price, {
[styles.discounted]: showDiscounted,
})}
>
{formatPrice(
intl,
totalPrice.local.price,
totalPrice.local.currency,
totalPrice.local.additionalPrice,
totalPrice.local.additionalPriceCurrency
)}
</span>
</Typography>
{showDiscounted && totalPrice.local.regularPrice ? (
<Typography variant="Body/Paragraph/mdRegular">
<p>
<s className={styles.strikeThroughRate}>
{formatPrice(
intl,
totalPrice.local.regularPrice,
totalPrice.local.currency
)}
</s>
</p>
</Typography>
) : null}
<Typography variant="Body/Supporting text (caption)/smBold">
<span className={styles.seeDetails}>
<span>
{intl.formatMessage({
defaultMessage: "See details",
})}
</span>
<MaterialIcon
icon="chevron_right"
color="CurrentColor"
size={20}
/>
</span>
</Typography>
</ButtonRAC>
<Button
variant="Primary"
color="Primary"
size="Large"
type="submit"
typography="Body/Paragraph/mdBold"
isDisabled={isSubmitting}
isPending={isSubmitting}
form={formId}
>
{intl.formatMessage({
defaultMessage: "Complete booking",
})}
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,68 @@
"use client"
import { useEnterDetailsStore } from "../../../../stores/enter-details"
import SignupPromoMobile from "../../../SignupPromo/Mobile"
import SummaryUI from "../UI"
import SummaryBottomSheet from "./BottomSheet"
import styles from "./mobile.module.css"
type Props = {
isUserLoggedIn: boolean
}
export default function MobileSummary({ isUserLoggedIn }: Props) {
const { isSummaryOpen, toggleSummaryOpen } = useEnterDetailsStore(
(state) => ({
isSummaryOpen: state.isSummaryOpen,
toggleSummaryOpen: state.actions.toggleSummaryOpen,
})
)
const { booking, rooms, totalPrice, vat, defaultCurrency } =
useEnterDetailsStore((state) => ({
booking: state.booking,
rooms: state.rooms,
totalPrice: state.totalPrice,
vat: state.vat,
defaultCurrency: state.defaultCurrency,
}))
const showPromo =
!isUserLoggedIn &&
rooms.length === 1 &&
!rooms[0].room.guest.join &&
!rooms[0].room.guest.membershipNo
return (
<div className={styles.mobileSummary}>
{showPromo ? (
<div className={styles.signupPromoWrapper}>
<SignupPromoMobile />
</div>
) : null}
{isSummaryOpen && (
<div
className={styles.overlay}
role="presentation"
aria-hidden="true"
onClick={toggleSummaryOpen}
/>
)}
<SummaryBottomSheet isUserLoggedIn={isUserLoggedIn}>
<div className={styles.wrapper}>
<SummaryUI
booking={booking}
rooms={rooms}
isUserLoggedIn={isUserLoggedIn}
totalPrice={totalPrice}
vat={vat}
toggleSummaryOpen={toggleSummaryOpen}
defaultCurrency={defaultCurrency}
/>
</div>
</SummaryBottomSheet>
</div>
)
}

View File

@@ -0,0 +1,34 @@
.mobileSummary {
display: block;
}
@media screen and (max-width: 1366px) {
.wrapper {
background-color: var(--Main-Grey-White);
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
border-style: solid;
border-width: 1px;
border-bottom: none;
}
.signupPromoWrapper {
position: relative;
z-index: var(--default-modal-z-index);
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--Overlay-40);
z-index: var(--default-modal-overlay-z-index);
}
}
@media screen and (min-width: 1367px) {
.mobileSummary {
display: none;
}
}