feat(SW-791): make confirmation page dynamic

This commit is contained in:
Simon Emanuelsson
2024-11-06 16:31:03 +01:00
parent e6a70a0a8a
commit 0897a398ee
35 changed files with 983 additions and 577 deletions

View File

@@ -0,0 +1,34 @@
.actions {
background-color: var(--Base-Surface-Subtle-Normal);
border-radius: var(--Corner-radius-Medium);
display: grid;
grid-area: actions;
padding: var(--Spacing-x1) var(--Spacing-x2);
}
@media screen and (max-width: 767px) {
.actions {
& > button[class*="btn"][class*="icon"][class*="small"] {
border-bottom: 1px solid var(--Base-Border-Subtle);
border-radius: 0;
justify-content: space-between;
&:last-of-type {
border-bottom: none;
}
& > svg {
order: 2;
}
}
}
}
@media screen and (min-width: 768px) {
.actions {
gap: var(--Spacing-x1);
grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
justify-content: center;
padding: var(--Spacing-x1) var(--Spacing-x3);
}
}

View File

@@ -0,0 +1,38 @@
import {
CalendarIcon,
ContractIcon,
DownloadIcon,
PrinterIcon,
} from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import { getIntl } from "@/i18n"
import styles from "./actions.module.css"
export default async function Actions() {
const intl = await getIntl()
return (
<div className={styles.actions}>
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
<CalendarIcon />
{intl.formatMessage({ id: "Add to calendar" })}
</Button>
<Divider color="subtle" variant="vertical" />
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
<ContractIcon />
{intl.formatMessage({ id: "View terms" })}
</Button>
<Divider color="subtle" variant="vertical" />
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
<PrinterIcon />
{intl.formatMessage({ id: "Print confirmation" })}
</Button>
<Divider color="subtle" variant="vertical" />
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
<DownloadIcon />
{intl.formatMessage({ id: "Download invoice" })}
</Button>
</div>
)
}

View File

@@ -0,0 +1,31 @@
.details {
background-color: var(--Base-Surface-Subtle-Normal);
border-radius: var(--Corner-radius-Medium);
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
grid-area: details;
padding: var(--Spacing-x2);
}
.list {
display: flex;
flex-direction: column;
gap: var(--Spacing-x-one-and-half);
list-style: none;
margin: 0;
padding: 0;
}
.listItem {
align-items: center;
display: flex;
gap: var(--Spacing-x1);
justify-content: space-between;
}
@media screen and (min-width: 768px) {
.details {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x2);
}
}

View File

@@ -0,0 +1,61 @@
import { dt } from "@/lib/dt"
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import styles from "./details.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default async function Details({
confirmationNumber,
}: BookingConfirmationProps) {
const intl = await getIntl()
const lang = getLang()
const { booking } = await getBookingConfirmation(confirmationNumber)
const fromDate = dt(booking.checkInDate).locale(lang)
const toDate = dt(booking.checkOutDate).locale(lang)
return (
<article className={styles.details}>
<header>
<Subtitle color="burgundy" type="two">
{intl.formatMessage(
{ id: "Reference #{bookingNr}" },
{ bookingNr: booking.confirmationNumber }
)}
</Subtitle>
</header>
<ul className={styles.list}>
<li className={styles.listItem}>
<Body>{intl.formatMessage({ id: "Check-in" })}</Body>
<Body>
{`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`}
</Body>
</li>
<li className={styles.listItem}>
<Body>{intl.formatMessage({ id: "Check-out" })}</Body>
<Body>
{`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`}
</Body>
</li>
<li className={styles.listItem}>
<Body>{intl.formatMessage({ id: "Breakfast" })}</Body>
<Body>N/A</Body>
</li>
<li className={styles.listItem}>
<Body>{intl.formatMessage({ id: "Cancellation policy" })}</Body>
<Body>N/A</Body>
</li>
<li className={styles.listItem}>
<Body>{intl.formatMessage({ id: "Rebooking" })}</Body>
<Body>N/A</Body>
</li>
</ul>
</article>
)
}

View File

