Feat/BOOK-240 hero video
Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Christel Westerberg
This commit is contained in:
@@ -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() {
|
export default function CollectionPageBreadcrumbs() {
|
||||||
const variants: Pick<BreadcrumbsProps, "color" | "size"> = {
|
return null
|
||||||
color: "Surface/Secondary/Default",
|
|
||||||
size: "contentWidth",
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<BreadcrumbsSkeleton {...variants} />}>
|
|
||||||
<Breadcrumbs {...variants} />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage"
|
import { CollectionPage } from "@/components/ContentType/CollectionPage"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { redirect } from "next/navigation"
|
|||||||
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
||||||
import { isSignupPage } from "@scandic-hotels/common/constants/routes/signup"
|
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 { getLang } from "@/i18n/serverContext"
|
||||||
import { isLoggedInUser } from "@/utils/isLoggedInUser"
|
import { isLoggedInUser } from "@/utils/isLoggedInUser"
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
139
apps/scandic-web/components/ContentType/CollectionPage/index.tsx
Normal file
139
apps/scandic-web/components/ContentType/CollectionPage/index.tsx
Normal file
@@ -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<BreadcrumbsProps, "color" | "size"> = {
|
||||||
|
color: "Surface/Secondary/Default",
|
||||||
|
size: "contentWidth",
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{hero_video ? null : (
|
||||||
|
<Suspense fallback={<BreadcrumbsSkeleton {...breadCrumbsVariants} />}>
|
||||||
|
<Breadcrumbs {...breadCrumbsVariants} />
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
<section className={styles.pageSection}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
{hero_video ? (
|
||||||
|
<>
|
||||||
|
<div className={styles.videoWrapper}>
|
||||||
|
<HeroVideo
|
||||||
|
className={styles.heroVideo}
|
||||||
|
src={hero_video.src}
|
||||||
|
focalPoint={hero_video.focalPoint}
|
||||||
|
captions={hero_video.captions}
|
||||||
|
isFullWidth
|
||||||
|
hasOverlay
|
||||||
|
/>
|
||||||
|
<div className={styles.headerContent}>
|
||||||
|
<Typography variant="Title/lg">
|
||||||
|
<h1 className={styles.heading}>{header.heading}</h1>
|
||||||
|
</Typography>
|
||||||
|
{header.top_primary_button?.url ? (
|
||||||
|
<ButtonLink
|
||||||
|
href={header.top_primary_button.url}
|
||||||
|
size="Medium"
|
||||||
|
variant="Primary"
|
||||||
|
color="Inverted"
|
||||||
|
>
|
||||||
|
{header.top_primary_button.title}
|
||||||
|
</ButtonLink>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Suspense
|
||||||
|
fallback={<BreadcrumbsSkeleton {...breadCrumbsVariants} />}
|
||||||
|
>
|
||||||
|
<Breadcrumbs {...breadCrumbsVariants} />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className={styles.headerContent}>
|
||||||
|
{hero_video ? (
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<p>{header.preamble}</p>
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography variant="Title/lg">
|
||||||
|
<h1 className={styles.heading}>{header.heading}</h1>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<p>{header.preamble}</p>
|
||||||
|
</Typography>
|
||||||
|
{header.top_primary_button?.url ? (
|
||||||
|
<ButtonLink
|
||||||
|
href={header.top_primary_button.url}
|
||||||
|
size="Medium"
|
||||||
|
variant="Tertiary"
|
||||||
|
>
|
||||||
|
{header.top_primary_button.title}
|
||||||
|
</ButtonLink>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{header.navigation_links ? (
|
||||||
|
<LinkChips chips={header.navigation_links} />
|
||||||
|
) : null}
|
||||||
|
{"dynamic_content" in header && header.dynamic_content ? (
|
||||||
|
<HeaderDynamicContent {...header.dynamic_content} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className={styles.content}>
|
||||||
|
{!hero_video && hero_image ? (
|
||||||
|
<Hero
|
||||||
|
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
||||||
|
src={hero_image.url}
|
||||||
|
focalPoint={hero_image.focalPoint}
|
||||||
|
dimensions={hero_image.dimensions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<main className={styles.main}>
|
||||||
|
{meeting_package?.show_widget && (
|
||||||
|
<MeetingPackageWidget
|
||||||
|
destination={meeting_package.location}
|
||||||
|
className={styles.meetingPackageWidget}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{blocks ? <Blocks blocks={blocks} /> : null}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<TrackingSDK pageData={tracking} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
apps/scandic-web/components/ContentType/ContentPage/index.tsx
Normal file
118
apps/scandic-web/components/ContentType/ContentPage/index.tsx
Normal file
@@ -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 && (
|
||||||
|
<StickyMeetingPackageWidget destination={meeting_package.location} />
|
||||||
|
)}
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<BreadcrumbsSkeleton
|
||||||
|
color="Surface/Secondary/Default"
|
||||||
|
size="contentWidth"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Breadcrumbs color="Surface/Secondary/Default" size="contentWidth" />
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<section className={styles.pageSection}>
|
||||||
|
{header ? (
|
||||||
|
<div className={styles.headerWrapper}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.intro}>
|
||||||
|
<Typography variant="Title/lg">
|
||||||
|
<h1 className={styles.heading}>{header.heading}</h1>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<p>{header.preamble}</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
{header.top_primary_button?.url ? (
|
||||||
|
<ButtonLink
|
||||||
|
href={header.top_primary_button.url}
|
||||||
|
size="Medium"
|
||||||
|
variant="Tertiary"
|
||||||
|
>
|
||||||
|
{header.top_primary_button.title}
|
||||||
|
</ButtonLink>
|
||||||
|
) : null}
|
||||||
|
{header.navigation_links ? (
|
||||||
|
<LinkChips chips={header.navigation_links} />
|
||||||
|
) : null}
|
||||||
|
{"dynamic_content" in header && header.dynamic_content ? (
|
||||||
|
<HeaderDynamicContent {...header.dynamic_content} />
|
||||||
|
) : null}
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className={styles.content}>
|
||||||
|
{hero_video || hero_image ? (
|
||||||
|
<>
|
||||||
|
{hero_video ? (
|
||||||
|
<HeroVideo
|
||||||
|
className={styles.hero}
|
||||||
|
src={hero_video.src}
|
||||||
|
focalPoint={hero_video.focalPoint}
|
||||||
|
captions={hero_video.captions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{!hero_video && hero_image ? (
|
||||||
|
<Hero
|
||||||
|
className={styles.hero}
|
||||||
|
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
||||||
|
src={hero_image.url}
|
||||||
|
focalPoint={hero_image.focalPoint}
|
||||||
|
dimensions={hero_image.dimensions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<main className={styles.main}>
|
||||||
|
{blocks ? <Blocks blocks={blocks} /> : null}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{sidebar?.length ? (
|
||||||
|
<Suspense fallback={<SidebarSkeleton />}>
|
||||||
|
<Sidebar blocks={sidebar} />
|
||||||
|
</Suspense>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<TrackingSDK pageData={tracking} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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 (
|
|
||||||
<StaticPage
|
|
||||||
content={collectionPage}
|
|
||||||
tracking={tracking}
|
|
||||||
pageType="collection"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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 && (
|
|
||||||
<StickyMeetingPackageWidget
|
|
||||||
destination={contentPage.meeting_package.location}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<BreadcrumbsSkeleton
|
|
||||||
color="Surface/Secondary/Default"
|
|
||||||
size="contentWidth"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Breadcrumbs color="Surface/Secondary/Default" size="contentWidth" />
|
|
||||||
</Suspense>
|
|
||||||
<StaticPage
|
|
||||||
content={contentPage}
|
|
||||||
tracking={tracking}
|
|
||||||
pageType="content"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
|||||||
import Blocks from "@/components/Blocks"
|
import Blocks from "@/components/Blocks"
|
||||||
import HeaderDynamicContent from "@/components/Headers/DynamicContent"
|
import HeaderDynamicContent from "@/components/Headers/DynamicContent"
|
||||||
import Hero from "@/components/Hero"
|
import Hero from "@/components/Hero"
|
||||||
|
import { HeroVideo } from "@/components/HeroVideo"
|
||||||
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
||||||
import Sidebar from "@/components/Sidebar"
|
import Sidebar from "@/components/Sidebar"
|
||||||
import SidebarSkeleton from "@/components/Sidebar/SidebarSkeleton"
|
import SidebarSkeleton from "@/components/Sidebar/SidebarSkeleton"
|
||||||
@@ -23,7 +24,7 @@ export default async function StaticPage({
|
|||||||
tracking,
|
tracking,
|
||||||
pageType,
|
pageType,
|
||||||
}: StaticPageProps) {
|
}: StaticPageProps) {
|
||||||
const { blocks, hero_image, header, meeting_package } = content
|
const { blocks, hero_image, hero_video, header, meeting_package } = content
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -63,14 +64,25 @@ export default async function StaticPage({
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{hero_image ? (
|
{hero_video || hero_image ? (
|
||||||
<div className={styles.heroContainer}>
|
<div className={styles.heroWrapper}>
|
||||||
<Hero
|
{hero_video ? (
|
||||||
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
<HeroVideo
|
||||||
src={hero_image.url}
|
className={styles.heroVideo}
|
||||||
focalPoint={hero_image.focalPoint}
|
src={hero_video.src}
|
||||||
dimensions={hero_image.dimensions}
|
focalPoint={hero_video.focalPoint}
|
||||||
/>
|
captions={hero_video.captions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{!hero_video && hero_image ? (
|
||||||
|
<Hero
|
||||||
|
className={styles.heroImage}
|
||||||
|
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
||||||
|
src={hero_image.url}
|
||||||
|
focalPoint={hero_image.focalPoint}
|
||||||
|
dimensions={hero_image.dimensions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,15 @@
|
|||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroContainer {
|
.heroWrapper {
|
||||||
width: 100%;
|
|
||||||
padding: var(--Space-x4) var(--Space-x2);
|
padding: var(--Space-x4) var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroContainer img {
|
.heroImage,
|
||||||
|
.heroVideo {
|
||||||
max-width: var(--max-width-content);
|
max-width: var(--max-width-content);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contentContainer {
|
.contentContainer {
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
|
import { cx } from "class-variance-authority"
|
||||||
|
|
||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
|
|
||||||
import styles from "./hero.module.css"
|
import styles from "./hero.module.css"
|
||||||
|
|
||||||
import type { HeroProps } from "./hero"
|
import type { ComponentProps } from "react"
|
||||||
|
|
||||||
export default async function Hero({
|
type HeroProps = Pick<
|
||||||
alt,
|
ComponentProps<typeof Image>,
|
||||||
src,
|
"className" | "alt" | "src" | "focalPoint" | "dimensions"
|
||||||
focalPoint,
|
>
|
||||||
dimensions,
|
|
||||||
}: HeroProps) {
|
export default async function Hero({ className, alt, ...props }: HeroProps) {
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
className={styles.hero}
|
className={cx(styles.hero, className)}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
height={480}
|
height={480}
|
||||||
width={1196}
|
width={1196}
|
||||||
src={src}
|
|
||||||
focalPoint={focalPoint}
|
|
||||||
dimensions={dimensions}
|
|
||||||
priority
|
priority
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
10
apps/scandic-web/components/HeroVideo/heroVideo.module.css
Normal file
10
apps/scandic-web/components/HeroVideo/heroVideo.module.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.videoWrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:not(.fullWidth) {
|
||||||
|
border-radius: var(--Corner-radius-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
apps/scandic-web/components/HeroVideo/index.tsx
Normal file
29
apps/scandic-web/components/HeroVideo/index.tsx
Normal file
@@ -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<ComponentProps<typeof VideoPlayer>, "className" | "variant"> {
|
||||||
|
className?: string
|
||||||
|
isFullWidth?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeroVideo({
|
||||||
|
className,
|
||||||
|
isFullWidth,
|
||||||
|
...props
|
||||||
|
}: HeroVideoProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(styles.videoWrapper, className, {
|
||||||
|
[styles.fullWidth]: isFullWidth,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<VideoPlayer variant="hero" {...props} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"./utils/chunk": "./utils/chunk.ts",
|
"./utils/chunk": "./utils/chunk.ts",
|
||||||
"./utils/dateFormatting": "./utils/dateFormatting.ts",
|
"./utils/dateFormatting": "./utils/dateFormatting.ts",
|
||||||
"./utils/debounce": "./utils/debounce.ts",
|
"./utils/debounce": "./utils/debounce.ts",
|
||||||
|
"./utils/focalPoint": "./utils/focalPoint.ts",
|
||||||
"./utils/imageVault": "./utils/imageVault.ts",
|
"./utils/imageVault": "./utils/imageVault.ts",
|
||||||
"./utils/isDefined": "./utils/isDefined.ts",
|
"./utils/isDefined": "./utils/isDefined.ts",
|
||||||
"./utils/isEdge": "./utils/isEdge.ts",
|
"./utils/isEdge": "./utils/isEdge.ts",
|
||||||
|
|||||||
11
packages/common/utils/focalPoint.ts
Normal file
11
packages/common/utils/focalPoint.ts
Normal file
@@ -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,
|
||||||
|
}))
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
const focalPointSchema = z.object({
|
import { focalPointSchema } from "./focalPoint"
|
||||||
x: z.number(),
|
|
||||||
y: z.number(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const deprecatedMetaDataSchema = z.object({
|
const deprecatedMetaDataSchema = z.object({
|
||||||
DefinitionType: z.number().nullish(),
|
DefinitionType: z.number().nullish(),
|
||||||
|
|||||||
@@ -18,8 +18,13 @@
|
|||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
&:hover .iconWrapper {
|
&:hover .iconWrapper {
|
||||||
background-color: var(--Component-Button-Inverted-Fill-Hover);
|
background:
|
||||||
color: var(--Component-Button-Inverted-On-fill-Hover);
|
linear-gradient(
|
||||||
|
0deg,
|
||||||
|
var(--Component-Button-Inverted-Fill-Hover) 0%,
|
||||||
|
var(--Component-Button-Inverted-Fill-Hover) 100%
|
||||||
|
),
|
||||||
|
var(--Component-Button-Inverted-Fill-Default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ interface VideoPlayerProps extends VariantProps<typeof variants> {
|
|||||||
captions?: Caption[]
|
captions?: Caption[]
|
||||||
focalPoint?: FocalPoint
|
focalPoint?: FocalPoint
|
||||||
autoPlay?: boolean
|
autoPlay?: boolean
|
||||||
|
hasOverlay?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoPlayer({
|
export function VideoPlayer({
|
||||||
@@ -31,6 +32,7 @@ export function VideoPlayer({
|
|||||||
className,
|
className,
|
||||||
variant = 'inline',
|
variant = 'inline',
|
||||||
autoPlay,
|
autoPlay,
|
||||||
|
hasOverlay,
|
||||||
}: VideoPlayerProps) {
|
}: VideoPlayerProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
@@ -84,7 +86,13 @@ export function VideoPlayer({
|
|||||||
const showMuteButton = variant === 'inline' && isActivated
|
const showMuteButton = variant === 'inline' && isActivated
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(classNames, { [styles.isActivated]: isActivated })}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
classNames,
|
||||||
|
{ [styles.isActivated]: isActivated },
|
||||||
|
{ [styles.hasOverlay]: hasOverlay }
|
||||||
|
)}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className={styles.video}
|
className={styles.video}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
&.inline {
|
&.inline {
|
||||||
border-radius: var(--Corner-radius-md);
|
border-radius: var(--Corner-radius-md);
|
||||||
@@ -22,6 +23,18 @@
|
|||||||
right: var(--Space-x2);
|
right: var(--Space-x2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hasOverlay::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0) 0%,
|
||||||
|
rgba(31, 28, 27, 0.25) 48.08%,
|
||||||
|
rgba(31, 28, 27, 0.8) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video {
|
.video {
|
||||||
@@ -32,17 +45,30 @@
|
|||||||
|
|
||||||
.playButton {
|
.playButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.muteButton {
|
.muteButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--Space-x2);
|
top: var(--Space-x2);
|
||||||
right: var(--Space-x2);
|
right: var(--Space-x2);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.videoPlayer.hero .playButton {
|
.videoPlayer {
|
||||||
bottom: var(--Space-x4);
|
&.hero .playButton {
|
||||||
right: var(--Space-x4);
|
bottom: var(--Space-x4);
|
||||||
|
right: var(--Space-x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hasOverlay::after {
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0) 0%,
|
||||||
|
rgba(31, 28, 27, 0.25) 53.8%,
|
||||||
|
rgba(31, 28, 27, 0.74) 97.6%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,7 @@
|
|||||||
"./Tooltip": "./lib/components/Tooltip/index.tsx",
|
"./Tooltip": "./lib/components/Tooltip/index.tsx",
|
||||||
"./TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
|
"./TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
|
||||||
"./Typography": "./lib/components/Typography/index.tsx",
|
"./Typography": "./lib/components/Typography/index.tsx",
|
||||||
|
"./VideoPlayer": "./lib/components/VideoPlayer/index.tsx",
|
||||||
"./base.css": "./lib/base.css",
|
"./base.css": "./lib/base.css",
|
||||||
"./design-system-new-deprecated.css": "./lib/design-system-new-deprecated.css",
|
"./design-system-new-deprecated.css": "./lib/design-system-new-deprecated.css",
|
||||||
"./downtown-camper.css": "./lib/styles/downtown-camper.css",
|
"./downtown-camper.css": "./lib/styles/downtown-camper.css",
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ export const System = gql`
|
|||||||
uid
|
uid
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const AssetSystem = gql`
|
||||||
|
fragment AssetSystem on SysAssetSystemField {
|
||||||
|
content_type_uid
|
||||||
|
uid
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
45
packages/trpc/lib/graphql/Fragments/Video.graphql.ts
Normal file
45
packages/trpc/lib/graphql/Fragments/Video.graphql.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { gql } from "graphql-tag"
|
||||||
|
|
||||||
|
import { AssetSystem } from "./System.graphql"
|
||||||
|
|
||||||
|
export const Video = gql`
|
||||||
|
fragment Video on Video {
|
||||||
|
sourceConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
focal_point {
|
||||||
|
x
|
||||||
|
y
|
||||||
|
}
|
||||||
|
captions {
|
||||||
|
is_default
|
||||||
|
language
|
||||||
|
fileConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const VideoRef = gql`
|
||||||
|
fragment VideoRef on Video {
|
||||||
|
sourceConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
system {
|
||||||
|
...AssetSystem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${AssetSystem}
|
||||||
|
`
|
||||||
@@ -25,11 +25,15 @@ import {
|
|||||||
TopPrimaryButtonRef_CollectionPage,
|
TopPrimaryButtonRef_CollectionPage,
|
||||||
} from "../../Fragments/CollectionPage/TopPrimaryButton.graphql"
|
} from "../../Fragments/CollectionPage/TopPrimaryButton.graphql"
|
||||||
import { System } from "../../Fragments/System.graphql"
|
import { System } from "../../Fragments/System.graphql"
|
||||||
|
import { Video, VideoRef } from "../../Fragments/Video.graphql"
|
||||||
|
|
||||||
export const GetCollectionPage = gql`
|
export const GetCollectionPage = gql`
|
||||||
query GetCollectionPage($locale: String!, $uid: String!) {
|
query GetCollectionPage($locale: String!, $uid: String!) {
|
||||||
collection_page(uid: $uid, locale: $locale) {
|
collection_page(uid: $uid, locale: $locale) {
|
||||||
hero_image
|
hero_image
|
||||||
|
hero_video {
|
||||||
|
...Video
|
||||||
|
}
|
||||||
title
|
title
|
||||||
header {
|
header {
|
||||||
heading
|
heading
|
||||||
@@ -64,11 +68,15 @@ export const GetCollectionPage = gql`
|
|||||||
${Shortcuts_CollectionPage}
|
${Shortcuts_CollectionPage}
|
||||||
${UspGrid_CollectionPage}
|
${UspGrid_CollectionPage}
|
||||||
${DynamicContent_CollectionPage}
|
${DynamicContent_CollectionPage}
|
||||||
|
${Video}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GetCollectionPageRefs = gql`
|
export const GetCollectionPageRefs = gql`
|
||||||
query GetCollectionPageRefs($locale: String!, $uid: String!) {
|
query GetCollectionPageRefs($locale: String!, $uid: String!) {
|
||||||
collection_page(locale: $locale, uid: $uid) {
|
collection_page(locale: $locale, uid: $uid) {
|
||||||
|
hero_video {
|
||||||
|
...VideoRef
|
||||||
|
}
|
||||||
header {
|
header {
|
||||||
...TopPrimaryButtonRef_CollectionPage
|
...TopPrimaryButtonRef_CollectionPage
|
||||||
...NavigationLinksRef_CollectionPage
|
...NavigationLinksRef_CollectionPage
|
||||||
@@ -92,6 +100,7 @@ export const GetCollectionPageRefs = gql`
|
|||||||
${Shortcuts_CollectionPageRefs}
|
${Shortcuts_CollectionPageRefs}
|
||||||
${UspGrid_CollectionPageRefs}
|
${UspGrid_CollectionPageRefs}
|
||||||
${DynamicContent_CollectionPageRefs}
|
${DynamicContent_CollectionPageRefs}
|
||||||
|
${VideoRef}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GetDaDeEnUrlsCollectionPage = gql`
|
export const GetDaDeEnUrlsCollectionPage = gql`
|
||||||
|
|||||||
@@ -60,11 +60,15 @@ import {
|
|||||||
TeaserCardSidebar_ContentPageRefs,
|
TeaserCardSidebar_ContentPageRefs,
|
||||||
} from "../../Fragments/Sidebar/TeaserCard.graphql"
|
} from "../../Fragments/Sidebar/TeaserCard.graphql"
|
||||||
import { System } from "../../Fragments/System.graphql"
|
import { System } from "../../Fragments/System.graphql"
|
||||||
|
import { Video, VideoRef } from "../../Fragments/Video.graphql"
|
||||||
|
|
||||||
export const GetContentPage = gql`
|
export const GetContentPage = gql`
|
||||||
query GetContentPage($locale: String!, $uid: String!) {
|
query GetContentPage($locale: String!, $uid: String!) {
|
||||||
content_page(uid: $uid, locale: $locale) {
|
content_page(uid: $uid, locale: $locale) {
|
||||||
hero_image
|
hero_image
|
||||||
|
hero_video {
|
||||||
|
...Video
|
||||||
|
}
|
||||||
title
|
title
|
||||||
header {
|
header {
|
||||||
heading
|
heading
|
||||||
@@ -110,6 +114,7 @@ export const GetContentPage = gql`
|
|||||||
${ScriptedCardSidebar_ContentPage}
|
${ScriptedCardSidebar_ContentPage}
|
||||||
${TeaserCardSidebar_ContentPage}
|
${TeaserCardSidebar_ContentPage}
|
||||||
${QuickLinksSidebar_ContentPage}
|
${QuickLinksSidebar_ContentPage}
|
||||||
|
${Video}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GetContentPageBlocksBatch1 = gql`
|
export const GetContentPageBlocksBatch1 = gql`
|
||||||
@@ -153,6 +158,9 @@ export const GetContentPageBlocksBatch2 = gql`
|
|||||||
export const GetContentPageRefs = gql`
|
export const GetContentPageRefs = gql`
|
||||||
query GetContentPageRefs($locale: String!, $uid: String!) {
|
query GetContentPageRefs($locale: String!, $uid: String!) {
|
||||||
content_page(locale: $locale, uid: $uid) {
|
content_page(locale: $locale, uid: $uid) {
|
||||||
|
hero_video {
|
||||||
|
...VideoRef
|
||||||
|
}
|
||||||
header {
|
header {
|
||||||
dynamic_content {
|
dynamic_content {
|
||||||
component
|
component
|
||||||
@@ -181,6 +189,7 @@ export const GetContentPageRefs = gql`
|
|||||||
${ScriptedCardSidebar_ContentPageRefs}
|
${ScriptedCardSidebar_ContentPageRefs}
|
||||||
${TeaserCardSidebar_ContentPageRefs}
|
${TeaserCardSidebar_ContentPageRefs}
|
||||||
${QuickLinksSidebar_ContentPageRefs}
|
${QuickLinksSidebar_ContentPageRefs}
|
||||||
|
${VideoRef}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const GetContentPageBlocksRefs = gql`
|
export const GetContentPageBlocksRefs = gql`
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
} from "../schemas/linkConnection"
|
} from "../schemas/linkConnection"
|
||||||
import { internalOrExternalLinkSchema } from "../schemas/pageLinks"
|
import { internalOrExternalLinkSchema } from "../schemas/pageLinks"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
import { transformedVideoSchema, videoRefSchema } from "../schemas/video"
|
||||||
|
|
||||||
// Block schemas
|
// Block schemas
|
||||||
export const collectionPageCards = z
|
export const collectionPageCards = z
|
||||||
@@ -78,6 +79,7 @@ const navigationLinksSchema = z
|
|||||||
export const collectionPageSchema = z.object({
|
export const collectionPageSchema = z.object({
|
||||||
collection_page: z.object({
|
collection_page: z.object({
|
||||||
hero_image: transformedImageVaultAssetSchema,
|
hero_image: transformedImageVaultAssetSchema,
|
||||||
|
hero_video: transformedVideoSchema,
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
header: z.object({
|
header: z.object({
|
||||||
@@ -145,6 +147,7 @@ const collectionPageHeaderRefs = z.object({
|
|||||||
|
|
||||||
export const collectionPageRefsSchema = z.object({
|
export const collectionPageRefsSchema = z.object({
|
||||||
collection_page: z.object({
|
collection_page: z.object({
|
||||||
|
hero_video: videoRefSchema.nullish(),
|
||||||
header: collectionPageHeaderRefs,
|
header: collectionPageHeaderRefs,
|
||||||
blocks: discriminatedUnionArray(
|
blocks: discriminatedUnionArray(
|
||||||
collectionPageBlockRefsItem.options
|
collectionPageBlockRefsItem.options
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
generateRefsResponseTag,
|
generateRefsResponseTag,
|
||||||
generateTag,
|
generateTag,
|
||||||
|
generateTagsFromAssetSystem,
|
||||||
generateTagsFromSystem,
|
generateTagsFromSystem,
|
||||||
} from "../../../utils/generateTag"
|
} from "../../../utils/generateTag"
|
||||||
import { collectionPageRefsSchema } from "./output"
|
import { collectionPageRefsSchema } from "./output"
|
||||||
@@ -84,6 +85,7 @@ export function generatePageTags(
|
|||||||
const connections = getConnections(validatedData)
|
const connections = getConnections(validatedData)
|
||||||
return [
|
return [
|
||||||
generateTagsFromSystem(lang, connections),
|
generateTagsFromSystem(lang, connections),
|
||||||
|
generateTagsFromAssetSystem(connections),
|
||||||
generateTag(lang, validatedData.collection_page.system.uid),
|
generateTag(lang, validatedData.collection_page.system.uid),
|
||||||
].flat()
|
].flat()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import {
|
|||||||
teaserCardsSchema,
|
teaserCardsSchema,
|
||||||
} from "../schemas/sidebar/teaserCard"
|
} from "../schemas/sidebar/teaserCard"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
import { transformedVideoSchema, videoRefSchema } from "../schemas/video"
|
||||||
|
|
||||||
// Block schemas
|
// Block schemas
|
||||||
export const contentPageCards = z
|
export const contentPageCards = z
|
||||||
@@ -194,6 +195,7 @@ const navigationLinksSchema = z
|
|||||||
export const contentPageSchema = z.object({
|
export const contentPageSchema = z.object({
|
||||||
content_page: z.object({
|
content_page: z.object({
|
||||||
hero_image: transformedImageVaultAssetSchema,
|
hero_image: transformedImageVaultAssetSchema,
|
||||||
|
hero_video: transformedVideoSchema,
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
||||||
sidebar: discriminatedUnionArray(sidebarSchema.options).nullable(),
|
sidebar: discriminatedUnionArray(sidebarSchema.options).nullable(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@@ -323,6 +325,7 @@ const contentPageHeaderRefs = z.object({
|
|||||||
|
|
||||||
export const contentPageRefsSchema = z.object({
|
export const contentPageRefsSchema = z.object({
|
||||||
content_page: z.object({
|
content_page: z.object({
|
||||||
|
hero_video: videoRefSchema.nullish(),
|
||||||
header: contentPageHeaderRefs,
|
header: contentPageHeaderRefs,
|
||||||
blocks: discriminatedUnionArray(
|
blocks: discriminatedUnionArray(
|
||||||
contentPageBlockRefsItem.options
|
contentPageBlockRefsItem.options
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ContentPageEnum } from "../../../types/contentPage"
|
|||||||
import {
|
import {
|
||||||
generateRefsResponseTag,
|
generateRefsResponseTag,
|
||||||
generateTag,
|
generateTag,
|
||||||
|
generateTagsFromAssetSystem,
|
||||||
generateTagsFromSystem,
|
generateTagsFromSystem,
|
||||||
} from "../../../utils/generateTag"
|
} from "../../../utils/generateTag"
|
||||||
import { contentPageRefsSchema } from "./output"
|
import { contentPageRefsSchema } from "./output"
|
||||||
@@ -75,12 +76,14 @@ export function generatePageTags(
|
|||||||
const connections = getConnections(validatedData)
|
const connections = getConnections(validatedData)
|
||||||
return [
|
return [
|
||||||
generateTagsFromSystem(lang, connections),
|
generateTagsFromSystem(lang, connections),
|
||||||
|
generateTagsFromAssetSystem(connections),
|
||||||
generateTag(lang, validatedData.content_page.system.uid),
|
generateTag(lang, validatedData.content_page.system.uid),
|
||||||
].flat()
|
].flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConnections({ content_page }: ContentPageRefs) {
|
export function getConnections({ content_page }: ContentPageRefs) {
|
||||||
const connections: System["system"][] = [content_page.system]
|
const connections: System["system"][] = [content_page.system]
|
||||||
|
|
||||||
if (content_page.blocks) {
|
if (content_page.blocks) {
|
||||||
content_page.blocks.forEach((block) => {
|
content_page.blocks.forEach((block) => {
|
||||||
switch (block.__typename) {
|
switch (block.__typename) {
|
||||||
|
|||||||
@@ -11,3 +11,12 @@ export const systemSchema = z.object({
|
|||||||
export interface System {
|
export interface System {
|
||||||
system: z.output<typeof systemSchema>
|
system: z.output<typeof systemSchema>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const assetSystemSchema = z.object({
|
||||||
|
content_type_uid: z.string(),
|
||||||
|
uid: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface AssetSystem {
|
||||||
|
system: z.output<typeof assetSystemSchema>
|
||||||
|
}
|
||||||
|
|||||||
75
packages/trpc/lib/routers/contentstack/schemas/video.ts
Normal file
75
packages/trpc/lib/routers/contentstack/schemas/video.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
import { focalPointSchema } from "@scandic-hotels/common/utils/focalPoint"
|
||||||
|
|
||||||
|
import { assetSystemSchema } from "./system"
|
||||||
|
|
||||||
|
export const videoSchema = z.object({
|
||||||
|
sourceConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
focal_point: focalPointSchema.nullish(),
|
||||||
|
captions: z.array(
|
||||||
|
z.object({
|
||||||
|
is_default: z.boolean(),
|
||||||
|
fileConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
language: z.nativeEnum(Lang),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const transformedVideoSchema = videoSchema
|
||||||
|
.nullish()
|
||||||
|
.transform((video) => {
|
||||||
|
const src = video?.sourceConnection.edges[0]?.node.url || ""
|
||||||
|
|
||||||
|
if (!video || !src) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const captions = video.captions
|
||||||
|
.filter((caption) => caption.fileConnection.edges[0]?.node.url)
|
||||||
|
.map((caption) => ({
|
||||||
|
src: caption.fileConnection.edges[0]?.node.url || "",
|
||||||
|
srcLang: caption.language,
|
||||||
|
isDefault: caption.is_default,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
src,
|
||||||
|
focalPoint: video.focal_point
|
||||||
|
? { x: video.focal_point.x, y: video.focal_point.y }
|
||||||
|
: { x: 50, y: 50 },
|
||||||
|
captions,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const videoRefSchema = z.object({
|
||||||
|
sourceConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
system: assetSystemSchema,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type Video = z.output<typeof transformedVideoSchema>
|
||||||
|
export type VideoCaptions = NonNullable<Video>["captions"]
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
|
||||||
import type { System } from "../routers/contentstack/schemas/system"
|
import type {
|
||||||
|
AssetSystem,
|
||||||
|
System,
|
||||||
|
} from "../routers/contentstack/schemas/system"
|
||||||
import type { Edges } from "../types/edges"
|
import type { Edges } from "../types/edges"
|
||||||
import type { NodeRefs } from "../types/refs"
|
import type { NodeRefs } from "../types/refs"
|
||||||
|
|
||||||
@@ -85,6 +88,14 @@ export function generateTagsFromSystem(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateTagsFromAssetSystem(
|
||||||
|
connections: AssetSystem["system"][]
|
||||||
|
) {
|
||||||
|
return connections.map((system) => {
|
||||||
|
return generateTag(Lang.en, system.content_type_uid, system.uid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to generate tags for loyalty configuration models
|
* Function to generate tags for loyalty configuration models
|
||||||
*
|
*
|
||||||
|
|||||||
BIN
shared/fonts/material-symbols/rounded-51f6e9ff.woff2
Normal file
BIN
shared/fonts/material-symbols/rounded-51f6e9ff.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user