feat(SW-2278): Added hotel listing to campaign page

Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-06-19 09:36:28 +00:00
parent 105c4d9cf3
commit af92f7183c
31 changed files with 703 additions and 57 deletions

View File

@@ -0,0 +1,28 @@
"use client"
import { Typography } from "@scandic-hotels/design-system/Typography"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import HotelListingItemSkeleton from "./HotelListingItem/HotelListingItemSkeleton"
import styles from "./campaignHotelListing.module.css"
export default function CampaignHotelListingSkeleton() {
return (
<section className={styles.hotelListingSection}>
<header className={styles.header}>
<Typography variant="Title/md">
<SkeletonShimmer width="40ch" />
</Typography>
</header>
<ul className={styles.list}>
{Array.from({ length: 3 }).map((_, index) => (
<li key={index} className={styles.listItem}>
<HotelListingItemSkeleton />
</li>
))}
</ul>
</section>
)
}

View File

@@ -0,0 +1,120 @@
"use client"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { Button } from "@scandic-hotels/design-system/Button"
import {
MaterialIcon,
type MaterialIconProps,
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import HotelListingItem from "./HotelListingItem"
import styles from "./campaignHotelListing.module.css"
import type { HotelDataWithUrl } from "@/types/hotel"
interface CampaignHotelListingClientProps {
heading: string
hotels: HotelDataWithUrl[]
}
export default function CampaignHotelListingClient({
heading,
hotels,
}: CampaignHotelListingClientProps) {
const intl = useIntl()
const isMobile = useMediaQuery("(max-width: 767px)")
const scrollRef = useRef<HTMLElement>(null)
const initialCount = isMobile ? 3 : 6 // Initial number of hotels to show
const thresholdCount = isMobile ? 6 : 9 // This is the threshold at which we start showing the "Show More" button
const showAllThreshold = isMobile ? 9 : 18 // This is the threshold at which we show the "Show All" button
const incrementCount = isMobile ? 3 : 6 // Number of hotels to increment when the button is clicked
const [visibleCount, setVisibleCount] = useState(() =>
// Set initial visible count based on the number of hotels and the threshold
hotels.length <= thresholdCount ? hotels.length : initialCount
)
// Only show the show more/less button if the length of hotels exceeds the threshold count
const showButton = hotels.length >= thresholdCount
// Determine if we are at the stage where the user can click to show all hotels
const canShowAll =
hotels.length > visibleCount &&
(visibleCount + incrementCount > showAllThreshold ||
visibleCount + incrementCount >= hotels.length)
function handleButtonClick() {
if (visibleCount < hotels.length) {
if (canShowAll) {
setVisibleCount(hotels.length)
} else {
setVisibleCount((prev) =>
Math.min(prev + incrementCount, hotels.length)
)
}
} else {
setVisibleCount(initialCount)
if (scrollRef.current) {
scrollRef.current.scrollIntoView({ behavior: "smooth" })
}
}
}
let buttonText = intl.formatMessage({
defaultMessage: "Show more",
})
let iconDirection: MaterialIconProps["icon"] = "keyboard_arrow_down"
if (visibleCount === hotels.length) {
buttonText = intl.formatMessage({
defaultMessage: "Show less",
})
iconDirection = "keyboard_arrow_up"
} else if (canShowAll) {
buttonText = intl.formatMessage({
defaultMessage: "Show all",
})
}
return (
<section className={styles.hotelListingSection} ref={scrollRef}>
<header className={styles.header}>
<Typography variant="Title/md">
<h2 className={styles.heading}>{heading}</h2>
</Typography>
</header>
<ul className={styles.list}>
{hotels.map(({ hotel, url }, index) => (
<li
key={hotel.id}
className={cx(styles.listItem, {
[styles.hidden]: index >= visibleCount,
})}
>
<HotelListingItem hotel={hotel} url={url} />
</li>
))}
</ul>
{showButton ? (
<Button
variant="Text"
color="Primary"
size="Medium"
typography="Body/Paragraph/mdBold"
onPress={handleButtonClick}
>
<MaterialIcon icon={iconDirection} color="CurrentColor" />
{buttonText}
</Button>
) : null}
</section>
)
}

View File

@@ -0,0 +1,59 @@
import { Divider } from "@scandic-hotels/design-system/Divider"
import { Typography } from "@scandic-hotels/design-system/Typography"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import styles from "./hotelListingItem.module.css"
export default function HotelListingItemSkeleton() {
return (
<article className={styles.hotelListingItem}>
<div className={styles.imageWrapper}>
<SkeletonShimmer width="100%" height="220px" />
</div>
<div className={styles.content}>
<div className={styles.intro}>
<SkeletonShimmer width="20ch" height="30px" />
<Typography variant="Title/Subtitle/md">
<SkeletonShimmer width="25ch" />
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.captions}>
<SkeletonShimmer width="10ch" />
<span>
<Divider
className={styles.divider}
variant="vertical"
color="Border/Divider/Default"
/>
</span>
<SkeletonShimmer width="20ch" />
</div>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<p>
<SkeletonShimmer width="100%" />
<SkeletonShimmer width="70%" />
<SkeletonShimmer width="35%" />
</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<ul className={styles.amenityList}>
{Array.from({ length: 5 }).map((_, index) => {
return (
<li className={styles.amenityItem} key={index}>
<SkeletonShimmer width="20px" height="20px" />
<SkeletonShimmer width="15ch" />
</li>
)
})}
</ul>
</Typography>
</div>
<div className={styles.ctaWrapper}>
<SkeletonShimmer width="100%" height="40px" />
</div>
</article>
)
}

