Merged in feature/tier-matching-component (pull request #1175)

SAS Tier matching comparison block (SW-921)

Approved-by: Joakim Jäderberg
Approved-by: Matilda Landström
This commit is contained in:
Anton Gunnarsson
2025-01-22 08:15:31 +00:00
parent dc115ca368
commit eacca33847
13 changed files with 416 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ import ShortcutsList from "@/components/Blocks/ShortcutsList"
import TextCols from "@/components/Blocks/TextCols"
import UspGrid from "@/components/Blocks/UspGrid"
import JsonToHtml from "@/components/JsonToHtml"
import { SasTierComparison } from "@/components/SasTierComparison"
import AccordionSection from "./Accordion"
import HotelListing from "./HotelListing"
@@ -90,7 +91,13 @@ export default function Blocks({ blocks }: BlocksProps) {
)
case BlocksEnums.block.UspGrid:
return <UspGrid usp_grid={block.usp_grid} />
case BlocksEnums.block.SasTierComparison:
return (
<SasTierComparison
content={block.sas_tier_comparison}
firstItem={firstItem}
/>
)
default:
return null
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CompareArrowsIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
{...props}
>
<path
d="M11.1498 9.61251C11.1498 9.35418 11.2415 9.13335 11.4248 8.95001C11.6081 8.76668 11.829 8.67501 12.0873 8.67501L18.1748 8.67501L16.2373 6.73751C16.054 6.55418 15.9623 6.33543 15.9623 6.08126C15.9623 5.8271 16.054 5.60418 16.2373 5.41251C16.429 5.22918 16.6519 5.13751 16.9061 5.13751C17.1602 5.13751 17.3831 5.23335 17.5748 5.42501L21.0873 8.95001C21.179 9.04168 21.2477 9.14376 21.2936 9.25626C21.3394 9.36876 21.3623 9.48751 21.3623 9.61251C21.3623 9.73751 21.3394 9.85626 21.2936 9.96876C21.2477 10.0813 21.179 10.1833 21.0873 10.275L17.5623 13.8C17.3706 13.9917 17.1519 14.0854 16.9061 14.0813C16.6602 14.0771 16.4415 13.9833 16.2498 13.8C16.0665 13.6083 15.9727 13.3875 15.9686 13.1375C15.9644 12.8875 16.0581 12.6667 16.2498 12.475L18.1748 10.55L12.0873 10.55C11.829 10.55 11.6081 10.4583 11.4248 10.275C11.2415 10.0917 11.1498 9.87084 11.1498 9.61251ZM2.6373 15.4C2.6373 15.275 2.66022 15.1563 2.70605 15.0438C2.75189 14.9313 2.82064 14.8292 2.9123 14.7375L6.4498 11.2C6.64147 11.0083 6.86022 10.9146 7.10605 10.9188C7.35189 10.9229 7.57064 11.0167 7.7623 11.2C7.94564 11.3917 8.03939 11.6125 8.04355 11.8625C8.04772 12.1125 7.95397 12.3333 7.7623 12.525L5.8248 14.4625L11.9123 14.4625C12.1706 14.4625 12.3915 14.5542 12.5748 14.7375C12.7581 14.9208 12.8498 15.1417 12.8498 15.4C12.8498 15.6583 12.7581 15.8792 12.5748 16.0625C12.3915 16.2458 12.1706 16.3375 11.9123 16.3375L5.8248 16.3375L7.7498 18.2625C7.93314 18.4458 8.0248 18.6646 8.0248 18.9188C8.0248 19.1729 7.93314 19.3958 7.7498 19.5875C7.55814 19.7708 7.33522 19.8625 7.08105 19.8625C6.82689 19.8625 6.60397 19.7667 6.4123 19.575L2.9123 16.0625C2.82064 15.9708 2.75189 15.8688 2.70605 15.7563C2.66022 15.6438 2.6373 15.525 2.6373 15.4Z"
fill="#4D001B"
/>
</svg>
)
}

View File

