Merged in feat/SW-2266-campaign-hero (pull request #2344)
Feat/SW-2266 campaign hero Approved-by: Erik Tiekstra
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
.content {
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
transform: translateY(-170px);
|
||||||
|
margin: 0 auto -170px;
|
||||||
|
width: 90%;
|
||||||
|
padding: var(--Space-x3);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 478px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefitList {
|
||||||
|
display: grid;
|
||||||
|
list-style-type: none;
|
||||||
|
gap: var(--Space-x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefitList > li {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peach {
|
||||||
|
.content {
|
||||||
|
background-color: var(--Surface-Brand-Accent-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
color: var(--Text-Brand-OnAccent-Heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: var(--Text-Brand-OnPrimary-1-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefitList > li {
|
||||||
|
color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.burgundy {
|
||||||
|
.content {
|
||||||
|
background-color: var(--Surface-Brand-Primary-3-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
color: var(--Text-Brand-OnPrimary-3-Accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefitList > li,
|
||||||
|
.text {
|
||||||
|
color: var(--Text-Brand-OnPrimary-3-Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
transform: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
grid-column: span 2;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||||
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import ButtonLink from "@/components/ButtonLink"
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
|
||||||
|
import { variants } from "./variants"
|
||||||
|
|
||||||
|
import styles from "./hero.module.css"
|
||||||
|
|
||||||
|
import type { HeroProps } from "./types"
|
||||||
|
|
||||||
|
export default async function CampaignHero({
|
||||||
|
image,
|
||||||
|
heading,
|
||||||
|
benefits,
|
||||||
|
rate_text,
|
||||||
|
button,
|
||||||
|
theme,
|
||||||
|
}: HeroProps) {
|
||||||
|
const classNames = variants({
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<header className={classNames}>
|
||||||
|
{image ? (
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<Image
|
||||||
|
src={image.url}
|
||||||
|
alt={image.meta.alt || image.meta.caption || ""}
|
||||||
|
className={styles.image}
|
||||||
|
fill
|
||||||
|
sizes="(min-width: 768px) 413px, 100vw"
|
||||||
|
focalPoint={image.focalPoint}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Typography variant="Title/xs" className={styles.heading}>
|
||||||
|
<h2>{heading}</h2>
|
||||||
|
</Typography>
|
||||||
|
{benefits?.length ? (
|
||||||
|
<ul className={styles.benefitList}>
|
||||||
|
{benefits.map((benefit) => (
|
||||||
|
<li key={benefit}>
|
||||||
|
<MaterialIcon
|
||||||
|
icon="check_circle"
|
||||||
|
color="CurrentColor"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="Body/Paragraph/mdRegular"
|
||||||
|
className={styles.text}
|
||||||
|
>
|
||||||
|
<span>{benefit}</span>
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
<Divider
|
||||||
|
color={
|
||||||
|
theme === "Peach"
|
||||||
|
? "Surface/Brand/Primary 1/OnSurface/Accent Secondary"
|
||||||
|
: "white"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{rate_text ? (
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{rate_text.bold_text ? (
|
||||||
|
<Typography variant="Title/Subtitle/lg">
|
||||||
|
<span>{rate_text.bold_text}</span>
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
{rate_text.bold_text && rate_text.text ? " " : null}
|
||||||
|
{rate_text.text ? (
|
||||||
|
<Typography variant="Tag/sm">
|
||||||
|
<span>{rate_text.text}</span>
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{button.link ? (
|
||||||
|
<ButtonLink
|
||||||
|
variant="Secondary"
|
||||||
|
color={theme === "Peach" ? "Primary" : "Inverted"}
|
||||||
|
href={button.link.url}
|
||||||
|
typography="Body/Paragraph/mdBold"
|
||||||
|
size="Medium"
|
||||||
|
>
|
||||||
|
{button.cta}
|
||||||
|
</ButtonLink>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import type { Hero } from "@/types/trpc/routers/contentstack/campaignPage"
|
||||||
|
import type { variants } from "./variants"
|
||||||
|
|
||||||
|
export interface HeroProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof variants>,
|
||||||
|
Omit<Hero, "theme"> {}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import styles from "./hero.module.css"
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
variants: {
|
||||||
|
theme: {
|
||||||
|
Peach: styles.peach,
|
||||||
|
Burgundy: styles.burgundy,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
theme: "Peach",
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const variants = cva(styles.hero, config)
|
||||||
@@ -6,16 +6,6 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
|
||||||
/* Temporary styles until we add the hero */
|
|
||||||
width: 100%;
|
|
||||||
height: 478px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--Surface-Brand-Accent-Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Space-x5);
|
gap: var(--Space-x5);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getCampaignPage } from "@/lib/trpc/memoizedRequests"
|
|||||||
|
|
||||||
import Blocks from "./Blocks"
|
import Blocks from "./Blocks"
|
||||||
import CampaignPageSkeleton from "./CampaignPageSkeleton"
|
import CampaignPageSkeleton from "./CampaignPageSkeleton"
|
||||||
|
import CampaignHero from "./Hero"
|
||||||
|
|
||||||
import styles from "./campaignPage.module.css"
|
import styles from "./campaignPage.module.css"
|
||||||
|
|
||||||
@@ -19,14 +20,13 @@ export default async function CampaignPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { campaignPage } = pageData
|
const { campaignPage } = pageData
|
||||||
const { heading, subheading, preamble, blocks } = campaignPage
|
const { heading, subheading, preamble, blocks, hero } = campaignPage
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense fallback={<CampaignPageSkeleton />}>
|
<Suspense fallback={<CampaignPageSkeleton />}>
|
||||||
<div className={styles.pageContainer}>
|
<div className={styles.pageContainer}>
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
<CampaignHero {...hero} />
|
||||||
<div className={styles.hero}>---PLACE FOR THE HERO---</div>
|
|
||||||
<div className={styles.intro}>
|
<div className={styles.intro}>
|
||||||
<div className={styles.headingWrapper}>
|
<div className={styles.headingWrapper}>
|
||||||
<Typography variant="Title/lg">
|
<Typography variant="Title/lg">
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#import "../PageLink/AccountPageLink.graphql"
|
||||||
|
#import "../PageLink/CampaignPageLink.graphql"
|
||||||
|
#import "../PageLink/CollectionPageLink.graphql"
|
||||||
|
#import "../PageLink/ContentPageLink.graphql"
|
||||||
|
#import "../PageLink/DestinationCityPageLink.graphql"
|
||||||
|
#import "../PageLink/DestinationCountryPageLink.graphql"
|
||||||
|
#import "../PageLink/DestinationOverviewPageLink.graphql"
|
||||||
|
#import "../PageLink/HotelPageLink.graphql"
|
||||||
|
#import "../PageLink/LoyaltyPageLink.graphql"
|
||||||
|
#import "../PageLink/StartPageLink.graphql"
|
||||||
|
|
||||||
|
#import "../AccountPage/Ref.graphql"
|
||||||
|
#import "../CampaignPage/Ref.graphql"
|
||||||
|
#import "../CollectionPage/Ref.graphql"
|
||||||
|
#import "../ContentPage/Ref.graphql"
|
||||||
|
#import "../DestinationCityPage/Ref.graphql"
|
||||||
|
#import "../DestinationCountryPage/Ref.graphql"
|
||||||
|
#import "../DestinationOverviewPage/Ref.graphql"
|
||||||
|
#import "../HotelPage/Ref.graphql"
|
||||||
|
#import "../LoyaltyPage/Ref.graphql"
|
||||||
|
#import "../StartPage/Ref.graphql"
|
||||||
|
|
||||||
|
fragment Hero_CampaignPage on CampaignPage {
|
||||||
|
hero {
|
||||||
|
image
|
||||||
|
heading
|
||||||
|
theme
|
||||||
|
benefits
|
||||||
|
rate_text {
|
||||||
|
bold_text
|
||||||
|
text
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
cta
|
||||||
|
linkConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
...AccountPageLink
|
||||||
|
...CampaignPageLink
|
||||||
|
...CollectionPageLink
|
||||||
|
...ContentPageLink
|
||||||
|
...DestinationCityPageLink
|
||||||
|
...DestinationCountryPageLink
|
||||||
|
...DestinationOverviewPageLink
|
||||||
|
...HotelPageLink
|
||||||
|
...LoyaltyPageLink
|
||||||
|
...StartPageLink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment HeroRef_CampaignPage on CampaignPage {
|
||||||
|
hero {
|
||||||
|
button {
|
||||||
|
linkConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
...AccountPageRef
|
||||||
|
...CampaignPageRef
|
||||||
|
...CollectionPageRef
|
||||||
|
...ContentPageRef
|
||||||
|
...DestinationCityPageRef
|
||||||
|
...DestinationCountryPageRef
|
||||||
|
...DestinationOverviewPageRef
|
||||||
|
...HotelPageRef
|
||||||
|
...LoyaltyPageRef
|
||||||
|
...StartPageRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#import "../../Fragments/Blocks/Accordion.graphql"
|
#import "../../Fragments/Blocks/Accordion.graphql"
|
||||||
#import "../../Fragments/Blocks/Essentials.graphql"
|
#import "../../Fragments/Blocks/Essentials.graphql"
|
||||||
#import "../../Fragments/Blocks/CarouselCards.graphql"
|
#import "../../Fragments/Blocks/CarouselCards.graphql"
|
||||||
|
#import "../../Fragments/CampaignPage/Hero.graphql"
|
||||||
|
|
||||||
query GetCampaignPage($locale: String!, $uid: String!) {
|
query GetCampaignPage($locale: String!, $uid: String!) {
|
||||||
campaign_page(uid: $uid, locale: $locale) {
|
campaign_page(uid: $uid, locale: $locale) {
|
||||||
@@ -26,6 +27,7 @@ query GetCampaignPage($locale: String!, $uid: String!) {
|
|||||||
created_at
|
created_at
|
||||||
updated_at
|
updated_at
|
||||||
}
|
}
|
||||||
|
...Hero_CampaignPage
|
||||||
}
|
}
|
||||||
trackingProps: campaign_page(locale: "en", uid: $uid) {
|
trackingProps: campaign_page(locale: "en", uid: $uid) {
|
||||||
url
|
url
|
||||||
@@ -39,6 +41,7 @@ query GetCampaignPageRefs($locale: String!, $uid: String!) {
|
|||||||
...CarouselCards_CampaignPageRefs
|
...CarouselCards_CampaignPageRefs
|
||||||
...Accordion_CampaignPageRefs
|
...Accordion_CampaignPageRefs
|
||||||
}
|
}
|
||||||
|
...HeroRef_CampaignPage
|
||||||
system {
|
system {
|
||||||
...System
|
...System
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import {
|
|||||||
carouselCardsSchema,
|
carouselCardsSchema,
|
||||||
} from "../schemas/blocks/carouselCards"
|
} from "../schemas/blocks/carouselCards"
|
||||||
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
|
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
|
||||||
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
|
import {
|
||||||
|
linkConnectionRefs,
|
||||||
|
linkConnectionSchema,
|
||||||
|
} from "../schemas/linkConnection"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
import { CampaignPageEnum } from "@/types/enums/campaignPage"
|
import { CampaignPageEnum } from "@/types/enums/campaignPage"
|
||||||
@@ -39,10 +44,25 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
campaignPageAccordion,
|
campaignPageAccordion,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
export const heroSchema = z.object({
|
||||||
|
image: tempImageVaultAssetSchema,
|
||||||
|
heading: z.string(),
|
||||||
|
theme: z.enum(["Peach", "Burgundy"]).default("Peach"),
|
||||||
|
benefits: z.array(z.string()).nullish(),
|
||||||
|
rate_text: z
|
||||||
|
.object({
|
||||||
|
bold_text: z.string().nullish(),
|
||||||
|
text: z.string().nullish(),
|
||||||
|
})
|
||||||
|
.nullish(),
|
||||||
|
button: z.intersection(z.object({ cta: z.string() }), linkConnectionSchema),
|
||||||
|
})
|
||||||
|
|
||||||
export const campaignPageSchema = z.object({
|
export const campaignPageSchema = z.object({
|
||||||
campaign_page: z.object({
|
campaign_page: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
campaign_identifier: z.string().nullish(),
|
campaign_identifier: z.string().nullish(),
|
||||||
|
hero: heroSchema,
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
subheading: z.string().nullish(),
|
subheading: z.string().nullish(),
|
||||||
preamble: z.object({
|
preamble: z.object({
|
||||||
@@ -80,9 +100,13 @@ const campaignPageBlockRefsItem = z.discriminatedUnion("__typename", [
|
|||||||
campaignPageCarouselCardsRef,
|
campaignPageCarouselCardsRef,
|
||||||
campaignPageAccordionRefs,
|
campaignPageAccordionRefs,
|
||||||
])
|
])
|
||||||
|
const heroRefsSchema = z.object({
|
||||||
|
button: linkConnectionRefs,
|
||||||
|
})
|
||||||
|
|
||||||
export const campaignPageRefsSchema = z.object({
|
export const campaignPageRefsSchema = z.object({
|
||||||
campaign_page: z.object({
|
campaign_page: z.object({
|
||||||
|
hero: heroRefsSchema,
|
||||||
blocks: discriminatedUnionArray(
|
blocks: discriminatedUnionArray(
|
||||||
campaignPageBlockRefsItem.options
|
campaignPageBlockRefsItem.options
|
||||||
).nullable(),
|
).nullable(),
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export const campaignPageQueryRouter = router({
|
|||||||
ttl: "max",
|
ttl: "max",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!refsResponse.data) {
|
if (!refsResponse.data) {
|
||||||
const notFoundError = notFound(refsResponse)
|
const notFoundError = notFound(refsResponse)
|
||||||
metricsGetCampaignPageRefs.noDataError()
|
metricsGetCampaignPageRefs.noDataError()
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ export function transformPageLink(data: Data) {
|
|||||||
title: data.title,
|
title: data.title,
|
||||||
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
|
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentEnum.blocks.CollectionPage:
|
case ContentEnum.blocks.CollectionPage:
|
||||||
case ContentEnum.blocks.ContentPage:
|
case ContentEnum.blocks.ContentPage:
|
||||||
case ContentEnum.blocks.LoyaltyPage:
|
case ContentEnum.blocks.LoyaltyPage:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
blocksSchema,
|
blocksSchema,
|
||||||
campaignPageRefsSchema,
|
campaignPageRefsSchema,
|
||||||
campaignPageSchema,
|
campaignPageSchema,
|
||||||
|
heroSchema,
|
||||||
} from "@/server/routers/contentstack/campaignPage/output"
|
} from "@/server/routers/contentstack/campaignPage/output"
|
||||||
import type { essentialsSchema } from "@/server/routers/contentstack/schemas/blocks/essentials"
|
import type { essentialsSchema } from "@/server/routers/contentstack/schemas/blocks/essentials"
|
||||||
|
|
||||||
@@ -21,3 +22,5 @@ export interface CampaignPageRefs
|
|||||||
export type Block = z.output<typeof blocksSchema>
|
export type Block = z.output<typeof blocksSchema>
|
||||||
|
|
||||||
export type EssentialsBlock = z.output<typeof essentialsSchema>["essentials"]
|
export type EssentialsBlock = z.output<typeof essentialsSchema>["essentials"]
|
||||||
|
|
||||||
|
export type Hero = z.output<typeof heroSchema>
|
||||||
|
|||||||
@@ -36,3 +36,7 @@
|
|||||||
.Border-Divider-Default {
|
.Border-Divider-Default {
|
||||||
background-color: var(--Border-Divider-Default);
|
background-color: var(--Border-Divider-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Surface-Brand-Primary-1-OnSurface-Accent-Secondary {
|
||||||
|
background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const dividerVariants = cva(styles.divider, {
|
|||||||
white: styles.white,
|
white: styles.white,
|
||||||
'Border/Divider/Default': styles['Border-Divider-Default'],
|
'Border/Divider/Default': styles['Border-Divider-Default'],
|
||||||
'Border/Divider/Subtle': styles['Border-Divider-Subtle'],
|
'Border/Divider/Subtle': styles['Border-Divider-Subtle'],
|
||||||
|
'Surface/Brand/Primary 1/OnSurface/Accent Secondary':
|
||||||
|
styles['Surface-Brand-Primary-1-OnSurface-Accent-Secondary'],
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
horizontal: styles.horizontal,
|
horizontal: styles.horizontal,
|
||||||
|
|||||||
Reference in New Issue
Block a user