Merged in feat/415-room-card-image-gallery (pull request #679)
feat(SW-415): Added Image gallery to room card Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -4,7 +4,9 @@ import { useIntl } from "react-intl"
|
||||
import { RateDefinition } from "@/server/routers/hotels/output"
|
||||
|
||||
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
|
||||
import { ChevronRightSmallIcon } from "@/components/Icons"
|
||||
import { ChevronRightSmallIcon, GalleryIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Lightbox from "@/components/Lightbox"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
@@ -51,18 +53,15 @@ export default function RoomCard({
|
||||
return rateDefinitions.find((def) => def.rateCode === rate?.rateCode)
|
||||
?.generalTerms
|
||||
}
|
||||
|
||||
const roomSize = roomCategories.find(
|
||||
(category) => category.name === roomConfiguration.roomType
|
||||
)?.roomSize
|
||||
|
||||
const occupancy = roomCategories.find(
|
||||
(category) => category.name === roomConfiguration.roomType
|
||||
)?.occupancy.total
|
||||
|
||||
const roomDescription = roomCategories.find(
|
||||
const selectedRoom = roomCategories.find(
|
||||
(room) => room.name === roomConfiguration.roomType
|
||||
)?.descriptions.short
|
||||
)
|
||||
|
||||
const roomSize = selectedRoom?.roomSize
|
||||
const occupancy = selectedRoom?.occupancy.total
|
||||
const roomDescription = selectedRoom?.descriptions.short
|
||||
const images = selectedRoom?.images
|
||||
const mainImage = images?.[0]
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
@@ -78,7 +77,10 @@ export default function RoomCard({
|
||||
)}
|
||||
</Caption>
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{roomSize?.min}-{roomSize?.max} m²
|
||||
{roomSize?.min === roomSize?.max
|
||||
? roomSize?.min
|
||||
: `${roomSize?.min}-${roomSize?.max}`}
|
||||
m²
|
||||
</Caption>
|
||||
<Button
|
||||
intent="text"
|
||||
@@ -103,7 +105,7 @@ export default function RoomCard({
|
||||
id: "Breakfast selection in next step.",
|
||||
})}
|
||||
</Caption>
|
||||
<div>
|
||||
<div className={styles.flexibilityOptions}>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Non-refundable" })}
|
||||
value="non-refundable"
|
||||
@@ -128,22 +130,41 @@ export default function RoomCard({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<span className={styles.roomsLeft}>
|
||||
<Footnote
|
||||
color="burgundy"
|
||||
textTransform="uppercase"
|
||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
||||
</span>
|
||||
{/* TODO: maybe use the `Image` component instead of the `img` tag. Waiting until we know how to get the image */}
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
alt={intl.formatMessage({ id: "A photo of the room" })}
|
||||
// TODO: Correct image URL
|
||||
src="https://www.scandichotels.se/imageVault/publishedmedia/xnmqnmz6mz0uhuat0917/scandic-helsinki-hub-room-standard-KR-7.jpg"
|
||||
/>
|
||||
</div>
|
||||
{mainImage && (
|
||||
<div className={styles.imageContainer}>
|
||||
{roomConfiguration.roomsLeft < 5 && (
|
||||
<span className={styles.roomsLeft}>
|
||||
<Footnote
|
||||
color="burgundy"
|
||||
textTransform="uppercase"
|
||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
||||
</span>
|
||||
)}
|
||||
{/*NOTE: images from the test API are hosted on test3.scandichotels.com,
|
||||
which can't be accessed unless on Scandic's Wifi or using Citrix. */}
|
||||
<Image
|
||||
src={mainImage.imageSizes.small}
|
||||
alt={mainImage.metaData.altText}
|
||||
width={330}
|
||||
height={185}
|
||||
/>
|
||||
{images && (
|
||||
<Lightbox
|
||||
images={images.map((image) => ({
|
||||
url: image.imageSizes.small,
|
||||
alt: image.metaData.altText,
|
||||
title: image.metaData.title,
|
||||
}))}
|
||||
dialogTitle={roomConfiguration.roomType}
|
||||
>
|
||||
<div className={styles.galleryIcon} id="lightboxTrigger">
|
||||
<GalleryIcon color="white" />
|
||||
<Footnote color="white">{images.length}</Footnote>
|
||||
</div>
|
||||
</Lightbox>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
margin-bottom: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.name {
|
||||
@@ -57,6 +58,12 @@
|
||||
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
||||
}
|
||||
|
||||
.flexibilityOptions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomsLeft {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
@@ -65,3 +72,22 @@
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
min-height: 185px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.galleryIcon {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
height: 24px;
|
||||
background-color: rgba(64, 57, 55, 0.9);
|
||||
padding: 0 var(--Spacing-x-half);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-quarter);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
import RoomCard from "./RoomCard"
|
||||
|
||||
@@ -16,7 +13,6 @@ export default function RoomSelection({
|
||||
}: RoomSelectionProps) {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const intl = useIntl()
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
@@ -44,12 +40,12 @@ export default function RoomSelection({
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className={styles.summary}>
|
||||
{/* <div className={styles.summary}>
|
||||
This is summary
|
||||
<Button type="submit" size="small" theme="primaryDark">
|
||||
{intl.formatMessage({ id: "Choose room" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
|
||||
36
components/Icons/Gallery.tsx
Normal file
36
components/Icons/Gallery.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function GalleryIcon({ className, color, ...props }: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
id="mask0_69_3274"
|
||||
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_5887_18142)">
|
||||
<path
|
||||
d="M9.15 13.9H18.65L15.3725 9.625L13.1875 12.475L11.715 10.575L9.15 13.9ZM8.2 17.7C7.6775 17.7 7.23021 17.514 6.85813 17.1419C6.48604 16.7698 6.3 16.3225 6.3 15.8V4.4C6.3 3.8775 6.48604 3.43021 6.85813 3.05813C7.23021 2.68604 7.6775 2.5 8.2 2.5H19.6C20.1225 2.5 20.5698 2.68604 20.9419 3.05813C21.314 3.43021 21.5 3.8775 21.5 4.4V15.8C21.5 16.3225 21.314 16.7698 20.9419 17.1419C20.5698 17.514 20.1225 17.7 19.6 17.7H8.2ZM8.2 15.8H19.6V4.4H8.2V15.8ZM4.4 21.5C3.8775 21.5 3.43021 21.314 3.05813 20.9419C2.68604 20.5698 2.5 20.1225 2.5 19.6V6.3H4.4V19.6H17.7V21.5H4.4Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
EyeHideIcon,
|
||||
EyeShowIcon,
|
||||
FitnessIcon,
|
||||
GalleryIcon,
|
||||
GiftIcon,
|
||||
GlobeIcon,
|
||||
HouseIcon,
|
||||
@@ -124,6 +125,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return FacebookIcon
|
||||
case IconName.Fitness:
|
||||
return FitnessIcon
|
||||
case IconName.Gallery:
|
||||
return GalleryIcon
|
||||
case IconName.Gift:
|
||||
return GiftIcon
|
||||
case IconName.Globe:
|
||||
|
||||
@@ -31,6 +31,7 @@ export { default as ErrorCircleIcon } from "./ErrorCircle"
|
||||
export { default as EyeHideIcon } from "./EyeHide"
|
||||
export { default as EyeShowIcon } from "./EyeShow"
|
||||
export { default as FitnessIcon } from "./Fitness"
|
||||
export { default as GalleryIcon } from "./Gallery"
|
||||
export { default as GiftIcon } from "./Gift"
|
||||
export { default as GlobeIcon } from "./Globe"
|
||||
export { default as HeartIcon } from "./Heart"
|
||||
|
||||
@@ -66,3 +66,7 @@
|
||||
.uiTextPlaceholder {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
}
|
||||
|
||||
.white {
|
||||
color: var(--Main-Grey-White);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const config = {
|
||||
peach50: styles.peach50,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||
white: styles.white,
|
||||
},
|
||||
textAlign: {
|
||||
center: styles.center,
|
||||
|
||||
@@ -36,6 +36,7 @@ export enum IconName {
|
||||
EyeShow = "EyeShow",
|
||||
Facebook = "Facebook",
|
||||
Fitness = "Fitness",
|
||||
Gallery = "Gallery",
|
||||
Gift = "Gift",
|
||||
Globe = "Globe",
|
||||
House = "House",
|
||||
|
||||
Reference in New Issue
Block a user