feat(SW-415): Added Image gallery
This commit is contained in:
@@ -4,7 +4,13 @@ import { useIntl } from "react-intl"
|
|||||||
import { RateDefinition } from "@/server/routers/hotels/output"
|
import { RateDefinition } from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
|
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
|
||||||
import { ChevronRightSmallIcon } from "@/components/Icons"
|
import {
|
||||||
|
ChevronRightSmallIcon,
|
||||||
|
GalleryIcon,
|
||||||
|
ImageIcon,
|
||||||
|
} from "@/components/Icons"
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
import Lightbox from "@/components/Lightbox"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
@@ -53,17 +59,22 @@ export default function RoomCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const roomSize = roomCategories.find(
|
const roomSize = roomCategories.find(
|
||||||
(category) => category.name === roomConfiguration.roomType
|
(room) => room.name === roomConfiguration.roomType
|
||||||
)?.roomSize
|
)?.roomSize
|
||||||
|
|
||||||
const occupancy = roomCategories.find(
|
const occupancy = roomCategories.find(
|
||||||
(category) => category.name === roomConfiguration.roomType
|
(room) => room.name === roomConfiguration.roomType
|
||||||
)?.occupancy.total
|
)?.occupancy.total
|
||||||
|
|
||||||
const roomDescription = roomCategories.find(
|
const roomDescription = roomCategories.find(
|
||||||
(room) => room.name === roomConfiguration.roomType
|
(room) => room.name === roomConfiguration.roomType
|
||||||
)?.descriptions.short
|
)?.descriptions.short
|
||||||
|
|
||||||
|
const images = roomCategories.find(
|
||||||
|
(room) => room.name === roomConfiguration.roomType
|
||||||
|
)?.images
|
||||||
|
const mainImage = images?.[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<div className={styles.cardBody}>
|
<div className={styles.cardBody}>
|
||||||
@@ -78,7 +89,10 @@ export default function RoomCard({
|
|||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{roomSize?.min}-{roomSize?.max} m²
|
{roomSize?.min === roomSize?.max
|
||||||
|
? roomSize?.min
|
||||||
|
: `${roomSize?.min}-${roomSize?.max}`}
|
||||||
|
m²
|
||||||
</Caption>
|
</Caption>
|
||||||
<Button
|
<Button
|
||||||
intent="text"
|
intent="text"
|
||||||
@@ -103,7 +117,7 @@ export default function RoomCard({
|
|||||||
id: "Breakfast selection in next step.",
|
id: "Breakfast selection in next step.",
|
||||||
})}
|
})}
|
||||||
</Caption>
|
</Caption>
|
||||||
<div>
|
<div className={styles.flexibilityOptions}>
|
||||||
<FlexibilityOption
|
<FlexibilityOption
|
||||||
name={intl.formatMessage({ id: "Non-refundable" })}
|
name={intl.formatMessage({ id: "Non-refundable" })}
|
||||||
value="non-refundable"
|
value="non-refundable"
|
||||||
@@ -128,22 +142,41 @@ export default function RoomCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{mainImage && (
|
||||||
<div className={styles.imageContainer}>
|
<div className={styles.imageContainer}>
|
||||||
<span className={styles.roomsLeft}>
|
{roomConfiguration.roomsLeft < 5 && (
|
||||||
<Footnote
|
<span className={styles.roomsLeft}>
|
||||||
color="burgundy"
|
<Footnote
|
||||||
textTransform="uppercase"
|
color="burgundy"
|
||||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
textTransform="uppercase"
|
||||||
</span>
|
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
||||||
{/* TODO: maybe use the `Image` component instead of the `img` tag. Waiting until we know how to get the image */}
|
</span>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
)}
|
||||||
<img
|
{/*NOTE: images from the test API are hosted on test3.scandichotels.com,
|
||||||
alt={intl.formatMessage({ id: "A photo of the room" })}
|
which can't be accessed unless on Scandic's Wifi or using Citrix. */}
|
||||||
// TODO: Correct image URL
|
<Image
|
||||||
src="https://www.scandichotels.se/imageVault/publishedmedia/xnmqnmz6mz0uhuat0917/scandic-helsinki-hub-room-standard-KR-7.jpg"
|
src={mainImage.imageSizes.small}
|
||||||
/>
|
alt={mainImage.metaData.altText}
|
||||||
</div>
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
|
margin-bottom: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
@@ -57,6 +58,12 @@
|
|||||||
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flexibilityOptions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
.roomsLeft {
|
.roomsLeft {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
@@ -65,3 +72,22 @@
|
|||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
border-radius: var(--Corner-radius-Small);
|
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"
|
"use client"
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
|
|
||||||
import RoomCard from "./RoomCard"
|
import RoomCard from "./RoomCard"
|
||||||
|
|
||||||
@@ -16,7 +13,6 @@ export default function RoomSelection({
|
|||||||
}: RoomSelectionProps) {
|
}: RoomSelectionProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const intl = useIntl()
|
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -44,12 +40,12 @@ export default function RoomSelection({
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className={styles.summary}>
|
{/* <div className={styles.summary}>
|
||||||
This is summary
|
This is summary
|
||||||
<Button type="submit" size="small" theme="primaryDark">
|
<Button type="submit" size="small" theme="primaryDark">
|
||||||
{intl.formatMessage({ id: "Choose room" })}
|
{intl.formatMessage({ id: "Choose room" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div> */}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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,
|
EyeHideIcon,
|
||||||
EyeShowIcon,
|
EyeShowIcon,
|
||||||
FitnessIcon,
|
FitnessIcon,
|
||||||
|
GalleryIcon,
|
||||||
GiftIcon,
|
GiftIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
HouseIcon,
|
HouseIcon,
|
||||||
@@ -124,6 +125,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
|||||||
return FacebookIcon
|
return FacebookIcon
|
||||||
case IconName.Fitness:
|
case IconName.Fitness:
|
||||||
return FitnessIcon
|
return FitnessIcon
|
||||||
|
case IconName.Gallery:
|
||||||
|
return GalleryIcon
|
||||||
case IconName.Gift:
|
case IconName.Gift:
|
||||||
return GiftIcon
|
return GiftIcon
|
||||||
case IconName.Globe:
|
case IconName.Globe:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export { default as ErrorCircleIcon } from "./ErrorCircle"
|
|||||||
export { default as EyeHideIcon } from "./EyeHide"
|
export { default as EyeHideIcon } from "./EyeHide"
|
||||||
export { default as EyeShowIcon } from "./EyeShow"
|
export { default as EyeShowIcon } from "./EyeShow"
|
||||||
export { default as FitnessIcon } from "./Fitness"
|
export { default as FitnessIcon } from "./Fitness"
|
||||||
|
export { default as GalleryIcon } from "./Gallery"
|
||||||
export { default as GiftIcon } from "./Gift"
|
export { default as GiftIcon } from "./Gift"
|
||||||
export { default as GlobeIcon } from "./Globe"
|
export { default as GlobeIcon } from "./Globe"
|
||||||
export { default as HeartIcon } from "./Heart"
|
export { default as HeartIcon } from "./Heart"
|
||||||
|
|||||||
@@ -66,3 +66,7 @@
|
|||||||
.uiTextPlaceholder {
|
.uiTextPlaceholder {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--UI-Text-Placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: var(--Main-Grey-White);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const config = {
|
|||||||
peach50: styles.peach50,
|
peach50: styles.peach50,
|
||||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||||
|
white: styles.white,
|
||||||
},
|
},
|
||||||
textAlign: {
|
textAlign: {
|
||||||
center: styles.center,
|
center: styles.center,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export enum IconName {
|
|||||||
EyeShow = "EyeShow",
|
EyeShow = "EyeShow",
|
||||||
Facebook = "Facebook",
|
Facebook = "Facebook",
|
||||||
Fitness = "Fitness",
|
Fitness = "Fitness",
|
||||||
|
Gallery = "Gallery",
|
||||||
Gift = "Gift",
|
Gift = "Gift",
|
||||||
Globe = "Globe",
|
Globe = "Globe",
|
||||||
House = "House",
|
House = "House",
|
||||||
|
|||||||
Reference in New Issue
Block a user