Merged in feat/sw-1493-revised-comparison-block (pull request #1236)
feat(SW-1493): Revised SAS comparison block * Base of new TierDetails for SAS tier comparison * Add backgrounds and content to TierDetails * Implement new cms schema for SasTierComparison * Override gap in jsontohtml styling to 0 * Add animations to comparison details * Redesign again * Update content model to new design * Add border to bottom item in tier match list * Wrap interpolate-size in @supports to be safe * Merge branch 'master' into feat/sw-1493-revised-comparison-block Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -135,7 +135,7 @@ export default function EnterDetailsTracking(props: Props) {
|
||||
roomPrice: roomPrice.perStay.local.price,
|
||||
discount: roomRate.memberRate
|
||||
? roomRate.publicRate.localPrice.pricePerStay -
|
||||
roomRate.memberRate.localPrice.pricePerStay
|
||||
roomRate.memberRate.localPrice.pricePerStay
|
||||
: 0,
|
||||
analyticsrateCode: getAnalyticsRateCode(roomRate.publicRate.rateCode),
|
||||
ancillaries: breakfastAncillary ? [breakfastAncillary] : [],
|
||||
|
||||
@@ -129,6 +129,10 @@
|
||||
--lightbox-z-index: 150;
|
||||
--default-modal-overlay-z-index: 100;
|
||||
--default-modal-z-index: 101;
|
||||
|
||||
@supports (interpolate-size: allow-keywords) {
|
||||
interpolate-size: allow-keywords;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -93,12 +93,7 @@ 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}
|
||||
/>
|
||||
)
|
||||
return <SasTierComparison content={block.sas_tier_comparison} />
|
||||
case BlocksEnums.block.FullWidthCampaign:
|
||||
return <FullWidthCampaign content={block.full_width_campaign} />
|
||||
default:
|
||||
|
||||
@@ -63,16 +63,16 @@ export function filterDuplicateRoomTypesByLowestPrice(
|
||||
if (
|
||||
!previousLowest ||
|
||||
currentRequestedPrice <
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].productType.public.requestedPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].productType.member?.requestedPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity
|
||||
) ||
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].productType.public.requestedPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].productType.member?.requestedPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity
|
||||
) ||
|
||||
(currentRequestedPrice ===
|
||||
Math.min(
|
||||
Number(
|
||||
@@ -85,16 +85,16 @@ export function filterDuplicateRoomTypesByLowestPrice(
|
||||
) ?? Infinity
|
||||
) &&
|
||||
currentLocalPrice <
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].productType.public.localPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].productType.member?.localPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity
|
||||
))
|
||||
Math.min(
|
||||
Number(
|
||||
previousLowest.products[0].productType.public.localPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity,
|
||||
Number(
|
||||
previousLowest.products[0].productType.member?.localPrice
|
||||
?.pricePerNight
|
||||
) ?? Infinity
|
||||
))
|
||||
) {
|
||||
roomMap.set(roomType, room)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { cx } from "class-variance-authority"
|
||||
|
||||
import { nodesToHtml } from "./utils"
|
||||
|
||||
import styles from "./jsontohtml.module.css"
|
||||
@@ -8,12 +10,13 @@ export default function JsonToHtml({
|
||||
embeds,
|
||||
nodes,
|
||||
renderOptions = {},
|
||||
className,
|
||||
}: JsonToHtmlProps) {
|
||||
if (!Array.isArray(nodes) || !nodes.length) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<section className={cx(styles.container, className)}>
|
||||
{nodesToHtml(nodes, embeds, renderOptions).filter(Boolean)}
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -1,163 +1,164 @@
|
||||
"use client"
|
||||
import { type Key, useState } from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
import JsonToHtml from "@/components/JsonToHtml"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
|
||||
import { ArrowRightIcon, CompareArrowsIcon } from "../Icons"
|
||||
import { ArrowRightIcon, ChevronDownIcon, CompareArrowsIcon } from "../Icons"
|
||||
import SectionContainer from "../Section/Container"
|
||||
import SectionHeader from "../Section/Header"
|
||||
import Button from "../TempDesignSystem/Button"
|
||||
import Select from "../TempDesignSystem/Select"
|
||||
import Body from "../TempDesignSystem/Text/Body"
|
||||
import Caption from "../TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "../TempDesignSystem/Text/Subtitle"
|
||||
import Title from "../TempDesignSystem/Text/Title"
|
||||
|
||||
import styles from "./sas-tier-comparison.module.css"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
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) {
|
||||
export function SasTierComparison({ content }: 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)
|
||||
}
|
||||
if (!comparisonContent) return null
|
||||
|
||||
return (
|
||||
<SectionContainer className={styles.comparisonSection}>
|
||||
<div className={styles.header}>
|
||||
<SectionHeader title={comparisonContent.title} topTitle={firstItem} />
|
||||
<Title level="h2">{comparisonContent.title}</Title>
|
||||
{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>
|
||||
<div className={styles.columnHeaders}>
|
||||
<Image
|
||||
alt="Scandic logo"
|
||||
height={46}
|
||||
src="/_static/img/scandic-logotype.svg"
|
||||
priority
|
||||
width={215}
|
||||
/>
|
||||
<Image
|
||||
alt="SAS logo"
|
||||
height={46}
|
||||
src="/_static/img/sas/sas-logotype.svg"
|
||||
priority
|
||||
width={215}
|
||||
/>
|
||||
<ColumnTitle>{comparisonContent.scandic_column_title}</ColumnTitle>
|
||||
<ColumnTitle>{comparisonContent.sas_column_title}</ColumnTitle>
|
||||
</div>
|
||||
<div className={styles.tierMatchList}>
|
||||
{comparisonContent.tier_matches.map((tierMatch, i) => (
|
||||
<TierDetails key={i} tierMatch={tierMatch}>
|
||||
<JsonToHtml
|
||||
nodes={tierMatch.content.json?.children}
|
||||
embeds={[]}
|
||||
className={styles.htmlContent}
|
||||
/>
|
||||
</TierDetails>
|
||||
))}
|
||||
</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>
|
||||
{comparisonContent.cta?.href && (
|
||||
<Button theme="primaryLight" asChild className={styles.ctaButton}>
|
||||
<Link href={comparisonContent.cta.href} color="white">
|
||||
{comparisonContent.cta.title}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function ReadMoreLink({ href, title }: { href: string; title: string }) {
|
||||
type TierMatch = NonNullable<
|
||||
SasTierComparisonContent["sasTierComparison"]
|
||||
>["tier_matches"][number]
|
||||
|
||||
function TierDetails({
|
||||
children,
|
||||
tierMatch,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
tierMatch: TierMatch
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
variant="underscored"
|
||||
className={styles.link}
|
||||
color="peach80"
|
||||
>
|
||||
{title}
|
||||
<ArrowRightIcon color="peach80" />
|
||||
<details className={styles.tierDetails} name="sas-scandic-tier-match">
|
||||
<summary className={styles.tierSummary}>
|
||||
<div className={styles.tierTitles}>
|
||||
<Body className={styles.tierTitle}>
|
||||
{tierMatch.scandic_friends_tier_name}
|
||||
</Body>
|
||||
<div className={styles.comparisonIcon}>
|
||||
<CompareArrowsIcon width={16} height={16} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Body className={styles.tierTitle}>
|
||||
{tierMatch.sas_eb_tier_name}
|
||||
</Body>
|
||||
<div className={styles.iconWrapper}>
|
||||
<ChevronDownIcon
|
||||
className={styles.chevron}
|
||||
color="burgundy"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div className={styles.tierContent}>
|
||||
<div className={styles.tierInfo}>
|
||||
<div className={styles.tierTitle}>
|
||||
<Subtitle color="burgundy">{tierMatch.title}</Subtitle>
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
{tierMatch.link?.href && (
|
||||
<ReadMoreLink href={tierMatch.link.href}>
|
||||
{tierMatch.link.title}
|
||||
</ReadMoreLink>
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
function ReadMoreLink({
|
||||
href,
|
||||
children,
|
||||
}: {
|
||||
href: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Link href={href} className={styles.link} weight="bold" color="burgundy">
|
||||
{children}
|
||||
<ArrowRightIcon color="burgundy" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function ColumnTitle({ children }: { children?: ReactNode }) {
|
||||
return (
|
||||
<div className={styles.columnTitle}>
|
||||
<Caption type="bold" asChild>
|
||||
<span>{children}</span>
|
||||
</Caption>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
.comparisonSection {
|
||||
width: 100%;
|
||||
gap: var(--Spacing-x4);
|
||||
gap: var(--Spacing-x6);
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
padding: var(--Spacing-x6) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preamble {
|
||||
@@ -14,59 +20,164 @@
|
||||
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);
|
||||
.tierMatchList {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: var(--Spacing-x4);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ctaLink {
|
||||
margin-top: var(--Spacing-x4);
|
||||
.tierDetails {
|
||||
overflow: hidden;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
transition: background-color 200ms;
|
||||
transition-delay: 50ms;
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
|
||||
&:hover,
|
||||
&[open] {
|
||||
background-color: var(--Base-Surface-Primary-light-Hover);
|
||||
|
||||
.comparisonIcon {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
}
|
||||
}
|
||||
|
||||
&[open] {
|
||||
.chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&::details-content {
|
||||
block-size: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&::details-content {
|
||||
block-size: 0;
|
||||
transition:
|
||||
block-size 0.4s,
|
||||
content-visibility 0.4s;
|
||||
transition-behavior: allow-discrete;
|
||||
}
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
position: absolute;
|
||||
right: var(--Spacing-x1);
|
||||
}
|
||||
.chevron {
|
||||
transition: transform 200ms;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tierSummary {
|
||||
list-style: none;
|
||||
|
||||
&::marker,
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.columnHeaders {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
row-gap: var(--Spacing-x2);
|
||||
margin-bottom: var(--Spacing-x2);
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.tierTitles {
|
||||
cursor: pointer;
|
||||
padding: var(--Spacing-x-half);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
.tierTitle {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.comparisonIcon {
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
transition: background-color 0.3s;
|
||||
transition-delay: 50ms;
|
||||
}
|
||||
|
||||
.tierContent {
|
||||
padding: var(--Spacing-x3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.tierInfo {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.htmlContent {
|
||||
gap: 0;
|
||||
|
||||
& ul {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.columnTitle {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
& > span {
|
||||
position: relative;
|
||||
padding: 0 var(--Spacing-x2);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
bottom: calc(50% - 1px);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: var(--Base-Border-Normal);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.tierInfo {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.ctaButton {
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.columnHeaders {
|
||||
column-gap: var(--Spacing-x3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,33 +9,23 @@ fragment SasTierComparison_ContentPage on ContentPageBlocksSasTierComparison {
|
||||
... on SasTierComparison {
|
||||
title
|
||||
preamble
|
||||
scandic_friends {
|
||||
scandic_column_title
|
||||
sas_column_title
|
||||
tier_matches {
|
||||
title
|
||||
label
|
||||
tiers {
|
||||
tier_code
|
||||
tier_label
|
||||
scandic_friends_tier_name
|
||||
sas_eb_tier_name
|
||||
content {
|
||||
json
|
||||
}
|
||||
read_more_link {
|
||||
href
|
||||
title
|
||||
}
|
||||
}
|
||||
sas_eb {
|
||||
title
|
||||
label
|
||||
tiers {
|
||||
tier_code
|
||||
tier_label
|
||||
}
|
||||
read_more_link {
|
||||
link {
|
||||
href
|
||||
title
|
||||
}
|
||||
}
|
||||
cta {
|
||||
href
|
||||
title
|
||||
href
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
public/_static/img/sas/sas-logotype.svg
Normal file
3
public/_static/img/sas/sas-logotype.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="144" height="51" viewBox="0 0 144 51" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M94.333 0.544556H79.8253C79.0833 0.544556 78.7102 0.908943 78.7102 0.908943L38.7269 49.5465C38.5404 49.9109 38.5404 50.0911 38.9134 50.0911H44.8637C45.4212 50.0911 45.6077 49.9089 45.6077 49.7267L54.1631 39.0177C54.1631 39.0177 54.3476 38.8376 54.5341 38.8376H68.8533C68.8533 38.8376 69.0398 38.8376 69.0398 39.0198C69.0398 39.202 66.9942 49.5465 66.9942 49.5465C66.9942 49.7287 66.9942 50.0911 67.5517 50.0911H84.847C85.0335 50.0911 85.22 50.0911 85.22 49.7267L94.8905 1.08709C94.704 1.08709 94.8905 0.540507 94.3309 0.540507L94.333 0.544556ZM69.7818 33.9386H58.6233H58.4388V33.7564L73.3155 15.0654L73.502 14.8832C73.6865 14.8832 73.502 15.0654 73.502 15.0654L69.7818 33.9406C69.9683 33.7584 69.9683 33.7584 69.7818 33.9406V33.9386ZM0.0451224 43.9208C0.0451224 43.9208 1.34869 39.5663 1.53319 39.7465C1.53319 39.0218 1.90621 38.8396 2.27723 39.202C3.95181 40.8356 9.15804 45.7366 16.4119 45.7366C24.4077 45.7366 23.8502 39.3842 23.8502 38.4772C23.8502 35.9366 20.5011 23.4119 19.5725 20.1465C16.9694 9.61981 23.4792 0 38.9134 0C47.0958 0 51.187 2.17822 52.675 2.90295C53.6056 3.44953 53.4171 4.17425 53.4171 4.17425C53.4171 4.17425 52.4885 7.98615 52.4885 8.34851C52.302 8.7129 51.931 8.7129 51.56 8.34851C49.1414 6.89704 46.3517 5.26337 42.0741 5.26337C37.0543 5.26337 34.4492 8.89307 35.7528 14.1564C36.3103 15.9723 39.4709 28.4951 40.0285 31.0356C42.2606 40.1109 35.9373 51 18.8305 51C9.34455 51 3.39428 47.5525 1.34869 45.9188C0.231632 45.0099 -0.139382 44.4653 0.0451224 43.9208ZM90.9838 44.103C90.9838 44.103 91.9143 39.3862 91.9143 39.7465C92.1008 39.202 92.6564 39.0218 93.0294 39.3842C94.8905 41.0178 99.7237 45.7366 106.978 45.7366C114.973 45.7366 114.418 39.3842 114.418 38.4772C114.418 35.9366 111.069 23.4119 110.138 20.1465C107.535 9.61981 114.047 0 129.479 0C137.661 0 141.753 2.17822 143.241 2.90295C144.171 3.44953 143.985 4.17425 143.985 4.17425C143.985 4.17425 143.054 7.98615 143.054 8.34851C142.87 8.7129 142.497 8.7129 142.126 8.34851C139.707 6.89704 136.917 5.26337 132.64 5.26337C127.62 5.26337 125.017 8.89307 126.318 14.1564C126.876 15.9723 130.037 28.4951 130.596 31.0356C132.826 40.1109 126.505 51 109.394 51C99.9102 51 94.333 47.7327 92.2874 46.101C91.1703 45.1921 90.7973 44.6475 90.9838 44.103Z" fill="#000099"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -22,34 +22,19 @@ export const sasTierComparisonSchema = 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(),
|
||||
}),
|
||||
scandic_column_title: z.string(),
|
||||
sas_column_title: z.string(),
|
||||
tier_matches: z.array(
|
||||
z.object({
|
||||
scandic_friends_tier_name: z.string(),
|
||||
sas_eb_tier_name: z.string(),
|
||||
title: z.string(),
|
||||
content: z.object({
|
||||
json: z.any(), // json
|
||||
}),
|
||||
link: link.optional(),
|
||||
})
|
||||
),
|
||||
cta: link.optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -73,7 +73,7 @@ export const getHotelsInput = z.object({
|
||||
.nullable(),
|
||||
hotelsToInclude: z.array(z.string()),
|
||||
})
|
||||
export interface GetHotelsInput extends z.infer<typeof getHotelsInput> { }
|
||||
export interface GetHotelsInput extends z.infer<typeof getHotelsInput> {}
|
||||
|
||||
export const nearbyHotelIdsInput = z.object({
|
||||
hotelId: z.string(),
|
||||
|
||||
@@ -34,8 +34,12 @@ export const metrics = {
|
||||
},
|
||||
hotelsByHotelIdAvailability: {
|
||||
counter: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id"),
|
||||
fail: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id-fail"),
|
||||
success: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id-success"),
|
||||
fail: meter.createCounter(
|
||||
"trpc.hotel.availability.hotels-by-hotel-id-fail"
|
||||
),
|
||||
success: meter.createCounter(
|
||||
"trpc.hotel.availability.hotels-by-hotel-id-success"
|
||||
),
|
||||
},
|
||||
meetingRooms: {
|
||||
counter: meter.createCounter("trpc.hotels.meetingRooms"),
|
||||
|
||||
@@ -34,32 +34,31 @@ export const extraPageSchema = z.object({
|
||||
mainBody: z.string().optional(),
|
||||
})
|
||||
|
||||
export const additionalDataSchema = z
|
||||
.object({
|
||||
attributes: z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
displayWebPage: z.object({
|
||||
healthGym: z.boolean(),
|
||||
meetingRoom: z.boolean(),
|
||||
parking: z.boolean(),
|
||||
specialNeeds: z.boolean(),
|
||||
}),
|
||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
||||
gallery: gallerySchema.optional(),
|
||||
conferencesAndMeetings: facilitySchema.optional(),
|
||||
healthAndWellness: facilitySchema.optional(),
|
||||
restaurantImages: facilitySchema.optional(),
|
||||
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
||||
meetingRooms: extraPageSchema,
|
||||
healthAndFitness: extraPageSchema,
|
||||
hotelParking: extraPageSchema,
|
||||
hotelSpecialNeeds: extraPageSchema,
|
||||
accessibilityElevatorPitchText: z.string().optional(),
|
||||
hotelRoomElevatorPitchText: z.string().optional(),
|
||||
export const additionalDataSchema = z.object({
|
||||
attributes: z.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
displayWebPage: z.object({
|
||||
healthGym: z.boolean(),
|
||||
meetingRoom: z.boolean(),
|
||||
parking: z.boolean(),
|
||||
specialNeeds: z.boolean(),
|
||||
}),
|
||||
type: z.literal("additionalData"),
|
||||
})
|
||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
||||
gallery: gallerySchema.optional(),
|
||||
conferencesAndMeetings: facilitySchema.optional(),
|
||||
healthAndWellness: facilitySchema.optional(),
|
||||
restaurantImages: facilitySchema.optional(),
|
||||
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
||||
meetingRooms: extraPageSchema,
|
||||
healthAndFitness: extraPageSchema,
|
||||
hotelParking: extraPageSchema,
|
||||
hotelSpecialNeeds: extraPageSchema,
|
||||
accessibilityElevatorPitchText: z.string().optional(),
|
||||
hotelRoomElevatorPitchText: z.string().optional(),
|
||||
}),
|
||||
type: z.literal("additionalData"),
|
||||
})
|
||||
|
||||
export function transformAdditionalData(
|
||||
data: z.output<typeof additionalDataSchema>
|
||||
@@ -67,6 +66,6 @@ export function transformAdditionalData(
|
||||
return {
|
||||
...data.attributes,
|
||||
id: data.attributes.id,
|
||||
type: data.type
|
||||
type: data.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
roomCategoriesSchema,
|
||||
transformRoomCategories,
|
||||
} from "@/server/routers/hotels/schemas/hotel/include/roomCategories"
|
||||
import { additionalDataSchema, transformAdditionalData } from "../../additionalData"
|
||||
|
||||
import {
|
||||
additionalDataSchema,
|
||||
transformAdditionalData,
|
||||
} from "../../additionalData"
|
||||
|
||||
export const includeSchema = z
|
||||
.discriminatedUnion("type", [
|
||||
|
||||
@@ -86,12 +86,12 @@ export function transformRoomCategories(
|
||||
totalOccupancy:
|
||||
data.attributes.occupancy.min === data.attributes.occupancy.max
|
||||
? {
|
||||
max: data.attributes.occupancy.max,
|
||||
range: `${data.attributes.occupancy.max}`,
|
||||
}
|
||||
max: data.attributes.occupancy.max,
|
||||
range: `${data.attributes.occupancy.max}`,
|
||||
}
|
||||
: {
|
||||
max: data.attributes.occupancy.max,
|
||||
range: `${data.attributes.occupancy.min}-${data.attributes.occupancy.max}`,
|
||||
},
|
||||
max: data.attributes.occupancy.max,
|
||||
range: `${data.attributes.occupancy.min}-${data.attributes.occupancy.max}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,49 +2,48 @@ import { z } from "zod"
|
||||
|
||||
import { imageSchema } from "./image"
|
||||
|
||||
export const meetingRoomsSchema = z
|
||||
.object({
|
||||
data: z.array(
|
||||
z.object({
|
||||
attributes: z.object({
|
||||
name: z.string(),
|
||||
email: z.string().optional(),
|
||||
phoneNumber: z.string(),
|
||||
size: z.number(),
|
||||
doorWidth: z.number(),
|
||||
doorHeight: z.number(),
|
||||
length: z.number(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
floorNumber: z.number(),
|
||||
content: z.object({
|
||||
images: z.array(imageSchema),
|
||||
texts: z.object({
|
||||
facilityInformation: z.string().optional(),
|
||||
surroundingInformation: z.string().optional(),
|
||||
descriptions: z.object({
|
||||
export const meetingRoomsSchema = z.object({
|
||||
data: z.array(
|
||||
z.object({
|
||||
attributes: z.object({
|
||||
name: z.string(),
|
||||
email: z.string().optional(),
|
||||
phoneNumber: z.string(),
|
||||
size: z.number(),
|
||||
doorWidth: z.number(),
|
||||
doorHeight: z.number(),
|
||||
length: z.number(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
floorNumber: z.number(),
|
||||
content: z.object({
|
||||
images: z.array(imageSchema),
|
||||
texts: z.object({
|
||||
facilityInformation: z.string().optional(),
|
||||
surroundingInformation: z.string().optional(),
|
||||
descriptions: z.object({
|
||||
short: z.string().optional(),
|
||||
medium: z.string().optional(),
|
||||
}),
|
||||
meetingDescription: z
|
||||
.object({
|
||||
short: z.string().optional(),
|
||||
medium: z.string().optional(),
|
||||
}),
|
||||
meetingDescription: z
|
||||
.object({
|
||||
short: z.string().optional(),
|
||||
medium: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
seatings: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
capacity: z.number(),
|
||||
})
|
||||
),
|
||||
lighting: z.string(),
|
||||
sortOrder: z.number().optional(),
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
),
|
||||
})
|
||||
seatings: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
capacity: z.number(),
|
||||
})
|
||||
),
|
||||
lighting: z.string(),
|
||||
sortOrder: z.number().optional(),
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { unstable_cache } from "next/cache"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
|
||||
import { metrics } from "./metrics"
|
||||
import {
|
||||
citiesByCountrySchema,
|
||||
citiesSchema,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
locationsSchema,
|
||||
} from "./output"
|
||||
|
||||
import type { Country } from "@/types/enums/country"
|
||||
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type {
|
||||
@@ -20,8 +22,6 @@ import type {
|
||||
} from "@/types/trpc/routers/hotel/locations"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type { Endpoint } from "@/lib/api/endpoints"
|
||||
import { Country } from "@/types/enums/country"
|
||||
import { metrics } from "./metrics"
|
||||
|
||||
export function getPoiGroupByCategoryName(category: string | undefined) {
|
||||
if (!category) return PointOfInterestGroupEnum.LOCATION
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { meetingRoomsSchema } from "@/server/routers/hotels/schemas/meetingRoom"
|
||||
|
||||
import type { z } from "zod"
|
||||
|
||||
import type { meetingRoomsSchema } from "@/server/routers/hotels/schemas/meetingRoom"
|
||||
|
||||
export type MeetingRoomData = z.output<typeof meetingRoomsSchema>
|
||||
export type MeetingRooms = MeetingRoomData["data"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ParkingAmenityProps } from "./parking"
|
||||
import type { Hotel, Restaurant, RestaurantOpeningHours } from "@/types/hotel"
|
||||
import type { ParkingAmenityProps } from "./parking"
|
||||
|
||||
export type AmenitiesSidePeekProps = {
|
||||
amenitiesList: Hotel["detailedFacilities"]
|
||||
|
||||
@@ -20,10 +20,10 @@ export interface ParkingListProps
|
||||
| "distanceToHotel"
|
||||
| "numberOfChargingSpaces"
|
||||
| "numberOfParkingSpots"
|
||||
> { }
|
||||
> {}
|
||||
|
||||
export interface ParkingPricesProps
|
||||
extends Pick<Parking["pricing"], "freeParking">,
|
||||
Pick<NonNullable<Parking["pricing"]["localCurrency"]>, "currency"> {
|
||||
Pick<NonNullable<Parking["pricing"]["localCurrency"]>, "currency"> {
|
||||
pricing: NonNullable<Parking["pricing"]["localCurrency"]>["ordinary"]
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ export interface BookingConfirmationProps {
|
||||
confirmationNumber: string
|
||||
}
|
||||
|
||||
export interface ConfirmationProps extends BookingConfirmation { }
|
||||
export interface ConfirmationProps extends BookingConfirmation {}
|
||||
|
||||
@@ -5,13 +5,13 @@ import type { breakfastPackagesSchema } from "@/server/routers/hotels/output"
|
||||
import type { breakfastPackageSchema } from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
export interface BreakfastFormSchema
|
||||
extends z.output<typeof breakfastFormSchema> { }
|
||||
extends z.output<typeof breakfastFormSchema> {}
|
||||
|
||||
export interface BreakfastPackages
|
||||
extends z.output<typeof breakfastPackagesSchema> { }
|
||||
extends z.output<typeof breakfastPackagesSchema> {}
|
||||
|
||||
export interface BreakfastPackage
|
||||
extends z.output<typeof breakfastPackageSchema> { }
|
||||
extends z.output<typeof breakfastPackageSchema> {}
|
||||
|
||||
export interface BreakfastProps {
|
||||
packages: BreakfastPackages
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CheckInData, Hotel, Parking } from "@/types/hotel"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type {
|
||||
AlternativeHotelsSearchParams,
|
||||
SelectHotelSearchParams,
|
||||
} from "./selectHotelSearchParams"
|
||||
import type { CheckInData, Hotel, Parking } from "@/types/hotel"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export enum AvailabilityEnum {
|
||||
Available = "Available",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HotelData } from "@/types/hotel"
|
||||
import type { HotelData } from "@/types/hotel"
|
||||
|
||||
export enum SidePeekEnum {
|
||||
hotelDetails = "hotel-detail-side-peek",
|
||||
|
||||
@@ -5,11 +5,11 @@ import type {
|
||||
Price,
|
||||
RoomPrice,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { RoomAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
import type { BedTypeSchema } from "./enterDetails/bedType"
|
||||
import type { BreakfastPackage } from "./enterDetails/breakfast"
|
||||
import type { DetailsSchema } from "./enterDetails/details"
|
||||
import type { Child, SelectRateSearchParams } from "./selectRate/selectRate"
|
||||
import type { RoomAvailability } from "@/types/trpc/routers/hotel/roomAvailability"
|
||||
|
||||
export type RoomsData = Pick<DetailsState, "roomPrice"> &
|
||||
Pick<RoomAvailability, "cancellationText" | "rateDetails"> &
|
||||
@@ -21,7 +21,7 @@ export type RoomsData = Pick<DetailsState, "roomPrice"> &
|
||||
|
||||
export interface SummaryProps
|
||||
extends Pick<RoomAvailability, "cancellationText" | "rateDetails">,
|
||||
Pick<RoomAvailability["selectedRoom"], "roomType"> {
|
||||
Pick<RoomAvailability["selectedRoom"], "roomType"> {
|
||||
isMember: boolean
|
||||
breakfastIncluded: boolean
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export type JsonToHtmlProps = {
|
||||
embeds: Node<Embeds>[]
|
||||
nodes: RTENode[]
|
||||
renderOptions?: RenderOptions
|
||||
className?: string
|
||||
}
|
||||
|
||||
export type EmbedByUid = Record<string, Node<Embeds>>
|
||||
|
||||
@@ -4,14 +4,14 @@ import type { Hotel, Room } from "@/types/hotel"
|
||||
import type { bookingConfirmationSchema } from "@/server/routers/booking/output"
|
||||
|
||||
export interface BookingConfirmationSchema
|
||||
extends z.output<typeof bookingConfirmationSchema> { }
|
||||
extends z.output<typeof bookingConfirmationSchema> {}
|
||||
|
||||
export interface BookingConfirmation {
|
||||
booking: BookingConfirmationSchema
|
||||
hotel: Hotel
|
||||
room:
|
||||
| (Room & {
|
||||
bedType: Room["roomTypes"][number]
|
||||
})
|
||||
| null
|
||||
| (Room & {
|
||||
bedType: Room["roomTypes"][number]
|
||||
})
|
||||
| null
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
locationsSchema,
|
||||
} from "@/server/routers/hotels/output"
|
||||
|
||||
export interface LocationSchema extends z.output<typeof locationsSchema> { }
|
||||
export interface LocationSchema extends z.output<typeof locationsSchema> {}
|
||||
|
||||
export type Locations = LocationSchema["data"]
|
||||
export type Location = Locations[number]
|
||||
@@ -20,7 +20,7 @@ export function isHotelLocation(
|
||||
return location?.type === "hotels"
|
||||
}
|
||||
export interface CitiesByCountry
|
||||
extends z.output<typeof citiesByCountrySchema> { }
|
||||
extends z.output<typeof citiesByCountrySchema> {}
|
||||
export type CitiesGroupedByCountry = Record<string, CitiesByCountry["data"]>
|
||||
|
||||
export interface Countries extends z.output<typeof countriesSchema> { }
|
||||
export interface Countries extends z.output<typeof countriesSchema> {}
|
||||
|
||||
Reference in New Issue
Block a user