@@ -0,0 +1,18 @@
.header,
.hgroup {
align-items: center;
display: flex;
flex-direction: column;
}
.header {
gap: var(--Spacing-x3);
}
.hgroup {
gap: var(--Spacing-x-half);
}
.body {
max-width: 560px;
}

View File

@@ -0,0 +1,60 @@
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import Link from "@/components/TempDesignSystem/Link"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import styles from "./header.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default async function Header({
confirmationNumber,
}: BookingConfirmationProps) {
const intl = await getIntl()
const { hotel } = await getBookingConfirmation(confirmationNumber)
const text = intl.formatMessage<React.ReactNode>(
{ id: "booking.confirmation.text" },
{
emailLink: (str) => (
<Link color="burgundy" href="#" textDecoration="underline">
{str}
</Link>
),
}
)
return (
<header className={styles.header}>
<hgroup className={styles.hgroup}>
<BiroScript color="red" tilted="small" type="two">
{intl.formatMessage({ id: "See you soon!" })}
</BiroScript>
<Title
as="h4"
color="red"
textAlign="center"
textTransform="regular"
type="h2"
>
{intl.formatMessage({ id: "booking.confirmation.title" })}
</Title>
<Title
as="h4"
color="burgundy"
textAlign="center"
textTransform="regular"
type="h1"
>
{hotel.name}
</Title>
</hgroup>
<Body className={styles.body} textAlign="center">
{text}
</Body>
</header>
)
}

View File

@@ -0,0 +1,7 @@
.imageContainer {
align-items: center;
border-radius: var(--Corner-radius-Medium);
display: flex;
grid-area: image;
justify-content: center;
}

View File

@@ -0,0 +1,24 @@
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import Image from "@/components/Image"
import styles from "./image.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default async function HotelImage({
confirmationNumber,
}: BookingConfirmationProps) {
const { hotel } = await getBookingConfirmation(confirmationNumber)
return (
<aside className={styles.imageContainer}>
<Image
alt={hotel.hotelContent.images.metaData.altText}
height={256}
src={hotel.hotelContent.images.imageSizes.medium}
title={hotel.hotelContent.images.metaData.title}
width={256}
/>
</aside>
)
}

View File