@@ -51,6 +51,7 @@ export { default as CoffeeIcon } from "./Coffee"
export { default as CoffeeAltIcon } from "./CoffeeAlt"
export { default as CoffeeMakerIcon } from "./CoffeeMaker"
export { default as CoinIcon } from "./Coin"
export { default as CompareArrowsIcon } from "./CompareArrows"
export { default as ConciergeIcon } from "./Concierge"
export { default as ContractIcon } from "./Contract"
export { default as ConvenienceStore24hIcon } from "./ConvenienceStore24h"

View File

@@ -0,0 +1,163 @@
"use client"
import { type Key, useState } from "react"
import Link from "@/components/TempDesignSystem/Link"
import Title from "@/components/TempDesignSystem/Text/Title"
import { ArrowRightIcon, CompareArrowsIcon } from "../Icons"
import SectionContainer from "../Section/Container"
import SectionHeader from "../Section/Header"
import Button from "../TempDesignSystem/Button"
import Select from "../TempDesignSystem/Select"
import styles from "./sas-tier-comparison.module.css"
import type { SasTierComparison } from "@/types/trpc/routers/contentstack/blocks"
type SasTierComparisonContent = SasTierComparison["sas_tier_comparison"]
type TierComparisonProps = {
content: SasTierComparisonContent
firstItem: boolean
}
const scandicSasTierMappings: [string[], string[]][] = [
[["L1", "L2", "L3"], ["Basic"]],
[["L4"], ["Silver"]],
[["L5"], ["Gold"]],
[
["L6", "L7"],
["Diamond", "Pandion"],
],
]
export function SasTierComparison({ content, firstItem }: TierComparisonProps) {
const comparisonContent = content.sasTierComparison
const scandic = comparisonContent?.scandic_friends
const sas = comparisonContent?.sas_eb
const [activeScandicTierCode, setActiveScandicTierCode] = useState(
scandic?.tiers.at(0)?.tier_code ?? ""
)
const [activeSasTierCode, setActiveSasTierCode] = useState(
sas?.tiers.at(0)?.tier_code ?? ""
)
if (!scandic || !sas) return null
const onChangeScandic = (k: Key) => {
const key = k.toString()
setActiveScandicTierCode(k.toString())
const mapping = scandicSasTierMappings.find(([tierMapping]) =>
tierMapping.includes(key)
)
if (!mapping) return
const sasTierCode = mapping[1][0]
setActiveSasTierCode(sasTierCode)
}
const onChangeSas = (k: Key) => {
const key = k.toString()
setActiveSasTierCode(key)
const mapping = scandicSasTierMappings.find(([_, tierMapping]) =>
tierMapping.includes(key)
)
if (!mapping) return
const scandicTierCode = mapping[0][0]
setActiveScandicTierCode(scandicTierCode)
}
return (
<SectionContainer className={styles.comparisonSection}>
<div className={styles.header}>
<SectionHeader title={comparisonContent.title} topTitle={firstItem} />
{comparisonContent.preamble && (
<p className={styles.preamble}>{comparisonContent.preamble}</p>
)}
</div>
<div className={styles.comparisonCard}>
<div className={styles.comparisonContainer}>
<div className={styles.comparisonBrand}>
<Title level="h3" textTransform="regular">
{scandic.title}
</Title>
<Select
name="friends-level"
label={scandic.label}
aria-label={scandic.label}
items={scandic.tiers.map((tier) => ({
label: tier.tier_label,
value: tier.tier_code,
}))}
value={activeScandicTierCode}
onSelect={onChangeScandic}
/>
{scandic.read_more_link && (
<ReadMoreLink
href={scandic.read_more_link.href}
title={scandic.read_more_link.title}
/>
)}
</div>
<div className={styles.comparisonIcon}>
<CompareArrowsIcon width={24} height={24} />
</div>
<div className={styles.comparisonBrand}>
<Title level="h3" textTransform="regular">
{sas.title}
</Title>
<Select
name="eb-level"
label={sas.label}
aria-label={sas.label}
items={sas.tiers.map((tier) => ({
label: tier.tier_label,
value: tier.tier_code,
}))}
value={activeSasTierCode}
onSelect={onChangeSas}
/>
{sas.read_more_link && (
<ReadMoreLink
href={sas.read_more_link.href}
title={sas.read_more_link.title}
/>
)}
</div>
</div>
{comparisonContent.cta && (
<div className={styles.ctaContainer}>
<Button
asChild
intent="primary"
size="small"
theme="base"
className={styles.ctaLink}
>
<Link color="none" href={comparisonContent.cta.href}>
{comparisonContent.cta.title}
</Link>
</Button>
</div>
)}
</div>
</SectionContainer>
)
}
function ReadMoreLink({ href, title }: { href: string; title: string }) {
return (
<Link
href={href}
variant="underscored"
className={styles.link}
color="peach80"
>
{title}
<ArrowRightIcon color="peach80" />
</Link>
)
}