View File

@@ -0,0 +1,64 @@
.hotelListingItem {
border-radius: var(--Corner-radius-md);
overflow: hidden;
display: grid;
grid-template-rows: auto 1fr auto;
gap: var(--Space-x2);
height: 100%;
padding-bottom: var(--Space-x2);
}
.imageWrapper {
height: 220px;
width: 100%;
position: relative;
}
.tripAdvisor {
position: absolute;
top: var(--Space-x2);
left: var(--Space-x2);
display: flex;
align-items: center;
gap: var(--Space-x05);
background-color: var(--Surface-Primary-Default);
padding: var(--Space-x025) var(--Space-x1);
border-radius: var(--Corner-radius-sm);
color: var(--Text-Interactive-Default);
}
.content {
padding: 0 var(--Space-x2);
display: grid;
gap: var(--Space-x15);
align-content: start;
}
.intro {
display: grid;
gap: var(--Space-x05);
}
.captions {
display: flex;
column-gap: var(--Space-x1);
flex-wrap: wrap;
color: var(--Text-Tertiary);
}
.amenityList {
display: flex;
gap: var(--Space-x025) var(--Space-x1);
flex-wrap: wrap;
color: var(--Text-Secondary);
}
.amenityItem {
display: flex;
gap: var(--Space-x05);
align-items: center;
}
.ctaWrapper {
padding: 0 var(--Space-x2);
}

View File

@@ -0,0 +1,129 @@
import { useIntl } from "react-intl"
import { Divider } from "@scandic-hotels/design-system/Divider"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
import TripadvisorIcon from "@scandic-hotels/design-system/Icons/TripadvisorIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ButtonLink from "@/components/ButtonLink"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import ImageGallery from "@/components/ImageGallery"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getSingleDecimal } from "@/utils/numberFormatting"
import styles from "./hotelListingItem.module.css"
import type { Hotel } from "@/types/hotel"
interface HotelListingItemProps {
hotel: Hotel
url: string
}
export default function HotelListingItem({
hotel,
url,
}: HotelListingItemProps) {
const intl = useIntl()
const galleryImages = mapApiImagesToGalleryImages(hotel.galleryImages || [])
const tripadvisorRating = hotel.ratings?.tripAdvisor.rating
const address = `${hotel.address.streetAddress}, ${hotel.address.city}`
const amenities = hotel.detailedFacilities.slice(0, 5)
const hotelDescription = hotel.hotelContent.texts.descriptions?.short
return (
<article className={styles.hotelListingItem}>
<div className={styles.imageWrapper}>
<ImageGallery
images={galleryImages}
fill
sizes="(min-width: 768px) 450px, 100vw"
title={intl.formatMessage(
{
defaultMessage: "{title} - Image gallery",
},
{ title: hotel.name }
)}
/>
{tripadvisorRating ? (
<Typography variant="Title/Overline/sm">
<div className={styles.tripAdvisor}>
<TripadvisorIcon color="CurrentColor" />
<span>{tripadvisorRating}</span>
</div>
</Typography>
) : null}
</div>
<div className={styles.content}>
<div className={styles.intro}>
<HotelLogoIcon
hotelId={hotel.operaId}
hotelType={hotel.hotelType}
height={30}
/>
<Typography variant="Title/Subtitle/md">
<h3>{hotel.name}</h3>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.captions}>
<span>{address}</span>
<span>
<Divider
className={styles.divider}
variant="vertical"
color="Border/Divider/Default"
/>
</span>
<span>
{intl.formatMessage(
{
defaultMessage: "{number} km to city center",
},
{
number: getSingleDecimal(
hotel.location.distanceToCentre / 1000
),
}
)}
</span>
</div>
</Typography>
</div>
{hotelDescription ? (
<Typography variant="Body/Paragraph/mdRegular">
<p>{hotelDescription}</p>
</Typography>
) : null}
<Typography variant="Body/Supporting text (caption)/smRegular">
<ul className={styles.amenityList}>
{amenities.map((amenity) => {
return (
<li className={styles.amenityItem} key={amenity.id}>
<FacilityToIcon
id={amenity.id}
color="CurrentColor"
size={20}
/>
{amenity.name}
</li>
)
})}
</ul>
</Typography>
</div>
<div className={styles.ctaWrapper}>
<ButtonLink
href={url}
variant="Tertiary"
color="Primary"
size="Small"
typography="Body/Supporting text (caption)/smBold"
>
{intl.formatMessage({
defaultMessage: "See hotel details",
})}
</ButtonLink>
</div>
</article>
)
}