@@ -0,0 +1,154 @@
import { profile } from "@/constants/routes/myPages"
import { dt } from "@/lib/dt"
import {
getBookingConfirmation,
getProfileSafely,
} from "@/lib/trpc/memoizedRequests"
import { CreditCardAddIcon, EditIcon, PersonIcon } from "@/components/Icons"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import styles from "./summary.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default async function Summary({
confirmationNumber,
}: BookingConfirmationProps) {
const intl = await getIntl()
const lang = getLang()
const { booking, hotel } = await getBookingConfirmation(confirmationNumber)
const user = await getProfileSafely()
const { firstName, lastName } = booking.guest
const membershipNumber = user?.membership?.membershipNumber
const totalNights = dt(booking.checkOutDate.setHours(0, 0, 0)).diff(
dt(booking.checkInDate.setHours(0, 0, 0)),
"days"
)
const breakfastPackage = booking.packages.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
return (
<div className={styles.summary}>
<div className={styles.container}>
<div className={styles.textContainer}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Guest" })}
</Body>
<Body color="uiTextHighContrast">{`${firstName} ${lastName}`}</Body>
{membershipNumber ? (
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "membership.no" },
{ membershipNumber }
)}
</Body>
) : null}
<Body color="uiTextHighContrast">{booking.guest.email}</Body>
<Body color="uiTextHighContrast">{booking.guest.phoneNumber}</Body>
</div>
{user ? (
<Link className={styles.link} href={profile[lang]} variant="icon">
<PersonIcon color="baseButtonTextOnFillNormal" />
<Caption color="burgundy" type="bold">
{intl.formatMessage({ id: "Go to profile" })}
</Caption>
</Link>
) : null}
</div>
<Divider color="primaryLightSubtle" />
<div className={styles.container}>
<div className={styles.textContainer}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Payment" })}
</Body>
<Body color="uiTextHighContrast">
{intl.formatMessage(
{ id: "guest.paid" },
{
amount: intl.formatNumber(booking.totalPrice),
currency: booking.currencyCode,
}
)}
</Body>
<Body color="uiTextHighContrast">Date information N/A</Body>
<Body color="uiTextHighContrast">Card information N/A</Body>
</div>
{/* # href until more info */}
{user ? (
<Link className={styles.link} href="#" variant="icon">
<CreditCardAddIcon color="baseButtonTextOnFillNormal" />
<Caption color="burgundy" type="bold">
{intl.formatMessage({ id: "Save card to profile" })}
</Caption>
</Link>
) : null}
</div>
<Divider color="primaryLightSubtle" />
<div className={styles.container}>
<div className={styles.textContainer}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Booking" })}
</Body>
<Body color="uiTextHighContrast">
N/A, {intl.formatMessage({ id: "booking.nights" }, { totalNights })}
,{" "}
{intl.formatMessage(
{ id: "booking.adults" },
{ totalAdults: booking.adults }
)}
</Body>
{breakfastPackage ? (
<Body color="uiTextHighContrast">
{intl.formatMessage({ id: "Breakfast added" })}
</Body>
) : null}
<Body color="uiTextHighContrast">Bedtype N/A</Body>
</div>
{/* # href until more info */}
<Link className={styles.link} href="#" variant="icon">
<EditIcon color="baseButtonTextOnFillNormal" />
<Caption color="burgundy" type="bold">
{intl.formatMessage({ id: "Manage booking" })}
</Caption>
</Link>
</div>
<Divider color="primaryLightSubtle" />
<div className={styles.container}>
<div className={styles.textContainer}>
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Hotel" })}
</Body>
<Body color="uiTextHighContrast">{hotel.name}</Body>
<Body color="uiTextHighContrast">
{`${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`}
</Body>
<Body color="uiTextHighContrast">
{hotel.contactInformation.phoneNumber}
</Body>
<Caption color="uiTextMediumContrast" className={styles.latLong}>
{`${intl.formatMessage({ id: "Longitude" }, { long: hotel.location.longitude })} ∙ ${intl.formatMessage({ id: "Latitude" }, { lat: hotel.location.latitude })}`}
</Caption>
</div>
<div className={styles.hotelLinks}>
<Link color="peach80" href={hotel.contactInformation.websiteUrl}>
{hotel.contactInformation.websiteUrl}
</Link>
<Link
color="peach80"
href={`mailto:${hotel.contactInformation.email}`}
>
{hotel.contactInformation.email}
</Link>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,31 @@
.summary {
display: grid;
gap: var(--Spacing-x3);
}
.container,
.textContainer {
display: flex;
flex-direction: column;
}
.container {
gap: var(--Spacing-x-one-and-half);
}
.textContainer {
gap: var(--Spacing-x-half);
}
.container .textContainer .latLong {
padding-top: var(--Spacing-x1);
}
.hotelLinks {
display: flex;
flex-direction: column;
}
.summary .container .link {
gap: var(--Spacing-x1);
}

View File