View File

@@ -0,0 +1,72 @@
.comparisonSection {
width: 100%;
gap: var(--Spacing-x4);
}
.header {
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
}
.preamble {
margin: 0;
white-space: pre-wrap;
}
.comparisonCard {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Spacing-x4) var(--Spacing-x3);
border-radius: var(--Corner-radius-Large);
}
.comparisonContainer {
display: flex;
gap: var(--Spacing-x2);
}
.comparisonBrand {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
width: 100%;
}
.comparisonIcon {
background-color: var(--Base-Surface-Secondary-light-Normal);
border-radius: 50%;
width: 48px;
height: 48px;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
margin-top: var(--Spacing-x7);
}
@media screen and (max-width: 767px) {
.comparisonIcon {
display: none;
}
.comparisonContainer {
flex-direction: column;
gap: var(--Spacing-x4);
}
}
.link {
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
}
.ctaContainer {
border-top: 1px solid var(--Base-Border-Subtle);
display: flex;
justify-content: center;
margin-top: var(--Spacing-x4);
}
.ctaLink {
margin-top: var(--Spacing-x4);
}

View File

@@ -0,0 +1,45 @@
fragment SasTierComparison_ContentPage on ContentPageBlocksSasTierComparison {
__typename
sas_tier_comparison {
comparisonConnection {
totalCount
edges {
node {
__typename
... on SasTierComparison {
title
preamble
scandic_friends {
title
label
tiers {
tier_code
tier_label
}
read_more_link {
href
title
}
}
sas_eb {
title
label
tiers {
tier_code
tier_label
}
read_more_link {
href
title
}
}
cta {
href
title
}
}
}
}
}
}
}

View File

@@ -9,6 +9,7 @@
#import "../../Fragments/Blocks/Table.graphql"
#import "../../Fragments/Blocks/TextCols.graphql"
#import "../../Fragments/Blocks/UspGrid.graphql"
#import "../../Fragments/Blocks/SasTierComparison.graphql"
#import "../../Fragments/ContentPage/NavigationLinks.graphql"
#import "../../Fragments/Sidebar/Content.graphql"
@@ -75,6 +76,15 @@ query GetContentPageBlocksBatch2($locale: String!, $uid: String!) {
}
}
query GetContentPageBlocksBatch3($locale: String!, $uid: String!) {
content_page(uid: $uid, locale: $locale) {
blocks {
__typename
...SasTierComparison_ContentPage
}
}
}
query GetContentPageRefs($locale: String!, $uid: String!) {
content_page(locale: $locale, uid: $uid) {
header {

View File

@@ -18,6 +18,7 @@ import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import { sasTierComparisonSchema } from "../schemas/blocks/sasTierComparison"
import { hotelListingSchema } from "../schemas/blocks/hotelListing"
import {
shortcutsRefsSchema,
@@ -104,6 +105,13 @@ export const contentPageAccordion = z
})
.merge(accordionSchema)
export const contentPageLoyaltyTierComparison = z
.object({
__typename: z.literal(
ContentPageEnum.ContentStack.blocks.SasTierComparison
),
})
.merge(sasTierComparisonSchema)
export const contentPageHotelListing = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.HotelListing),
@@ -119,6 +127,7 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
contentPageTable,
contentPageTextCols,
contentPageUspGrid,
contentPageLoyaltyTierComparison,
contentPageHotelListing,
])

