feat: Add AmenitiesList and IntroSection components to Hotel Page

This commit is contained in:
Chuma McPhoy
2024-07-05 19:26:34 +02:00
parent 1e3bbed6d1
commit 26815cc9bc
17 changed files with 257 additions and 7 deletions

View File

@@ -0,0 +1,18 @@
.amenitiesContainer {
display: grid;
gap: var(--Spacing-x-one-and-half);
height: fit-content;
}
.showAllButton {
padding: var(--Spacing-x-one-and-half) var(--Spacing-x0) !important;
}
@media screen and (min-width: 1367px) {
.amenitiesContainer {
background-color: var(--Scandic-Beige-10);
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x-one-and-half)
var(--Spacing-x3);
border-radius: var(--Corner-radius-Large);
}
}

View File

@@ -0,0 +1,39 @@
import { ChevronRightIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./amenitiesList.module.css"
import { HotelData } from "@/types/hotel"
export default function AmenitiesList({
detailedFacilities,
}: {
detailedFacilities: HotelData["data"]["attributes"]["detailedFacilities"]
}) {
const sortedAmenities = detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5)
return (
<section className={styles.amenitiesContainer}>
<Subtitle color="black">At the hotel</Subtitle>
<div>
{sortedAmenities.map((facility, index) => (
<Body color="textMediumContrast" key={facility.id}>
{facility.name}
</Body>
))}
</div>
<Button
className={styles.showAllButton}
theme="base"
variant="icon"
intent="text"
>
Show all amenities
<ChevronRightIcon />
</Button>
</section>
)
}

View File

@@ -1,6 +1,7 @@
import { serverClient } from "@/lib/trpc/server"
import Title from "@/components/TempDesignSystem/Text/Title"
import AmenitiesList from "./AmenitiesList"
import IntroSection from "./IntroSection"
import styles from "./hotelPage.module.css"
@@ -18,9 +19,23 @@ export default async function HotelPage({ lang }: LangParams) {
hotelId: hotelPageIdentifierData.hotel_page_id,
language: lang,
})
return (
<section className={styles.content}>
<Title>{hotelPageData.data.attributes.name}</Title>
</section>
<main className={styles.pageContainer}>
<div className={styles.introContainer}>
<IntroSection
hotelName={hotelPageData.data.attributes.name}
hotelDescription={
hotelPageData.data.attributes.hotelContent.texts.descriptions.short
}
location={hotelPageData.data.attributes.location}
address={hotelPageData.data.attributes.address}
tripAdvisor={hotelPageData.data.attributes.ratings.tripAdvisor}
/>
<AmenitiesList
detailedFacilities={hotelPageData.data.attributes.detailedFacilities}
/>
</div>
</main>
)
}

View File

@@ -0,0 +1,73 @@
import ArrowRight from "@/components/Icons/ArrowRight"
import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
import Link from "@/components/TempDesignSystem/Link"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import styles from "./introSection.module.css"
import { HotelAddress, HotelData, HotelLocation } from "@/types/hotel"
export default async function IntroSection({
hotelName,
hotelDescription,
location,
address,
tripAdvisor,
}: {
hotelName: HotelData["data"]["attributes"]["name"]
hotelDescription: HotelData["data"]["attributes"]["hotelContent"]["texts"]["descriptions"]["short"]
location: HotelLocation
address: HotelAddress
tripAdvisor: HotelData["data"]["attributes"]["ratings"]["tripAdvisor"]
}) {
const intl = await getIntl()
const { formatMessage } = intl
const { streetAddress, city } = address
const { distanceToCentre } = location
const formattedDistanceText = formatMessage(
{ id: "Distance to city centre" },
{ number: distanceToCentre }
)
const formattedLocationText = `${streetAddress}, ${city} (${formattedDistanceText})`
const formattedTripAdvisorText = `${tripAdvisor.rating} (${tripAdvisor.numberOfReviews} reviews on tripadvisor)`
return (
<section className={styles.introSection}>
<BiroScript className={styles.welcomeLabel} color="red">
{formatMessage({ id: "Welcome to" })}:
</BiroScript>
<div className={styles.mainContent}>
<Title level="h2">{hotelName}</Title>
<Body color="textMediumContrast">{formattedLocationText}</Body>
<Link
className={styles.introLink}
target="_blank"
variant="icon"
color="peach80"
href={tripAdvisor.webUrl}
>
<TripAdvisorIcon color="peach80" />
{formattedTripAdvisorText}
</Link>
</div>
<div className={styles.subtitleContent}>
<Subtitle color="black">{hotelDescription}</Subtitle>
<Link
className={styles.introLink}
target="_blank"
color="peach80"
variant="icon"
href="#"
>
{/*TODO: Ask content team where this should link to. */}
Read more about the hotel
<ArrowRight color="peach80" />
</Link>
</div>
</section>
)
}

