feat/SW-1756-meeting-package-content-pages
* feat(SW-1230): Changes to script to be able to reload after soft navigation * feat(SW-1756): Added meeting package widget on content pages Approved-by: Matilda Landström
This commit is contained in:
@@ -8,6 +8,7 @@ import { PageContentTypeEnum } from "@/types/requests/contentType"
|
|||||||
|
|
||||||
const IGNORED_CONTENT_TYPES = [
|
const IGNORED_CONTENT_TYPES = [
|
||||||
PageContentTypeEnum.hotelPage,
|
PageContentTypeEnum.hotelPage,
|
||||||
|
PageContentTypeEnum.contentPage,
|
||||||
PageContentTypeEnum.destinationCityPage,
|
PageContentTypeEnum.destinationCityPage,
|
||||||
PageContentTypeEnum.destinationCountryPage,
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
|
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
import Hero from "@/components/Hero"
|
import Hero from "@/components/Hero"
|
||||||
|
import { meetingPackageDestinationByHotelId } from "@/components/MeetingPackageWidget/utils"
|
||||||
|
import StickyMeetingPackageWidget from "@/components/StickyMeetingPackageWidget"
|
||||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
@@ -21,7 +23,6 @@ import { safeTry } from "@/utils/safeTry"
|
|||||||
import MeetingsAdditionalContent from "./AdditionalContent/Meetings"
|
import MeetingsAdditionalContent from "./AdditionalContent/Meetings"
|
||||||
import HotelSubpageAdditionalContent from "./AdditionalContent"
|
import HotelSubpageAdditionalContent from "./AdditionalContent"
|
||||||
import HtmlContent from "./HtmlContent"
|
import HtmlContent from "./HtmlContent"
|
||||||
import { MeetingWidget } from "./MeetingWidget"
|
|
||||||
import HotelSubpageSidebar from "./Sidebar"
|
import HotelSubpageSidebar from "./Sidebar"
|
||||||
import { getSubpageData, verifySubpageShouldExist } from "./utils"
|
import { getSubpageData, verifySubpageShouldExist } from "./utils"
|
||||||
|
|
||||||
@@ -63,13 +64,18 @@ export default async function HotelSubpage({
|
|||||||
const restaurantButton = restaurants.find(
|
const restaurantButton = restaurants.find(
|
||||||
(restaurant) => restaurant.nameInUrl === subpage
|
(restaurant) => restaurant.nameInUrl === subpage
|
||||||
)
|
)
|
||||||
|
const meetingPackageDestination = hotelId
|
||||||
|
? meetingPackageDestinationByHotelId[hotelId]
|
||||||
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section
|
<section
|
||||||
className={`${styles.hotelSubpage} ${restaurantButton?.bookTableUrl ? styles.hasStickyButton : ""} `}
|
className={`${styles.hotelSubpage} ${restaurantButton?.bookTableUrl ? styles.hasStickyButton : ""} `}
|
||||||
>
|
>
|
||||||
{meetingRooms && <MeetingWidget hotelId={hotelId} />}
|
{meetingRooms && (
|
||||||
|
<StickyMeetingPackageWidget destination={meetingPackageDestination} />
|
||||||
|
)}
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
|
import StickyMeetingPackageWidget from "@/components/StickyMeetingPackageWidget"
|
||||||
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
|
|
||||||
import StaticPage from ".."
|
import StaticPage from ".."
|
||||||
|
|
||||||
|
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
||||||
|
|
||||||
export default async function ContentPage() {
|
export default async function ContentPage() {
|
||||||
const contentPageRes = await serverClient().contentstack.contentPage.get()
|
const contentPageRes = await serverClient().contentstack.contentPage.get()
|
||||||
|
|
||||||
@@ -12,6 +20,20 @@ export default async function ContentPage() {
|
|||||||
const { tracking, contentPage } = contentPageRes
|
const { tracking, contentPage } = contentPageRes
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StaticPage content={contentPage} tracking={tracking} pageType="content" />
|
<>
|
||||||
|
{contentPage.meeting_package?.show_widget && (
|
||||||
|
<StickyMeetingPackageWidget
|
||||||
|
destination={contentPage.meeting_package.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
|
<Breadcrumbs variant={PageContentTypeEnum.contentPage} />
|
||||||
|
</Suspense>
|
||||||
|
<StaticPage
|
||||||
|
content={contentPage}
|
||||||
|
tracking={tracking}
|
||||||
|
pageType="content"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { staticPageVariants } from "./variants"
|
|||||||
|
|
||||||
import styles from "./staticPage.module.css"
|
import styles from "./staticPage.module.css"
|
||||||
|
|
||||||
|
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
||||||
import type { StaticPageProps } from "./staticPage"
|
import type { StaticPageProps } from "./staticPage"
|
||||||
|
|
||||||
export default async function StaticPage({
|
export default async function StaticPage({
|
||||||
@@ -22,7 +23,7 @@ export default async function StaticPage({
|
|||||||
tracking,
|
tracking,
|
||||||
pageType,
|
pageType,
|
||||||
}: StaticPageProps) {
|
}: StaticPageProps) {
|
||||||
const { blocks, hero_image, header } = content
|
const { blocks, hero_image, header, meeting_package } = content
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -68,6 +69,12 @@ export default async function StaticPage({
|
|||||||
|
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainer}>
|
||||||
<main className={styles.mainContent}>
|
<main className={styles.mainContent}>
|
||||||
|
{pageType === "collection" && meeting_package?.show_widget && (
|
||||||
|
<MeetingPackageWidget
|
||||||
|
destination={meeting_package.location}
|
||||||
|
className={styles.meetingPackageWidget}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{blocks ? <Blocks blocks={blocks} /> : null}
|
{blocks ? <Blocks blocks={blocks} /> : null}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,13 @@
|
|||||||
grid-area: main;
|
grid-area: main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meetingPackageWidget {
|
||||||
|
border-radius: var(--Corner-radius-lg);
|
||||||
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
|
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
.mobile,
|
.mobile,
|
||||||
.desktop {
|
.desktop {
|
||||||
padding: var(--Spacing-x2) 0;
|
padding: var(--Spacing-x2) var(--Spacing-x2);
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop {
|
.desktop {
|
||||||
display: none;
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--max-width-page);
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Meeting booking widget changes design at 948px */
|
/* Meeting booking widget changes design at 948px */
|
||||||
|
|||||||
@@ -1,53 +1,58 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import Script from "next/script"
|
import { useEffect, useState } from "react"
|
||||||
import { useState } from "react"
|
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import MeetingPackageWidgetSkeleton from "./Skeleton"
|
import MeetingPackageWidgetSkeleton from "./Skeleton"
|
||||||
import { meetingPackageDestinationByHotelId } from "./utils"
|
|
||||||
|
|
||||||
import styles from "./meetingPackageWidget.module.css"
|
import styles from "./meetingPackageWidget.module.css"
|
||||||
|
|
||||||
const SOURCE = "https://bookingengine-mp.s3.eu-west-2.amazonaws.com/script.js"
|
const SOURCE =
|
||||||
|
"https://scandic-bookingengine.s3.eu-central-1.amazonaws.com/script_stage.js"
|
||||||
|
|
||||||
interface MeetingPackageWidgetProps {
|
interface MeetingPackageWidgetProps {
|
||||||
hotelId?: string
|
destination?: string
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MeetingPackageWidget({
|
export default function MeetingPackageWidget({
|
||||||
hotelId,
|
destination,
|
||||||
className = "",
|
className,
|
||||||
}: MeetingPackageWidgetProps) {
|
}: MeetingPackageWidgetProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const destination = hotelId
|
|
||||||
? meetingPackageDestinationByHotelId[hotelId]
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
function handleOnReady() {
|
useEffect(() => {
|
||||||
setIsLoading(false)
|
const script = document.createElement("script")
|
||||||
}
|
script.src = SOURCE
|
||||||
|
script.setAttribute("langcode", lang)
|
||||||
|
script.setAttribute("whitelabel_id", "224905")
|
||||||
|
script.setAttribute("widget_id", "scandic_default_new")
|
||||||
|
script.setAttribute("version", "frontpage-scandic")
|
||||||
|
if (destination) {
|
||||||
|
script.setAttribute("destination", destination)
|
||||||
|
}
|
||||||
|
document.body.appendChild(script)
|
||||||
|
|
||||||
|
function onLoad() {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
script.addEventListener("load", onLoad)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
script.removeEventListener("load", onLoad)
|
||||||
|
document.body.removeChild(script)
|
||||||
|
}
|
||||||
|
}, [destination, lang])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{isLoading && <MeetingPackageWidgetSkeleton />}
|
{isLoading && <MeetingPackageWidgetSkeleton />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="mp-booking-engine-iframe-container"
|
id="mp-booking-engine-iframe-container"
|
||||||
className={`${styles.widget} ${!isLoading ? styles.ready : ""}`}
|
className={`${styles.widget} ${isLoading ? styles.isLoading : ""}`}
|
||||||
/>
|
|
||||||
<Script
|
|
||||||
//@ts-expect-error: invalid attributes because of external script
|
|
||||||
langcode={lang}
|
|
||||||
destination={destination}
|
|
||||||
whitelabel_id="224905"
|
|
||||||
widget_id="scandic_default_new"
|
|
||||||
version="frontpage-scandic"
|
|
||||||
src={SOURCE}
|
|
||||||
onReady={handleOnReady}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
/* Hiding widget on mobile for now as the widget is not ready for mobile use at the moment */
|
|
||||||
|
|
||||||
.widget {
|
.widget {
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
width: 100%;
|
||||||
|
max-width: var(--max-width-page);
|
||||||
|
margin: 0 auto;
|
||||||
|
/* Hiding widget on mobile for now as the widget is not ready for mobile use at the moment */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget.isLoading {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,8 +14,6 @@
|
|||||||
@media screen and (min-width: 948px) {
|
@media screen and (min-width: 948px) {
|
||||||
.widget {
|
.widget {
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
}
|
|
||||||
.widget.ready {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,24 @@ import { StickyElementNameEnum } from "@/stores/sticky-position"
|
|||||||
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
||||||
import useStickyPosition from "@/hooks/useStickyPosition"
|
import useStickyPosition from "@/hooks/useStickyPosition"
|
||||||
|
|
||||||
import styles from "./meetingWidget.module.css"
|
import styles from "./stickyMeetingPackageWidget.module.css"
|
||||||
|
|
||||||
interface MeetingWidgetProps {
|
interface StickyMeetingPackageWidgetProps {
|
||||||
hotelId: string
|
destination?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MeetingWidget({ hotelId }: MeetingWidgetProps) {
|
export default function StickyMeetingPackageWidget({
|
||||||
|
destination,
|
||||||
|
}: StickyMeetingPackageWidgetProps) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
useStickyPosition({
|
useStickyPosition({
|
||||||
ref,
|
ref,
|
||||||
name: StickyElementNameEnum.MEETING_BOOKING_WIDGET,
|
name: StickyElementNameEnum.MEETING_PACKAGE_WIDGET,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={styles.wrapper}>
|
<div ref={ref} className={styles.wrapper}>
|
||||||
<MeetingPackageWidget
|
<MeetingPackageWidget destination={destination} />
|
||||||
hotelId={hotelId}
|
|
||||||
className={styles.meetingPackageWidget}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,3 @@
|
|||||||
box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.08);
|
box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.08);
|
||||||
z-index: var(--booking-widget-z-index);
|
z-index: var(--booking-widget-z-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meetingPackageWidget {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--max-width-page);
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,10 @@ query GetCollectionPage($locale: String!, $uid: String!) {
|
|||||||
...TopPrimaryButton_CollectionPage
|
...TopPrimaryButton_CollectionPage
|
||||||
...NavigationLinks_CollectionPage
|
...NavigationLinks_CollectionPage
|
||||||
}
|
}
|
||||||
|
meeting_package {
|
||||||
|
show_widget
|
||||||
|
location
|
||||||
|
}
|
||||||
blocks {
|
blocks {
|
||||||
__typename
|
__typename
|
||||||
...CardsGrid_CollectionPage
|
...CardsGrid_CollectionPage
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ query GetContentPage($locale: String!, $uid: String!) {
|
|||||||
...TopPrimaryButton_ContentPage
|
...TopPrimaryButton_ContentPage
|
||||||
...NavigationLinks_ContentPage
|
...NavigationLinks_ContentPage
|
||||||
}
|
}
|
||||||
|
meeting_package {
|
||||||
|
show_widget
|
||||||
|
location
|
||||||
|
}
|
||||||
blocks {
|
blocks {
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
cardsGridSchema,
|
cardsGridSchema,
|
||||||
} from "../schemas/blocks/cardsGrid"
|
} from "../schemas/blocks/cardsGrid"
|
||||||
import {
|
import {
|
||||||
dynamicContentRefsSchema,
|
|
||||||
dynamicContentSchema as blockDynamicContentSchema,
|
dynamicContentSchema as blockDynamicContentSchema,
|
||||||
|
dynamicContentRefsSchema,
|
||||||
} from "../schemas/blocks/dynamicContent"
|
} from "../schemas/blocks/dynamicContent"
|
||||||
import {
|
import {
|
||||||
shortcutsRefsSchema,
|
shortcutsRefsSchema,
|
||||||
@@ -98,6 +98,12 @@ export const collectionPageSchema = z.object({
|
|||||||
top_primary_button: topPrimaryButtonSchema,
|
top_primary_button: topPrimaryButtonSchema,
|
||||||
navigation_links: navigationLinksSchema,
|
navigation_links: navigationLinksSchema,
|
||||||
}),
|
}),
|
||||||
|
meeting_package: z
|
||||||
|
.object({
|
||||||
|
show_widget: z.boolean(),
|
||||||
|
location: z.string(),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
system: systemSchema.merge(
|
system: systemSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
|
|||||||
@@ -211,6 +211,12 @@ export const contentPageSchema = z.object({
|
|||||||
top_primary_button: topPrimaryButtonSchema,
|
top_primary_button: topPrimaryButtonSchema,
|
||||||
navigation_links: navigationLinksSchema,
|
navigation_links: navigationLinksSchema,
|
||||||
}),
|
}),
|
||||||
|
meeting_package: z
|
||||||
|
.object({
|
||||||
|
show_widget: z.boolean(),
|
||||||
|
location: z.string(),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
system: systemSchema.merge(
|
system: systemSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { create } from "zustand"
|
|||||||
export enum StickyElementNameEnum {
|
export enum StickyElementNameEnum {
|
||||||
SITEWIDE_ALERT = "SITEWIDE_ALERT",
|
SITEWIDE_ALERT = "SITEWIDE_ALERT",
|
||||||
BOOKING_WIDGET = "BOOKING_WIDGET",
|
BOOKING_WIDGET = "BOOKING_WIDGET",
|
||||||
MEETING_BOOKING_WIDGET = "MEETING_BOOKING_WIDGET",
|
MEETING_PACKAGE_WIDGET = "MEETING_PACKAGE_WIDGET",
|
||||||
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
|
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
|
||||||
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
|
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
|
||||||
DESTINATION_SIDEBAR = "DESTINATION_SIDEBAR",
|
DESTINATION_SIDEBAR = "DESTINATION_SIDEBAR",
|
||||||
@@ -37,7 +37,7 @@ const priorityMap: Record<StickyElementNameEnum, number> = {
|
|||||||
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
|
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
|
||||||
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,
|
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,
|
||||||
|
|
||||||
[StickyElementNameEnum.MEETING_BOOKING_WIDGET]: 3,
|
[StickyElementNameEnum.MEETING_PACKAGE_WIDGET]: 3,
|
||||||
[StickyElementNameEnum.DESTINATION_SIDEBAR]: 3,
|
[StickyElementNameEnum.DESTINATION_SIDEBAR]: 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user