From 7ee76992befa7b2192745578daea064f41e90e8a Mon Sep 17 00:00:00 2001 From: "Chuma Mcphoy (We Ahead)" Date: Wed, 24 Sep 2025 14:36:31 +0000 Subject: [PATCH] Merged in feat/LOY-361-Promo-Campaign-Hero (pull request #2857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feat(LOY-363): Promo Campaign Hero * feat(LOY-361): Add Promo Campaign Hero * feat(LOY-361): auth cta's wip * fix(LOY-361): improve hero card css * fix(LOY-361): correct size for button * fix(LOY-361): Make Promo Hero Required * fix(LOY-361): semantic css classes Approved-by: Matilda Landström --- .../Hero/ActivateOfferButton/index.tsx | 24 +++ .../PromoCampaignPage/Hero/hero.module.css | 135 +++++++++++++++++ .../PromoCampaignPage/Hero/index.tsx | 143 ++++++++++++++++++ .../ContentType/PromoCampaignPage/index.tsx | 4 +- .../PromoCampaignPage.graphql | 5 + .../contentstack/promoCampaignPage/output.ts | 11 ++ packages/trpc/lib/types/promoCampaignPage.ts | 2 + 7 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/ActivateOfferButton/index.tsx create mode 100644 apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/hero.module.css create mode 100644 apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/index.tsx diff --git a/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/ActivateOfferButton/index.tsx b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/ActivateOfferButton/index.tsx new file mode 100644 index 000000000..fe16c1ce8 --- /dev/null +++ b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/ActivateOfferButton/index.tsx @@ -0,0 +1,24 @@ +"use client" + +import { useIntl } from "react-intl" + +import { Button } from "@scandic-hotels/design-system/Button" + +import styles from "../hero.module.css" + +// TODO: Trigger acivation. +export default function ActivateOfferButton() { + const intl = useIntl() + return ( + + ) +} diff --git a/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/hero.module.css b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/hero.module.css new file mode 100644 index 000000000..b398bdf23 --- /dev/null +++ b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/hero.module.css @@ -0,0 +1,135 @@ +.hero { + --card-float-offset: -170px; + --card-width: 90%; + --card-radius: var(--Corner-radius-Large); + + position: relative; + display: block; + min-height: 478px; + border-radius: var(--card-radius); + overflow: hidden; + margin-bottom: var(--card-float-offset); +} + +.imageContainer { + overflow: hidden; + width: 100%; + height: 478px; + position: relative; + border-radius: var(--card-radius); +} + +.image { + object-fit: cover; +} + +.cardSection { + display: grid; +} + +.heroLoggedIn .cardSection { + grid-template-rows: 1fr; +} + +.heroLoggedOut .cardSection { + grid-template-rows: 1fr 1fr; +} + +.card { + width: var(--card-width); + margin-inline: auto; + padding: var(--Space-x3); + transform: translateY(var(--card-float-offset)); + display: grid; + gap: var(--Space-x2); + align-content: center; +} + +.benefitsCard { + background-color: var(--Surface-Brand-Accent-Default); +} + +@media (max-width: 767px) { + .cardSection > .benefitsCard:only-child { + border-radius: var(--card-radius); + } + + .cardSection > .benefitsCard:not(:only-child) { + border-radius: var(--card-radius) var(--card-radius) 0 0; + } +} + +.authCard { + background-color: var(--Surface-Brand-Primary-3-Default); + border-radius: 0 0 var(--card-radius) var(--card-radius); +} + +.heading { + color: var(--Text-Brand-OnAccent-Heading); +} + +.benefitList { + display: grid; + list-style-type: none; + gap: var(--Space-x05); +} + +.benefitList > li { + display: flex; + gap: var(--Space-x1); + color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary); +} + +.text { + color: var(--Text-Brand-OnPrimary-1-Default); +} + +.activateButton { + width: 100%; +} + +.authHeading { + color: var(--Text-Inverted); +} + +.orSection { + display: flex; + align-items: center; + gap: var(--Space-x2); +} + +.orText { + color: var(--Text-Brand-OnPrimary-3-Default); + margin: 0; + white-space: nowrap; +} + +@media (min-width: 768px) { + .hero { + --card-float-offset: 0; + --card-width: 388px; + + grid-template-columns: 1fr var(--card-width); + display: grid; + } + + .imageContainer { + height: 100%; + border-radius: 0; + } + + .card { + width: 100%; + margin: 0; + transform: none; + } + + .benefitsCard { + border-radius: 0; + padding: var(--Space-x7) var(--Space-x3); + } + + .authCard { + border-radius: 0; + } +} diff --git a/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/index.tsx b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/index.tsx new file mode 100644 index 000000000..37a43b8ae --- /dev/null +++ b/apps/scandic-web/components/ContentType/PromoCampaignPage/Hero/index.tsx @@ -0,0 +1,143 @@ +import { cx } from "class-variance-authority" + +import { login } from "@scandic-hotels/common/constants/routes/handleAuth" +import { signup } from "@scandic-hotels/common/constants/routes/signup" +import ButtonLink from "@scandic-hotels/design-system/ButtonLink" +import { Divider } from "@scandic-hotels/design-system/Divider" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import Image from "@scandic-hotels/design-system/Image" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" +import { isLoggedInUser } from "@/utils/isLoggedInUser" + +import ActivateOfferButton from "./ActivateOfferButton" + +import styles from "./hero.module.css" + +import type { PromoHero } from "@scandic-hotels/trpc/types/promoCampaignPage" + +interface PromoCampaignHeroProps extends React.HTMLAttributes { + promoHero: PromoHero +} + +export default async function PromoCampaignHero({ + promoHero, + className, + ...props +}: PromoCampaignHeroProps) { + const { image, heading, benefits } = promoHero + const isLoggedIn = await isLoggedInUser() + const intl = await getIntl() + const lang = await getLang() + + return ( +
+ {image ? ( +
+ {image.meta.alt +
+ ) : null} + +
+
+ +

{heading}

+
+ {benefits?.length ? ( +
    + {benefits.map((benefit) => ( +
  • + + + {benefit} + +
  • + ))} +
+ ) : null} + + {isLoggedIn && ( +
+ {/* TODO: Account for more activation states. */} + +
+ )} +
+ + {!isLoggedIn && ( +
+ +

+ {intl.formatMessage({ + defaultMessage: "TO ACTIVATE OFFER", + })} +

+
+ + {intl.formatMessage({ + defaultMessage: "Login", + })} + +
+ + + + {intl.formatMessage({ + defaultMessage: "or", + })} + + + +
+ + {intl.formatMessage({ + defaultMessage: "Sign up", + })} + +
+ )} +
+
+ ) +} diff --git a/apps/scandic-web/components/ContentType/PromoCampaignPage/index.tsx b/apps/scandic-web/components/ContentType/PromoCampaignPage/index.tsx index fbb9ed954..70e1970f2 100644 --- a/apps/scandic-web/components/ContentType/PromoCampaignPage/index.tsx +++ b/apps/scandic-web/components/ContentType/PromoCampaignPage/index.tsx @@ -6,6 +6,7 @@ import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" import { getPromoCampaignPage } from "@/lib/trpc/memoizedRequests" +import PromoCampaignHero from "./Hero" import PromoCampaignPageSkeleton from "./PromoCampaignPageSkeleton" import styles from "./promoCampaignPage.module.css" @@ -17,12 +18,13 @@ export default async function PromoCampaignPage() { } //const isUserLoggedIn = await isLoggedInUser() const { promo_campaign_page, tracking } = pageData - const { heading, subheading } = promo_campaign_page + const { heading, subheading, promo_hero } = promo_campaign_page return ( <> }>
+
diff --git a/packages/trpc/lib/graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql b/packages/trpc/lib/graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql index cd5b8253f..09e54a7ea 100644 --- a/packages/trpc/lib/graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql +++ b/packages/trpc/lib/graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql @@ -13,6 +13,11 @@ query GetPromoCampaignPage($locale: String!, $uid: String!) { title heading subheading + promo_hero { + image + heading + benefits + } page_settings { booking_code } diff --git a/packages/trpc/lib/routers/contentstack/promoCampaignPage/output.ts b/packages/trpc/lib/routers/contentstack/promoCampaignPage/output.ts index 300c6a226..c4e85ebbd 100644 --- a/packages/trpc/lib/routers/contentstack/promoCampaignPage/output.ts +++ b/packages/trpc/lib/routers/contentstack/promoCampaignPage/output.ts @@ -1,5 +1,6 @@ import { z } from "zod" +import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault" import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/stringValidator" import { systemSchema } from "../schemas/system" @@ -9,12 +10,22 @@ export const CAMPAIGN_TYPES = { POINT: "POINT", } as const +export const promoHeroSchema = z.object({ + image: transformedImageVaultAssetSchema, + heading: z.string(), + benefits: z + .array(z.string()) + .nullish() + .transform((data) => data || []), +}) + export const promoCampaignPageSchema = z .object({ promo_campaign_page: z.object({ title: z.string(), heading: z.string(), subheading: z.string().nullish(), + promo_hero: promoHeroSchema, page_settings: z .object({ booking_code: z.string().nullish(), diff --git a/packages/trpc/lib/types/promoCampaignPage.ts b/packages/trpc/lib/types/promoCampaignPage.ts index 866239c22..c7157f217 100644 --- a/packages/trpc/lib/types/promoCampaignPage.ts +++ b/packages/trpc/lib/types/promoCampaignPage.ts @@ -21,6 +21,8 @@ export interface PromoCampaignPage export type PromoCampaignPageData = PromoCampaignPage["promo_campaign_page"] +export type PromoHero = NonNullable + /* REFS */ export interface GetPromoCampaignPageRefsData extends z.input {}