View File

@@ -0,0 +1,56 @@
.hotelListingSection {
--scroll-margin-top: calc(
var(--booking-widget-mobile-height) + var(--Spacing-x2)
);
display: grid;
gap: var(--Space-x3);
scroll-margin-top: var(--scroll-margin-top);
}
.heading {
color: var(--Text-Heading);
}
.list {
list-style: none;
display: grid;
gap: var(--Space-x4);
}
.listItem.hidden {
display: none;
}
@media screen and (min-width: 768px) {
.hotelListingSection {
--scroll-margin-top: calc(
var(--booking-widget-tablet-height) + var(--Spacing-x2)
);
gap: var(--Space-x5);
}
.list {
row-gap: var(--Space-x5);
column-gap: var(--Space-x2);
}
}
@media screen and (min-width: 768px) and (max-width: 949px) {
.list {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 950px) {
.list {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (min-width: 1367px) {
.hotelListingSection {
--scroll-margin-top: calc(
var(--booking-widget-desktop-height) + var(--Spacing-x2)
);
}
}

View File

@@ -0,0 +1,21 @@
import { getHotelsByCSFilter } from "@/lib/trpc/memoizedRequests"
import CampaignHotelListingClient from "./Client"
interface CampaignHotelListingProps {
heading: string
hotelIds: string[]
}
export default async function CampaignHotelListing({
heading,
hotelIds,
}: CampaignHotelListingProps) {
const hotels = await getHotelsByCSFilter({ hotelsToInclude: hotelIds })
if (!hotels.length) {
return null
}
return <CampaignHotelListingClient heading={heading} hotels={hotels} />
}

View File

@@ -18,7 +18,7 @@ export default async function Essentials({ content }: EssentialsProps) {
return (
<section className={styles.essentialsSection}>
<header className={styles.header}>
<Typography variant="Title/sm">
<Typography variant="Title/smRegular">
<h3 className={styles.heading}>{title}</h3>
</Typography>
{preamble ? (

View File

@@ -66,7 +66,7 @@ export default function Blocks({ blocks }: BlocksProps) {
key={`${block.card_gallery.heading}-${idx}`}
/>
)
case BlocksEnums.block.HotelListing:
case BlocksEnums.block.ContentPageHotelListing:
const { heading, contentType, locationFilter, hotelsToInclude } =
block.hotel_listing
if (!locationFilter && !hotelsToInclude.length) {

View File

@@ -1,4 +1,8 @@
import { Suspense } from "react"
import AccordionSection from "@/components/Blocks/Accordion"
import CampaignHotelListing from "@/components/Blocks/CampaignHotelListing"
import CampaignHotelListingSkeleton from "@/components/Blocks/CampaignHotelListing/CampaignHotelListingSkeleton"
import CarouselCards from "@/components/Blocks/CarouselCards"
import Essentials from "@/components/Blocks/Essentials"
@@ -27,6 +31,15 @@ export default function Blocks({ blocks }: BlocksProps) {
key={block.accordion.title}
/>
)
case BlocksEnums.block.CampaignPageHotelListing:
return (
<Suspense fallback={<CampaignHotelListingSkeleton />}>
<CampaignHotelListing
heading={block.hotel_listing.heading}
hotelIds={block.hotel_listing.hotelIds}
/>
</Suspense>
)
default:
return null
}

View File

@@ -27,8 +27,8 @@
.tripAdvisor {
position: absolute;
top: 16px;
left: 16px;
top: var(--Space-x2);
left: var(--Space-x2);
display: flex;
align-items: center;
gap: var(--Space-x05);

View File

@@ -22,3 +22,10 @@ fragment HotelListing_ContentPage on ContentPageBlocksHotelListing {
...HotelListing
}
}
fragment HotelListing_CampaignPage on CampaignPageBlocksHotelListing {
hotel_listing {
heading
}
}

View File

@@ -0,0 +1,45 @@
fragment CampaignPageIncludedHotels on CampaignPageIncludedHotels {
list_1Connection {
edges {
node {
... on HotelPage {
hotel_page_id
}
}
}
}
list_2Connection {
edges {
node {
... on HotelPage {
hotel_page_id
}
}
}
}
}
fragment CampaignPageIncludedHotelsRef on CampaignPageIncludedHotels {
list_1Connection {
edges {
node {
... on HotelPage {
system {
...System
}
}
}
}
}
list_2Connection {
edges {
node {
... on HotelPage {
system {
...System
}
}
}
}
}
}

View File

@@ -1,9 +1,12 @@
#import "../../Fragments/System.graphql"
#import "../../Fragments/CampaignPage/IncludedHotels.graphql"
#import "../../Fragments/CampaignPage/Hero.graphql"
#import "../../Fragments/Blocks/Accordion.graphql"
#import "../../Fragments/Blocks/Essentials.graphql"
#import "../../Fragments/Blocks/CarouselCards.graphql"
#import "../../Fragments/CampaignPage/Hero.graphql"
#import "../../Fragments/Blocks/HotelListing.graphql"
query GetCampaignPage($locale: String!, $uid: String!) {
campaign_page(uid: $uid, locale: $locale) {
@@ -16,11 +19,15 @@ query GetCampaignPage($locale: String!, $uid: String!) {
first_column
second_column
}
included_hotels {
...CampaignPageIncludedHotels
}
blocks {
__typename
...Essentials_CampaignPage
...CarouselCards_CampaignPage
...Accordion_CampaignPage
...HotelListing_CampaignPage
}
system {
...System
@@ -36,6 +43,9 @@ query GetCampaignPage($locale: String!, $uid: String!) {
query GetCampaignPageRefs($locale: String!, $uid: String!) {
campaign_page(locale: $locale, uid: $uid) {
included_hotels {
...CampaignPageIncludedHotelsRef
}
blocks {
__typename
...CarouselCards_CampaignPageRefs

View File

@@ -11,6 +11,7 @@ import {
carouselCardsSchema,
} from "../schemas/blocks/carouselCards"
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
import { campaignPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
linkConnectionRefs,
@@ -38,10 +39,17 @@ export const campaignPageAccordion = z
})
.merge(accordionSchema)
export const campaignPageHotelListing = z
.object({
__typename: z.literal(CampaignPageEnum.ContentStack.blocks.HotelListing),
})
.merge(campaignPageHotelListingSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [
campaignPageEssentials,
campaignPageCarouselCards,
campaignPageAccordion,
campaignPageHotelListing,
])
export const heroSchema = z.object({
@@ -58,30 +66,88 @@ export const heroSchema = z.object({
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({
is_two_columns: z.boolean().default(false),
first_column: z.string(),
second_column: z.string(),
const includedHotelsSchema = z
.object({
list_1Connection: z.object({
edges: z.array(
z.object({
node: z.object({
hotel_page_id: z.string(),
}),
})
),
}),
blocks: discriminatedUnionArray(blocksSchema.options),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
}),
trackingProps: z.object({
url: z.string(),
}),
})
list_2Connection: z.object({
edges: z.array(
z.object({
node: z.object({
hotel_page_id: z.string(),
}),
})
),
}),
})
.transform((data) => {
const list1HotelIds = data.list_1Connection.edges
.map((edge) => edge.node.hotel_page_id)
.filter(Boolean)
const list2HotelIds = data.list_2Connection.edges
.map((edge) => edge.node.hotel_page_id)
.filter(Boolean)
return [...new Set([...list1HotelIds, ...list2HotelIds])]
})
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(),
included_hotels: includedHotelsSchema,
preamble: z.object({
is_two_columns: z.boolean().default(false),
first_column: z.string(),
second_column: z.string(),
}),
blocks: discriminatedUnionArray(blocksSchema.options),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
}),
trackingProps: z.object({
url: z.string(),
}),
})
.transform((data) => {
const blocks = data.campaign_page.blocks.map((block) => {
if (
block.__typename === CampaignPageEnum.ContentStack.blocks.HotelListing
) {
return {
...block,
hotel_listing: {
...block.hotel_listing,
hotelIds: data.campaign_page.included_hotels,
},
}
}
return block
})
return {
...data,
campaign_page: {
...data.campaign_page,
blocks: [...blocks],
},
}
})
/** REFS */
const campaignPageCarouselCardsRef = z

View File

@@ -18,7 +18,7 @@ import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import { hotelListingSchema } from "../schemas/blocks/hotelListing"
import { contentPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import {
shortcutsRefsSchema,
shortcutsSchema,
@@ -113,7 +113,7 @@ export const contentPageHotelListing = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.HotelListing),
})
.merge(hotelListingSchema)
.merge(contentPageHotelListingSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [
contentPageAccordion,

View File

@@ -36,10 +36,10 @@ export const locationFilterSchema = z
}
})
export const hotelListingSchema = z.object({
export const contentPageHotelListingSchema = z.object({
typename: z
.literal(BlocksEnums.block.HotelListing)
.default(BlocksEnums.block.HotelListing),
.literal(BlocksEnums.block.ContentPageHotelListing)
.default(BlocksEnums.block.ContentPageHotelListing),
hotel_listing: z
.object({
heading: z.string().optional(),
@@ -60,3 +60,12 @@ export const hotelListingSchema = z.object({
}
}),
})
export const campaignPageHotelListingSchema = z.object({
typename: z
.literal(BlocksEnums.block.CampaignPageHotelListing)
.default(BlocksEnums.block.CampaignPageHotelListing),
hotel_listing: z.object({
heading: z.string(),
}),
})

View File

@@ -237,7 +237,7 @@ export const getHotelsByCSFilterInput = z.object({
country: z.nativeEnum(Country).nullable(),
excluded: z.array(z.string()),
})
.nullable(),
.nullish(),
hotelsToInclude: z.array(z.string()),
})
export interface GetHotelsByCSFilterInput

View File

@@ -7,7 +7,8 @@ export namespace BlocksEnums {
Content = "Content",
DynamicContent = "DynamicContent",
FullWidthCampaign = "FullWidthCampaign",
HotelListing = "HotelListing",
CampaignPageHotelListing = "CampaignPageHotelListing",
ContentPageHotelListing = "ContentPageHotelListing",
JoinScandicFriends = "JoinScandicFriends",
Shortcuts = "Shortcuts",
Table = "Table",

View File

@@ -4,6 +4,7 @@ export namespace CampaignPageEnum {
Essentials = "CampaignPageBlocksEssentials",
CarouselCards = "CampaignPageBlocksCarouselCards",
Accordion = "CampaignPageBlocksAccordion",
HotelListing = "CampaignPageBlocksHotelListing",
}
}
}

View File

@@ -6,7 +6,7 @@ import type { cardsGridSchema } from "@/server/routers/contentstack/schemas/bloc
import type { carouselCardsSchema } from "@/server/routers/contentstack/schemas/blocks/carouselCards"
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 { contentPageHotelListingSchema } from "@/server/routers/contentstack/schemas/blocks/hotelListing"
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"
@@ -22,7 +22,8 @@ export interface TableBlock extends z.output<typeof tableSchema> {}
export type TableData = TableBlock["table"]
export interface TextCols extends z.output<typeof textColsSchema> {}
export interface UspGrid extends z.output<typeof uspGridSchema> {}
interface GetHotelListing extends z.output<typeof hotelListingSchema> {}
interface GetHotelListing
extends z.output<typeof contentPageHotelListingSchema> {}
export type HotelListing = GetHotelListing["hotel_listing"]
export interface CarouselCards extends z.output<typeof carouselCardsSchema> {}
export interface CardGallery extends z.output<typeof cardGallerySchema> {}

View File

@@ -1,7 +1,6 @@
import type { z } from "zod"
import type {
blocksSchema,
campaignPageRefsSchema,
campaignPageSchema,
heroSchema,
@@ -19,7 +18,7 @@ export interface GetCampaignPageRefsData
export interface CampaignPageRefs
extends z.output<typeof campaignPageRefsSchema> {}
export type Block = z.output<typeof blocksSchema>
export type Block = CampaignPageData["blocks"][number]
export type EssentialsBlock = z.output<typeof essentialsSchema>["essentials"]

View File

@@ -4,6 +4,7 @@ import { iconVariants } from '../variants'
export default function DowntownCamperIcon({
className,
color,
height = 30,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -11,7 +12,7 @@ export default function DowntownCamperIcon({
<svg
className={classNames}
width="123"
height="30"
height={height}
viewBox="0 0 123 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function GrandHotelOsloLogoIcon({
className,
color,
height = 30,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function GrandHotelOsloLogoIcon({
<svg
className={classNames}
width="69"
height="30"
height={height}
viewBox="0 0 69 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function HaymarketIcon({
className,
color,
height = 26,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function HaymarketIcon({
<svg
className={classNames}
width="100"
height="26"
height={height}
viewBox="0 0 100 26"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function HotelNorgeIcon({
className,
color,
height = 24,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function HotelNorgeIcon({
<svg
className={classNames}
width="117"
height="24"
height={height}
viewBox="0 0 117 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function MarskiLogoIcon({
className,
color,
height = 30,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function MarskiLogoIcon({
<svg
className={classNames}
width="92"
height="30"
height={height}
viewBox="0 0 92 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function ScandicGoLogoIcon({
className,
color,
height = 18,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function ScandicGoLogoIcon({
<svg
className={classNames}
width="90"
height="18"
height={height}
viewBox="0 0 90 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -5,6 +5,7 @@ import type { LogoProps } from '../icon'
export default function ScandicLogoIcon({
className,
color,
height = 14,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
@@ -12,7 +13,7 @@ export default function ScandicLogoIcon({
<svg
className={classNames}
width="58"
height="14"
height={height}
viewBox="0 0 58 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,14 +1,19 @@
import type { LogoProps } from '../icon'
import { iconVariants } from '../variants'
export default function TheDockIcon({ className, color, ...props }: LogoProps) {
export default function TheDockIcon({
className,
color,
height = 30,
...props
}: LogoProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="128"
height="30"
height={height}
viewBox="0 0 128 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -10,6 +10,7 @@ import TheDockIcon from './TheDock'
type HotelLogoProps = {
hotelId: string
hotelType: string
height?: number
}
enum HotelTypeEnum {
@@ -27,25 +28,29 @@ enum SignatureHotelEnum {
TheDock = '796',
}
export default function HotelLogoIcon({ hotelId, hotelType }: HotelLogoProps) {
export default function HotelLogoIcon({
hotelId,
hotelType,
height,
}: HotelLogoProps) {
if (hotelType === HotelTypeEnum.ScandicGo) {
return <ScandicGoLogoIcon />
return <ScandicGoLogoIcon height={height} />
}
switch (hotelId) {
case SignatureHotelEnum.Haymarket:
return <HaymarketIcon />
return <HaymarketIcon height={height} />
case SignatureHotelEnum.HotelNorge:
return <HotelNorgeIcon />
return <HotelNorgeIcon height={height} />
case SignatureHotelEnum.DowntownCamper:
return <DowntownCamperIcon />
return <DowntownCamperIcon height={height} />
case SignatureHotelEnum.GrandHotelOslo:
return <GrandHotelOsloLogoIcon />
return <GrandHotelOsloLogoIcon height={height} />
case SignatureHotelEnum.Marski:
return <MarskiLogoIcon />
return <MarskiLogoIcon height={height} />
case SignatureHotelEnum.TheDock:
return <TheDockIcon />
return <TheDockIcon height={height} />
default:
return <ScandicLogoIcon color="Icon/Interactive/Accent" />
return <ScandicLogoIcon height={height} color="Icon/Interactive/Accent" />
}
}