View File

@@ -3,6 +3,7 @@ import {
GetContentPage,
GetContentPageBlocksBatch1,
GetContentPageBlocksBatch2,
GetContentPageBlocksBatch3,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
@@ -72,6 +73,17 @@ export const contentPageQueryRouter = router({
},
},
},
{
document: GetContentPageBlocksBatch3,
variables: { locale: lang, uid },
options: {
cache: "force-cache",
next: {
tags,
},
},
},
])
const contentPage = contentPageSchema.safeParse(contentPageRequest.data)

View File

@@ -0,0 +1,64 @@
import { z } from "zod"
import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { BlocksEnums } from "@/types/enums/blocks"
const link = z.object({
href: z.string(),
title: z.string(),
})
export const sasTierComparisonSchema = z.object({
typename: z
.literal(BlocksEnums.block.SasTierComparison)
.optional()
.default(BlocksEnums.block.SasTierComparison),
sas_tier_comparison: z
.object({
comparisonConnection: z.object({
edges: z.array(
z.object({
node: z.object({
title: z.string(),
preamble: z.string().optional(),
scandic_friends: z.object({
title: z.string(),
label: z.string(),
tiers: z.array(
z.object({
tier_code: z.nativeEnum(MembershipLevelEnum),
tier_label: z.string(),
})
),
read_more_link: link.optional(),
}),
sas_eb: z.object({
title: z.string(),
label: z.string(),
tiers: z.array(
z.object({
tier_code: z.enum([
"Basic",
"Silver",
"Gold",
"Diamond",
"Pandion",
]),
tier_label: z.string(),
})
),
read_more_link: link.optional(),
}),
cta: link.optional(),
}),
})
),
}),
})
.transform((data) => {
return {
sasTierComparison: data.comparisonConnection.edges.at(0)?.node ?? null,
}
}),
})

View File

@@ -9,6 +9,7 @@ export namespace BlocksEnums {
TextCols = "TextCols",
TextContent = "TextContent",
UspGrid = "UspGrid",
SasTierComparison = "SasTierComparison",
HotelListing = "HotelListing",
}
}

View File

@@ -9,6 +9,7 @@ export namespace ContentPageEnum {
TextCols = "ContentPageBlocksTextCols",
UspGrid = "ContentPageBlocksUspGrid",
Table = "ContentPageBlocksTable",
SasTierComparison = "ContentPageBlocksSasTierComparison",
HotelListing = "ContentPageBlocksHotelListing",
}

View File

@@ -7,6 +7,7 @@ import type {
import type { contentSchema } from "@/server/routers/contentstack/schemas/blocks/content"
import type { dynamicContentSchema } from "@/server/routers/contentstack/schemas/blocks/dynamicContent"
import type { hotelListingSchema } from "@/server/routers/contentstack/schemas/blocks/hotelListing"
import type { sasTierComparisonSchema } from "@/server/routers/contentstack/schemas/blocks/sasTierComparison"
import type { shortcutsSchema } from "@/server/routers/contentstack/schemas/blocks/shortcuts"
import type { tableSchema } from "@/server/routers/contentstack/schemas/blocks/table"
import type { textColsSchema } from "@/server/routers/contentstack/schemas/blocks/textCols"
@@ -24,3 +25,5 @@ export interface TextCols extends z.output<typeof textColsSchema> {}
export interface UspGrid extends z.output<typeof uspGridSchema> {}
interface GetHotelListing extends z.output<typeof hotelListingSchema> {}
export type HotelListing = GetHotelListing["hotel_listing"]
export interface SasTierComparison
extends z.output<typeof sasTierComparisonSchema> {}