Merged in feature/SW-3595-sas-info-boxes (pull request #3177)

Feature/SW-3595 Add info boxes to SAS start page & Eurobonus alert to select-hotel page on SAS

* wip

* feat(SW-3595): Add info boxes to SAS start page

* Add InfoBox to design-system
* Add background gradient to SAS start page

* update variable naming and conditionalize the eurobonus message on select-hotel

* SAS startpage update default message

* make select-hotel a bit more generic with slot={} instead of alert={}


Approved-by: Anton Gunnarsson
This commit is contained in:
Joakim Jäderberg
2025-11-19 10:50:04 +00:00
parent 32e5c8d357
commit db30588f63
15 changed files with 459 additions and 105 deletions

View File

@@ -1,7 +1,6 @@
.floatingBookingWidget {
width: var(--max-width-content);
margin: 0 auto;
min-height: 88px;
position: relative;
.floatingBackground {
@@ -25,9 +24,3 @@
}
}
}
@media screen and (min-width: 768px) and (max-width: 1366px) {
.floatingBookingWidget {
min-height: 150px;
}
}

View File

@@ -16,10 +16,10 @@ export function FloatingBookingWidgetClient(props: Props) {
useEffect(() => {
observerRef.current = new IntersectionObserver(
([entry]) => {
const hasScrolledPastTop = entry.boundingClientRect.top < 0
const hasScrolledPastTop = entry.boundingClientRect.bottom < 0
setStickyTop(hasScrolledPastTop)
},
{ threshold: 0, rootMargin: "0px 0px -100% 0px" }
{ threshold: 0, rootMargin: "0px 0px 0% 0px" }
)
if (containerRef.current) {

View File

@@ -32,6 +32,7 @@ interface SelectHotelProps {
isBookingCodeRateAvailable?: boolean
title: ReactNode
lang: Lang
topSlot?: ReactNode
}
export function SelectHotel({
@@ -42,6 +43,7 @@ export function SelectHotel({
isBookingCodeRateAvailable = false,
title,
lang,
topSlot,
}: SelectHotelProps) {
const isAllUnavailable = hotels.every(
(hotel) => hotel.availability.status !== "Available"
@@ -83,54 +85,60 @@ export function SelectHotel({
</div>
</header>
<main className={styles.main}>
{showBookingCodeFilter ? <BookingCodeFilter /> : null}
<div className={styles.sideBar}>
{hotels.length ? (
<Link
className={styles.link}
href={
isAlternative
? alternativeHotelsMap(lang)
: selectHotelMap(lang)
}
keepSearchParams
>
<MapWithButtonWrapper>
{topSlot && <div className={styles.topSlotContainer}>{topSlot}</div>}
<div className={styles.availabilityContainer}>
{showBookingCodeFilter ? <BookingCodeFilter /> : null}
<div className={styles.sideBar}>
{hotels.length ? (
<Link
className={styles.link}
href={
isAlternative
? alternativeHotelsMap(lang)
: selectHotelMap(lang)
}
keepSearchParams
>
<MapWithButtonWrapper>
<StaticMap
city={city.name}
country={isCityWithCountry(city) ? city.country : undefined}
width={340}
height={200}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${city.name} city center`}
/>
</MapWithButtonWrapper>
</Link>
) : (
<div className={styles.mapContainer}>
<StaticMap
city={city.name}
country={isCityWithCountry(city) ? city.country : undefined}
width={340}
height={200}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${city.name} city center`}
/>
</MapWithButtonWrapper>
</Link>
) : (
<div className={styles.mapContainer}>
<StaticMap
city={city.name}
width={340}
height={200}
zoomLevel={11}
mapType="roadmap"
altText={`Map of ${city.name} city center`}
/>
</div>
)}
<HotelFilter filters={filterList} className={styles.filter} />
</div>
<div className={styles.hotelList}>
<NoAvailabilityAlert
hotelsLength={hotels.length}
isAlternative={isAlternative}
isAllUnavailable={isAllUnavailable}
operaId={hotels?.[0]?.hotel.operaId}
bookingCode={bookingCode}
isBookingCodeRateNotAvailable={!isBookingCodeRateAvailable}
/>
<HotelCardListing hotelData={hotels} isAlternative={isAlternative} />
</div>
)}
<HotelFilter filters={filterList} className={styles.filter} />
</div>
<div className={styles.hotelList}>
<NoAvailabilityAlert
hotelsLength={hotels.length}
isAlternative={isAlternative}
isAllUnavailable={isAllUnavailable}
operaId={hotels?.[0]?.hotel.operaId}
bookingCode={bookingCode}
isBookingCodeRateNotAvailable={!isBookingCodeRateAvailable}
/>
<HotelCardListing
hotelData={hotels}
isAlternative={isAlternative}
/>
</div>
</div>
</main>
</>

View File

@@ -1,14 +1,38 @@
.main {
display: flex;
flex-direction: column;
gap: var(--Space-x5);
justify-items: center;
padding-top: var(--Space-x4);
}
.topSlotContainer {
width: var(--max-width-page);
margin: 0 auto;
}
.availabilityContainer {
display: flex;
background-color: var(--Scandic-Brand-Warm-White);
min-height: min(100dvh, 750px);
flex-direction: column;
max-width: var(--max-width-page);
width: var(--max-width-page);
margin: 0 auto;
@media (min-width: 768px) {
flex-direction: row;
gap: var(--Space-x5);
flex-wrap: wrap;
}
}
.header {
padding: var(--Space-x3) 0 var(--Space-x2);
@media (min-width: 768px) {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Space-x4) 0 var(--Space-x3);
}
}
.headerContent {
@@ -17,6 +41,9 @@
display: flex;
flex-direction: column;
gap: var(--Space-x2);
@media (min-width: 768px) {
display: block;
}
}
.cityInformation {
@@ -28,19 +55,37 @@
.sorter {
display: none;
@media (min-width: 768px) {
display: block;
width: 339px;
}
}
.sideBar {
display: flex;
flex-direction: column;
@media (min-width: 768px) {
max-width: 340px;
}
}
.sideBarItem {
display: none;
@media (min-width: 768px) {
display: block;
}
}
.link {
display: none;
@media (min-width: 768px) {
display: flex;
margin-bottom: var(--Space-x6);
}
}
.hotelList {
@@ -56,59 +101,26 @@
.skeletonContainer .title {
margin-bottom: var(--Space-x3);
@media (min-width: 768px) {
margin-bottom: 0;
}
}
@media (min-width: 768px) {
.main {
padding: var(--Space-x5) 0;
flex-direction: row;
gap: var(--Space-x5);
flex-wrap: wrap;
}
.headerContent {
display: block;
}
.header {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Space-x4) 0 var(--Space-x3);
}
.sorter {
display: block;
width: 339px;
}
.title {
.title {
@media (min-width: 768px) {
margin: 0 auto;
display: flex;
max-width: var(--max-width-navigation);
align-items: center;
justify-content: space-between;
}
}
.sideBar {
max-width: 340px;
}
.sideBarItem {
display: block;
}
@media (min-width: 768px) {
.filter {
display: block;
}
.link {
display: flex;
margin-bottom: var(--Space-x6);
}
.skeletonContainer .title {
margin-bottom: 0;
}
.skeletonContainer .sideBar {
gap: var(--Space-x3);
}

View File

@@ -14,6 +14,7 @@ import { getSelectHotelTracking } from "../misc/selectHotelTracking"
import { parseSelectHotelSearchParams } from "../utils/url"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { ReactNode } from "react"
import type { NextSearchParams } from "../types"
@@ -23,10 +24,12 @@ export async function SelectHotelPage({
lang,
searchParams,
config,
topSlot,
}: {
lang: Lang
searchParams: NextSearchParams
config: BookingFlowConfig
topSlot?: ReactNode
}) {
const booking = parseSelectHotelSearchParams(searchParams)
@@ -111,6 +114,7 @@ export async function SelectHotelPage({
hotels={hotels}
title={city.name}
lang={lang}
topSlot={topSlot}
/>
<TrackingSDK hotelInfo={hotelsTrackingData} pageData={pageTrackingData} />