diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/@breadcrumbs/collection_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/@breadcrumbs/collection_page/[uid]/page.tsx index ede2db8ae..32415b446 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/@breadcrumbs/collection_page/[uid]/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/@breadcrumbs/collection_page/[uid]/page.tsx @@ -1,19 +1,3 @@ -import { Suspense } from "react" - -import Breadcrumbs from "@/components/Breadcrumbs" -import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" - -import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" - export default function CollectionPageBreadcrumbs() { - const variants: Pick = { - color: "Surface/Secondary/Default", - size: "contentWidth", - } - - return ( - }> - - - ) + return null } diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/collection_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/collection_page/[uid]/page.tsx index 3bed19467..77f67ae0a 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/collection_page/[uid]/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/collection_page/[uid]/page.tsx @@ -1,4 +1,4 @@ -import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage" +import { CollectionPage } from "@/components/ContentType/CollectionPage" import styles from "./page.module.css" diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/content_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/content_page/[uid]/page.tsx index 45dee0c4d..d8a8f85e3 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/content_page/[uid]/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/content_page/[uid]/page.tsx @@ -4,7 +4,7 @@ import { redirect } from "next/navigation" import { overview } from "@scandic-hotels/common/constants/routes/myPages" import { isSignupPage } from "@scandic-hotels/common/constants/routes/signup" -import ContentPage from "@/components/ContentType/StaticPages/ContentPage" +import { ContentPage } from "@/components/ContentType/ContentPage" import { getLang } from "@/i18n/serverContext" import { isLoggedInUser } from "@/utils/isLoggedInUser" diff --git a/apps/scandic-web/components/ContentType/CollectionPage/collectionPage.module.css b/apps/scandic-web/components/ContentType/CollectionPage/collectionPage.module.css new file mode 100644 index 000000000..2a9ae8c87 --- /dev/null +++ b/apps/scandic-web/components/ContentType/CollectionPage/collectionPage.module.css @@ -0,0 +1,85 @@ +.pageSection { + display: grid; + gap: var(--Space-x4); + padding-bottom: var(--Space-x9); +} + +.header { + background-color: var(--Base-Surface-Subtle-Normal); +} + +.videoWrapper { + width: 100%; + height: 520px; + position: relative; + padding: var(--Space-x2) 0; + + .heading { + color: var(--Text-Inverted); + } + + .headerContent { + height: 100%; + align-content: end; + padding-bottom: var(--Space-x15); + } +} + +.heroVideo { + position: absolute; + inset: 0; +} + +.headerContent { + position: relative; + display: grid; + gap: var(--Space-x3); + padding-bottom: var(--Space-x4); + width: var(--max-width-content); + margin: 0 auto; + justify-items: start; + z-index: 1; +} + +.intro { + display: grid; + max-width: var(--max-width-text-block); + gap: var(--Space-x3); +} + +.heading { + color: var(--Text-Heading); + text-wrap: balance; + hyphens: auto; +} + +.content { + display: flex; + flex-direction: column; + gap: var(--Space-x4); + width: var(--max-width-content); + margin: 0 auto; +} + +.main { + display: grid; + gap: var(--Space-x6); +} + +@media (min-width: 1367px) { + .videoWrapper { + height: 480px; + + .headerContent { + padding-bottom: var(--Space-x5); + } + } + + .content { + gap: var(--Space-x9); + } + + .main { + gap: var(--Space-x9); + } +} diff --git a/apps/scandic-web/components/ContentType/CollectionPage/index.tsx b/apps/scandic-web/components/ContentType/CollectionPage/index.tsx new file mode 100644 index 000000000..c57e62352 --- /dev/null +++ b/apps/scandic-web/components/ContentType/CollectionPage/index.tsx @@ -0,0 +1,139 @@ +import { Suspense } from "react" + +import ButtonLink from "@scandic-hotels/design-system/ButtonLink" +import { Typography } from "@scandic-hotels/design-system/Typography" +import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" + +import { serverClient } from "@/lib/trpc/server" + +import Blocks from "@/components/Blocks" +import Breadcrumbs from "@/components/Breadcrumbs" +import HeaderDynamicContent from "@/components/Headers/DynamicContent" +import Hero from "@/components/Hero" +import { HeroVideo } from "@/components/HeroVideo" +import MeetingPackageWidget from "@/components/MeetingPackageWidget" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" +import LinkChips from "@/components/TempDesignSystem/LinkChips" + +import styles from "./collectionPage.module.css" + +import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" + +export async function CollectionPage() { + const caller = await serverClient() + const collectionPageRes = await caller.contentstack.collectionPage.get() + + if (!collectionPageRes) { + return null + } + + const { tracking, collectionPage } = collectionPageRes + const { blocks, hero_image, hero_video, header, meeting_package } = + collectionPage + + const breadCrumbsVariants: Pick = { + color: "Surface/Secondary/Default", + size: "contentWidth", + } + + return ( + <> + {hero_video ? null : ( + }> + + + )} +
+
+ {hero_video ? ( + <> +
+ +
+ +

{header.heading}

+
+ {header.top_primary_button?.url ? ( + + {header.top_primary_button.title} + + ) : null} +
+
+ } + > + + + + ) : null} + +
+ {hero_video ? ( + +

{header.preamble}

+
+ ) : ( + <> + +

{header.heading}

+
+ +

{header.preamble}

+
+ {header.top_primary_button?.url ? ( + + {header.top_primary_button.title} + + ) : null} + + )} + {header.navigation_links ? ( + + ) : null} + {"dynamic_content" in header && header.dynamic_content ? ( + + ) : null} +
+
+ +
+ {!hero_video && hero_image ? ( + + ) : null} +
+ {meeting_package?.show_widget && ( + + )} + {blocks ? : null} +
+
+
+ + + ) +} diff --git a/apps/scandic-web/components/ContentType/ContentPage/contentPage.module.css b/apps/scandic-web/components/ContentType/ContentPage/contentPage.module.css new file mode 100644 index 000000000..8410ee3d0 --- /dev/null +++ b/apps/scandic-web/components/ContentType/ContentPage/contentPage.module.css @@ -0,0 +1,70 @@ +.pageSection { + display: grid; + gap: var(--Space-x4); + padding-bottom: var(--Space-x9); +} + +.headerWrapper { + background-color: var(--Base-Surface-Subtle-Normal); + padding-bottom: var(--Space-x4); +} + +.header { + display: grid; + gap: var(--Space-x3); + width: var(--max-width-content); + margin: 0 auto; + justify-items: start; +} + +.intro { + display: grid; + max-width: var(--max-width-text-block); + gap: var(--Space-x3); +} + +.heading { + color: var(--Text-Heading); + text-wrap: balance; + hyphens: auto; +} + +.content { + display: flex; + flex-direction: column; + gap: var(--Space-x4); + width: var(--max-width-content); + margin: 0 auto; +} + +.hero { + grid-area: hero; + width: 100%; + height: 400px; + display: flex; +} + +.main { + grid-area: main; + display: grid; + gap: var(--Space-x6); +} + +@media (min-width: 1367px) { + .content { + display: grid; + grid-template-areas: + "hero hero" + "main sidebar"; + grid-template-columns: var(--max-width-text-block) 1fr; + gap: var(--Space-x9); + } + + .main { + gap: var(--Space-x9); + } + + .hero { + height: 480px; + } +} diff --git a/apps/scandic-web/components/ContentType/ContentPage/index.tsx b/apps/scandic-web/components/ContentType/ContentPage/index.tsx new file mode 100644 index 000000000..4e40e1997 --- /dev/null +++ b/apps/scandic-web/components/ContentType/ContentPage/index.tsx @@ -0,0 +1,118 @@ +import { Suspense } from "react" + +import ButtonLink from "@scandic-hotels/design-system/ButtonLink" +import { Typography } from "@scandic-hotels/design-system/Typography" +import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" + +import { serverClient } from "@/lib/trpc/server" + +import Blocks from "@/components/Blocks" +import Breadcrumbs from "@/components/Breadcrumbs" +import HeaderDynamicContent from "@/components/Headers/DynamicContent" +import Hero from "@/components/Hero" +import { HeroVideo } from "@/components/HeroVideo" +import Sidebar from "@/components/Sidebar" +import SidebarSkeleton from "@/components/Sidebar/SidebarSkeleton" +import StickyMeetingPackageWidget from "@/components/StickyMeetingPackageWidget" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" +import LinkChips from "@/components/TempDesignSystem/LinkChips" + +import styles from "./contentPage.module.css" + +export async function ContentPage() { + const caller = await serverClient() + const contentPageRes = await caller.contentstack.contentPage.get() + + if (!contentPageRes) { + return null + } + + const { tracking, contentPage } = contentPageRes + const { blocks, hero_image, hero_video, header, meeting_package, sidebar } = + contentPage + + return ( + <> + {meeting_package?.show_widget && ( + + )} + + } + > + + + +
+ {header ? ( +
+
+
+ +

{header.heading}

+
+ +

{header.preamble}

+
+
+ {header.top_primary_button?.url ? ( + + {header.top_primary_button.title} + + ) : null} + {header.navigation_links ? ( + + ) : null} + {"dynamic_content" in header && header.dynamic_content ? ( + + ) : null} +
+
+ ) : null} + +
+ {hero_video || hero_image ? ( + <> + {hero_video ? ( + + ) : null} + {!hero_video && hero_image ? ( + + ) : null} + + ) : null} + +
+ {blocks ? : null} +
+ + {sidebar?.length ? ( + }> + + + ) : null} +
+
+ + + ) +} diff --git a/apps/scandic-web/components/ContentType/StaticPages/CollectionPage/index.tsx b/apps/scandic-web/components/ContentType/StaticPages/CollectionPage/index.tsx deleted file mode 100644 index 4dad567a3..000000000 --- a/apps/scandic-web/components/ContentType/StaticPages/CollectionPage/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { serverClient } from "@/lib/trpc/server" - -import StaticPage from ".." - -export default async function CollectionPage() { - const caller = await serverClient() - const collectionPageRes = await caller.contentstack.collectionPage.get() - - if (!collectionPageRes) { - return null - } - - const { tracking, collectionPage } = collectionPageRes - - return ( - - ) -} diff --git a/apps/scandic-web/components/ContentType/StaticPages/ContentPage/index.tsx b/apps/scandic-web/components/ContentType/StaticPages/ContentPage/index.tsx deleted file mode 100644 index ea0f10380..000000000 --- a/apps/scandic-web/components/ContentType/StaticPages/ContentPage/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Suspense } from "react" - -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 ".." - -export default async function ContentPage() { - const caller = await serverClient() - const contentPageRes = await caller.contentstack.contentPage.get() - - if (!contentPageRes) { - return null - } - - const { tracking, contentPage } = contentPageRes - - return ( - <> - {contentPage.meeting_package?.show_widget && ( - - )} - - } - > - - - - - ) -} diff --git a/apps/scandic-web/components/ContentType/StaticPages/index.tsx b/apps/scandic-web/components/ContentType/StaticPages/index.tsx index a69e3e57d..9104a20f8 100644 --- a/apps/scandic-web/components/ContentType/StaticPages/index.tsx +++ b/apps/scandic-web/components/ContentType/StaticPages/index.tsx @@ -7,6 +7,7 @@ import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" import Blocks from "@/components/Blocks" import HeaderDynamicContent from "@/components/Headers/DynamicContent" import Hero from "@/components/Hero" +import { HeroVideo } from "@/components/HeroVideo" import MeetingPackageWidget from "@/components/MeetingPackageWidget" import Sidebar from "@/components/Sidebar" import SidebarSkeleton from "@/components/Sidebar/SidebarSkeleton" @@ -23,7 +24,7 @@ export default async function StaticPage({ tracking, pageType, }: StaticPageProps) { - const { blocks, hero_image, header, meeting_package } = content + const { blocks, hero_image, hero_video, header, meeting_package } = content return ( <> @@ -63,14 +64,25 @@ export default async function StaticPage({ - {hero_image ? ( -
- + {hero_video || hero_image ? ( +
+ {hero_video ? ( + + ) : null} + {!hero_video && hero_image ? ( + + ) : null}
) : null} diff --git a/apps/scandic-web/components/ContentType/StaticPages/staticPage.module.css b/apps/scandic-web/components/ContentType/StaticPages/staticPage.module.css index 5e2710d49..8f9cf496c 100644 --- a/apps/scandic-web/components/ContentType/StaticPages/staticPage.module.css +++ b/apps/scandic-web/components/ContentType/StaticPages/staticPage.module.css @@ -26,15 +26,15 @@ hyphens: auto; } -.heroContainer { - width: 100%; +.heroWrapper { padding: var(--Space-x4) var(--Space-x2); } -.heroContainer img { +.heroImage, +.heroVideo { max-width: var(--max-width-content); margin: 0 auto; - display: block; + display: flex; } .contentContainer { diff --git a/apps/scandic-web/components/Hero/index.tsx b/apps/scandic-web/components/Hero/index.tsx index bd856ba5b..08be993b2 100644 --- a/apps/scandic-web/components/Hero/index.tsx +++ b/apps/scandic-web/components/Hero/index.tsx @@ -1,25 +1,25 @@ +import { cx } from "class-variance-authority" + import Image from "@scandic-hotels/design-system/Image" import styles from "./hero.module.css" -import type { HeroProps } from "./hero" +import type { ComponentProps } from "react" -export default async function Hero({ - alt, - src, - focalPoint, - dimensions, -}: HeroProps) { +type HeroProps = Pick< + ComponentProps, + "className" | "alt" | "src" | "focalPoint" | "dimensions" +> + +export default async function Hero({ className, alt, ...props }: HeroProps) { return ( {alt} ) } diff --git a/apps/scandic-web/components/HeroVideo/heroVideo.module.css b/apps/scandic-web/components/HeroVideo/heroVideo.module.css new file mode 100644 index 000000000..3167d19dc --- /dev/null +++ b/apps/scandic-web/components/HeroVideo/heroVideo.module.css @@ -0,0 +1,10 @@ +.videoWrapper { + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; + + &:not(.fullWidth) { + border-radius: var(--Corner-radius-xl); + } +} diff --git a/apps/scandic-web/components/HeroVideo/index.tsx b/apps/scandic-web/components/HeroVideo/index.tsx new file mode 100644 index 000000000..ce3b42c04 --- /dev/null +++ b/apps/scandic-web/components/HeroVideo/index.tsx @@ -0,0 +1,29 @@ +import { cx } from "class-variance-authority" + +import { VideoPlayer } from "@scandic-hotels/design-system/VideoPlayer" + +import styles from "./heroVideo.module.css" + +import type { ComponentProps } from "react" + +interface HeroVideoProps + extends Omit, "className" | "variant"> { + className?: string + isFullWidth?: boolean +} + +export function HeroVideo({ + className, + isFullWidth, + ...props +}: HeroVideoProps) { + return ( +
+ +
+ ) +} diff --git a/packages/common/package.json b/packages/common/package.json index c90eec1b2..b7afc222a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -53,6 +53,7 @@ "./utils/chunk": "./utils/chunk.ts", "./utils/dateFormatting": "./utils/dateFormatting.ts", "./utils/debounce": "./utils/debounce.ts", + "./utils/focalPoint": "./utils/focalPoint.ts", "./utils/imageVault": "./utils/imageVault.ts", "./utils/isDefined": "./utils/isDefined.ts", "./utils/isEdge": "./utils/isEdge.ts", diff --git a/packages/common/utils/focalPoint.ts b/packages/common/utils/focalPoint.ts new file mode 100644 index 000000000..fc1b1d9f6 --- /dev/null +++ b/packages/common/utils/focalPoint.ts @@ -0,0 +1,11 @@ +import { z } from "zod" + +export const focalPointSchema = z + .object({ + x: z.number().nullish(), + y: z.number().nullish(), + }) + .transform(({ x, y }) => ({ + x: x ?? 50, + y: y ?? 50, + })) diff --git a/packages/common/utils/imageVault.ts b/packages/common/utils/imageVault.ts index 03765d52f..1640bf1db 100644 --- a/packages/common/utils/imageVault.ts +++ b/packages/common/utils/imageVault.ts @@ -1,9 +1,6 @@ import { z } from "zod" -const focalPointSchema = z.object({ - x: z.number(), - y: z.number(), -}) +import { focalPointSchema } from "./focalPoint" const deprecatedMetaDataSchema = z.object({ DefinitionType: z.number().nullish(), diff --git a/packages/design-system/lib/components/VideoPlayer/Button/button.module.css b/packages/design-system/lib/components/VideoPlayer/Button/button.module.css index 185f21523..6c81df387 100644 --- a/packages/design-system/lib/components/VideoPlayer/Button/button.module.css +++ b/packages/design-system/lib/components/VideoPlayer/Button/button.module.css @@ -18,8 +18,13 @@ @media (hover: hover) { &:hover .iconWrapper { - background-color: var(--Component-Button-Inverted-Fill-Hover); - color: var(--Component-Button-Inverted-On-fill-Hover); + background: + linear-gradient( + 0deg, + var(--Component-Button-Inverted-Fill-Hover) 0%, + var(--Component-Button-Inverted-Fill-Hover) 100% + ), + var(--Component-Button-Inverted-Fill-Default); } } diff --git a/packages/design-system/lib/components/VideoPlayer/index.tsx b/packages/design-system/lib/components/VideoPlayer/index.tsx index 41d07f24d..30616d06b 100644 --- a/packages/design-system/lib/components/VideoPlayer/index.tsx +++ b/packages/design-system/lib/components/VideoPlayer/index.tsx @@ -22,6 +22,7 @@ interface VideoPlayerProps extends VariantProps { captions?: Caption[] focalPoint?: FocalPoint autoPlay?: boolean + hasOverlay?: boolean } export function VideoPlayer({ @@ -31,6 +32,7 @@ export function VideoPlayer({ className, variant = 'inline', autoPlay, + hasOverlay, }: VideoPlayerProps) { const intl = useIntl() const videoRef = useRef(null) @@ -84,7 +86,13 @@ export function VideoPlayer({ const showMuteButton = variant === 'inline' && isActivated return ( -
+