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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: grid;
|
||||
gap: var(--Space-x5);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getCampaignPage } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Blocks from "./Blocks"
|
||||
import CampaignPageSkeleton from "./CampaignPageSkeleton"
|
||||
import CampaignHero from "./Hero"
|
||||
|
||||
import styles from "./campaignPage.module.css"
|
||||
|
||||
@@ -19,14 +20,13 @@ export default async function CampaignPage() {
|
||||
}
|
||||
|
||||
const { campaignPage } = pageData
|
||||
const { heading, subheading, preamble, blocks } = campaignPage
|
||||
const { heading, subheading, preamble, blocks, hero } = campaignPage
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<CampaignPageSkeleton />}>
|
||||
<div className={styles.pageContainer}>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<div className={styles.hero}>---PLACE FOR THE HERO---</div>
|
||||
<CampaignHero {...hero} />
|
||||
<div className={styles.intro}>
|
||||
<div className={styles.headingWrapper}>
|
||||
<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/Essentials.graphql"
|
||||
#import "../../Fragments/Blocks/CarouselCards.graphql"
|
||||
#import "../../Fragments/CampaignPage/Hero.graphql"
|
||||
|
||||
query GetCampaignPage($locale: String!, $uid: String!) {
|
||||
campaign_page(uid: $uid, locale: $locale) {
|
||||
@@ -26,6 +27,7 @@ query GetCampaignPage($locale: String!, $uid: String!) {
|
||||
created_at
|
||||
updated_at
|
||||
}
|
||||
...Hero_CampaignPage
|
||||
}
|
||||
trackingProps: campaign_page(locale: "en", uid: $uid) {
|
||||
url
|
||||
@@ -39,6 +41,7 @@ query GetCampaignPageRefs($locale: String!, $uid: String!) {
|
||||
...CarouselCards_CampaignPageRefs
|
||||
...Accordion_CampaignPageRefs
|
||||
}
|
||||
...HeroRef_CampaignPage
|
||||
system {
|
||||
...System
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
carouselCardsSchema,
|
||||
} from "../schemas/blocks/carouselCards"
|
||||
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
|
||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||
import {
|
||||
linkConnectionRefs,
|
||||
linkConnectionSchema,
|
||||
} from "../schemas/linkConnection"
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
import { CampaignPageEnum } from "@/types/enums/campaignPage"
|
||||
@@ -39,10 +44,25 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
||||
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({
|
||||
campaign_page: z.object({
|
||||
title: z.string(),
|
||||
campaign_identifier: z.string().nullish(),
|
||||
hero: heroSchema,
|
||||
heading: z.string(),
|
||||
subheading: z.string().nullish(),
|
||||
preamble: z.object({
|
||||
@@ -80,9 +100,13 @@ const campaignPageBlockRefsItem = z.discriminatedUnion("__typename", [
|
||||
campaignPageCarouselCardsRef,
|
||||
campaignPageAccordionRefs,
|
||||
])
|
||||
const heroRefsSchema = z.object({
|
||||
button: linkConnectionRefs,
|
||||
})
|
||||
|
||||
export const campaignPageRefsSchema = z.object({
|
||||
campaign_page: z.object({
|
||||
hero: heroRefsSchema,
|
||||
blocks: discriminatedUnionArray(
|
||||
campaignPageBlockRefsItem.options
|
||||
).nullable(),
|
||||
|
||||
@@ -46,7 +46,6 @@ export const campaignPageQueryRouter = router({
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
if (!refsResponse.data) {
|
||||
const notFoundError = notFound(refsResponse)
|
||||
metricsGetCampaignPageRefs.noDataError()
|
||||
|
||||
@@ -173,6 +173,7 @@ export function transformPageLink(data: Data) {
|
||||
title: data.title,
|
||||
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
|
||||
}
|
||||
|
||||
case ContentEnum.blocks.CollectionPage:
|
||||
case ContentEnum.blocks.ContentPage:
|
||||
case ContentEnum.blocks.LoyaltyPage:
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
blocksSchema,
|
||||
campaignPageRefsSchema,
|
||||
campaignPageSchema,
|
||||
heroSchema,
|
||||
} from "@/server/routers/contentstack/campaignPage/output"
|
||||
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 EssentialsBlock = z.output<typeof essentialsSchema>["essentials"]
|
||||
|
||||
export type Hero = z.output<typeof heroSchema>
|
||||
|
||||
@@ -36,3 +36,7 @@
|
||||
.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,
|
||||
'Border/Divider/Default': styles['Border-Divider-Default'],
|
||||
'Border/Divider/Subtle': styles['Border-Divider-Subtle'],
|
||||
'Surface/Brand/Primary 1/OnSurface/Accent Secondary':
|
||||
styles['Surface-Brand-Primary-1-OnSurface-Accent-Secondary'],
|
||||
},
|
||||
variant: {
|
||||
horizontal: styles.horizontal,
|
||||
|
||||
Reference in New Issue
Block a user