@@ -0,0 +1,136 @@
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import {
CoffeeIcon,
DiscountIcon,
DoorClosedIcon,
PriceTagIcon,
} from "@/components/Icons"
import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import styles from "./totalPrice.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default async function TotalPrice({
confirmationNumber,
}: BookingConfirmationProps) {
const intl = await getIntl()
const { booking } = await getBookingConfirmation(confirmationNumber)
const totalPrice = intl.formatNumber(booking.totalPrice, {
currency: booking.currencyCode,
style: "currency",
})
const breakfastPackage = booking.packages.find(
(p) => p.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
return (
<section className={styles.container}>
<hgroup>
<Subtitle color="uiTextPlaceholder" type="two">
{intl.formatMessage({ id: "Total price" })}
</Subtitle>
<Subtitle color="uiTextHighContrast" type="two">
{totalPrice} (~ EUR)
</Subtitle>
</hgroup>
<div className={styles.items}>
<div>
<DoorClosedIcon />
<Body color="uiTextPlaceholder">
{`${intl.formatMessage({ id: "Room" })}, ${intl.formatMessage({ id: "booking.nights" }, { totalNights: 1 })}`}
</Body>
<Body color="uiTextHighContrast">{totalPrice}</Body>
</div>
<div>
<CoffeeIcon />
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Breakfast" })}
</Body>
<Body color="uiTextHighContrast">
{breakfastPackage
? intl.formatNumber(breakfastPackage.totalPrice, {
currency: breakfastPackage.currency,
style: "currency",
})
: intl.formatMessage({ id: "No breakfast" })}
</Body>
</div>
<div>
<DiscountIcon />
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Member discount" })}
</Body>
<Body color="uiTextHighContrast">N/A</Body>
</div>
<div>
<PriceTagIcon height={20} width={20} />
<Body color="uiTextPlaceholder">
{intl.formatMessage({ id: "Points used" })}
</Body>
<Body color="uiTextHighContrast">N/A</Body>
</div>
</div>
<Divider color="primaryLightSubtle" />
<div className={styles.items}>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "Price excl VAT" })}
</Caption>
<Caption color="uiTextHighContrast">
{intl.formatNumber(booking.totalPriceExVat, {
currency: booking.currencyCode,
style: "currency",
})}
</Caption>
</div>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "VAT" })}
</Caption>
<Caption color="uiTextHighContrast">{booking.vatPercentage}%</Caption>
</div>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "VAT amount" })}
</Caption>
<Caption color="uiTextHighContrast">
{intl.formatNumber(booking.vatAmount, {
currency: booking.currencyCode,
style: "currency",
})}
</Caption>
</div>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "Price incl VAT" })}
</Caption>
<Caption color="uiTextHighContrast">
{intl.formatNumber(booking.totalPrice, {
currency: booking.currencyCode,
style: "currency",
})}
</Caption>
</div>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "Payment method" })}
</Caption>
<Caption color="uiTextHighContrast">N/A</Caption>
</div>
<div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage({ id: "Payment status" })}
</Caption>
<Caption color="uiTextHighContrast">N/A</Caption>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,14 @@
.container {
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Large);
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x3);
}
.items {
display: grid;
gap: var(--Spacing-x3) var(--Spacing-x1);
grid-template-columns: repeat(4, minmax(100px, 1fr));
}

View File

@@ -0,0 +1,23 @@
.section {
display: flex;
flex-direction: column;
gap: var(--Spacing-x9);
}
.booking {
display: grid;
gap: var(--Spacing-x-one-and-half);
grid-template-areas:
"image"
"details"
"actions";
}
@media screen and (min-width: 768px) {
.booking {
grid-template-areas:
"details image"
"actions actions";
grid-template-columns: 1fr minmax(256px, min(256px, 100%));
}
}

View File

@@ -0,0 +1,31 @@
import Actions from "./Actions"
import Details from "./Details"
import Header from "./Header"
import HotelImage from "./HotelImage"
import Summary from "./Summary"
import TotalPrice from "./TotalPrice"
import styles from "./bookingConfirmation.module.css"
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
export default function BookingConfirmation({
confirmationNumber,
}: BookingConfirmationProps) {
return (
<>
<Header confirmationNumber={confirmationNumber} />
<section className={styles.section}>
<div className={styles.booking}>
<Details confirmationNumber={confirmationNumber} />
<HotelImage confirmationNumber={confirmationNumber} />
<Actions />
</div>
{/* Supposed Ancillaries */}
<Summary confirmationNumber={confirmationNumber} />
<TotalPrice confirmationNumber={confirmationNumber} />
{/* Supposed Info Card - Where should it come from?? */}
</section>
</>
)
}

View File

