feat: Add AmenitiesList and IntroSection components to Hotel Page
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
39
components/ContentType/HotelPage/AmenitiesList/index.tsx
Normal file
39
components/ContentType/HotelPage/AmenitiesList/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
73
components/ContentType/HotelPage/IntroSection/index.tsx
Normal file
73
components/ContentType/HotelPage/IntroSection/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
42
components/Icons/TripAdvisor.tsx
Normal file
42
components/Icons/TripAdvisor.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -61,3 +61,7 @@
|
||||
.plosa {
|
||||
color: var(--Theme-Primary-Light-On-Surface-Accent);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ const config = {
|
||||
pale: styles.pale,
|
||||
peach80: styles.peach80,
|
||||
primaryLightOnSurfaceAccent: styles.plosa,
|
||||
red: styles.red,
|
||||
},
|
||||
textAlign: {
|
||||
center: styles.center,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const config = {
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
red: styles.red,
|
||||
textMediumContrast: styles.textMediumContrast,
|
||||
white: styles.white,
|
||||
},
|
||||
textAlign: {
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
8
types/hotel.ts
Normal 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"]
|
||||
Reference in New Issue
Block a user