View File

@@ -0,0 +1,27 @@
.introSection {
margin-top: var(--Spacing-x3);
display: grid;
gap: var(--Spacing-x3);
position: relative;
}
.mainContent {
display: grid;
gap: var(--Spacing-x1);
}
.subtitleContent {
display: grid;
gap: var(--Spacing-x-one-and-half);
}
.welcomeLabel {
/* TODO: Update and use absolute position. */
transform: rotate(-3.378deg) translate(0px, -15px);
}
.introLink {
text-decoration: underline !important;
text-decoration-color: var(--Scandic-Peach-80);
width: fit-content;
}

View File

@@ -1,11 +1,23 @@
.content {
.pageContainer {
display: grid;
gap: var(--Spacing-x4);
gap: var(--Spacing-x9);
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
}
.introContainer {
display: grid;
grid-template-columns: 1fr;
gap: var(--Spacing-x4);
}
@media screen and (min-width: 1367px) {
.content {
.pageContainer {
gap: var(--Spacing-x3);
padding-left: var(--Spacing-x5);
}
.introContainer {
display: grid;
gap: 105px;
grid-template-columns: 607px 340px;
}
}

View File

@@ -0,0 +1,42 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function TripAdvisorIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_3856_6685"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_3856_6685)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.05211 12.7618C9.05211 13.6531 8.29824 14.3757 7.3683 14.3757C6.43836 14.3757 5.68449 13.6531 5.68449 12.7618C5.68449 11.8705 6.43836 11.148 7.3683 11.148C8.29809 11.1473 9.05234 11.8692 9.05299 12.7604V12.7618H9.05211ZM16.8618 11.148C15.9318 11.148 15.1779 11.8705 15.1779 12.7618C15.1779 13.6531 15.9318 14.3757 16.8618 14.3757C17.7917 14.3757 18.5456 13.6531 18.5456 12.7618C18.5456 11.8705 17.7917 11.148 16.8618 11.148ZM16.8618 15.8404C15.0878 15.8404 13.6497 14.4621 13.6497 12.7618C13.6497 11.0615 15.0878 9.6832 16.8618 9.6832C18.6357 9.6832 20.0738 11.0615 20.0738 12.7618C20.0738 14.4621 18.6357 15.8404 16.8618 15.8404ZM12.1165 12.673C12.1165 10.6472 10.5782 8.90887 8.55158 8.16488C10.833 7.25401 13.3994 7.25401 15.6808 8.16488C13.6536 8.90915 12.1165 10.6472 12.1165 12.673ZM7.36919 15.8404C5.59524 15.8404 4.15717 14.4621 4.15717 12.7618C4.15717 11.0615 5.59524 9.6832 7.36919 9.6832C9.14313 9.6832 10.5812 11.0615 10.5812 12.7618C10.5812 14.4621 9.14313 15.8404 7.36919 15.8404ZM20.0619 9.39966L21.615 7.78013H18.1719C14.5206 5.39849 9.72128 5.39849 6.06994 7.78013H2.61802L4.17108 9.39966C2.2302 11.0885 2.08519 13.9656 3.84718 15.8258C5.60917 17.6862 8.61095 17.8251 10.5518 16.1363C10.5663 16.1237 10.5805 16.1111 10.5948 16.0984L12.1165 17.6862L13.6373 16.1004C14.5135 16.8807 15.6657 17.3138 16.8618 17.3122C19.483 17.3122 21.61 15.2753 21.61 12.7629C21.6119 11.4828 21.0497 10.2613 20.0619 9.39966Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -61,3 +61,7 @@
.plosa {
color: var(--Theme-Primary-Light-On-Surface-Accent);
}
.red {
color: var(--Scandic-Brand-Scandic-Red);
}

View File

@@ -10,6 +10,7 @@ const config = {
pale: styles.pale,
peach80: styles.peach80,
primaryLightOnSurfaceAccent: styles.plosa,
red: styles.red,
},
textAlign: {
center: styles.center,

View File

@@ -58,6 +58,10 @@
color: var(--Scandic-Brand-Scandic-Red);
}
.textMediumContrast {
color: var(--Base-Text-UI-Medium-contrast);
}
.white {
color: var(--Scandic-Opacity-White-100);
}

View File

@@ -9,6 +9,7 @@ const config = {
burgundy: styles.burgundy,
pale: styles.pale,
red: styles.red,
textMediumContrast: styles.textMediumContrast,
white: styles.white,
},
textAlign: {

View File

@@ -91,6 +91,7 @@
"User information": "Brugeroplysninger",
"uppercase letter": "stort bogstav",
"Visiting address": "Besøgsadresse",
"Welcome to": "Velkommen til",
"Where should you go next?": "Hvor skal du tage hen næste gang?",
"Year": "År",
"You have no previous stays.": "Du har ingen tidligere ophold.",

View File

@@ -91,6 +91,7 @@
"User information": "Nutzerinformation",
"uppercase letter": "großbuchstabe",
"Visiting address": "Besuchsadresse",
"Welcome to": "Willkommen zu",
"Where should you go next?": "Wohin soll es als nächstes gehen?",
"Year": "Jahr",
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",

View File

@@ -27,6 +27,7 @@
"Day": "Day",
"Description": "Description",
"Discard changes": "Discard changes",
"Distance to city centre": "{number}km to city centre",
"Edit": "Edit",
"Edit profile": "Edit profile",
"Email": "Email",
@@ -91,6 +92,7 @@
"User information": "User information",
"uppercase letter": "uppercase letter",
"Visiting address": "Visiting address",
"Welcome to": "Welcome to",
"Where should you go next?": "Where should you go next?",
"Year": "Year",
"You have no previous stays.": "You have no previous stays.",

View File

@@ -91,6 +91,7 @@
"User information": "Käyttäjän tiedot",
"uppercase letter": "iso kirjain",
"Visiting address": "Käyntiosoite",
"Welcome to": "Tervetuloa",
"Where should you go next?": "Minne sinun pitäisi mennä seuraavaksi?",
"Year": "Vuosi",
"You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.",

View File

@@ -91,6 +91,7 @@
"User information": "Brukerinformasjon",
"uppercase letter": "stor bokstav",
"Visiting address": "Besøksadresse",
"Welcome to": "Velkommen til",
"Where should you go next?": "Hvor bør du gå videre?",
"Year": "År",
"You have no previous stays.": "Du har ingen tidligere opphold.",

8
types/hotel.ts Normal file
View File

@@ -0,0 +1,8 @@
import { z } from "zod"
import { getHotelDataSchema } from "@/server/routers/hotels/output"
export type HotelData = z.infer<typeof getHotelDataSchema>
export type HotelAddress = HotelData["data"]["attributes"]["address"]
export type HotelLocation = HotelData["data"]["attributes"]["location"]