@@ -1,6 +1,5 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useCallback } from "react"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -44,7 +43,6 @@ export default function Details({ user }: DetailsProps) {
firstName: user?.firstName ?? initialData.firstName,
lastName: user?.lastName ?? initialData.lastName,
phoneNumber: user?.phoneNumber ?? initialData.phoneNumber,
//@ts-expect-error: We use a literal for join to be true or false, which does not convert to a boolean
join: initialData.join,
dateOfBirth: initialData.dateOfBirth,
zipCode: initialData.zipCode,
@@ -58,14 +56,6 @@ export default function Details({ user }: DetailsProps) {
const completeStep = useEnterDetailsStore((state) => state.completeStep)
const onSubmit = useCallback(
async function (values: DetailsSchema) {
completeStep(values)
},
[completeStep]
)
return (
<FormProvider {...methods}>
<section className={styles.container}>
@@ -77,7 +67,7 @@ export default function Details({ user }: DetailsProps) {
<form
className={styles.form}
id={formID}
onSubmit={methods.handleSubmit(onSubmit)}
onSubmit={methods.handleSubmit(completeStep)}
>
<Input
label={intl.formatMessage({ id: "First name" })}

View File

@@ -12,7 +12,7 @@ export const baseDetailsSchema = z.object({
export const notJoinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal(false),
join: z.literal<boolean>(false),
zipCode: z.string().optional(),
dateOfBirth: z.string().optional(),
termsAccepted: z.boolean().default(false),
@@ -21,10 +21,10 @@ export const notJoinDetailsSchema = baseDetailsSchema.merge(
export const joinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal(true),
join: z.literal<boolean>(true),
zipCode: z.string().min(1, { message: "Zip code is required" }),
dateOfBirth: z.string().min(1, { message: "Date of birth is required" }),
termsAccepted: z.literal(true, {
termsAccepted: z.literal<boolean>(true, {
errorMap: (err, ctx) => {
switch (err.code) {
case "invalid_literal":

View File

@@ -0,0 +1,23 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CoffeeIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M4.32422 17.2918C4.10894 17.2918 3.92491 17.2154 3.77214 17.0627C3.61936 16.9099 3.54297 16.7259 3.54297 16.5106C3.54297 16.2953 3.61936 16.1113 3.77214 15.9585C3.92491 15.8057 4.10894 15.7293 4.32422 15.7293H15.6784C15.8937 15.7293 16.0777 15.8057 16.2305 15.9585C16.3832 16.1113 16.4596 16.2953 16.4596 16.5106C16.4596 16.7259 16.3832 16.9099 16.2305 17.0627C16.0777 17.2154 15.8937 17.2918 15.6784 17.2918H4.32422ZM6.7513 14.1668C5.86241 14.1668 5.10547 13.8543 4.48047 13.2293C3.85547 12.6043 3.54297 11.8474 3.54297 10.9585V4.271C3.54297 3.8413 3.69596 3.47346 4.00195 3.16748C4.30793 2.86149 4.67577 2.7085 5.10547 2.7085H16.5638C16.9935 2.7085 17.3613 2.86149 17.6673 3.16748C17.9733 3.47346 18.1263 3.8413 18.1263 4.271V6.97933C18.1263 7.40902 17.9733 7.77686 17.6673 8.08285C17.3613 8.38884 16.9935 8.54183 16.5638 8.54183H14.793V10.9612C14.793 11.8483 14.478 12.6043 13.8481 13.2293C13.2181 13.8543 12.4609 14.1668 11.5763 14.1668H6.7513ZM6.7513 12.6043H11.5742C12.0256 12.6043 12.4145 12.4432 12.7409 12.1209C13.0673 11.7986 13.2305 11.4111 13.2305 10.9585V4.271H5.10547V10.9585C5.10547 11.4111 5.26662 11.7986 5.58893 12.1209C5.91125 12.4432 6.2987 12.6043 6.7513 12.6043ZM14.793 6.97933H16.5638V4.271H14.793V6.97933ZM6.7513 12.6043H5.10547H13.2305H6.7513Z"
fill="#57514E"
/>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ContractIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5.47786 18.125C4.81814 18.125 4.25391 17.8941 3.78516 17.4323C3.31641 16.9705 3.08203 16.4097 3.08203 15.75V14.7917C3.08203 14.362 3.23502 13.9941 3.54101 13.6881C3.847 13.3822 4.21484 13.2292 4.64453 13.2292H5.3737V3.4375C5.3737 3.00781 5.52669 2.63997 5.83268 2.33398C6.13866 2.02799 6.5065 1.875 6.9362 1.875H16.1029C16.5326 1.875 16.9004 2.02799 17.2064 2.33398C17.5124 2.63997 17.6654 3.00781 17.6654 3.4375V15.75C17.6654 16.4097 17.431 16.9705 16.9622 17.4323C16.4935 17.8941 15.9293 18.125 15.2695 18.125H5.47786ZM15.2695 16.5625C15.5056 16.5625 15.7036 16.4846 15.8633 16.3289C16.023 16.1732 16.1029 15.9802 16.1029 15.75V3.4375H6.9362V13.2292H12.8945C13.3242 13.2292 13.6921 13.3822 13.9981 13.6881C14.304 13.9941 14.457 14.362 14.457 14.7917V15.75C14.457 15.9802 14.5349 16.1732 14.6906 16.3289C14.8464 16.4846 15.0393 16.5625 15.2695 16.5625ZM8.65495 7.29167C8.43967 7.29167 8.25564 7.21528 8.10286 7.0625C7.95009 6.90972 7.8737 6.72569 7.8737 6.51042C7.8737 6.29514 7.95009 6.11111 8.10286 5.95833C8.25564 5.80556 8.43967 5.72917 8.65495 5.72917H14.3841C14.5994 5.72917 14.7834 5.80556 14.9362 5.95833C15.089 6.11111 15.1654 6.29514 15.1654 6.51042C15.1654 6.72569 15.089 6.90972 14.9362 7.0625C14.7834 7.21528 14.5994 7.29167 14.3841 7.29167H8.65495ZM8.65495 9.79167C8.43967 9.79167 8.25564 9.71528 8.10286 9.5625C7.95009 9.40972 7.8737 9.22569 7.8737 9.01042C7.8737 8.79514 7.95009 8.61111 8.10286 8.45833C8.25564 8.30556 8.43967 8.22917 8.65495 8.22917H14.3841C14.5994 8.22917 14.7834 8.30556 14.9362 8.45833C15.089 8.61111 15.1654 8.79514 15.1654 9.01042C15.1654 9.22569 15.089 9.40972 14.9362 9.5625C14.7834 9.71528 14.5994 9.79167 14.3841 9.79167H8.65495ZM5.47786 16.5625H12.8945V14.7917H4.64453V15.75C4.64453 15.9802 4.72439 16.1732 4.88411 16.3289C5.04384 16.4846 5.24175 16.5625 5.47786 16.5625Z"
fill="#4D001B"
/>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import * as variants from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CreditCardAddIcon({
className,
color,
...props
}: IconProps) {
const classNames = variants.iconVariants({ className, color })
return (
<svg
className={classNames}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.4375 16.4792C3.00781 16.4792 2.63997 16.3262 2.33398 16.0202C2.02799 15.7142 1.875 15.3464 1.875 14.9167V5.0625C1.875 4.63281 2.02799 4.26497 2.33398 3.95898C2.63997 3.65299 3.00781 3.5 3.4375 3.5H16.5625C16.9922 3.5 17.36 3.65299 17.666 3.95898C17.972 4.26497 18.125 4.63281 18.125 5.0625V9.91667H3.4375V14.9167H10.8021C11.0174 14.9167 11.2014 14.9931 11.3542 15.1458C11.5069 15.2986 11.5833 15.4826 11.5833 15.6979C11.5833 15.9132 11.5069 16.0972 11.3542 16.25C11.2014 16.4028 11.0174 16.4792 10.8021 16.4792H3.4375ZM3.4375 6.77083H16.5625V5.0625H3.4375V6.77083ZM15.8854 15.7812H14.1667C13.9514 15.7812 13.7674 15.7049 13.6146 15.5521C13.4618 15.3993 13.3854 15.2153 13.3854 15C13.3854 14.7847 13.4618 14.6007 13.6146 14.4479C13.7674 14.2951 13.9514 14.2188 14.1667 14.2188H15.8854V12.5C15.8854 12.2847 15.9618 12.1007 16.1146 11.9479C16.2674 11.7951 16.4514 11.7188 16.6667 11.7188C16.8819 11.7188 17.066 11.7951 17.2188 11.9479C17.3715 12.1007 17.4479 12.2847 17.4479 12.5V14.2188H19.1667C19.3819 14.2188 19.566 14.2951 19.7188 14.4479C19.8715 14.6007 19.9479 14.7847 19.9479 15C19.9479 15.2153 19.8715 15.3993 19.7188 15.5521C19.566 15.7049 19.3819 15.7812 19.1667 15.7812H17.4479V17.5C17.4479 17.7153 17.3715 17.8993 17.2188 18.0521C17.066 18.2049 16.8819 18.2812 16.6667 18.2812C16.4514 18.2812 16.2674 18.2049 16.1146 18.0521C15.9618 17.8993 15.8854 17.7153 15.8854 17.5V15.7812Z"
fill="#4D001B"
/>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function DiscountIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M9.9987 12.0106L11.582 12.9689C11.7279 13.0592 11.872 13.054 12.0143 12.9533C12.1567 12.8526 12.207 12.7189 12.1654 12.5522L11.7487 10.746L13.1654 9.521C13.2973 9.40519 13.3355 9.26686 13.2799 9.10602C13.2244 8.94517 13.1098 8.8578 12.9362 8.84391L11.082 8.68766L10.3529 6.97933C10.2834 6.81961 10.1654 6.73975 9.9987 6.73975C9.83203 6.73975 9.71398 6.81961 9.64453 6.97933L8.91536 8.68766L7.0612 8.84391C6.88759 8.8578 6.773 8.94517 6.71745 9.10602C6.66189 9.26686 6.70009 9.40519 6.83203 9.521L8.2487 10.746L7.83203 12.5522C7.79037 12.7189 7.84071 12.8526 7.98307 12.9533C8.12543 13.054 8.26953 13.0592 8.41537 12.9689L9.9987 12.0106ZM7.2932 16.4585H5.10286C4.67317 16.4585 4.30533 16.3055 3.99934 15.9995C3.69336 15.6935 3.54036 15.3257 3.54036 14.896V12.7057L1.9362 11.0939C1.78342 10.9342 1.67057 10.7623 1.59766 10.5783C1.52474 10.3943 1.48828 10.2016 1.48828 10.0002C1.48828 9.79877 1.52474 9.60607 1.59766 9.42204C1.67057 9.23801 1.78342 9.06614 1.9362 8.90641L3.54036 7.29466V5.10433C3.54036 4.67464 3.69336 4.30679 3.99934 4.00081C4.30533 3.69482 4.67317 3.54183 5.10286 3.54183H7.2932L8.90495 1.93766C9.06467 1.78488 9.23655 1.67204 9.42057 1.59912C9.6046 1.5262 9.79731 1.48975 9.9987 1.48975C10.2001 1.48975 10.3928 1.5262 10.5768 1.59912C10.7609 1.67204 10.9327 1.78488 11.0924 1.93766L12.7042 3.54183H14.8945C15.3242 3.54183 15.6921 3.69482 15.9981 4.00081C16.304 4.30679 16.457 4.67464 16.457 5.10433V7.29466L18.0612 8.90641C18.214 9.06614 18.3268 9.23801 18.3997 9.42204C18.4727 9.60607 18.5091 9.79877 18.5091 10.0002C18.5091 10.2016 18.4727 10.3943 18.3997 10.5783C18.3268 10.7623 18.214 10.9342 18.0612 11.0939L16.457 12.7057V14.896C16.457 15.3257 16.304 15.6935 15.9981 15.9995C15.6921 16.3055 15.3242 16.4585 14.8945 16.4585H12.7042L11.0924 18.0627C10.9327 18.2154 10.7609 18.3283 10.5768 18.4012C10.3928 18.4741 10.2001 18.5106 9.9987 18.5106C9.79731 18.5106 9.6046 18.4741 9.42057 18.4012C9.23655 18.3283 9.06467 18.2154 8.90495 18.0627L7.2932 16.4585ZM7.9362 14.896L9.9987 16.9585L12.0612 14.896H14.8945V12.0627L16.957 10.0002L14.8945 7.93766V5.10433H12.0612L9.9987 3.04183L7.9362 5.10433H5.10286V7.93766L3.04036 10.0002L5.10286 12.0627V14.896H7.9362Z"
fill="#57514E"
/>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function DoorClosedIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M3.46875 17.3125C3.25347 17.3125 3.06944 17.2361 2.91667 17.0833C2.76389 16.9306 2.6875 16.7465 2.6875 16.5312C2.6875 16.316 2.76389 16.1319 2.91667 15.9792C3.06944 15.8264 3.25347 15.75 3.46875 15.75H4.33333V4.25C4.33333 3.82031 4.48633 3.45247 4.79231 3.14648C5.0983 2.84049 5.46614 2.6875 5.89583 2.6875H14.1042C14.5339 2.6875 14.9017 2.84049 15.2077 3.14648C15.5137 3.45247 15.6667 3.82031 15.6667 4.25V15.75H16.5312C16.7465 15.75 16.9306 15.8264 17.0833 15.9792C17.2361 16.1319 17.3125 16.316 17.3125 16.5312C17.3125 16.7465 17.2361 16.9306 17.0833 17.0833C16.9306 17.2361 16.7465 17.3125 16.5312 17.3125H3.46875ZM5.89583 15.75H14.1042V4.25H5.89583V15.75ZM8.33223 10.8125C8.56213 10.8125 8.75521 10.7347 8.91146 10.5792C9.06771 10.4237 9.14583 10.231 9.14583 10.0011C9.14583 9.7712 9.06808 9.57813 8.91256 9.42188C8.75704 9.26563 8.56433 9.1875 8.33444 9.1875C8.10453 9.1875 7.91146 9.26526 7.75521 9.42077C7.59896 9.5763 7.52083 9.76901 7.52083 9.9989C7.52083 10.2288 7.59859 10.4219 7.7541 10.5781C7.90963 10.7344 8.10234 10.8125 8.33223 10.8125Z"
fill="#57514E"
/>
</svg>
)
}

View File

@@ -25,15 +25,20 @@ export { default as ChevronRightSmallIcon } from "./ChevronRightSmall"
export { default as CityIcon } from "./City"
export { default as CloseIcon } from "./Close"
export { default as CloseLargeIcon } from "./CloseLarge"
export { default as CoffeeIcon } from "./Coffee"
export { default as CoffeeAltIcon } from "./CoffeeAlt"
export { default as ConciergeIcon } from "./Concierge"
export { default as ContractIcon } from "./Contract"
export { default as ConvenienceStore24hIcon } from "./ConvenienceStore24h"
export { default as CoolIcon } from "./Cool"
export { default as CreditCard } from "./CreditCard"
export { default as CreditCardAddIcon } from "./CreditCardAdd"
export { default as CrossCircle } from "./CrossCircle"
export { default as CulturalIcon } from "./Cultural"
export { default as DeleteIcon } from "./Delete"
export { default as DeskIcon } from "./Desk"
export { default as DiscountIcon } from "./Discount"
export { default as DoorClosedIcon } from "./DoorClosed"
export { default as DoorOpenIcon } from "./DoorOpen"
export { default as DownloadIcon } from "./Download"
export { default as DresserIcon } from "./Dresser"

View File

@@ -67,6 +67,10 @@
color: var(--UI-Text-Medium-contrast);
}
.uiTextPlaceholder {
color: var(--UI-Text-Placeholder);
}
.red {
color: var(--Scandic-Brand-Scandic-Red);
}

View File

@@ -11,6 +11,7 @@ const config = {
pale: styles.pale,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextMediumContrast: styles.uiTextMediumContrast,
uiTextPlaceholder: styles.uiTextPlaceholder,
red: styles.red,
},
textAlign: {