Merge branch 'develop' into feature/tracking

This commit is contained in:
Linus Flood
2024-10-28 15:23:18 +01:00
102 changed files with 2272 additions and 338 deletions

View File

@@ -4,7 +4,7 @@ import { z } from "zod"
import { ApiLang } from "@/constants/languages" import { ApiLang } from "@/constants/languages"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import { protectedServerActionProcedure } from "@/server/trpc" import { protectedServerActionProcedure } from "@/server/trpc"
import { editProfileSchema } from "@/components/Forms/Edit/Profile/schema" import { editProfileSchema } from "@/components/Forms/Edit/Profile/schema"
@@ -68,7 +68,7 @@ export const editProfile = protectedServerActionProcedure
} }
} }
const profile = await serverClient().user.get() const profile = await getProfile()
if (!profile || "error" in profile) { if (!profile || "error" in profile) {
console.error( console.error(
"editProfile profile fetch error", "editProfile profile fetch error",

View File

@@ -1,4 +1,7 @@
import { Suspense } from "react"
import Breadcrumbs from "@/components/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import { LangParams, PageArgs } from "@/types/params"
@@ -6,5 +9,9 @@ import { LangParams, PageArgs } from "@/types/params"
export default function AllBreadcrumbs({ params }: PageArgs<LangParams>) { export default function AllBreadcrumbs({ params }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
return <Breadcrumbs /> return (
<Suspense fallback={<BreadcrumbsSkeleton />}>
<Breadcrumbs />
</Suspense>
)
} }

View File

@@ -1,5 +1,9 @@
import { Suspense } from "react"
import LoadingSpinner from "@/components/LoadingSpinner"
import Sidebar from "@/components/MyPages/Sidebar" import Sidebar from "@/components/MyPages/Sidebar"
// import Surprises from "@/components/MyPages/Surprises"
import styles from "./layout.module.css" import styles from "./layout.module.css"
export default async function MyPagesLayout({ export default async function MyPagesLayout({
@@ -13,10 +17,16 @@ export default async function MyPagesLayout({
<section className={styles.layout}> <section className={styles.layout}>
{breadcrumbs} {breadcrumbs}
<section className={styles.content}> <section className={styles.content}>
<Sidebar /> <Suspense fallback={<LoadingSpinner />}>
<Sidebar />
</Suspense>
{children} {children}
</section> </section>
</section> </section>
{/* TODO: Waiting on new API stuff
<Surprises />
*/}
</div> </div>
) )
} }

View File

@@ -1,4 +1,4 @@
import { serverClient } from "@/lib/trpc/server" import { getMembershipCards } from "@/lib/trpc/memoizedRequests"
import { PlusCircleIcon } from "@/components/Icons" import { PlusCircleIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
@@ -16,7 +16,7 @@ export default async function MembershipCardSlot({
}: PageArgs<LangParams>) { }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const membershipCards = await serverClient().user.membershipCards() const membershipCards = await getMembershipCards()
return ( return (
<section className={styles.container}> <section className={styles.container}>

View File

@@ -1,4 +1,7 @@
import { Suspense } from "react"
import Breadcrumbs from "@/components/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import { LangParams, PageArgs } from "@/types/params"
@@ -6,5 +9,9 @@ import { LangParams, PageArgs } from "@/types/params"
export default function PageBreadcrumbs({ params }: PageArgs<LangParams>) { export default function PageBreadcrumbs({ params }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
return <Breadcrumbs /> return (
<Suspense fallback={<BreadcrumbsSkeleton />}>
<Breadcrumbs />
</Suspense>
)
} }

View File

@@ -16,7 +16,7 @@ import {
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadata } from "@/utils/generateMetadata"
export default async function ContentTypePage({ export default function ContentTypePage({
params, params,
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) { }: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
setLang(params.lang) setLang(params.lang)

View File

@@ -1,7 +1,9 @@
import { differenceInCalendarDays, format, isWeekend } from "date-fns" import { differenceInCalendarDays, format, isWeekend } from "date-fns"
import { notFound } from "next/navigation"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { selectHotelMap } from "@/constants/routes/hotelReservation" import { selectHotelMap } from "@/constants/routes/hotelReservation"
import { getLocations } from "@/lib/trpc/memoizedRequests"
import { import {
fetchAvailableHotels, fetchAvailableHotels,
@@ -9,6 +11,7 @@ import {
} from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils" } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils"
import HotelCardListing from "@/components/HotelReservation/HotelCardListing" import HotelCardListing from "@/components/HotelReservation/HotelCardListing"
import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter" import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter"
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
import { ChevronRightIcon } from "@/components/Icons" import { ChevronRightIcon } from "@/components/Icons"
import StaticMap from "@/components/Maps/StaticMap" import StaticMap from "@/components/Maps/StaticMap"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
@@ -18,6 +21,7 @@ import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css" import styles from "./page.module.css"
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
TrackingSDKHotelInfo, TrackingSDKHotelInfo,
@@ -27,21 +31,38 @@ import { LangParams, PageArgs } from "@/types/params"
export default async function SelectHotelPage({ export default async function SelectHotelPage({
params, params,
}: PageArgs<LangParams>) { searchParams,
}: PageArgs<LangParams, SelectHotelSearchParams>) {
setLang(params.lang) setLang(params.lang)
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
const city = locations.data.find(
(location) =>
location.name.toLowerCase() === searchParams.city.toLowerCase()
)
if (!city) return notFound()
const tempSearchTerm = "Stockholm"
const tempArrivalDate = new Date("2024-10-25")
const tempDepartureDate = new Date("2024-10-26")
const intl = await getIntl() const intl = await getIntl()
const selectHotelParams = new URLSearchParams(searchParams)
const selectHotelParamsObject =
getHotelReservationQueryParams(selectHotelParams)
const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms
const children = selectHotelParamsObject.room[0].child?.length // TODO: Handle multiple rooms
const hotels = await fetchAvailableHotels({ const hotels = await fetchAvailableHotels({
cityId: "8ec4bba3-1c38-4606-82d1-bbe3f6738e54", cityId: city.id,
roomStayStartDate: "2024-11-02", roomStayStartDate: searchParams.fromDate,
roomStayEndDate: "2024-11-03", roomStayEndDate: searchParams.toDate,
adults: 1, adults,
children,
}) })
const arrivalDate = new Date(searchParams.fromDate)
const departureDate = new Date(searchParams.toDate)
const filterList = getFiltersFromHotels(hotels) const filterList = getFiltersFromHotels(hotels)
const pageTrackingData: TrackingSDKPageData = { const pageTrackingData: TrackingSDKPageData = {
@@ -55,16 +76,16 @@ export default async function SelectHotelPage({
const hotelsTrackingData: TrackingSDKHotelInfo = { const hotelsTrackingData: TrackingSDKHotelInfo = {
availableResults: hotels.length, availableResults: hotels.length,
searchTerm: tempSearchTerm, searchTerm: searchParams.city,
arrivalDate: format(tempArrivalDate, "yyyy-MM-dd"), arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
departureDate: format(tempDepartureDate, "yyyy-MM-dd"), departureDate: format(departureDate, "yyyy-MM-dd"),
noOfAdults: 1, // TODO: Use values from searchParams noOfAdults: adults,
noOfChildren: 0, // TODO: Use values from searchParams noOfChildren: children,
noOfRooms: 1, // TODO: Use values from searchParams noOfRooms: 1, // // TODO: Handle multiple rooms
duration: differenceInCalendarDays(tempDepartureDate, tempArrivalDate), duration: differenceInCalendarDays(departureDate, arrivalDate),
leadTime: differenceInCalendarDays(tempArrivalDate, new Date()), leadTime: differenceInCalendarDays(arrivalDate, new Date()),
searchType: "destination", searchType: "destination",
bookingTypeofDay: isWeekend(tempArrivalDate) ? "weekend" : "weekday", bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
country: hotels[0].hotelData.address.country, country: hotels[0].hotelData.address.country,
region: hotels[0].hotelData.address.city, region: hotels[0].hotelData.address.city,
} }
@@ -74,12 +95,12 @@ export default async function SelectHotelPage({
<section className={styles.section}> <section className={styles.section}>
<Link href={selectHotelMap[params.lang]} keepSearchParams> <Link href={selectHotelMap[params.lang]} keepSearchParams>
<StaticMap <StaticMap
city={tempSearchTerm} city={searchParams.city}
width={340} width={340}
height={180} height={180}
zoomLevel={11} zoomLevel={11}
mapType="roadmap" mapType="roadmap"
altText={`Map of ${tempSearchTerm} city center`} altText={`Map of ${searchParams.city} city center`}
/> />
</Link> </Link>
<Link <Link

View File

@@ -3,7 +3,13 @@ import { serverClient } from "@/lib/trpc/server"
import BookingWidget, { preload } from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
export default async function BookingWidgetPage() { import { BookingWidgetSearchParams } from "@/types/components/bookingWidget"
import { LangParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage({
params,
searchParams,
}: PageArgs<LangParams, BookingWidgetSearchParams>) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.HIDE_FOR_NEXT_RELEASE) {
return null return null
} }
@@ -18,5 +24,5 @@ export default async function BookingWidgetPage() {
return null return null
} }
return <BookingWidget /> return <BookingWidget searchParams={searchParams} />
} }

View File

@@ -0,0 +1,5 @@
import LoadingSpinner from "@/components/LoadingSpinner"
export default function Loading() {
return <LoadingSpinner />
}

View File

@@ -115,7 +115,8 @@
--header-z-index: 10; --header-z-index: 10;
--menu-overlay-z-index: 10; --menu-overlay-z-index: 10;
--dialog-z-index: 9; --dialog-z-index: 9;
--sidepeek-z-index: 11; --sidepeek-z-index: 100;
--lightbox-z-index: 150;
} }
* { * {

View File

@@ -14,11 +14,12 @@ export default async function Points({ user }: UserProps) {
const membership = getMembership(user.memberships) const membership = getMembership(user.memberships)
const nextLevel = membership?.nextLevel const nextLevel =
? await serverClient().contentstack.loyaltyLevels.byLevel({ membership?.nextLevel && MembershipLevelEnum[membership.nextLevel]
level: MembershipLevelEnum[membership.nextLevel], ? await serverClient().contentstack.loyaltyLevels.byLevel({
}) level: MembershipLevelEnum[membership.nextLevel],
: null })
: null
return ( return (
<PointsContainer> <PointsContainer>

View File

@@ -1,5 +1,5 @@
.divider { .divider {
padding-top: var(--Spacing-x2); margin-top: var(--Spacing-x2);
} }
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {

View File

@@ -1,6 +1,5 @@
.rewardCard { .rewardCard {
padding-bottom: var(--Spacing-x-one-and-half); padding-bottom: var(--Spacing-x-one-and-half);
z-index: 2;
grid-column: 1/3; grid-column: 1/3;
} }

View File

@@ -1,3 +1,4 @@
import { getMembershipLevelSafely } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import SectionWrapper from "../SectionWrapper" import SectionWrapper from "../SectionWrapper"
@@ -9,8 +10,11 @@ export default async function OverviewTable({
dynamic_content, dynamic_content,
firstItem, firstItem,
}: OverviewTableProps) { }: OverviewTableProps) {
const levels = await serverClient().contentstack.rewards.all() const [levels, membershipLevel] = await Promise.all([
const membershipLevel = await serverClient().user.safeMembershipLevel() serverClient().contentstack.rewards.all(),
getMembershipLevelSafely(),
])
return ( return (
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}> <SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
<OverviewTableClient <OverviewTableClient

View File

@@ -28,7 +28,6 @@
display: contents; display: contents;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
z-index: 2;
} }
.columnHeader { .columnHeader {

View File

@@ -1,4 +1,4 @@
import { serverClient } from "@/lib/trpc/server" import { getMembershipLevel } from "@/lib/trpc/memoizedRequests"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
@@ -12,7 +12,7 @@ export default async function ExpiringPoints({
subtitle, subtitle,
title, title,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const membershipLevel = await serverClient().user.membershipLevel() const membershipLevel = await getMembershipLevel()
if (!membershipLevel?.pointsToExpire || !membershipLevel?.pointsExpiryDate) { if (!membershipLevel?.pointsToExpire || !membershipLevel?.pointsExpiryDate) {
return null return null

View File

@@ -20,7 +20,7 @@ export default async function Points({ user, lang }: UserProps & LangParams) {
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const membership = getMembership(user.memberships) const membership = getMembership(user.memberships)
if (!membership?.nextLevel) { if (!membership?.nextLevel || !MembershipLevelEnum[membership.nextLevel]) {
return null return null
} }
const nextLevel = await serverClient().contentstack.loyaltyLevels.byLevel({ const nextLevel = await serverClient().contentstack.loyaltyLevels.byLevel({

View File

@@ -1,5 +1,5 @@
.divider { .divider {
padding-top: var(--Spacing-x2); margin-top: var(--Spacing-x2);
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {

View File

@@ -1,6 +1,7 @@
import { Lock } from "react-feather" import { Lock } from "react-feather"
import { MembershipLevelEnum } from "@/constants/membershipLevels" import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { getMembershipLevel } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
@@ -22,7 +23,7 @@ export default async function NextLevelRewardsBlock({
link, link,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const intl = await getIntl() const intl = await getIntl()
const membershipLevel = await serverClient().user.membershipLevel() const membershipLevel = await getMembershipLevel()
if (!membershipLevel || !membershipLevel?.nextLevel) { if (!membershipLevel || !membershipLevel?.nextLevel) {
return null return null

View File

@@ -52,9 +52,10 @@
left: 0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 70px; bottom: 0;
background: rgb(255 255 255 / 80%); background: rgb(255 255 255 / 80%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-bottom: 70px;
} }

View File

@@ -1,3 +1,5 @@
import { Suspense } from "react"
import { env } from "@/env/server" import { env } from "@/env/server"
import HowItWorks from "@/components/Blocks/DynamicContent/HowItWorks" import HowItWorks from "@/components/Blocks/DynamicContent/HowItWorks"
@@ -14,14 +16,21 @@ import SignUpVerification from "@/components/Blocks/DynamicContent/SignUpVerific
import PreviousStays from "@/components/Blocks/DynamicContent/Stays/Previous" import PreviousStays from "@/components/Blocks/DynamicContent/Stays/Previous"
import SoonestStays from "@/components/Blocks/DynamicContent/Stays/Soonest" import SoonestStays from "@/components/Blocks/DynamicContent/Stays/Soonest"
import UpcomingStays from "@/components/Blocks/DynamicContent/Stays/Upcoming" import UpcomingStays from "@/components/Blocks/DynamicContent/Stays/Upcoming"
import LoadingSpinner from "@/components/LoadingSpinner"
import type { DynamicContentProps } from "@/types/components/blocks/dynamicContent" import type { DynamicContentProps } from "@/types/components/blocks/dynamicContent"
import { DynamicContentEnum } from "@/types/enums/dynamicContent" import { DynamicContentEnum } from "@/types/enums/dynamicContent"
export default async function DynamicContent({ export default function DynamicContent(props: DynamicContentProps) {
dynamic_content, return (
firstItem, <Suspense fallback={<LoadingSpinner />}>
}: DynamicContentProps) { <DynamicContentBlocks {...props} />
</Suspense>
)
}
function DynamicContentBlocks(props: DynamicContentProps) {
const { dynamic_content, firstItem } = props
switch (dynamic_content.component) { switch (dynamic_content.component) {
case DynamicContentEnum.Blocks.components.current_benefits: case DynamicContentEnum.Blocks.components.current_benefits:
return <CurrentRewardsBlock {...dynamic_content} /> return <CurrentRewardsBlock {...dynamic_content} />

View File

@@ -10,6 +10,7 @@ import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema"
import { CloseLargeIcon } from "@/components/Icons" import { CloseLargeIcon } from "@/components/Icons"
import { debounce } from "@/utils/debounce" import { debounce } from "@/utils/debounce"
import getHotelReservationQueryParams from "../HotelReservation/SelectRate/RoomSelection/utils"
import MobileToggleButton from "./MobileToggleButton" import MobileToggleButton from "./MobileToggleButton"
import styles from "./bookingWidget.module.css" import styles from "./bookingWidget.module.css"
@@ -23,6 +24,7 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
export default function BookingWidgetClient({ export default function BookingWidgetClient({
locations, locations,
type, type,
searchParams,
}: BookingWidgetClientProps) { }: BookingWidgetClientProps) {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
@@ -33,17 +35,59 @@ export default function BookingWidgetClient({
const initialSelectedLocation: Location | undefined = sessionStorageSearchData const initialSelectedLocation: Location | undefined = sessionStorageSearchData
? JSON.parse(sessionStorageSearchData) ? JSON.parse(sessionStorageSearchData)
: undefined : undefined
const bookingWidgetSearchParams = searchParams
? new URLSearchParams(searchParams)
: undefined
const bookingWidgetSearchData = bookingWidgetSearchParams
? getHotelReservationQueryParams(bookingWidgetSearchParams)
: undefined
const getLocationObj = (destination: string): Location | undefined => {
if (destination) {
const location: Location | undefined = locations.find((location) => {
return (
location.name.toLowerCase() === destination.toLowerCase() ||
//@ts-ignore (due to operaId not property error)
(location.operaId && location.operaId == destination)
)
})
return location
}
return undefined
}
const isDateParamValid =
bookingWidgetSearchData?.fromDate &&
bookingWidgetSearchData?.toDate &&
dt(bookingWidgetSearchData?.toDate.toString()).isAfter(
dt(bookingWidgetSearchData?.fromDate.toString())
)
const selectedLocation = bookingWidgetSearchData
? getLocationObj(
(bookingWidgetSearchData.city ??
bookingWidgetSearchData.hotel) as string
)
: undefined
const methods = useForm<BookingWidgetSchema>({ const methods = useForm<BookingWidgetSchema>({
defaultValues: { defaultValues: {
search: initialSelectedLocation?.name ?? "", search: selectedLocation?.name ?? initialSelectedLocation?.name ?? "",
location: sessionStorageSearchData location: selectedLocation
? encodeURIComponent(sessionStorageSearchData) ? JSON.stringify(selectedLocation)
: undefined, : sessionStorageSearchData
? encodeURIComponent(sessionStorageSearchData)
: undefined,
date: { date: {
// UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507 // UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507
// This is specifically to handle timezones falling in different dates. // This is specifically to handle timezones falling in different dates.
fromDate: dt().utc().format("YYYY-MM-DD"), fromDate: isDateParamValid
toDate: dt().utc().add(1, "day").format("YYYY-MM-DD"), ? bookingWidgetSearchData?.fromDate.toString()
: dt().utc().format("YYYY-MM-DD"),
toDate: isDateParamValid
? bookingWidgetSearchData?.toDate?.toString()
: dt().utc().add(1, "day").format("YYYY-MM-DD"),
}, },
bookingCode: "", bookingCode: "",
redemption: false, redemption: false,

View File

@@ -33,10 +33,10 @@ export default function MobileToggleButton({
? JSON.parse(decodeURIComponent(location)) ? JSON.parse(decodeURIComponent(location))
: null : null
const nights = dt(d.to).diff(dt(d.from), "days") const nights = dt(d.toDate).diff(dt(d.fromDate), "days")
const selectedFromDate = dt(d.from).locale(lang).format("D MMM") const selectedFromDate = dt(d.fromDate).locale(lang).format("D MMM")
const selectedToDate = dt(d.to).locale(lang).format("D MMM") const selectedToDate = dt(d.toDate).locale(lang).format("D MMM")
useEffect(() => { useEffect(() => {
setHasMounted(true) setHasMounted(true)

View File

@@ -50,3 +50,9 @@
display: none; display: none;
} }
} }
@media screen and (min-width: 1367px) {
.container {
z-index: 9;
}
}

View File

@@ -8,12 +8,21 @@ export function preload() {
void getLocations() void getLocations()
} }
export default async function BookingWidget({ type }: BookingWidgetProps) { export default async function BookingWidget({
type,
searchParams,
}: BookingWidgetProps) {
const locations = await getLocations() const locations = await getLocations()
if (!locations || "error" in locations) { if (!locations || "error" in locations) {
return null return null
} }
return <BookingWidgetClient locations={locations.data} type={type} /> return (
<BookingWidgetClient
locations={locations.data}
type={type}
searchParams={searchParams}
/>
)
} }

View File

@@ -0,0 +1,25 @@
import { ChevronRightIcon, HouseIcon } from "@/components/Icons"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import styles from "./breadcrumbs.module.css"
export default function BreadcrumbsSkeleton() {
return (
<nav className={styles.breadcrumbs}>
<ul className={styles.list}>
<li className={styles.listItem}>
<span className={styles.homeLink} color="peach80">
<HouseIcon color="peach80" />
</span>
<ChevronRightIcon aria-hidden="true" color="peach80" />
</li>
<li className={styles.listItem}>
<Footnote color="burgundy" type="bold">
...
</Footnote>
</li>
</ul>
</nav>
)
}

View File

@@ -1,7 +1,5 @@
.contentPage { .contentPage {
padding-bottom: var(--Spacing-x9); padding-bottom: var(--Spacing-x9);
container-name: content-page;
container-type: inline-size;
} }
.header { .header {
@@ -35,12 +33,16 @@
.contentContainer { .contentContainer {
display: grid; display: grid;
padding: var(--Spacing-x4) var(--Spacing-x2) 0; grid-template-areas:
"main"
"sidebar";
gap: var(--Spacing-x4); gap: var(--Spacing-x4);
align-items: start; align-items: start;
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
} }
.mainContent { .mainContent {
grid-area: main;
display: grid; display: grid;
gap: var(--Spacing-x4); gap: var(--Spacing-x4);
width: 100%; width: 100%;
@@ -57,6 +59,7 @@
padding: var(--Spacing-x4) 0; padding: var(--Spacing-x4) 0;
} }
.contentContainer { .contentContainer {
grid-template-areas: "main sidebar";
grid-template-columns: var(--max-width-text-block) 1fr; grid-template-columns: var(--max-width-text-block) 1fr;
gap: var(--Spacing-x9); gap: var(--Spacing-x9);
max-width: var(--max-width-content); max-width: var(--max-width-content);

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import { useMap } from "@vis.gl/react-google-maps"
import { useState } from "react" import { useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -11,15 +12,19 @@ import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./sidebar.module.css" import styles from "./sidebar.module.css"
import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar" import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar"
import type { Coordinates } from "@/types/components/maps/coordinates"
export default function Sidebar({ export default function Sidebar({
activePoi, activePoi,
hotelName, hotelName,
pointsOfInterest, pointsOfInterest,
onActivePoiChange, onActivePoiChange,
coordinates,
}: SidebarProps) { }: SidebarProps) {
const intl = useIntl() const intl = useIntl()
const map = useMap()
const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false) const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false)
const [isClicking, setIsClicking] = useState(false)
const poiGroups = new Set(pointsOfInterest.map(({ group }) => group)) const poiGroups = new Set(pointsOfInterest.map(({ group }) => group))
const poisInGroups = Array.from(poiGroups).map((group) => ({ const poisInGroups = Array.from(poiGroups).map((group) => ({
group, group,
@@ -30,6 +35,48 @@ export default function Sidebar({
setIsFullScreenSidebar((prev) => !prev) setIsFullScreenSidebar((prev) => !prev)
} }
function moveToPoi(poiCoordinates: Coordinates) {
if (map) {
const bounds = new google.maps.LatLngBounds()
const boundPadding = 0.02
const minLat = Math.min(coordinates.lat, poiCoordinates.lat)
const maxLat = Math.max(coordinates.lat, poiCoordinates.lat)
const minLng = Math.min(coordinates.lng, poiCoordinates.lng)
const maxLng = Math.max(coordinates.lng, poiCoordinates.lng)
bounds.extend(
new google.maps.LatLng(minLat - boundPadding, minLng - boundPadding)
)
bounds.extend(
new google.maps.LatLng(maxLat + boundPadding, maxLng + boundPadding)
)
map.fitBounds(bounds)
}
}
function handleMouseEnter(poiName: string) {
if (!isClicking) {
onActivePoiChange(poiName)
}
}
function handleMouseLeave() {
if (!isClicking) {
onActivePoiChange(null)
}
}
function handlePoiClick(poiName: string, poiCoordinates: Coordinates) {
setIsClicking(true)
toggleFullScreenSidebar()
onActivePoiChange(poiName)
moveToPoi(poiCoordinates)
setTimeout(() => {
setIsClicking(false)
}, 200)
}
return ( return (
<aside <aside
className={`${styles.sidebar} ${ className={`${styles.sidebar} ${
@@ -77,13 +124,9 @@ export default function Sidebar({
<li key={poi.name} className={styles.poiItem}> <li key={poi.name} className={styles.poiItem}>
<button <button
className={`${styles.poiButton} ${activePoi === poi.name ? styles.active : ""}`} className={`${styles.poiButton} ${activePoi === poi.name ? styles.active : ""}`}
onMouseEnter={() => onActivePoiChange(poi.name)} onMouseEnter={() => handleMouseEnter(poi.name)}
onMouseLeave={() => onActivePoiChange(null)} onMouseLeave={handleMouseLeave}
onClick={() => onClick={() => handlePoiClick(poi.name, poi.coordinates)}
onActivePoiChange(
activePoi === poi.name ? null : poi.name
)
}
> >
<span>{poi.name}</span> <span>{poi.name}</span>
<span>{poi.distance} km</span> <span>{poi.distance} km</span>

View File

@@ -83,6 +83,7 @@ export default function DynamicMap({
hotelName={hotelName} hotelName={hotelName}
pointsOfInterest={pointsOfInterest} pointsOfInterest={pointsOfInterest}
onActivePoiChange={setActivePoi} onActivePoiChange={setActivePoi}
coordinates={coordinates}
/> />
<InteractiveMap <InteractiveMap
closeButton={closeButton} closeButton={closeButton}

View File

@@ -1,21 +1,24 @@
.content { .content {
display: grid; display: grid;
padding-bottom: var(--Spacing-x9); grid-template-areas: "main";
padding-left: var(--Spacing-x0); grid-template-columns: 1fr;
padding-right: var(--Spacing-x0); gap: var(--Spacing-x5);
padding: 0 var(--Spacing-x2) var(--Spacing-x9);
container-name: loyalty-page;
container-type: inline-size;
max-width: var(--max-width); max-width: var(--max-width);
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
} }
.content:has(> aside) {
grid-template-areas:
"main"
"sidebar";
}
.blocks { .blocks {
grid-area: main;
display: grid; display: grid;
gap: var(--Spacing-x7); gap: var(--Spacing-x7);
padding-left: var(--Spacing-x2);
padding-right: var(--Spacing-x2);
} }
.header { .header {
@@ -25,23 +28,16 @@
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.content { .content {
gap: var(--Spacing-x5); padding: 0 var(--Spacing-x5) var(--Spacing-x9);
padding-left: var(--Spacing-x5);
padding-right: var(--Spacing-x5);
} }
.content:has(> aside) { .content:has(> aside) {
grid-template-areas: "sidebar main";
grid-template-columns: 360px 1fr; grid-template-columns: 360px 1fr;
} }
.content:has(> aside) .blocks {
grid-column: 2 / -1;
}
.blocks { .blocks {
gap: var(--Spacing-x9); gap: var(--Spacing-x9);
padding-left: var(--Spacing-x0);
padding-right: var(--Spacing-x0);
} }
.blocks > section:first-of-type > header { .blocks > section:first-of-type > header {

View File

@@ -1,6 +1,6 @@
import { homeHrefs } from "@/constants/homeHrefs" import { homeHrefs } from "@/constants/homeHrefs"
import { env } from "@/env/server" import { env } from "@/env/server"
import { serverClient } from "@/lib/trpc/server" import { getCurrentHeader } from "@/lib/trpc/memoizedRequests"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
@@ -11,9 +11,7 @@ import TopMenu from "../TopMenu"
import styles from "../header.module.css" import styles from "../header.module.css"
export default async function HeaderFallback() { export default async function HeaderFallback() {
const data = await serverClient().contentstack.base.currentHeader({ const data = await getCurrentHeader(getLang())
lang: getLang(),
})
if (!data?.header) { if (!data?.header) {
return null return null

View File

@@ -1,6 +1,6 @@
import { logout } from "@/constants/routes/handleAuth" import { logout } from "@/constants/routes/handleAuth"
import { overview } from "@/constants/routes/myPages" import { overview } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server" import { getName } from "@/lib/trpc/memoizedRequests"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
@@ -23,7 +23,7 @@ export default async function TopMenu({
languageSwitcher, languageSwitcher,
}: TopMenuProps) { }: TopMenuProps) {
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const user = await serverClient().user.name() const user = await getName()
return ( return (
<div className={styles.topMenu}> <div className={styles.topMenu}>
<div className={styles.container}> <div className={styles.container}>

View File

@@ -1,7 +1,11 @@
import { homeHrefs } from "@/constants/homeHrefs" import { homeHrefs } from "@/constants/homeHrefs"
import { env } from "@/env/server" import { env } from "@/env/server"
import { getLanguageSwitcher } from "@/lib/trpc/memoizedRequests" import {
import { serverClient } from "@/lib/trpc/server" getCurrentHeader,
getLanguageSwitcher,
getMyPagesNavigation,
getName,
} from "@/lib/trpc/memoizedRequests"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
@@ -15,12 +19,10 @@ import styles from "./header.module.css"
export default async function Header() { export default async function Header() {
const [data, user, languages, navigation] = await Promise.all([ const [data, user, languages, navigation] = await Promise.all([
serverClient().contentstack.base.currentHeader({ getCurrentHeader(getLang()),
lang: getLang(), getName(),
}),
serverClient().user.name(),
getLanguageSwitcher(), getLanguageSwitcher(),
serverClient().contentstack.myPages.navigation.get(), getMyPagesNavigation(),
]) ])
if (!navigation || !languages || !data?.header) { if (!navigation || !languages || !data?.header) {

View File

@@ -100,13 +100,21 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
close={close} close={close}
handleOnSelect={handleSelectDate} handleOnSelect={handleSelectDate}
locales={locales} locales={locales}
selectedDate={selectedDate} // DayPicker lib needs Daterange in form as below to show appropriate UI
selectedDate={{
from: selectedDate.fromDate,
to: selectedDate.toDate,
}}
/> />
<DatePickerMobile <DatePickerMobile
close={close} close={close}
handleOnSelect={handleSelectDate} handleOnSelect={handleSelectDate}
locales={locales} locales={locales}
selectedDate={selectedDate} // DayPicker lib needs Daterange in form as below to show appropriate UI
selectedDate={{
from: selectedDate.fromDate,
to: selectedDate.toDate,
}}
/> />
</div> </div>
</div> </div>

View File

@@ -1,5 +1,10 @@
import { MembershipLevelEnum } from "@/constants/membershipLevels" import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { myPages } from "@/constants/routes/myPages" import { myPages } from "@/constants/routes/myPages"
import {
getMembershipLevelSafely,
getMyPagesNavigation,
getName,
} from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
@@ -16,9 +21,9 @@ export default async function MyPagesMenuWrapper() {
const lang = getLang() const lang = getLang()
const [intl, myPagesNavigation, user, membership] = await Promise.all([ const [intl, myPagesNavigation, user, membership] = await Promise.all([
getIntl(), getIntl(),
serverClient().contentstack.myPages.navigation.get(), getMyPagesNavigation(),
serverClient().user.name(), getName(),
serverClient().user.safeMembershipLevel(), getMembershipLevelSafely(),
]) ])
const membershipLevel = membership?.membershipLevel const membershipLevel = membership?.membershipLevel

View File

@@ -21,7 +21,6 @@ export default function ImageGallery({ images, title }: ImageGalleryProps) {
<Image <Image
src={images[0].imageSizes.medium} src={images[0].imageSizes.medium}
alt={images[0].metaData.altText} alt={images[0].metaData.altText}
className={styles.image}
fill fill
/> />
<div className={styles.galleryIcon}> <div className={styles.galleryIcon}>

View File

@@ -5,14 +5,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 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"
import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import ImageGallery from "../../ImageGallery" import ImageGallery from "../../ImageGallery"
import RoomSidePeek from "../RoomSidePeek"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"
@@ -83,16 +82,10 @@ export default function RoomCard({
: `${roomSize?.min}-${roomSize?.max}`} : `${roomSize?.min}-${roomSize?.max}`}
m² m²
</Caption> </Caption>
<Button <RoomSidePeek
intent="text" selectedRoom={selectedRoom}
type="button" roomConfiguration={roomConfiguration}
size="small" />
theme="base"
className={styles.button}
>
{intl.formatMessage({ id: "See room details" })}
<ChevronRightSmallIcon color="burgundy" width={20} height={20} />
</Button>
</div> </div>
<div className={styles.container}> <div className={styles.container}>
<div className={styles.roomDetails}> <div className={styles.roomDetails}>

View File

@@ -0,0 +1,124 @@
import { FC } from "react"
import {
AcIcon,
BathtubIcon,
BedDoubleIcon,
ChairIcon,
CityIcon,
DeskIcon,
HairdryerIcon,
HandSoapIcon,
HeartIcon,
IronIcon,
MirrorIcon,
NatureIcon,
NoSmokingIcon,
SafetyBoxIcon,
ShowerIcon,
StreetIcon,
WifiIcon,
WindowCurtainsAltIcon,
WindowNotAvailableIcon,
WineBarIcon,
WoodFloorIcon,
YardIcon,
} from "@/components/Icons"
import { IconProps } from "@/types/components/icon"
export function getFacilityIcon(name: string): FC<IconProps> | null {
const iconMappings = [
{
icon: DeskIcon,
texts: ["Desk and chair"],
},
{
icon: HairdryerIcon,
texts: ["Hairdryer"],
},
{
icon: AcIcon,
texts: ["Air Condition"],
},
{
icon: ChairIcon,
texts: ["Armchair / armchairs"],
},
{
icon: BathtubIcon,
texts: ["Bathroom with shower or bathtub"],
},
{
icon: WindowCurtainsAltIcon,
texts: ["Blackout curtains"],
},
{
icon: MirrorIcon,
texts: ["Cosmetic mirror"],
},
{
icon: WifiIcon,
texts: ["Free WiFi"],
},
{
icon: ChairIcon,
texts: ["Connecting rooms"],
},
{
icon: YardIcon,
texts: ["View - atrium view"],
},
{
icon: CityIcon,
texts: ["View - city view"],
},
{
icon: NatureIcon,
texts: ["View - park view"],
},
{
icon: StreetIcon,
texts: ["View - street view"],
},
{
icon: WineBarIcon,
texts: ["Minibar"],
},
{
icon: NoSmokingIcon,
texts: ["Non smoking"],
},
{
icon: ShowerIcon,
texts: ["Rain shower"],
},
{
icon: SafetyBoxIcon,
texts: ["Safety box"],
},
{
icon: BedDoubleIcon,
texts: ["Set of two pillows"],
},
{
icon: IronIcon,
texts: ["Iron and ironing board"],
},
{
icon: HandSoapIcon,
texts: ["Toiletries"],
},
{
icon: WoodFloorIcon,
texts: ["Wooden floor"],
},
{
icon: WindowNotAvailableIcon,
texts: ["Not window"],
},
]
const icon = iconMappings.find((icon) => icon.texts.includes(name))
return icon ? icon.icon : HeartIcon
}

View File

@@ -0,0 +1,96 @@
import { useState } from "react"
import { useIntl } from "react-intl"
import { ChevronRightSmallIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import ImageGallery from "../../ImageGallery"
import { getFacilityIcon } from "./facilityIcon"
import styles from "./roomSidePeek.module.css"
import type { RoomSidePeekProps } from "@/types/components/hotelReservation/selectRate/roomSidePeek"
export default function RoomSidePeek({
selectedRoom,
roomConfiguration,
}: RoomSidePeekProps) {
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false)
const intl = useIntl()
const roomSize = selectedRoom?.roomSize
const occupancy = selectedRoom?.occupancy.total
const roomDescription = selectedRoom?.descriptions.medium
const images = selectedRoom?.images
return (
<div>
<Button
intent="text"
type="button"
size="small"
theme="base"
className={styles.button}
onClick={() => setIsSidePeekOpen(true)}
>
{intl.formatMessage({ id: "See room details" })}
<ChevronRightSmallIcon color="burgundy" width={20} height={20} />
</Button>
<SidePeek
title={roomConfiguration.roomType}
isOpen={isSidePeekOpen}
handleClose={() => setIsSidePeekOpen(false)}
>
<Body color="baseTextMediumContrast">
{roomSize?.min === roomSize?.max
? roomSize?.min
: `${roomSize?.min} - ${roomSize?.max}`}
m².{" "}
{intl.formatMessage(
{
id: "booking.accommodatesUpTo",
},
{ nrOfGuests: occupancy }
)}
</Body>
{images && (
<div className={styles.imageContainer}>
<ImageGallery images={images} title={roomConfiguration.roomType} />
</div>
)}
<Body className={styles.description} color="uiTextHighContrast">
{roomDescription}
</Body>
<Subtitle type="two" color="uiTextHighContrast">
{intl.formatMessage({ id: "booking.thisRoomIsEquippedWith" })}
</Subtitle>
<ul className={styles.facilityList}>
{selectedRoom?.roomFacilities
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
const Icon = getFacilityIcon(facility.name)
return (
<li key={facility.name}>
{Icon && <Icon color="uiTextMediumContrast" />}
<Body
asChild
className={!Icon ? styles.noIcon : undefined}
color="uiTextMediumContrast"
>
<span>{facility.name}</span>
</Body>
</li>
)
})}
</ul>
</SidePeek>
</div>
)
}

View File

@@ -0,0 +1,29 @@
.button {
margin-left: auto;
padding: 0 0 0 var(--Spacing-x-half);
text-decoration: none;
}
.imageContainer {
min-height: 185px;
position: relative;
}
.description {
margin-top: var(--Spacing-x-one-and-half);
margin-bottom: var(--Spacing-x2);
}
.facilityList {
margin-top: var(--Spacing-x-one-and-half);
column-count: 2;
column-gap: var(--Spacing-x2);
}
.facilityList li {
display: flex;
gap: var(--Spacing-x1);
margin-bottom: var(--Spacing-x-half);
}
.noIcon {
margin-left: var(--Spacing-x4);
}

36
components/Icons/Ac.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function AcIcon({ 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
style={{ maskType: "alpha" }}
id="mask0_69_3450"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3450)">
<path
d="M3.19995 20.8V6.13458C3.19995 5.31983 3.48554 4.62703 4.05673 4.0562C4.62791 3.48537 5.32148 3.19995 6.13745 3.19995H9.99995C10.8159 3.19995 11.5095 3.48513 12.0807 4.0555C12.6519 4.62587 12.9375 5.31846 12.9375 6.13328V20.8H3.19995ZM9.93745 15.0625H11.0625V9.93745H9.93745V15.0625ZM5.07495 18.925H11.0625V16.9375H9.93745C9.42182 16.9375 8.98041 16.7539 8.61323 16.3867C8.24604 16.0195 8.06245 15.5781 8.06245 15.0625V9.93745C8.06245 9.42182 8.24604 8.98041 8.61323 8.61323C8.98041 8.24604 9.42182 8.06245 9.93745 8.06245H11.0625V6.13745C11.0625 5.83642 10.9606 5.58408 10.757 5.38043C10.5533 5.17678 10.301 5.07495 9.99995 5.07495H6.13745C5.83642 5.07495 5.58408 5.17678 5.38043 5.38043C5.17678 5.58408 5.07495 5.83642 5.07495 6.13745V18.925ZM17.0875 13.5125C16.6671 13.5125 16.2549 13.4565 15.8507 13.3445C15.4465 13.2325 15.0505 13.0885 14.6625 12.9125L15.25 11.1375C15.5748 11.2803 15.8956 11.3994 16.2124 11.4946C16.5291 11.5898 16.8291 11.6375 17.1125 11.6375C17.318 11.6375 17.5236 11.6041 17.7291 11.5375C17.9347 11.4708 18.15 11.3666 18.375 11.225C18.7686 10.9465 19.1622 10.7581 19.5558 10.6599C19.9494 10.5616 20.3266 10.5125 20.6875 10.5125C21.0881 10.5125 21.5007 10.5666 21.9254 10.675C22.3501 10.7833 22.7541 10.9208 23.1375 11.0875L22.55 12.875C22.2333 12.7666 21.9062 12.6583 21.5687 12.55C21.2312 12.4416 20.9391 12.3875 20.6923 12.3875C20.4807 12.3875 20.252 12.427 20.0062 12.5062C19.7604 12.5854 19.5 12.7166 19.225 12.9C18.8833 13.1333 18.536 13.2937 18.1831 13.3812C17.8302 13.4687 17.465 13.5125 17.0875 13.5125ZM17.1125 9.61245C16.6958 9.61245 16.2791 9.5562 15.8625 9.4437C15.4458 9.3312 15.0458 9.18908 14.6625 9.01733L15.25 7.23745C15.675 7.42078 16.0333 7.54995 16.325 7.62495C16.6166 7.69995 16.8791 7.73745 17.1125 7.73745C17.318 7.73745 17.5236 7.7062 17.7291 7.6437C17.9347 7.5812 18.15 7.47495 18.375 7.32495C18.785 7.0465 19.1827 6.85814 19.5681 6.75988C19.9535 6.66159 20.3266 6.61245 20.6875 6.61245C21.0902 6.61245 21.493 6.66453 21.8958 6.7687C22.2986 6.87287 22.7125 7.01245 23.1375 7.18745L22.55 8.97495C22.125 8.83328 21.7666 8.71662 21.475 8.62495C21.1833 8.53328 20.9208 8.48745 20.6875 8.48745C20.4627 8.48745 20.2336 8.52078 20.0001 8.58745C19.7667 8.65412 19.5083 8.79162 19.225 8.99995C18.9333 9.20828 18.6027 9.36245 18.2332 9.46245C17.8637 9.56245 17.4902 9.61245 17.1125 9.61245ZM17.1147 17.4125C16.6882 17.4125 16.2708 17.3562 15.8625 17.2437C15.4541 17.1312 15.0541 16.9875 14.6625 16.8125L15.25 15.0375C15.6 15.1875 15.9333 15.3083 16.25 15.4C16.5666 15.4916 16.8541 15.5375 17.1125 15.5375C17.318 15.5375 17.5236 15.5062 17.7291 15.4437C17.9347 15.3812 18.15 15.275 18.375 15.125C18.7583 14.8666 19.1604 14.6833 19.5812 14.575C20.002 14.4666 20.3791 14.4125 20.7125 14.4125C21.1166 14.4125 21.5289 14.4684 21.9492 14.5802C22.3695 14.692 22.7656 14.8277 23.1375 14.9875L22.55 16.775C22.125 16.6333 21.7625 16.5166 21.4625 16.425C21.1625 16.3333 20.9041 16.2875 20.6875 16.2875C20.4541 16.2875 20.2125 16.327 19.9625 16.4062C19.7125 16.4854 19.4666 16.6166 19.225 16.8C18.95 16.9916 18.6279 17.1416 18.2588 17.25C17.8898 17.3583 17.5084 17.4125 17.1147 17.4125Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function BathtubIcon({ 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
style={{ maskType: "alpha" }}
id="mask0_69_3451"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3451)">
<path
d="M7.075 9.13755C6.54563 9.13755 6.09246 8.94906 5.71548 8.57207C5.33849 8.19511 5.15 7.74193 5.15 7.21255C5.15 6.68317 5.33849 6.22999 5.71548 5.85302C6.09246 5.47604 6.54563 5.28755 7.075 5.28755C7.60438 5.28755 8.05756 5.47604 8.43453 5.85302C8.81151 6.22999 9 6.68317 9 7.21255C9 7.74193 8.81151 8.19511 8.43453 8.57207C8.05756 8.94906 7.60438 9.13755 7.075 9.13755ZM5.125 21.75C4.84167 21.75 4.60417 21.6554 4.4125 21.4661C4.22083 21.2769 4.125 21.0423 4.125 20.7625C3.60937 20.7625 3.16796 20.579 2.80077 20.2118C2.43359 19.8446 2.25 19.4032 2.25 18.8875V13.9875C2.25 13.7292 2.34167 13.5084 2.525 13.325C2.70833 13.1417 2.92917 13.05 3.1875 13.05H5.175V12.3276C5.175 11.7176 5.38444 11.2021 5.80332 10.7813C6.22222 10.3605 6.73778 10.15 7.35 10.15C7.66667 10.15 7.96667 10.2125 8.25 10.3375C8.53333 10.4625 8.78333 10.6417 9 10.875L10.35 12.375C10.475 12.5 10.6 12.6209 10.725 12.7375C10.85 12.8542 10.9833 12.9584 11.125 13.05H17.875V5.00005C17.875 4.75642 17.7892 4.54759 17.6177 4.37357C17.4461 4.19956 17.2402 4.11255 17 4.11255C16.8941 4.11255 16.7926 4.13338 16.6956 4.17505C16.5985 4.21672 16.509 4.27869 16.4269 4.36097L15.1962 5.59522C15.2782 5.87497 15.2946 6.15061 15.2454 6.42215C15.1962 6.69368 15.0977 6.94465 14.95 7.17505L12.3 4.50005C12.5333 4.35005 12.7833 4.25422 13.05 4.21255C13.3167 4.17088 13.5833 4.20005 13.85 4.30005L15.075 3.06255C15.3323 2.80373 15.6258 2.60152 15.9554 2.45592C16.2851 2.31034 16.6348 2.23755 17.0047 2.23755C17.7766 2.23755 18.4271 2.50411 18.9563 3.03722C19.4854 3.57034 19.75 4.22462 19.75 5.00005V13.05H20.8125C21.0708 13.05 21.2917 13.1417 21.475 13.325C21.6583 13.5084 21.75 13.7292 21.75 13.9875V18.8875C21.75 19.4032 21.5664 19.8446 21.1992 20.2118C20.832 20.579 20.3906 20.7625 19.875 20.7625C19.875 21.0423 19.7792 21.2769 19.5875 21.4661C19.3958 21.6554 19.1583 21.75 18.875 21.75H5.125ZM4.125 18.8875H19.875V14.925H4.125V18.8875Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function BedDoubleIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3418"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3418)">
<path
d="M3.25 17.8875V13C3.25 12.575 3.3375 12.1813 3.5125 11.8188C3.6875 11.4563 3.925 11.1417 4.225 10.875V8.17505C4.225 7.38338 4.50625 6.7063 5.06875 6.1438C5.63125 5.5813 6.30833 5.30005 7.1 5.30005H10.075C10.4417 5.30005 10.7875 5.37088 11.1125 5.51255C11.4375 5.65422 11.7333 5.85005 12 6.10005C12.2667 5.85005 12.5625 5.65422 12.8875 5.51255C13.2125 5.37088 13.5583 5.30005 13.925 5.30005H16.9C17.6917 5.30005 18.3687 5.5813 18.9312 6.1438C19.4937 6.7063 19.775 7.38338 19.775 8.17505V10.875C20.075 11.1417 20.3125 11.4563 20.4875 11.8188C20.6625 12.1813 20.75 12.575 20.75 13V17.8875C20.75 18.1459 20.6583 18.3667 20.475 18.55C20.2917 18.7334 20.0708 18.825 19.8125 18.825C19.5542 18.825 19.3333 18.7334 19.15 18.55C18.9667 18.3667 18.875 18.1459 18.875 17.8875V16.85H5.125V17.8875C5.125 18.1459 5.03333 18.3667 4.85 18.55C4.66667 18.7334 4.44583 18.825 4.1875 18.825C3.92917 18.825 3.70833 18.7334 3.525 18.55C3.34167 18.3667 3.25 18.1459 3.25 17.8875ZM12.95 10.15H17.9V8.17067C17.9 7.89026 17.8046 7.65422 17.6138 7.46255C17.423 7.27088 17.1866 7.17505 16.9046 7.17505H13.923C13.641 7.17505 13.4083 7.27088 13.225 7.46255C13.0417 7.65422 12.95 7.89172 12.95 8.17505V10.15ZM6.1 10.15H11.05V8.17067C11.05 7.89026 10.9583 7.65422 10.775 7.46255C10.5917 7.27088 10.359 7.17505 10.077 7.17505H7.0954C6.81337 7.17505 6.57696 7.27088 6.38618 7.46255C6.19539 7.65422 6.1 7.89172 6.1 8.17505V10.15ZM5.125 14.975H18.875V12.9957C18.875 12.7153 18.7833 12.4834 18.6 12.3C18.4167 12.1167 18.1852 12.025 17.9057 12.025H6.09427C5.81476 12.025 5.58333 12.1167 5.4 12.3C5.21667 12.4834 5.125 12.7153 5.125 12.9957V14.975Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ChairIcon({ 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
style={{ maskType: "alpha" }}
id="mask0_69_3444"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3444)">
<path
d="M5.1202 20.7C4.85675 20.7 4.63336 20.6113 4.45002 20.4341C4.26669 20.2568 4.17502 20.0371 4.17502 19.775V18.775H4.12502C3.33336 18.775 2.66044 18.4979 2.10627 17.9437C1.55211 17.3896 1.27502 16.7166 1.27502 15.925V11C1.27502 10.1916 1.55419 9.51456 2.11252 8.96873C2.67086 8.42289 3.34169 8.14998 4.12502 8.14998V6.19998C4.12502 5.40831 4.40627 4.73539 4.96877 4.18123C5.53127 3.62706 6.20836 3.34998 7.00002 3.34998H17.025C17.8167 3.34998 18.4938 3.62706 19.0563 4.18123C19.6188 4.73539 19.9 5.40831 19.9 6.19998V8.14998C20.6917 8.14998 21.3646 8.42706 21.9188 8.98123C22.4729 9.53539 22.75 10.2083 22.75 11V15.925C22.75 16.7166 22.4729 17.3896 21.9188 17.9437C21.3646 18.4979 20.6917 18.775 19.9 18.775H19.85V19.775C19.85 20.0371 19.759 20.2568 19.5769 20.4341C19.3948 20.6113 19.1692 20.7 18.9 20.7C18.6379 20.7 18.4183 20.6113 18.241 20.4341C18.0637 20.2568 17.975 20.0371 17.975 19.775V18.775H6.05002V19.775C6.05002 20.0333 5.96092 20.2521 5.7827 20.4312C5.60448 20.6104 5.38365 20.7 5.1202 20.7ZM4.12502 16.9H19.9C20.1667 16.9 20.3959 16.8083 20.5875 16.625C20.7792 16.4416 20.875 16.2101 20.875 15.9304V10.9945C20.875 10.7148 20.7792 10.4833 20.5875 10.3C20.3959 10.1166 20.1667 10.025 19.9 10.025C19.6167 10.025 19.3792 10.1166 19.1875 10.3C18.9959 10.4833 18.9 10.7166 18.9 11V14.9H5.12502V11C5.12502 10.7166 5.02919 10.4833 4.83752 10.3C4.64586 10.1166 4.40836 10.025 4.12502 10.025C3.85836 10.025 3.62919 10.1166 3.43752 10.3C3.24586 10.4833 3.15002 10.7148 3.15002 10.9945V15.9304C3.15002 16.2101 3.24586 16.4416 3.43752 16.625C3.62919 16.8083 3.85836 16.9 4.12502 16.9ZM7.00002 13.025H17.025V11C17.025 10.5583 17.1167 10.1604 17.3 9.80623C17.4834 9.45206 17.725 9.1401 18.025 8.87035V6.2046C18.025 5.93485 17.929 5.70414 17.7369 5.51248C17.5449 5.32081 17.3069 5.22498 17.0229 5.22498H7.0021C6.71818 5.22498 6.48019 5.32081 6.28812 5.51248C6.09606 5.70414 6.00002 5.93505 6.00002 6.2052V8.87498C6.30002 9.14164 6.54169 9.45206 6.72502 9.80623C6.90836 10.1604 7.00002 10.5583 7.00002 11V13.025Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -18,23 +18,10 @@ export default function ChevronRightSmallIcon({
fill="none" fill="none"
{...props} {...props}
> >
<mask <path
id="mask0_69_3311" d="M12.65 12L8.77495 8.12497C8.59995 7.94997 8.51245 7.73538 8.51245 7.48122C8.51245 7.22705 8.59995 7.0083 8.77495 6.82497C8.94995 6.64163 9.16662 6.54788 9.42495 6.54372C9.68328 6.53955 9.90412 6.62913 10.0875 6.81247L14.6125 11.3375C14.7041 11.4291 14.7729 11.5312 14.8187 11.6437C14.8645 11.7562 14.8875 11.875 14.8875 12C14.8875 12.125 14.8645 12.2437 14.8187 12.3562C14.7729 12.4687 14.7041 12.5708 14.6125 12.6625L10.0875 17.1875C9.90412 17.3708 9.68328 17.4604 9.42495 17.4562C9.16662 17.4521 8.94995 17.3583 8.77495 17.175C8.59995 16.9916 8.51245 16.7729 8.51245 16.5187C8.51245 16.2646 8.59995 16.05 8.77495 15.875L12.65 12Z"
style={{ maskType: "alpha" }} fill="#26201E"
maskUnits="userSpaceOnUse" />
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3311)">
<path
d="M12.65 12L8.77495 8.12497C8.59995 7.94997 8.51245 7.73538 8.51245 7.48122C8.51245 7.22705 8.59995 7.0083 8.77495 6.82497C8.94995 6.64163 9.16662 6.54788 9.42495 6.54372C9.68328 6.53955 9.90412 6.62913 10.0875 6.81247L14.6125 11.3375C14.7041 11.4291 14.7729 11.5312 14.8187 11.6437C14.8645 11.7562 14.8875 11.875 14.8875 12C14.8875 12.125 14.8645 12.2437 14.8187 12.3562C14.7729 12.4687 14.7041 12.5708 14.6125 12.6625L10.0875 17.1875C9.90412 17.3708 9.68328 17.4604 9.42495 17.4562C9.16662 17.4521 8.94995 17.3583 8.77495 17.175C8.59995 16.9916 8.51245 16.7729 8.51245 16.5187C8.51245 16.2646 8.59995 16.05 8.77495 15.875L12.65 12Z"
fill="#26201E"
/>
</g>
</svg> </svg>
) )
} }

36
components/Icons/City.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CityIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3390"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3390)">
<path
d="M3.57495 18.4248V9.0486C3.57495 8.5329 3.75854 8.09079 4.12573 7.72227C4.49291 7.35374 4.93432 7.16947 5.44995 7.16947H9.19995V6.06392C9.19995 5.81301 9.24578 5.57713 9.33745 5.3563C9.42912 5.13547 9.55828 4.93755 9.72495 4.76255L10.6639 3.7943C11.0238 3.42313 11.4698 3.23755 12.0019 3.23755C12.5339 3.23755 12.9833 3.42505 13.35 3.80005L14.2911 4.76082C14.4637 4.93697 14.5958 5.13648 14.6875 5.35935C14.7791 5.58222 14.825 5.81662 14.825 6.06255V10.922H18.575C19.0906 10.922 19.532 11.1059 19.8992 11.4737C20.2664 11.8415 20.45 12.2836 20.45 12.8V18.425C20.45 18.9407 20.2663 19.3821 19.8991 19.7493C19.5319 20.1165 19.0904 20.3 18.5748 20.3H5.44848C4.93279 20.3 4.49162 20.1164 4.12495 19.7492C3.75828 19.382 3.57495 18.9405 3.57495 18.4248ZM5.44995 18.425H7.32495V16.55H5.44995V18.425ZM5.44995 14.675H7.32495V12.8H5.44995V14.675ZM5.44995 10.925H7.32495V9.05005H5.44995V10.925ZM11.075 18.425H12.95V16.55H11.075V18.425ZM11.075 14.675H12.95V12.8H11.075V14.675ZM11.075 10.925H12.95V9.05005H11.075V10.925ZM11.075 7.17505H12.95V5.30005H11.075V7.17505ZM16.7 18.425H18.575V16.55H16.7V18.425ZM16.7 14.675H18.575V12.8H16.7V14.675Z"
fill="#26201E"
/>
</g>
</svg>
)
}

36
components/Icons/Desk.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function DeskIcon({ 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_81_882"
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_81_882)">
<path
d="M2.0625 17V7.9375C2.0625 7.42187 2.24609 6.98046 2.61327 6.61328C2.98046 6.24609 3.42187 6.0625 3.9375 6.0625H20.0625C20.5781 6.0625 21.0195 6.24609 21.3867 6.61328C21.7539 6.98046 21.9375 7.42187 21.9375 7.9375V17C21.9375 17.2583 21.8458 17.4792 21.6625 17.6625C21.4792 17.8458 21.2583 17.9375 21 17.9375C20.7417 17.9375 20.5208 17.8458 20.3375 17.6625C20.1542 17.4792 20.0625 17.2583 20.0625 17V15.9375H15.9375V17C15.9375 17.2583 15.8458 17.4792 15.6625 17.6625C15.4792 17.8458 15.2583 17.9375 15 17.9375C14.7417 17.9375 14.5208 17.8458 14.3375 17.6625C14.1542 17.4792 14.0625 17.2583 14.0625 17V7.9375H3.9375V17C3.9375 17.2583 3.84583 17.4792 3.6625 17.6625C3.47917 17.8458 3.25833 17.9375 3 17.9375C2.74167 17.9375 2.52083 17.8458 2.3375 17.6625C2.15417 17.4792 2.0625 17.2583 2.0625 17ZM15.9375 10.0625H20.0625V7.9375H15.9375V10.0625ZM15.9375 14.0625H20.0625V11.9375H15.9375V14.0625Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,58 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function HairdryerIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
id="mask0_83_335"
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_83_335)">
<path
d="M20.3933 9.705C20.3933 8.89072 20.1059 8.19833 19.5311 7.62786C18.9563 7.05739 18.2584 6.77215 17.4372 6.77215H8.74704C8.49762 6.77215 8.2844 6.68439 8.1074 6.50886C7.93039 6.33332 7.84188 6.12189 7.84188 5.87455C7.84188 5.62722 7.93039 5.41578 8.1074 5.24025C8.2844 5.06472 8.49762 4.97696 8.74704 4.97696H17.4365C18.7554 4.97696 19.8797 5.43793 20.8093 6.35988C21.7389 7.28184 22.2037 8.39686 22.2037 9.70493V18.5425C22.2037 19.0713 21.7714 19.4999 21.2382 19.4999H17.6417C17.1085 19.4999 16.6762 19.0713 16.6762 18.5425V14.3764C15.8235 14.2551 15.0899 13.918 14.4037 13.3545C13.5291 12.6365 13.5745 12.5766 12.749 11.5593H9.71253C9.46311 11.5593 9.2499 11.4716 9.07289 11.296C8.89589 11.1205 8.80738 10.9091 8.80738 10.6617C8.80738 10.4144 8.89589 10.203 9.07289 10.0274C9.2499 9.85191 9.46311 9.76415 9.71253 9.76415H13.4811C13.9906 9.76415 14.3777 10.1879 14.5547 10.6617C14.7126 11.0846 14.987 11.4285 15.3419 11.7808C15.9167 12.3512 16.6147 12.6365 17.4358 12.6365C17.5165 12.6365 17.596 12.6337 17.6743 12.6282C18.077 12.5999 18.4865 12.8831 18.4865 13.2834V17.4563C18.4865 17.6675 18.6589 17.8388 18.8719 17.8393L20.0063 17.8417C20.2199 17.8422 20.3933 17.6706 20.3933 17.4587V9.705Z"
fill="#26201E"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.438 11.4968C16.914 11.4968 16.465 11.3118 16.0909 10.9418C15.7167 10.5718 15.5297 10.127 15.5297 9.60736C15.5297 9.08776 15.7162 8.64246 16.0894 8.27145C16.4625 7.90044 16.911 7.71494 17.435 7.71494C17.959 7.71494 18.408 7.89995 18.7822 8.26996C19.1563 8.63998 19.3434 9.08479 19.3434 9.60439C19.3434 10.124 19.1568 10.5693 18.7837 10.9403C18.4105 11.3113 17.962 11.4968 17.438 11.4968ZM16.7667 10.2716C16.9477 10.4503 17.1714 10.5396 17.4378 10.5396C17.7041 10.5396 17.9274 10.4498 18.1076 10.2703C18.2878 10.0908 18.3779 9.86897 18.3779 9.60483C18.3779 9.3407 18.2874 9.11929 18.1063 8.9406C17.9253 8.76193 17.7016 8.67259 17.4352 8.67259C17.1689 8.67259 16.9456 8.76235 16.7654 8.94187C16.5852 9.12139 16.4952 9.34322 16.4952 9.60736C16.4952 9.87149 16.5857 10.0929 16.7667 10.2716Z"
fill="#26201E"
/>
<path
d="M5.98812 5.09562C6.07942 4.92317 6.07619 4.74726 5.97901 4.57747L5.9783 4.57625C5.84295 4.34975 5.66047 4.17098 5.43173 4.04089C5.20285 3.91072 4.95481 3.84558 4.68894 3.84558C4.28066 3.84558 3.93106 3.98777 3.64455 4.27118C3.35796 4.55468 3.21397 4.90158 3.21397 5.30745C3.21397 5.71333 3.35796 6.06065 3.64446 6.34505C3.93094 6.62943 4.28058 6.77215 4.68894 6.77215H6.807C6.95148 6.77215 7.07707 6.72046 7.17963 6.61876C7.28221 6.51703 7.33458 6.3922 7.33458 6.24835C7.33458 6.10451 7.28221 5.97967 7.17963 5.87795C7.07707 5.77624 6.95148 5.72455 6.807 5.72455L4.68636 5.72455C4.57124 5.72455 4.47437 5.6846 4.39176 5.60268C4.30937 5.52098 4.26913 5.42445 4.26913 5.30887C4.26913 5.19328 4.30937 5.09675 4.39176 5.01505C4.47417 4.93333 4.57182 4.89318 4.68894 4.89318C4.75984 4.89318 4.82693 4.90631 4.89079 4.93246C4.95178 4.95745 4.99815 4.99387 5.03232 5.04145L5.03304 5.04243C5.09367 5.12402 5.1651 5.1959 5.24712 5.25797C5.3369 5.32591 5.44013 5.35993 5.55412 5.35993C5.64869 5.35993 5.73479 5.33849 5.80957 5.29285C5.88428 5.24726 5.94335 5.18019 5.98812 5.09562Z"
fill="#26201E"
/>
<path
d="M8.55441 7.88364C8.45185 7.78193 8.32626 7.73024 8.18178 7.73024L2.52761 7.73024C2.38313 7.73024 2.25754 7.78193 2.15498 7.88364C2.0524 7.98536 2.00003 8.1102 2.00003 8.25404C2.00003 8.39789 2.0524 8.52273 2.15498 8.62445C2.25754 8.72615 2.38313 8.77784 2.52761 8.77784L8.18178 8.77784C8.32626 8.77784 8.45185 8.72615 8.55441 8.62445C8.65699 8.52273 8.70935 8.39789 8.70935 8.25404C8.70935 8.1102 8.65699 7.98536 8.55441 7.88364Z"
fill="#26201E"
/>
<path
d="M6.2737 12.0792C6.33521 11.9188 6.31116 11.7643 6.20583 11.625C6.10083 11.4862 5.95678 11.4157 5.78093 11.4157C5.6522 11.4157 5.53973 11.4612 5.44757 11.5506C5.36483 11.6309 5.2936 11.7206 5.23395 11.8195C5.17676 11.9163 5.09834 11.9908 4.99816 12.0427C4.89649 12.0954 4.78683 12.1218 4.66806 12.1218C4.48377 12.1218 4.32963 12.0586 4.20124 11.9312C4.07292 11.8038 4.00942 11.6507 4.00942 11.4675C4.00942 11.2844 4.07312 11.131 4.20198 11.0031C4.33086 10.8752 4.4856 10.8117 4.67061 10.8117H7.71796C7.86244 10.8117 7.98803 10.7601 8.09059 10.6584C8.19317 10.5566 8.24554 10.4318 8.24554 10.2879C8.24554 10.1441 8.19317 10.0193 8.09059 9.91754C7.98803 9.81584 7.86244 9.76415 7.71796 9.76415L4.67061 9.76415C4.19765 9.76415 3.79177 9.93043 3.45719 10.2616C3.12254 10.5928 2.95427 10.9955 2.95427 11.4655C2.95427 11.9354 3.12253 12.3386 3.45712 12.6706C3.79168 13.0027 4.19758 13.1694 4.67061 13.1694C5.03173 13.1694 5.36026 13.0715 5.65414 12.8756C5.94859 12.6793 6.15561 12.4131 6.2737 12.0792Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,49 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function HandSoapIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<g clipPath="url(#clip0_4037_3127)">
<mask
style={{ maskType: "alpha" }}
id="mask0_4037_3127"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<path d="M24 0H0V24H24V0Z" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_4037_3127)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.93432 2.50091C8.93432 2.01716 9.32648 1.625 9.81023 1.625H15.6496C16.2691 1.625 16.8632 1.87109 17.3013 2.30913C17.7393 2.74717 17.9854 3.34128 17.9854 3.96077C17.9854 4.44452 17.5933 4.83668 17.1095 4.83668C16.6257 4.83668 16.2336 4.44452 16.2336 3.96077C16.2336 3.8059 16.1721 3.65737 16.0626 3.54786C15.953 3.43835 15.8045 3.37682 15.6496 3.37682H12.8759V5.6396H14.1898C15.0029 5.6396 15.7826 5.96259 16.3576 6.53752C16.9325 7.11245 17.2555 7.89222 17.2555 8.70529V9.39167C17.9345 9.55274 18.562 9.89942 19.0635 10.4009C19.7753 11.1128 20.1752 12.0782 20.1752 13.0849V20.0192C20.1752 20.4451 20.006 20.8535 19.7049 21.1547C19.4037 21.4558 18.9952 21.625 18.5694 21.625H5.43067C5.00477 21.625 4.59632 21.4558 4.29517 21.1547C3.99402 20.8535 3.82483 20.4451 3.82483 20.0192V13.0849C3.82483 12.0782 4.22472 11.1128 4.93654 10.4009C5.43807 9.89942 6.0655 9.55274 6.74454 9.39167V8.70529C6.74454 7.89222 7.06753 7.11245 7.64246 6.53752C8.21739 5.96259 8.99716 5.6396 9.81023 5.6396H11.1241V3.37682H9.81023C9.32648 3.37682 8.93432 2.98467 8.93432 2.50091ZM9.81023 7.39142C9.46177 7.39142 9.12758 7.52985 8.88119 7.77625C8.63479 8.02264 8.49636 8.35683 8.49636 8.70529V9.28923H15.5037V8.70529C15.5037 8.35683 15.3652 8.02264 15.1188 7.77625C14.8724 7.52985 14.5383 7.39142 14.1898 7.39142H9.81023ZM7.62045 11.0411C7.0784 11.0411 6.55855 11.2564 6.17527 11.6397C5.79198 12.023 5.57665 12.5428 5.57665 13.0849V19.8732H18.4234V13.0849C18.4234 12.5428 18.208 12.023 17.8248 11.6397C17.4415 11.2564 16.9216 11.0411 16.3796 11.0411H7.62045Z"
fill="black"
/>
</g>
</g>
<defs>
<clipPath id="clip0_4037_3127">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
)
}

36
components/Icons/Iron.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function IronIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3453"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3453)">
<path
d="M4.125 15.875H14.9875V12.9375H6.125C5.575 12.9375 5.10417 13.1271 4.7125 13.5062C4.32083 13.8854 4.125 14.3462 4.125 14.8887V15.875ZM20.8125 6.25C21.0708 6.25 21.2917 6.34167 21.475 6.525C21.6583 6.70833 21.75 6.92917 21.75 7.1875C21.75 7.44583 21.6583 7.66667 21.475 7.85C21.2917 8.03333 21.0708 8.125 20.8125 8.125C20.5256 8.125 20.2852 8.22083 20.0911 8.4125C19.897 8.60417 19.8 8.83647 19.8 9.1094V12.9625C19.8 13.7576 19.5125 14.4335 18.9375 14.9901C18.3625 15.5467 17.6708 15.825 16.8625 15.825V16.8125C16.8625 17.0708 16.7708 17.2917 16.5875 17.475C16.4042 17.6583 16.1833 17.75 15.925 17.75H3.1875C2.92917 17.75 2.70833 17.6583 2.525 17.475C2.34167 17.2917 2.25 17.0708 2.25 16.8125V14.8879C2.25 13.8293 2.62708 12.9271 3.38125 12.1813C4.13542 11.4354 5.05 11.0625 6.125 11.0625H14.9875V10.0801C14.9875 9.80168 14.8875 9.56667 14.6875 9.375C14.4875 9.18333 14.2458 9.0875 13.9625 9.0875H10.0458C9.91528 9.0875 9.785 9.11667 9.655 9.175C9.525 9.23333 9.41667 9.30417 9.33 9.3875C9.24333 9.47083 9.14583 9.53542 9.0375 9.58125C8.92917 9.62708 8.80833 9.65 8.675 9.65C8.41667 9.65 8.19375 9.55833 8.00625 9.375C7.81875 9.19167 7.725 8.97083 7.725 8.7125C7.725 8.57917 7.75208 8.45417 7.80625 8.3375C7.86042 8.22083 7.92917 8.12083 8.0125 8.0375C8.27917 7.77917 8.58508 7.57708 8.93025 7.43125C9.2754 7.28542 9.64865 7.2125 10.05 7.2125H13.9625C14.7681 7.2125 15.4528 7.4908 16.0167 8.0474C16.5806 8.604 16.8625 9.27987 16.8625 10.075V13.95C17.1542 13.95 17.4042 13.8554 17.6125 13.6661C17.8208 13.4768 17.925 13.2423 17.925 12.9625V9.1125C17.925 8.31737 18.2057 7.6415 18.7672 7.0849C19.3287 6.5283 20.0104 6.25 20.8125 6.25Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,25 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function MirrorIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.26544 9.89998H5.87357C6.53214 12.6815 8.99277 14.7 12.0001 14.7C15.0074 14.7 17.468 12.6815 18.1266 9.89998H18.7347C18.0448 12.9797 15.2797 15.3 12 15.3C8.72041 15.3 5.95525 12.9797 5.26544 9.89998ZM18.9446 8.09998C18.9856 7.96928 19.055 7.85077 19.1529 7.75287C19.3218 7.58403 19.5519 7.5 19.8 7.5C20.0482 7.5 20.2783 7.58403 20.4472 7.75287C20.616 7.9217 20.7 8.15183 20.7 8.4C20.7 12.9416 17.3143 16.6123 12.9 17.0552V20.1H15.0001C15.2483 20.1 15.4784 20.1841 15.6472 20.3529C15.8161 20.5217 15.9001 20.7519 15.9001 21C15.9001 21.2482 15.8161 21.4783 15.6472 21.6472C15.4784 21.816 15.2483 21.9 15.0001 21.9H9.0001C8.75192 21.9 8.5218 21.816 8.35297 21.6472C8.18413 21.4783 8.1001 21.2482 8.1001 21C8.1001 20.7519 8.18413 20.5217 8.35297 20.3529C8.5218 20.1841 8.75192 20.1 9.0001 20.1H11.1V17.0551C6.68567 16.6122 3.30005 12.9415 3.30005 8.4C3.30005 8.15183 3.38408 7.9217 3.55292 7.75287C3.72175 7.58403 3.95187 7.5 4.20005 7.5C4.44822 7.5 4.67835 7.58403 4.84718 7.75287C4.94508 7.85077 5.01447 7.96928 5.0555 8.09998H5.70681C5.85905 4.71819 8.57502 2.09998 12.0001 2.09998C15.4251 2.09998 18.1411 4.71819 18.2933 8.09998H18.9446ZM12.0001 3.89998C9.52576 3.89998 7.50007 5.92566 7.50007 8.39998C7.50007 10.8743 9.52576 12.9 12.0001 12.9C14.4744 12.9 16.5001 10.8743 16.5001 8.39998C16.5001 5.92566 14.4744 3.89998 12.0001 3.89998ZM13.108 8.42789C12.7508 8.78504 12.7508 9.33499 13.108 9.69215C13.2345 9.81866 13.4 9.86247 13.5108 9.88094C13.6255 9.90006 13.7361 9.90003 13.7956 9.90002L13.8071 9.90002C13.8639 9.90007 13.9791 9.90015 14.0839 9.87919C14.1372 9.86854 14.2177 9.84713 14.2915 9.79605L14.3059 9.78613C14.3607 9.74859 14.4548 9.68419 14.5201 9.60002L15.5722 8.49215C15.9294 8.13499 15.9294 7.58504 15.5722 7.22789C15.2151 6.87073 14.6651 6.87073 14.308 7.22789L13.108 8.42789ZM10.8495 9.5809C10.9603 9.56244 11.0691 9.52149 11.2523 9.39212L14.1347 6.38975C14.4895 6.03253 14.4887 5.48422 14.1323 5.12785C13.7752 4.7707 13.2252 4.7707 12.868 5.12785L9.86805 8.12785C9.51089 8.48501 9.51089 9.03496 9.86805 9.39212C9.99455 9.51862 10.1601 9.56244 10.2709 9.5809C10.3856 9.60002 10.4962 9.6 10.5556 9.59999H10.5557H10.5647H10.5647C10.6242 9.6 10.7348 9.60002 10.8495 9.5809Z"
fill="black"
/>
</svg>
)
}

View File

@@ -0,0 +1,46 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function SafetyBoxIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_82_314"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_82_314)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.875 18.55C4.35937 18.55 3.91796 18.3664 3.55078 17.9992C3.18359 17.632 3 17.1906 3 16.675V4.875C3 4.35937 3.18359 3.91796 3.55078 3.55078C3.91796 3.18359 4.35937 3 4.875 3H18.675C19.1906 3 19.632 3.18359 19.9992 3.55078C20.3664 3.91796 20.55 4.35937 20.55 4.875V16.675C20.55 17.1906 20.3664 17.632 19.9992 17.9992C19.632 18.3664 19.1906 18.55 18.675 18.55V19.5875C18.675 19.8458 18.5833 20.0667 18.4 20.25C18.2167 20.4333 17.9958 20.525 17.7375 20.525C17.4792 20.525 17.2583 20.4333 17.075 20.25C16.8917 20.0667 16.8 19.8458 16.8 19.5875V18.55H6.75V19.5875C6.75 19.8458 6.65833 20.0667 6.475 20.25C6.29167 20.4333 6.07083 20.525 5.8125 20.525C5.55417 20.525 5.33333 20.4333 5.15 20.25C4.96667 20.0667 4.875 19.8458 4.875 19.5875V18.55ZM4.875 4.875V16.675H18.675V14.275H17.675C17.4167 14.275 17.1958 14.1834 17.0125 14C16.8292 13.8167 16.7375 13.5959 16.7375 13.3375C16.7375 13.0792 16.8292 12.8584 17.0125 12.675C17.1958 12.4917 17.4167 12.4 17.675 12.4H18.675V9.15002H17.675C17.4167 9.15002 17.1958 9.05836 17.0125 8.87502C16.8292 8.69169 16.7375 8.47086 16.7375 8.21252C16.7375 7.95419 16.8292 7.73336 17.0125 7.55002C17.1958 7.36669 17.4167 7.27502 17.675 7.27502H18.675V4.875H4.875Z"
fill="#26201E"
/>
<path
d="M13.2845 11.2845C13.6615 10.9076 13.85 10.4544 13.85 9.925C13.85 9.39562 13.6615 8.94244 13.2845 8.56548C12.9076 8.18849 12.4544 8 11.925 8C11.3956 8 10.9424 8.18849 10.5655 8.56548C10.1885 8.94244 10 9.39562 10 9.925C10 10.4544 10.1885 10.9076 10.5655 11.2845C10.7069 11.426 10.8419 11.5315 11.0049 11.6199V12.6375C11.0049 12.8959 11.0965 13.1167 11.2799 13.3C11.4632 13.4834 11.684 13.575 11.9424 13.575C12.2007 13.575 12.4215 13.4834 12.6049 13.3C12.7882 13.1167 12.8799 12.8959 12.8799 12.6375V11.6C13.0318 11.514 13.1516 11.4175 13.2845 11.2845Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ShowerIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3474"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3474)">
<path
d="M8.02495 17.7625C7.75828 17.7625 7.53537 17.6709 7.3562 17.4875C7.17703 17.3042 7.08745 17.0834 7.08745 16.825C7.08745 16.5667 7.17703 16.3459 7.3562 16.1625C7.53537 15.9792 7.75828 15.8875 8.02495 15.8875C8.28328 15.8875 8.50412 15.9792 8.68745 16.1625C8.87078 16.3459 8.96245 16.5667 8.96245 16.825C8.96245 17.0834 8.87078 17.3042 8.68745 17.4875C8.50412 17.6709 8.28328 17.7625 8.02495 17.7625ZM12 17.7625C11.7416 17.7625 11.5208 17.6709 11.3375 17.4875C11.1541 17.3042 11.0625 17.0834 11.0625 16.825C11.0625 16.5667 11.1541 16.3459 11.3375 16.1625C11.5208 15.9792 11.7416 15.8875 12 15.8875C12.2666 15.8875 12.4895 15.9792 12.6687 16.1625C12.8479 16.3459 12.9375 16.5667 12.9375 16.825C12.9375 17.0834 12.8479 17.3042 12.6687 17.4875C12.4895 17.6709 12.2666 17.7625 12 17.7625ZM16 17.7625C15.7416 17.7625 15.5208 17.6709 15.3375 17.4875C15.1541 17.3042 15.0625 17.0834 15.0625 16.825C15.0625 16.5667 15.1541 16.3459 15.3375 16.1625C15.5208 15.9792 15.7416 15.8875 16 15.8875C16.2666 15.8875 16.4895 15.9792 16.6687 16.1625C16.8479 16.3459 16.9375 16.5667 16.9375 16.825C16.9375 17.0834 16.8479 17.3042 16.6687 17.4875C16.4895 17.6709 16.2666 17.7625 16 17.7625ZM6.13745 14.0125C5.87912 14.0125 5.65828 13.9209 5.47495 13.7375C5.29162 13.5542 5.19995 13.3334 5.19995 13.075V12.1375C5.19995 10.4209 5.7562 8.92713 6.8687 7.6563C7.9812 6.38547 9.37912 5.64172 11.0625 5.42505V4.30005C11.0625 4.04172 11.1541 3.82088 11.3375 3.63755C11.5208 3.45422 11.7416 3.36255 12 3.36255C12.2583 3.36255 12.4791 3.45422 12.6625 3.63755C12.8458 3.82088 12.9375 4.04172 12.9375 4.30005V5.42505C14.6291 5.64172 16.0291 6.38547 17.1375 7.6563C18.2458 8.92713 18.8 10.4209 18.8 12.1375V13.075C18.8 13.3334 18.7083 13.5542 18.525 13.7375C18.3416 13.9209 18.1208 14.0125 17.8625 14.0125H6.13745ZM7.07495 12.1375H16.925C16.925 10.7709 16.4458 9.60838 15.4875 8.65005C14.5291 7.69172 13.3666 7.21255 12 7.21255C10.6416 7.21255 9.4812 7.69172 8.5187 8.65005C7.5562 9.60838 7.07495 10.7709 7.07495 12.1375ZM8.02495 20.575C7.75828 20.575 7.53537 20.4834 7.3562 20.3C7.17703 20.1167 7.08745 19.8959 7.08745 19.6375C7.08745 19.3792 7.17703 19.1584 7.3562 18.975C7.53537 18.7917 7.75828 18.7 8.02495 18.7C8.28328 18.7 8.50412 18.7917 8.68745 18.975C8.87078 19.1584 8.96245 19.3792 8.96245 19.6375C8.96245 19.8959 8.87078 20.1167 8.68745 20.3C8.50412 20.4834 8.28328 20.575 8.02495 20.575ZM12 20.575C11.7416 20.575 11.5208 20.4834 11.3375 20.3C11.1541 20.1167 11.0625 19.8959 11.0625 19.6375C11.0625 19.3792 11.1541 19.1584 11.3375 18.975C11.5208 18.7917 11.7416 18.7 12 18.7C12.2666 18.7 12.4895 18.7917 12.6687 18.975C12.8479 19.1584 12.9375 19.3792 12.9375 19.6375C12.9375 19.8959 12.8479 20.1167 12.6687 20.3C12.4895 20.4834 12.2666 20.575 12 20.575ZM16 20.575C15.7416 20.575 15.5208 20.4834 15.3375 20.3C15.1541 20.1167 15.0625 19.8959 15.0625 19.6375C15.0625 19.3792 15.1541 19.1584 15.3375 18.975C15.5208 18.7917 15.7416 18.7 16 18.7C16.2666 18.7 16.4895 18.7917 16.6687 18.975C16.8479 19.1584 16.9375 19.3792 16.9375 19.6375C16.9375 19.8959 16.8479 20.1167 16.6687 20.3C16.4895 20.4834 16.2666 20.575 16 20.575Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function WindowCurtainsAltIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3395"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3395)">
<path
d="M4.0625 19.0625V4.9375C4.0625 4.42187 4.24609 3.98046 4.61328 3.61328C4.98046 3.24609 5.42187 3.0625 5.9375 3.0625H18.0625C18.5781 3.0625 19.0195 3.24609 19.3867 3.61328C19.7539 3.98046 19.9375 4.42187 19.9375 4.9375V19.0625H21C21.2583 19.0625 21.4792 19.1542 21.6625 19.3375C21.8458 19.5208 21.9375 19.7417 21.9375 20C21.9375 20.2583 21.8458 20.4792 21.6625 20.6625C21.4792 20.8458 21.2583 20.9375 21 20.9375H3C2.74167 20.9375 2.52083 20.8458 2.3375 20.6625C2.15417 20.4792 2.0625 20.2583 2.0625 20C2.0625 19.7417 2.15417 19.5208 2.3375 19.3375C2.52083 19.1542 2.74167 19.0625 3 19.0625H4.0625ZM5.9375 19.0625H9.0625V4.9375H5.9375V19.0625ZM10.9375 19.0625H13.0625V4.9375H10.9375V19.0625ZM14.9375 19.0625H18.0625V4.9375H14.9375V19.0625Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,52 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function WindowNotAvailableIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_4398_1753"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_4398_1753)">
<path
d="M3.12638 4.26255L10.8139 11.95L13.6889 14.8625L15.0264 16.2L16.3764 17.5125L19.9264 21.05C20.1097 21.2334 20.3285 21.325 20.5826 21.325C20.8368 21.325 21.0555 21.2334 21.2389 21.05C21.4222 20.8667 21.5118 20.648 21.5076 20.3938C21.5035 20.1396 21.4139 19.925 21.2389 19.75L4.43888 2.95005C4.25555 2.76672 4.0368 2.67505 3.78263 2.67505C3.52846 2.67505 3.30971 2.76672 3.12638 2.95005C2.94305 3.13338 2.85346 3.35422 2.85763 3.61255C2.8618 3.87088 2.95138 4.08755 3.12638 4.26255Z"
fill="#26201E"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.95054 4.46519C5.52518 4.49131 5.15729 4.65958 4.84687 4.97C4.50793 5.30894 4.33846 5.71639 4.33846 6.19236V19.2308H3.35769C3.11923 19.2308 2.91539 19.3154 2.74616 19.4847C2.57692 19.6539 2.49231 19.8577 2.49231 20.0962C2.49231 20.3347 2.57692 20.5385 2.74616 20.7077C2.91539 20.877 3.11923 20.9616 3.35769 20.9616H19.9731C20.2115 20.9616 20.4154 20.877 20.5846 20.7077C20.7538 20.5385 20.8385 20.3347 20.8385 20.0962C20.8385 19.8577 20.7538 19.6539 20.5846 19.4847C20.4154 19.3154 20.2115 19.2308 19.9731 19.2308H18.9923V17.5049L18.4182 16.9328L17.2615 15.8083V19.2308H13.5577C13.6808 18.1385 14.0423 17.0366 14.6423 15.9251C14.9739 15.3107 15.389 14.8168 15.8874 14.4433L15.4619 14.0178L14.6843 13.2301C13.938 13.84 13.3356 14.6095 12.8769 15.5385C12.2769 16.7539 11.9231 17.9847 11.8154 19.2308H11.5154C11.4077 17.9847 11.0538 16.7539 10.4538 15.5385C9.85385 14.3231 9.00769 13.3808 7.91539 12.7116C9.00769 12.0424 9.85385 11.1001 10.4538 9.88467C10.557 9.67565 10.6529 9.46617 10.7416 9.25623L9.3933 7.90796C9.21395 8.4372 8.98001 8.96913 8.69149 9.50374C8.08947 10.6192 7.21539 11.3385 6.06923 11.6616V6.19236H7.6777L5.95054 4.46519ZM9.78462 19.2308H6.06923V13.7731C7.21539 14.0962 8.09039 14.8135 8.69423 15.9251C9.29808 17.0366 9.66154 18.1385 9.78462 19.2308Z"
fill="#26201E"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.4051 7.03227L8.83444 4.46159H17.2615C17.7375 4.46159 18.145 4.63106 18.4839 4.97C18.8228 5.30894 18.9923 5.71639 18.9923 6.19236V14.6195L12.0659 7.69308C11.9423 7.19534 11.8588 6.6951 11.8154 6.19236H11.5154C11.4911 6.47311 11.4544 6.75308 11.4051 7.03227ZM13.5462 6.19236H17.2615V11.6616C16.1154 11.3385 15.2404 10.6193 14.6365 9.5039C14.0327 8.38852 13.6692 7.28467 13.5462 6.19236Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function WineBarIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_69_3753"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3753)">
<path
d="M11.0625 19.0625V14.85C9.62917 14.6083 8.4375 13.9437 7.4875 12.8562C6.5375 11.7688 6.0625 10.4833 6.0625 9V4C6.0625 3.74167 6.15417 3.52083 6.3375 3.3375C6.52083 3.15417 6.74167 3.0625 7 3.0625H17C17.2583 3.0625 17.4792 3.15417 17.6625 3.3375C17.8458 3.52083 17.9375 3.74167 17.9375 4V9C17.9375 10.4833 17.4625 11.7688 16.5125 12.8562C15.5625 13.9437 14.3708 14.6083 12.9375 14.85V19.0625H15C15.2583 19.0625 15.4792 19.1542 15.6625 19.3375C15.8458 19.5208 15.9375 19.7417 15.9375 20C15.9375 20.2583 15.8458 20.4792 15.6625 20.6625C15.4792 20.8458 15.2583 20.9375 15 20.9375H9C8.74167 20.9375 8.52083 20.8458 8.3375 20.6625C8.15417 20.4792 8.0625 20.2583 8.0625 20C8.0625 19.7417 8.15417 19.5208 8.3375 19.3375C8.52083 19.1542 8.74167 19.0625 9 19.0625H11.0625ZM12 13.0625C12.9417 13.0625 13.7729 12.7688 14.4938 12.1813C15.2146 11.5938 15.6917 10.8458 15.925 9.9375H8.075C8.31667 10.8458 8.79583 11.5938 9.5125 12.1813C10.2292 12.7688 11.0583 13.0625 12 13.0625ZM7.9375 8.0625H16.0625V4.9375H7.9375V8.0625Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,84 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function WoodFloorIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_81_934"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_81_934)">
<path
d="M3.93297 21C3.67588 21 3.45612 20.9083 3.27367 20.725C3.09122 20.5417 3 20.3208 3 20.0625C3 19.8042 3.09122 19.5833 3.27367 19.4C3.45612 19.2167 3.67588 19.125 3.93297 19.125H10.0283C10.2854 19.125 10.5052 19.2167 10.6876 19.4C10.8701 19.5833 10.9613 19.8042 10.9613 20.0625C10.9613 20.3208 10.8701 20.5417 10.6876 20.725C10.5052 20.9083 10.2854 21 10.0283 21H3.93297Z"
fill="#26201E"
/>
<path
d="M3.93296 17.5C3.67588 17.5 3.45612 17.4083 3.27367 17.225C3.09122 17.0417 3 16.8208 3 16.5625C3 16.3042 3.09122 16.0833 3.27367 15.9C3.45612 15.7167 3.67588 15.625 3.93296 15.625H10.0283C10.2854 15.625 10.5052 15.7167 10.6876 15.9C10.8701 16.0833 10.9613 16.3042 10.9613 16.5625C10.9613 16.8208 10.8701 17.0417 10.6876 17.225C10.5052 17.4083 10.2854 17.5 10.0283 17.5H3.93296Z"
fill="#26201E"
/>
<path
d="M3.93297 13.9688C3.67588 13.9688 3.45612 13.8771 3.27367 13.6938C3.09122 13.5104 3 13.2896 3 13.0312C3 12.7729 3.09122 12.5521 3.27367 12.3688C3.45612 12.1854 3.67588 12.0938 3.93297 12.0938H10.0283C10.2854 12.0938 10.5052 12.1854 10.6876 12.3688C10.8701 12.5521 10.9613 12.7729 10.9613 13.0312C10.9613 13.2896 10.8701 13.5104 10.6876 13.6938C10.5052 13.8771 10.2854 13.9688 10.0283 13.9688H3.93297Z"
fill="#26201E"
/>
<path
d="M11.8321 10.0625C11.8321 10.3208 11.7408 10.5417 11.5584 10.725C11.376 10.9083 11.1562 11 10.8991 11C10.642 11 10.4223 10.9083 10.2398 10.725C10.0574 10.5417 9.96614 10.3208 9.96614 10.0625V3.9375C9.96614 3.67917 10.0574 3.45833 10.2398 3.275C10.4223 3.09167 10.642 3 10.8991 3C11.1562 3 11.376 3.09167 11.5584 3.275C11.7408 3.45833 11.8321 3.67917 11.8321 3.9375V10.0625Z"
fill="#26201E"
/>
<path
d="M8.36593 10.0625C8.36593 10.3208 8.27471 10.5417 8.09226 10.725C7.90981 10.9083 7.69005 11 7.43297 11C7.17588 11 6.95612 10.9083 6.77367 10.725C6.59122 10.5417 6.5 10.3208 6.5 10.0625V3.9375C6.5 3.67917 6.59122 3.45833 6.77367 3.275C6.95612 3.09167 7.17588 3 7.43297 3C7.69005 3 7.90981 3.09167 8.09226 3.275C8.27471 3.45833 8.36593 3.67917 8.36593 3.9375V10.0625Z"
fill="#26201E"
/>
<path
d="M4.86593 10.0625C4.86593 10.3208 4.77471 10.5417 4.59226 10.725C4.40981 10.9083 4.19005 11 3.93297 11C3.67588 11 3.45612 10.9083 3.27367 10.725C3.09122 10.5417 3 10.3208 3 10.0625V3.9375C3 3.67917 3.09122 3.45833 3.27367 3.275C3.45612 3.09167 3.67588 3 3.93297 3C4.19005 3 4.40981 3.09167 4.59226 3.275C4.77471 3.45833 4.86593 3.67917 4.86593 3.9375V10.0625Z"
fill="#26201E"
/>
<path
d="M20.067 3C20.3241 3 20.5439 3.09167 20.7263 3.275C20.9088 3.45833 21 3.67917 21 3.9375C21 4.19583 20.9088 4.41667 20.7263 4.6C20.5439 4.78333 20.3241 4.875 20.067 4.875H13.933C13.6759 4.875 13.4561 4.78333 13.2737 4.6C13.0912 4.41667 13 4.19583 13 3.9375C13 3.67917 13.0912 3.45833 13.2737 3.275C13.4561 3.09167 13.6759 3 13.933 3H20.067Z"
fill="#26201E"
/>
<path
d="M20.067 6.5C20.3241 6.5 20.5439 6.59167 20.7263 6.775C20.9088 6.95833 21 7.17917 21 7.4375C21 7.69583 20.9088 7.91667 20.7263 8.1C20.5439 8.28333 20.3241 8.375 20.067 8.375H13.933C13.6759 8.375 13.4561 8.28333 13.2737 8.1C13.0912 7.91667 13 7.69583 13 7.4375C13 7.17917 13.0912 6.95833 13.2737 6.775C13.4561 6.59167 13.6759 6.5 13.933 6.5H20.067Z"
fill="#26201E"
/>
<path
d="M12.1679 13.9375C12.1679 13.6792 12.2592 13.4583 12.4416 13.275C12.624 13.0917 12.8438 13 13.1009 13C13.358 13 13.5777 13.0917 13.7602 13.275C13.9426 13.4583 14.0339 13.6792 14.0339 13.9375V20.0312C14.0339 20.2896 13.9426 20.5104 13.7602 20.6938C13.5777 20.8771 13.358 20.9688 13.1009 20.9688C12.8438 20.9688 12.624 20.8771 12.4416 20.6938C12.2592 20.5104 12.1679 20.2896 12.1679 20.0312V13.9375Z"
fill="#26201E"
/>
<path
d="M15.6341 13.9688C15.6341 13.7104 15.7253 13.4896 15.9077 13.3062C16.0902 13.1229 16.31 13.0312 16.567 13.0312C16.8241 13.0312 17.0439 13.1229 17.2263 13.3062C17.4088 13.4896 17.5 13.7104 17.5 13.9688V20.0625C17.5 20.3208 17.4088 20.5417 17.2263 20.725C17.0439 20.9083 16.8241 21 16.567 21C16.31 21 16.0902 20.9083 15.9077 20.725C15.7253 20.5417 15.6341 20.3208 15.6341 20.0625V13.9688Z"
fill="#26201E"
/>
<path
d="M19.1341 13.9375C19.1341 13.6792 19.2253 13.4583 19.4077 13.275C19.5902 13.0917 19.81 13 20.067 13C20.3241 13 20.5439 13.0917 20.7263 13.275C20.9088 13.4583 21 13.6792 21 13.9375V20.0312C21 20.2896 20.9088 20.5104 20.7263 20.6938C20.5439 20.8771 20.3241 20.9688 20.067 20.9688C19.81 20.9688 19.5902 20.8771 19.4077 20.6938C19.2253 20.5104 19.1341 20.2896 19.1341 20.0312V13.9375Z"
fill="#26201E"
/>
<path
d="M20.067 10C20.3241 10 20.5439 10.0917 20.7263 10.275C20.9088 10.4583 21 10.6792 21 10.9375C21 11.1958 20.9088 11.4167 20.7263 11.6C20.5439 11.7833 20.3241 11.875 20.067 11.875H13.933C13.6759 11.875 13.4561 11.7833 13.2737 11.6C13.0912 11.4167 13 11.1958 13 10.9375C13 10.6792 13.0912 10.4583 13.2737 10.275C13.4561 10.0917 13.6759 10 13.933 10H20.067Z"
fill="#26201E"
/>
</g>
</svg>
)
}

36
components/Icons/Yard.tsx Normal file
View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function YardIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classNames}
{...props}
>
<mask
style={{ maskType: "alpha" }}
id="mask0_4039_3234"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_4039_3234)">
<path
d="M12 19C12 17.3333 11.4167 15.9167 10.25 14.75C9.08333 13.5833 7.66667 13 6 13C6 14.6667 6.58333 16.0833 7.75 17.25C8.91667 18.4167 10.3333 19 12 19ZM12 13.95C12.4333 13.95 12.8 13.8 13.1 13.5C13.4 13.2 13.55 12.8333 13.55 12.4V12.25C13.6833 12.35 13.8208 12.425 13.9625 12.475C14.1042 12.525 14.2667 12.55 14.45 12.55C14.8833 12.55 15.25 12.4 15.55 12.1C15.85 11.8 16 11.4333 16 11C16 10.6667 15.9208 10.375 15.7625 10.125C15.6042 9.875 15.3833 9.7 15.1 9.6C15.3833 9.5 15.6042 9.325 15.7625 9.075C15.9208 8.825 16 8.53333 16 8.2C16 7.76667 15.85 7.4 15.55 7.1C15.25 6.8 14.8833 6.65 14.45 6.65C14.2667 6.65 14.1042 6.675 13.9625 6.725C13.8208 6.775 13.6833 6.85 13.55 6.95V6.8C13.55 6.36667 13.4 6 13.1 5.7C12.8 5.4 12.4333 5.25 12 5.25C11.5667 5.25 11.2 5.4 10.9 5.7C10.6 6 10.45 6.36667 10.45 6.8V6.95C10.3167 6.85 10.1792 6.775 10.0375 6.725C9.89583 6.675 9.73333 6.65 9.55 6.65C9.11667 6.65 8.75 6.8 8.45 7.1C8.15 7.4 8 7.76667 8 8.2C8 8.53333 8.07917 8.825 8.2375 9.075C8.39583 9.325 8.61667 9.5 8.9 9.6C8.61667 9.7 8.39583 9.875 8.2375 10.125C8.07917 10.375 8 10.6667 8 11C8 11.4333 8.15 11.8 8.45 12.1C8.75 12.4 9.11667 12.55 9.55 12.55C9.73333 12.55 9.89583 12.525 10.0375 12.475C10.1792 12.425 10.3167 12.35 10.45 12.25V12.4C10.45 12.8333 10.6 13.2 10.9 13.5C11.2 13.8 11.5667 13.95 12 13.95ZM12 11.15C11.5667 11.15 11.2 11.0042 10.9 10.7125C10.6 10.4208 10.45 10.05 10.45 9.6C10.45 9.16667 10.6 8.8 10.9 8.5C11.2 8.2 11.5667 8.05 12 8.05C12.4333 8.05 12.8 8.2 13.1 8.5C13.4 8.8 13.55 9.16667 13.55 9.6C13.55 10.05 13.4 10.4208 13.1 10.7125C12.8 11.0042 12.4333 11.15 12 11.15ZM12 19C13.6667 19 15.0833 18.4167 16.25 17.25C17.4167 16.0833 18 14.6667 18 13C16.3333 13 14.9167 13.5833 13.75 14.75C12.5833 15.9167 12 17.3333 12 19ZM4 22C3.45 22 2.97917 21.8042 2.5875 21.4125C2.19583 21.0208 2 20.55 2 20V4C2 3.45 2.19583 2.97917 2.5875 2.5875C2.97917 2.19583 3.45 2 4 2H20C20.55 2 21.0208 2.19583 21.4125 2.5875C21.8042 2.97917 22 3.45 22 4V20C22 20.55 21.8042 21.0208 21.4125 21.4125C21.0208 21.8042 20.55 22 20 22H4ZM4 20H20V4H4V20Z"
fill="#1C1B1F"
/>
</g>
</svg>
)
}

View File

@@ -1,3 +1,4 @@
export { default as AcIcon } from "./Ac"
export { default as AccesoriesIcon } from "./Accesories" export { default as AccesoriesIcon } from "./Accesories"
export { default as AccessibilityIcon } from "./Accessibility" export { default as AccessibilityIcon } from "./Accessibility"
export { default as AccountCircleIcon } from "./AccountCircle" export { default as AccountCircleIcon } from "./AccountCircle"
@@ -5,18 +6,22 @@ export { default as AirIcon } from "./Air"
export { default as AirplaneIcon } from "./Airplane" export { default as AirplaneIcon } from "./Airplane"
export { default as ArrowRightIcon } from "./ArrowRight" export { default as ArrowRightIcon } from "./ArrowRight"
export { default as BarIcon } from "./Bar" export { default as BarIcon } from "./Bar"
export { default as BathtubIcon } from "./Bathtub"
export { default as BedDoubleIcon } from "./BedDouble"
export { default as BikingIcon } from "./Biking" export { default as BikingIcon } from "./Biking"
export { default as BreakfastIcon } from "./Breakfast" export { default as BreakfastIcon } from "./Breakfast"
export { default as BusinessIcon } from "./Business" export { default as BusinessIcon } from "./Business"
export { default as CalendarIcon } from "./Calendar" export { default as CalendarIcon } from "./Calendar"
export { default as CameraIcon } from "./Camera" export { default as CameraIcon } from "./Camera"
export { default as CellphoneIcon } from "./Cellphone" export { default as CellphoneIcon } from "./Cellphone"
export { default as ChairIcon } from "./Chair"
export { default as CheckIcon } from "./Check" export { default as CheckIcon } from "./Check"
export { default as CheckCircleIcon } from "./CheckCircle" export { default as CheckCircleIcon } from "./CheckCircle"
export { default as ChevronDownIcon } from "./ChevronDown" export { default as ChevronDownIcon } from "./ChevronDown"
export { default as ChevronLeftIcon } from "./ChevronLeft" export { default as ChevronLeftIcon } from "./ChevronLeft"
export { default as ChevronRightIcon } from "./ChevronRight" export { default as ChevronRightIcon } from "./ChevronRight"
export { default as ChevronRightSmallIcon } from "./ChevronRightSmall" export { default as ChevronRightSmallIcon } from "./ChevronRightSmall"
export { default as CityIcon } from "./City"
export { default as CloseIcon } from "./Close" export { default as CloseIcon } from "./Close"
export { default as CloseLargeIcon } from "./CloseLarge" export { default as CloseLargeIcon } from "./CloseLarge"
export { default as CoffeeAltIcon } from "./CoffeeAlt" export { default as CoffeeAltIcon } from "./CoffeeAlt"
@@ -27,6 +32,7 @@ export { default as CreditCard } from "./CreditCard"
export { default as CrossCircle } from "./CrossCircle" export { default as CrossCircle } from "./CrossCircle"
export { default as CulturalIcon } from "./Cultural" export { default as CulturalIcon } from "./Cultural"
export { default as DeleteIcon } from "./Delete" export { default as DeleteIcon } from "./Delete"
export { default as DeskIcon } from "./Desk"
export { default as DoorOpenIcon } from "./DoorOpen" export { default as DoorOpenIcon } from "./DoorOpen"
export { default as DownloadIcon } from "./Download" export { default as DownloadIcon } from "./Download"
export { default as DresserIcon } from "./Dresser" export { default as DresserIcon } from "./Dresser"
@@ -46,6 +52,8 @@ export { default as GiftIcon } from "./Gift"
export { default as GlobeIcon } from "./Globe" export { default as GlobeIcon } from "./Globe"
export { default as GolfIcon } from "./Golf" export { default as GolfIcon } from "./Golf"
export { default as GroceriesIcon } from "./Groceries" export { default as GroceriesIcon } from "./Groceries"
export { default as HairdryerIcon } from "./Hairdryer"
export { default as HandSoapIcon } from "./HandSoap"
export { default as HangerIcon } from "./Hanger" export { default as HangerIcon } from "./Hanger"
export { default as HangerAltIcon } from "./HangerAlt" export { default as HangerAltIcon } from "./HangerAlt"
export { default as HeartIcon } from "./Heart" export { default as HeartIcon } from "./Heart"
@@ -53,6 +61,8 @@ export { default as HeatIcon } from "./Heat"
export { default as HouseIcon } from "./House" export { default as HouseIcon } from "./House"
export { default as ImageIcon } from "./Image" export { default as ImageIcon } from "./Image"
export { default as InfoCircleIcon } from "./InfoCircle" export { default as InfoCircleIcon } from "./InfoCircle"
export { default as InstagramIcon } from "./Instagram"
export { default as IronIcon } from "./Iron"
export { default as KayakingIcon } from "./Kayaking" export { default as KayakingIcon } from "./Kayaking"
export { default as KettleIcon } from "./Kettle" export { default as KettleIcon } from "./Kettle"
export { default as KingBedIcon } from "./KingBed" export { default as KingBedIcon } from "./KingBed"
@@ -63,6 +73,7 @@ export { default as LocationIcon } from "./Location"
export { default as LockIcon } from "./Lock" export { default as LockIcon } from "./Lock"
export { default as MapIcon } from "./Map" export { default as MapIcon } from "./Map"
export { default as MinusIcon } from "./Minus" export { default as MinusIcon } from "./Minus"
export { default as MirrorIcon } from "./Mirror"
export { default as MuseumIcon } from "./Museum" export { default as MuseumIcon } from "./Museum"
export { default as NatureIcon } from "./Nature" export { default as NatureIcon } from "./Nature"
export { default as NightlifeIcon } from "./Nightlife" export { default as NightlifeIcon } from "./Nightlife"
@@ -80,11 +91,13 @@ export { default as PriceTagIcon } from "./PriceTag"
export { default as PrinterIcon } from "./Printer" export { default as PrinterIcon } from "./Printer"
export { default as RestaurantIcon } from "./Restaurant" export { default as RestaurantIcon } from "./Restaurant"
export { default as RoomServiceIcon } from "./RoomService" export { default as RoomServiceIcon } from "./RoomService"
export { default as SafetyBoxIcon } from "./SafetyBox"
export { default as SaunaIcon } from "./Sauna" export { default as SaunaIcon } from "./Sauna"
export { default as ScandicLogoIcon } from "./ScandicLogo" export { default as ScandicLogoIcon } from "./ScandicLogo"
export { default as SearchIcon } from "./Search" export { default as SearchIcon } from "./Search"
export { default as ServiceIcon } from "./Service" export { default as ServiceIcon } from "./Service"
export { default as ShoppingIcon } from "./Shopping" export { default as ShoppingIcon } from "./Shopping"
export { default as ShowerIcon } from "./Shower"
export { default as SkateboardingIcon } from "./Skateboarding" export { default as SkateboardingIcon } from "./Skateboarding"
export { default as SmokingIcon } from "./Smoking" export { default as SmokingIcon } from "./Smoking"
export { default as SnowflakeIcon } from "./Snowflake" export { default as SnowflakeIcon } from "./Snowflake"
@@ -99,3 +112,8 @@ export { default as TshirtWashIcon } from "./TshirtWash"
export { default as TvCastingIcon } from "./TvCasting" export { default as TvCastingIcon } from "./TvCasting"
export { default as WarningTriangle } from "./WarningTriangle" export { default as WarningTriangle } from "./WarningTriangle"
export { default as WifiIcon } from "./Wifi" export { default as WifiIcon } from "./Wifi"
export { default as WindowCurtainsAltIcon } from "./WindowCurtainsAlt"
export { default as WindowNotAvailableIcon } from "./WindowNotAvailable"
export { default as WineBarIcon } from "./WineBar"
export { default as WoodFloorIcon } from "./WoodFloor"
export { default as YardIcon } from "./Yard"

View File

@@ -1,6 +1,4 @@
"use client" "use client"
import { DialogTitle } from "@radix-ui/react-dialog"
import { VisuallyHidden } from "@radix-ui/react-visually-hidden"
import { AnimatePresence, motion } from "framer-motion" import { AnimatePresence, motion } from "framer-motion"
import { ChevronRightIcon } from "@/components/Icons" import { ChevronRightIcon } from "@/components/Icons"
@@ -16,7 +14,6 @@ import type { GalleryProps } from "@/types/components/lightbox/lightbox"
export default function Gallery({ export default function Gallery({
images, images,
dialogTitle,
onClose, onClose,
onSelectImage, onSelectImage,
onImageClick, onImageClick,
@@ -57,11 +54,6 @@ export default function Gallery({
</Button> </Button>
{/* Desktop Gallery */} {/* Desktop Gallery */}
<div className={styles.desktopGallery}> <div className={styles.desktopGallery}>
<VisuallyHidden asChild>
<DialogTitle asChild>
<VisuallyHidden>{dialogTitle}</VisuallyHidden>
</DialogTitle>
</VisuallyHidden>
<div className={styles.galleryHeader}> <div className={styles.galleryHeader}>
{mainImage.title && ( {mainImage.title && (
<div className={styles.imageCaption}> <div className={styles.imageCaption}>

View File

@@ -1,3 +1,13 @@
@keyframes darken-background {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}
.mobileGallery { .mobileGallery {
height: 100%; height: 100%;
position: relative; position: relative;
@@ -33,14 +43,22 @@
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
z-index: 10; z-index: var(--lightbox-z-index);
} }
.overlay { .overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
z-index: 10; z-index: var(--lightbox-z-index);
}
.overlay[data-entering] {
animation: darken-background 0.2s;
}
.overlay[data-exiting] {
animation: darken-background 0.2s reverse;
} }
.galleryContainer { .galleryContainer {

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import * as Dialog from "@radix-ui/react-dialog"
import { AnimatePresence, motion } from "framer-motion" import { AnimatePresence, motion } from "framer-motion"
import React, { useState } from "react" import React, { useState } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import FullView from "./FullView" import FullView from "./FullView"
import Gallery from "./Gallery" import Gallery from "./Gallery"
@@ -62,20 +62,16 @@ export default function Lightbox({
return ( return (
<> <>
{triggerElement} {triggerElement}
<Dialog.Root open={isOpen} onOpenChange={handleOpenChange}> <ModalOverlay
<AnimatePresence> isOpen={isOpen}
{isOpen && ( onOpenChange={handleOpenChange}
<Dialog.Portal forceMount> className={styles.overlay}
<Dialog.Overlay asChild> isDismissable
<motion.div >
initial={{ opacity: 0 }} <Modal>
animate={{ opacity: 1 }} <AnimatePresence>
exit={{ opacity: 0 }} {isOpen && (
transition={{ duration: 0.2 }} <Dialog>
className={styles.overlay}
/>
</Dialog.Overlay>
<Dialog.Content asChild>
<motion.div <motion.div
className={`${styles.content} ${ className={`${styles.content} ${
isFullView ? styles.fullViewContent : styles.galleryContent isFullView ? styles.fullViewContent : styles.galleryContent
@@ -109,11 +105,11 @@ export default function Lightbox({
/> />
)} )}
</motion.div> </motion.div>
</Dialog.Content> </Dialog>
</Dialog.Portal> )}
)} </AnimatePresence>
</AnimatePresence> </Modal>
</Dialog.Root> </ModalOverlay>
</> </>
) )
} }

View File

@@ -79,7 +79,6 @@ export default function InteractiveMap({
<PoiMarker <PoiMarker
group={poi.group} group={poi.group}
categoryName={poi.categoryName} categoryName={poi.categoryName}
className={styles.poiMarker}
size={activePoi === poi.name ? 20 : 16} size={activePoi === poi.name ? 20 : 16}
/> />
<Body className={styles.poiLabel} asChild> <Body className={styles.poiLabel} asChild>

View File

@@ -1,7 +1,7 @@
import { Fragment } from "react" import { Fragment } from "react"
import { logout } from "@/constants/routes/handleAuth" import { logout } from "@/constants/routes/handleAuth"
import { serverClient } from "@/lib/trpc/server" import { getMyPagesNavigation } from "@/lib/trpc/memoizedRequests"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
@@ -12,7 +12,7 @@ import { getLang } from "@/i18n/serverContext"
import styles from "./sidebar.module.css" import styles from "./sidebar.module.css"
export default async function SidebarMyPages() { export default async function SidebarMyPages() {
const navigation = await serverClient().contentstack.myPages.navigation.get() const navigation = await getMyPagesNavigation()
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
return ( return (

View File

@@ -0,0 +1,247 @@
"use client"
import { usePathname } from "next/navigation"
import React, { useState } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import { useIntl } from "react-intl"
import { benefits } from "@/constants/routes/myPages"
import { dt } from "@/lib/dt"
import { trpc } from "@/lib/trpc/client"
import { ChevronRightSmallIcon, CloseLargeIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"
import styles from "./surprises.module.css"
import type { SurprisesProps } from "@/types/components/blocks/surprises"
export default function SurprisesNotification({
surprises,
membershipNumber,
}: SurprisesProps) {
const lang = useLang()
const pathname = usePathname()
const [open, setOpen] = useState(true)
const [selectedSurprise, setSelectedSurprise] = useState(0)
const [showSurprises, setShowSurprises] = useState(false)
const update = trpc.contentstack.rewards.update.useMutation()
const intl = useIntl()
if (!surprises.length) {
return null
}
const surprise = surprises[selectedSurprise]
function showSurprise(n: number) {
setSelectedSurprise((surprise) => surprise + n)
}
function viewRewards() {
if (surprise.reward_id) {
update.mutate({ id: surprise.reward_id })
}
}
function closeModal(close: VoidFunction) {
viewRewards()
close()
if (pathname.indexOf(benefits[lang]) !== 0) {
toast.success(
<>
{intl.formatMessage(
{ id: "Gift(s) added to your benefits" },
{ amount: surprises.length }
)}
<br />
<Link href={benefits[lang]} variant="underscored" color="burgundy">
{intl.formatMessage({ id: "Go to My Benefits" })}
</Link>
</>
)
}
}
return (
<ModalOverlay
className={styles.overlay}
isOpen={open}
onOpenChange={setOpen}
isKeyboardDismissDisabled
>
<Modal className={styles.modal}>
<Dialog aria-label="Surprises" className={styles.dialog}>
{({ close }) => {
return (
<>
<div className={styles.top}>
{surprises.length > 1 && showSurprises && (
<Caption type="label" uppercase>
{intl.formatMessage(
{ id: "{amount} out of {total}" },
{
amount: selectedSurprise + 1,
total: surprises.length,
}
)}
</Caption>
)}
<button
onClick={() => closeModal(close)}
type="button"
className={styles.close}
>
<CloseLargeIcon />
</button>
</div>
{showSurprises ? (
<>
<div className={styles.content}>
<Surprise title={surprise.label}>
<Body textAlign="center">{surprise.description}</Body>
<div className={styles.badge}>
<Caption>
{intl.formatMessage({ id: "Valid through" })}{" "}
{dt(surprise.endsAt)
.locale(lang)
.format("DD MMM YYYY")}
</Caption>
<Caption>
{intl.formatMessage({ id: "Membership ID" })}{" "}
{membershipNumber}
</Caption>
</div>
</Surprise>
</div>
{surprises.length > 1 && (
<>
<nav className={styles.nav}>
<Button
variant="icon"
intent="tertiary"
disabled={selectedSurprise === 0}
onPress={() => showSurprise(-1)}
size="small"
>
<ChevronRightSmallIcon
className={styles.chevron}
width={20}
height={20}
/>
{intl.formatMessage({ id: "Previous" })}
</Button>
<Button
variant="icon"
intent="tertiary"
disabled={selectedSurprise === surprises.length - 1}
onPress={() => showSurprise(1)}
size="small"
>
{intl.formatMessage({ id: "Next" })}
<ChevronRightSmallIcon width={20} height={20} />
</Button>
</nav>
</>
)}
</>
) : (
<div className={styles.content}>
{surprises.length > 1 ? (
<Surprise title={intl.formatMessage({ id: "Surprise!" })}>
<Body textAlign="center">
{intl.formatMessage<React.ReactNode>(
{
id: "You have <b>#</b> gifts waiting for you!",
},
{
amount: surprises.length,
b: (str) => <b>{str}</b>,
}
)}
<br />
{intl.formatMessage({
id: "Hurry up and use them before they expire!",
})}
</Body>
<Caption>
{intl.formatMessage({
id: "You'll find all your gifts in 'My benefits'",
})}
</Caption>
</Surprise>
) : (
<Surprise title={intl.formatMessage({ id: "Surprise!" })}>
<Body textAlign="center">
{intl.formatMessage({
id: "We have a special gift waiting for you!",
})}
</Body>
<Caption>
{intl.formatMessage({
id: "You'll find all your gifts in 'My benefits'",
})}
</Caption>
</Surprise>
)}
<Button
intent="primary"
onPress={() => {
viewRewards()
setShowSurprises(true)
}}
size="medium"
theme="base"
fullWidth
autoFocus
>
{intl.formatMessage(
{
id: "Open gift(s)",
},
{ amount: surprises.length }
)}
</Button>
</div>
)}
</>
)
}}
</Dialog>
</Modal>
</ModalOverlay>
)
}
function Surprise({
title,
children,
}: {
title?: string
children?: React.ReactNode
}) {
return (
<>
<Image
src="/_static/img/loyalty-award.png"
width={113}
height={125}
alt="Gift"
/>
<Title textAlign="center" level="h4">
{title}
</Title>
{children}
</>
)
}

View File

@@ -0,0 +1,25 @@
import { getProfile } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server"
import SurprisesNotification from "./SurprisesNotification"
export default async function Surprises() {
const user = await getProfile()
if (!user || "error" in user) {
return null
}
const surprises = await serverClient().contentstack.rewards.surprises()
if (!surprises) {
return null
}
return (
<SurprisesNotification
surprises={surprises}
membershipNumber={user.membership?.membershipNumber}
/>
)
}

View File

@@ -0,0 +1,143 @@
@keyframes modal-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.overlay {
background: rgba(0, 0, 0, 0.5);
height: var(--visual-viewport-height);
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: 100;
&[data-entering] {
animation: modal-fade 200ms;
}
&[data-exiting] {
animation: modal-fade 200ms reverse ease-in;
}
}
@media screen and (min-width: 768px) {
.overlay {
display: flex;
justify-content: center;
align-items: center;
}
}
.modal {
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-Medium);
box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
width: 100%;
position: absolute;
left: 0;
bottom: 0;
&[data-entering] {
animation: slide-up 200ms;
}
&[data-exiting] {
animation: slide-up 200ms reverse ease-in-out;
}
}
.dialog {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
padding-bottom: var(--Spacing-x2);
}
@media screen and (min-width: 768px) {
.modal {
left: auto;
bottom: auto;
width: 400px;
}
}
.top {
--button-height: 32px;
box-sizing: content-box;
display: flex;
align-items: center;
height: var(--button-height);
position: relative;
justify-content: center;
padding: var(--Spacing-x2) var(--Spacing-x2) 0;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 var(--Spacing-x3);
gap: var(--Spacing-x2);
}
.nav {
border-top: 1px solid var(--Base-Border-Subtle);
display: flex;
justify-content: space-between;
padding: 0 var(--Spacing-x2);
width: 100%;
}
.nav button {
&:nth-child(1) {
padding-left: 0;
}
&:nth-child(2) {
padding-right: 0;
}
&[disabled] {
visibility: hidden;
}
}
.chevron {
transform: rotate(180deg);
}
.badge {
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--Spacing-x-half);
background-color: var(--Base-Surface-Secondary-light-Normal);
border-radius: var(--Corner-radius-Small);
}
.close {
background: none;
border: none;
cursor: pointer;
position: absolute;
right: var(--Spacing-x2);
width: 32px;
height: var(--button-height);
display: flex;
align-items: center;
}

View File

@@ -1,19 +1,29 @@
.contactContainer { .contactContainer {
display: none; border-top: 1px solid var(--UI-Grey-30);
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
justify-content: center;
padding-top: var(--Spacing-x2);
align-items: center;
}
.contact {
display: grid;
gap: var(--Spacing-x-one-and-half);
}
.contact > div {
display: flex;
justify-content: center;
} }
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.contactContainer { .contactContainer {
border-top: 1px solid var(--UI-Grey-30); align-items: start;
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
justify-content: center;
padding-top: var(--Spacing-x2);
} }
.contact { .contact > div {
display: grid; justify-content: start;
gap: var(--Spacing-x-one-and-half);
} }
} }

View File

@@ -1,4 +1,4 @@
import { serverClient } from "@/lib/trpc/server" import { getName } from "@/lib/trpc/memoizedRequests"
import LoginButton from "@/components/Current/Header/LoginButton" import LoginButton from "@/components/Current/Header/LoginButton"
import ArrowRight from "@/components/Icons/ArrowRight" import ArrowRight from "@/components/Icons/ArrowRight"
@@ -19,7 +19,7 @@ export default async function JoinLoyaltyContact({
block, block,
}: JoinLoyaltyContactProps) { }: JoinLoyaltyContactProps) {
const intl = await getIntl() const intl = await getIntl()
const user = await serverClient().user.name() const user = await getName()
// Check if we have user, that means we are logged in. // Check if we have user, that means we are logged in.
if (user) { if (user) {
@@ -32,7 +32,9 @@ export default async function JoinLoyaltyContact({
{block.title} {block.title}
</Title> </Title>
<ScandicFriends color="red" /> <ScandicFriends color="red" />
{block.preamble ? <Body>{block.preamble}</Body> : null} {block.preamble ? (
<Body className={styles.preamble}>{block.preamble}</Body>
) : null}
{block.button ? ( {block.button ? (
<Button <Button
asChild asChild

View File

@@ -3,6 +3,11 @@
gap: var(--Spacing-x3); gap: var(--Spacing-x3);
padding-bottom: var(--Spacing-x5); padding-bottom: var(--Spacing-x5);
padding-top: var(--Spacing-x4); padding-top: var(--Spacing-x4);
justify-items: center;
}
article.wrapper .preamble {
text-align: center;
} }
.loginContainer { .loginContainer {
@@ -22,3 +27,13 @@
.icon { .icon {
align-self: center; align-self: center;
} }
@media screen and (min-width: 1367px) {
.wrapper {
justify-items: start;
}
article.wrapper .preamble {
text-align: left;
}
}

View File

@@ -1,9 +1,9 @@
import { serverClient } from "@/lib/trpc/server" import { getName } from "@/lib/trpc/memoizedRequests"
import MyPagesSidebar from "@/components/MyPages/Sidebar" import MyPagesSidebar from "@/components/MyPages/Sidebar"
export default async function MyPagesNavigation() { export default async function MyPagesNavigation() {
const user = await serverClient().user.name() const user = await getName()
// Check if we have user, that means we are logged in andt the My Pages menu can show. // Check if we have user, that means we are logged in andt the My Pages menu can show.
if (!user) { if (!user) {

View File

@@ -1,5 +1,7 @@
.aside { .aside {
grid-area: sidebar;
display: grid; display: grid;
gap: var(--Spacing-x4);
container-name: sidebar; container-name: sidebar;
container-type: inline-size; container-type: inline-size;
gap: var(--Spacing-x3); gap: var(--Spacing-x3);
@@ -16,14 +18,7 @@
.aside { .aside {
align-content: flex-start; align-content: flex-start;
gap: var(--Spacing-x4); gap: var(--Spacing-x4);
border-top: 0; border-top: 0;
padding-top: 0; padding-top: 0;
} }
} }
@container loyalty-page (max-width: 1366px) {
.aside {
display: none;
}
}

View File

@@ -51,7 +51,7 @@ export default function Alert({
<span> {phoneContact.displayText} </span> <span> {phoneContact.displayText} </span>
<Link <Link
color="burgundy" color="burgundy"
href={`tel:${phoneContact.phoneNumber}`} href={`tel:${phoneContact.phoneNumber.replace(/ /g, "")}`}
> >
{phoneContact.phoneNumber} {phoneContact.phoneNumber}
</Link> </Link>

View File

@@ -37,7 +37,7 @@ export default function Phone({
}, },
}: PhoneProps) { }: PhoneProps) {
const { formatMessage } = useIntl() const { formatMessage } = useIntl()
const { control, setValue } = useFormContext() const { control, setValue, trigger } = useFormContext()
const phone = useWatch({ name }) const phone = useWatch({ name })
const { field, fieldState, formState } = useController({ const { field, fieldState, formState } = useController({
@@ -59,6 +59,7 @@ export default function Phone({
value: phone, value: phone,
onChange: (value) => { onChange: (value) => {
setValue(name, value.phone) setValue(name, value.phone)
trigger(name)
}, },
}) })

View File

@@ -39,7 +39,7 @@
width: 100%; width: 100%;
height: 100vh; height: 100vh;
background-color: var(--Base-Background-Primary-Normal); background-color: var(--Base-Background-Primary-Normal);
z-index: 100; z-index: var(--sidepeek-z-index);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.85); box-shadow: 0 0 10px rgba(0, 0, 0, 0.85);
} }

View File

@@ -80,6 +80,10 @@
color: var(--UI-Text-High-contrast); color: var(--UI-Text-High-contrast);
} }
.baseTextMediumContrast {
color: var(--Base-Text-Medium-contrast);
}
.white { .white {
color: var(--UI-Opacity-White-100); color: var(--UI-Opacity-White-100);
} }

View File

@@ -12,6 +12,7 @@ const config = {
pale: styles.pale, pale: styles.pale,
red: styles.red, red: styles.red,
textMediumContrast: styles.textMediumContrast, textMediumContrast: styles.textMediumContrast,
baseTextMediumContrast: styles.baseTextMediumContrast,
textHighContrast: styles.textHighContrast, textHighContrast: styles.textHighContrast,
white: styles.white, white: styles.white,
peach50: styles.peach50, peach50: styles.peach50,

View File

@@ -49,7 +49,7 @@ export function Toast({ message, onClose, variant }: ToastsProps) {
} }
export const toast = { export const toast = {
success: (message: string, options?: ExternalToast) => success: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom( sonnerToast.custom(
(t) => ( (t) => (
<Toast <Toast
@@ -60,7 +60,7 @@ export const toast = {
), ),
options options
), ),
info: (message: string, options?: ExternalToast) => info: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom( sonnerToast.custom(
(t) => ( (t) => (
<Toast <Toast
@@ -71,7 +71,7 @@ export const toast = {
), ),
options options
), ),
error: (message: string, options?: ExternalToast) => error: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom( sonnerToast.custom(
(t) => ( (t) => (
<Toast <Toast
@@ -82,7 +82,7 @@ export const toast = {
), ),
options options
), ),
warning: (message: string, options?: ExternalToast) => warning: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom( sonnerToast.custom(
(t) => ( (t) => (
<Toast <Toast

View File

@@ -5,6 +5,6 @@ import type { VariantProps } from "class-variance-authority"
export interface ToastsProps export interface ToastsProps
extends Omit<React.AnchorHTMLAttributes<HTMLDivElement>, "color">, extends Omit<React.AnchorHTMLAttributes<HTMLDivElement>, "color">,
VariantProps<typeof toastVariants> { VariantProps<typeof toastVariants> {
message: string message: React.ReactNode
onClose: () => void onClose: () => void
} }

View File

@@ -1,3 +1,4 @@
import { getUserTracking } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import RouterTransition from "@/components/TrackingSDK/RouterTransition" import RouterTransition from "@/components/TrackingSDK/RouterTransition"
@@ -8,7 +9,7 @@ import {
} from "@/types/components/tracking" } from "@/types/components/tracking"
export const preloadUserTracking = () => { export const preloadUserTracking = () => {
void serverClient().user.tracking() void getUserTracking()
} }
export default async function TrackingSDK({ export default async function TrackingSDK({
@@ -18,7 +19,7 @@ export default async function TrackingSDK({
pageData: TrackingSDKPageData pageData: TrackingSDKPageData
hotelInfo?: TrackingSDKHotelInfo hotelInfo?: TrackingSDKHotelInfo
}) { }) {
const userTrackingData = await serverClient().user.tracking() const userTrackingData = await getUserTracking()
return ( return (
<RouterTransition <RouterTransition

View File

@@ -115,6 +115,7 @@
"From": "Fra", "From": "Fra",
"Get inspired": "Bliv inspireret", "Get inspired": "Bliv inspireret",
"Get member benefits & offers": "Få medlemsfordele og tilbud", "Get member benefits & offers": "Få medlemsfordele og tilbud",
"Gift(s) added to your benefits": "{amount, plural, one {Gave} other {Gaver}} tilføjet til dine fordele",
"Go back to edit": "Gå tilbage til redigering", "Go back to edit": "Gå tilbage til redigering",
"Go back to overview": "Gå tilbage til oversigten", "Go back to overview": "Gå tilbage til oversigten",
"Guest information": "Gæsteinformation", "Guest information": "Gæsteinformation",
@@ -128,6 +129,7 @@
"Hotels": "Hoteller", "Hotels": "Hoteller",
"How do you want to sleep?": "Hvordan vil du sove?", "How do you want to sleep?": "Hvordan vil du sove?",
"How it works": "Hvordan det virker", "How it works": "Hvordan det virker",
"Hurry up and use them before they expire!": "Skynd dig og brug dem, før de udløber!",
"I would like to get my booking confirmation via sms": "Jeg vil gerne få min booking bekræftelse via SMS", "I would like to get my booking confirmation via sms": "Jeg vil gerne få min booking bekræftelse via SMS",
"Image gallery": "Billedgalleri", "Image gallery": "Billedgalleri",
"In adults bed": "i de voksnes seng", "In adults bed": "i de voksnes seng",
@@ -199,6 +201,7 @@
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På din rejse", "On your journey": "På din rejse",
"Open": "Åben", "Open": "Åben",
"Open gift(s)": "Åbne {amount, plural, one {gave} other {gaver}}",
"Open language menu": "Åbn sprogmenuen", "Open language menu": "Åbn sprogmenuen",
"Open menu": "Åbn menuen", "Open menu": "Åbn menuen",
"Open my pages menu": "Åbn mine sider menuen", "Open my pages menu": "Åbn mine sider menuen",
@@ -219,6 +222,7 @@
"Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.", "Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.",
"Points needed to level up": "Point nødvendige for at stige i niveau", "Points needed to level up": "Point nødvendige for at stige i niveau",
"Points needed to stay on level": "Point nødvendige for at holde sig på niveau", "Points needed to stay on level": "Point nødvendige for at holde sig på niveau",
"Previous": "Forudgående",
"Previous victories": "Tidligere sejre", "Previous victories": "Tidligere sejre",
"Proceed to login": "Fortsæt til login", "Proceed to login": "Fortsæt til login",
"Proceed to payment method": "Fortsæt til betalingsmetode", "Proceed to payment method": "Fortsæt til betalingsmetode",
@@ -279,7 +283,6 @@
"Street": "Gade", "Street": "Gade",
"Successfully updated profile!": "Profilen er opdateret med succes!", "Successfully updated profile!": "Profilen er opdateret med succes!",
"Summary": "Opsummering", "Summary": "Opsummering",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
"Terms and conditions": "Vilkår og betingelser", "Terms and conditions": "Vilkår og betingelser",
"Thank you": "Tak", "Thank you": "Tak",
@@ -298,12 +301,14 @@
"Use bonus cheque": "Brug Bonus Cheque", "Use bonus cheque": "Brug Bonus Cheque",
"Use code/voucher": "Brug kode/voucher", "Use code/voucher": "Brug kode/voucher",
"User information": "Brugeroplysninger", "User information": "Brugeroplysninger",
"Valid through": "Gyldig igennem",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kort", "View as map": "Vis som kort",
"View your booking": "Se din booking", "View your booking": "Se din booking",
"Visiting address": "Besøgsadresse", "Visiting address": "Besøgsadresse",
"We could not add a card right now, please try again later.": "Vi kunne ikke tilføje et kort lige nu. Prøv venligst igen senere.", "We could not add a card right now, please try again later.": "Vi kunne ikke tilføje et kort lige nu. Prøv venligst igen senere.",
"We couldn't find a matching location for your search.": "Vi kunne ikke finde en matchende lokation til din søgning.", "We couldn't find a matching location for your search.": "Vi kunne ikke finde en matchende lokation til din søgning.",
"We have a special gift waiting for you!": "Vi har en speciel gave, der venter på dig!",
"We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljeret bekræftelse af din booking til din email:", "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljeret bekræftelse af din booking til din email:",
"We look forward to your visit!": "Vi ser frem til dit besøg!", "We look forward to your visit!": "Vi ser frem til dit besøg!",
"Weekdays": "Hverdage", "Weekdays": "Hverdage",
@@ -321,9 +326,9 @@
"Yes, remove my card": "Ja, fjern mit kort", "Yes, remove my card": "Ja, fjern mit kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.", "You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
"You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.", "You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.",
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver, der venter på dig!",
"You have no previous stays.": "Du har ingen tidligere ophold.", "You have no previous stays.": "Du har ingen tidligere ophold.",
"You have no upcoming stays.": "Du har ingen kommende ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.",
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
"Your card was successfully removed!": "Dit kort blev fjernet!", "Your card was successfully removed!": "Dit kort blev fjernet!",
"Your card was successfully saved!": "Dit kort blev gemt!", "Your card was successfully saved!": "Dit kort blev gemt!",
"Your current level": "Dit nuværende niveau", "Your current level": "Dit nuværende niveau",
@@ -335,33 +340,7 @@
"Zoo": "Zoo", "Zoo": "Zoo",
"Zoom in": "Zoom ind", "Zoom in": "Zoom ind",
"Zoom out": "Zoom ud", "Zoom out": "Zoom ud",
"as of today": "pr. dags dato",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
"booking.children": "{totalChildren, plural, one {# barn} other {# børn}}",
"booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}",
"booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}",
"booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}",
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
"by": "inden",
"characters": "tegn",
"guest": "gæst", "guest": "gæst",
"guests": "gæster",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer",
"km to city center": "km til byens centrum",
"lowercase letter": "lille bogstav",
"n/a": "n/a",
"next level:": "Næste niveau:",
"night": "nat",
"nights": "nætter",
"number": "nummer",
"or": "eller",
"points": "Point",
"special character": "speciel karakter",
"spendable points expiring by": "{points} Brugbare point udløber den {date}",
"to": "til",
"uppercase letter": "stort bogstav",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm" "{width} cm × {length} cm": "{width} cm × {length} cm"

View File

@@ -115,8 +115,10 @@
"From": "Fromm", "From": "Fromm",
"Get inspired": "Lassen Sie sich inspieren", "Get inspired": "Lassen Sie sich inspieren",
"Get member benefits & offers": "Holen Sie sich Vorteile und Angebote für Mitglieder", "Get member benefits & offers": "Holen Sie sich Vorteile und Angebote für Mitglieder",
"Gift(s) added to your benefits": "{amount, plural, one {Geschenk zu Ihren Vorteilen hinzugefügt} other {Geschenke, die zu Ihren Vorteilen hinzugefügt werden}}",
"Go back to edit": "Zurück zum Bearbeiten", "Go back to edit": "Zurück zum Bearbeiten",
"Go back to overview": "Zurück zur Übersicht", "Go back to overview": "Zurück zur Übersicht",
"Go to My Benefits": "Gehen Sie zu „Meine Vorteile“",
"Guest information": "Informationen für Gäste", "Guest information": "Informationen für Gäste",
"Guests & Rooms": "Gäste & Zimmer", "Guests & Rooms": "Gäste & Zimmer",
"Hi": "Hallo", "Hi": "Hallo",
@@ -128,6 +130,7 @@
"Hotels": "Hotels", "Hotels": "Hotels",
"How do you want to sleep?": "Wie möchtest du schlafen?", "How do you want to sleep?": "Wie möchtest du schlafen?",
"How it works": "Wie es funktioniert", "How it works": "Wie es funktioniert",
"Hurry up and use them before they expire!": "Beeilen Sie sich und nutzen Sie sie, bevor sie ablaufen!",
"I would like to get my booking confirmation via sms": "Ich möchte meine Buchungsbestätigung per SMS erhalten", "I would like to get my booking confirmation via sms": "Ich möchte meine Buchungsbestätigung per SMS erhalten",
"Image gallery": "Bildergalerie", "Image gallery": "Bildergalerie",
"In adults bed": "Im Bett der Eltern", "In adults bed": "Im Bett der Eltern",
@@ -199,6 +202,7 @@
"OTHER PAYMENT METHODS": "ANDERE BEZAHLMETHODE", "OTHER PAYMENT METHODS": "ANDERE BEZAHLMETHODE",
"On your journey": "Auf deiner Reise", "On your journey": "Auf deiner Reise",
"Open": "Offen", "Open": "Offen",
"Open gift(s)": "{amount, plural, one {Geschenk} other {Geschenke}} öffnen",
"Open language menu": "Sprachmenü öffnen", "Open language menu": "Sprachmenü öffnen",
"Open menu": "Menü öffnen", "Open menu": "Menü öffnen",
"Open my pages menu": "Meine Seiten Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen",
@@ -219,6 +223,7 @@
"Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.", "Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.",
"Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden", "Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden",
"Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben", "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben",
"Previous": "Früher",
"Previous victories": "Bisherige Siege", "Previous victories": "Bisherige Siege",
"Proceed to login": "Weiter zum Login", "Proceed to login": "Weiter zum Login",
"Proceed to payment method": "Weiter zur Zahlungsmethode", "Proceed to payment method": "Weiter zur Zahlungsmethode",
@@ -279,6 +284,7 @@
"Street": "Straße", "Street": "Straße",
"Successfully updated profile!": "Profil erfolgreich aktualisiert!", "Successfully updated profile!": "Profil erfolgreich aktualisiert!",
"Summary": "Zusammenfassung", "Summary": "Zusammenfassung",
"Surprise!": "Überraschung!",
"TUI Points": "TUI Points", "TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
"Terms and conditions": "Geschäftsbedingungen", "Terms and conditions": "Geschäftsbedingungen",
@@ -298,12 +304,14 @@
"Use bonus cheque": "Bonusscheck nutzen", "Use bonus cheque": "Bonusscheck nutzen",
"Use code/voucher": "Code/Gutschein nutzen", "Use code/voucher": "Code/Gutschein nutzen",
"User information": "Nutzerinformation", "User information": "Nutzerinformation",
"Valid through": "Gültig bis",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",
"View as map": "Als Karte anzeigen", "View as map": "Als Karte anzeigen",
"View your booking": "Ihre Buchung ansehen", "View your booking": "Ihre Buchung ansehen",
"Visiting address": "Besuchsadresse", "Visiting address": "Besuchsadresse",
"We could not add a card right now, please try again later.": "Wir konnten momentan keine Karte hinzufügen. Bitte versuchen Sie es später noch einmal.", "We could not add a card right now, please try again later.": "Wir konnten momentan keine Karte hinzufügen. Bitte versuchen Sie es später noch einmal.",
"We couldn't find a matching location for your search.": "Wir konnten keinen passenden Standort für Ihre Suche finden.", "We couldn't find a matching location for your search.": "Wir konnten keinen passenden Standort für Ihre Suche finden.",
"We have a special gift waiting for you!": "Wir haben ein besonderes Geschenk für Sie!",
"We have sent a detailed confirmation of your booking to your email:": "Wir haben eine detaillierte Bestätigung Ihrer Buchung an Ihre E-Mail gesendet:", "We have sent a detailed confirmation of your booking to your email:": "Wir haben eine detaillierte Bestätigung Ihrer Buchung an Ihre E-Mail gesendet:",
"We look forward to your visit!": "Wir freuen uns auf Ihren Besuch!", "We look forward to your visit!": "Wir freuen uns auf Ihren Besuch!",
"Weekdays": "Wochentage", "Weekdays": "Wochentage",
@@ -321,8 +329,10 @@
"Yes, remove my card": "Ja, meine Karte entfernen", "Yes, remove my card": "Ja, meine Karte entfernen",
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.", "You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
"You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.", "You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.",
"You have <b>#</b> gifts waiting for you!": "Es warten <b>{amount}</b> Geschenke auf Sie!",
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
"You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.",
"You'll find all your gifts in 'My benefits'": "Alle Ihre Geschenke finden Sie unter „Meine Vorteile“",
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
"Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!", "Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!",
"Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!", "Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!",
@@ -336,12 +346,14 @@
"Zoom in": "Vergrößern", "Zoom in": "Vergrößern",
"Zoom out": "Verkleinern", "Zoom out": "Verkleinern",
"as of today": "Stand heute", "as of today": "Stand heute",
"booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}",
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}", "booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}",
"booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}", "booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}",
"booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}", "booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}",
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}", "booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.", "booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
"by": "bis", "by": "bis",
"characters": "figuren", "characters": "figuren",
"guest": "gast", "guest": "gast",
@@ -362,6 +374,7 @@
"spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}",
"to": "zu", "to": "zu",
"uppercase letter": "großbuchstabe", "uppercase letter": "großbuchstabe",
"{amount} out of {total}": "{amount} von {total}",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm" "{width} cm × {length} cm": "{width} cm × {length} cm"

View File

@@ -125,8 +125,10 @@
"From": "From", "From": "From",
"Get inspired": "Get inspired", "Get inspired": "Get inspired",
"Get member benefits & offers": "Get member benefits & offers", "Get member benefits & offers": "Get member benefits & offers",
"Gift(s) added to your benefits": "{amount, plural, one {Gift} other {Gifts}} added to your benefits",
"Go back to edit": "Go back to edit", "Go back to edit": "Go back to edit",
"Go back to overview": "Go back to overview", "Go back to overview": "Go back to overview",
"Go to My Benefits": "Go to My Benefits",
"Guest": "Guest", "Guest": "Guest",
"Guest information": "Guest information", "Guest information": "Guest information",
"Guests & Rooms": "Guests & Rooms", "Guests & Rooms": "Guests & Rooms",
@@ -139,6 +141,7 @@
"Hotels": "Hotels", "Hotels": "Hotels",
"How do you want to sleep?": "How do you want to sleep?", "How do you want to sleep?": "How do you want to sleep?",
"How it works": "How it works", "How it works": "How it works",
"Hurry up and use them before they expire!": "Hurry up and use them before they expire!",
"I would like to get my booking confirmation via sms": "I would like to get my booking confirmation via sms", "I would like to get my booking confirmation via sms": "I would like to get my booking confirmation via sms",
"Image gallery": "Image gallery", "Image gallery": "Image gallery",
"In adults bed": "In adults bed", "In adults bed": "In adults bed",
@@ -210,6 +213,7 @@
"OTHER PAYMENT METHODS": "OTHER PAYMENT METHODS", "OTHER PAYMENT METHODS": "OTHER PAYMENT METHODS",
"On your journey": "On your journey", "On your journey": "On your journey",
"Open": "Open", "Open": "Open",
"Open gift(s)": "Open {amount, plural, one {gift} other {gifts}}",
"Open language menu": "Open language menu", "Open language menu": "Open language menu",
"Open menu": "Open menu", "Open menu": "Open menu",
"Open my pages menu": "Open my pages menu", "Open my pages menu": "Open my pages menu",
@@ -231,6 +235,7 @@
"Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.", "Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.",
"Points needed to level up": "Points needed to level up", "Points needed to level up": "Points needed to level up",
"Points needed to stay on level": "Points needed to stay on level", "Points needed to stay on level": "Points needed to stay on level",
"Previous": "Previous",
"Previous victories": "Previous victories", "Previous victories": "Previous victories",
"Print confirmation": "Print confirmation", "Print confirmation": "Print confirmation",
"Proceed to login": "Proceed to login", "Proceed to login": "Proceed to login",
@@ -294,6 +299,7 @@
"Street": "Street", "Street": "Street",
"Successfully updated profile!": "Successfully updated profile!", "Successfully updated profile!": "Successfully updated profile!",
"Summary": "Summary", "Summary": "Summary",
"Surprise!": "Surprise!",
"TUI Points": "TUI Points", "TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
"Terms and conditions": "Terms and conditions", "Terms and conditions": "Terms and conditions",
@@ -315,12 +321,14 @@
"Use code/voucher": "Use code/voucher", "Use code/voucher": "Use code/voucher",
"User information": "User information", "User information": "User information",
"VAT": "VAT", "VAT": "VAT",
"Valid through": "Valid through",
"View as list": "View as list", "View as list": "View as list",
"View as map": "View as map", "View as map": "View as map",
"View your booking": "View your booking", "View your booking": "View your booking",
"Visiting address": "Visiting address", "Visiting address": "Visiting address",
"We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.", "We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.",
"We couldn't find a matching location for your search.": "We couldn't find a matching location for your search.", "We couldn't find a matching location for your search.": "We couldn't find a matching location for your search.",
"We have a special gift waiting for you!": "We have a special gift waiting for you!",
"We have sent a detailed confirmation of your booking to your email:": "We have sent a detailed confirmation of your booking to your email: ", "We have sent a detailed confirmation of your booking to your email:": "We have sent a detailed confirmation of your booking to your email: ",
"We look forward to your visit!": "We look forward to your visit!", "We look forward to your visit!": "We look forward to your visit!",
"Weekdays": "Weekdays", "Weekdays": "Weekdays",
@@ -338,8 +346,10 @@
"Yes, remove my card": "Yes, remove my card", "Yes, remove my card": "Yes, remove my card",
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.", "You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
"You canceled adding a new credit card.": "You canceled adding a new credit card.", "You canceled adding a new credit card.": "You canceled adding a new credit card.",
"You have <b>#</b> gifts waiting for you!": "You have <b>{amount}</b> gifts waiting for you!",
"You have no previous stays.": "You have no previous stays.", "You have no previous stays.": "You have no previous stays.",
"You have no upcoming stays.": "You have no upcoming stays.", "You have no upcoming stays.": "You have no upcoming stays.",
"You'll find all your gifts in 'My benefits'": "Youll find all your gifts in My benefits",
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your card was successfully removed!": "Your card was successfully removed!", "Your card was successfully removed!": "Your card was successfully removed!",
"Your card was successfully saved!": "Your card was successfully saved!", "Your card was successfully saved!": "Your card was successfully saved!",
@@ -354,6 +364,7 @@
"Zoom in": "Zoom in", "Zoom in": "Zoom in",
"Zoom out": "Zoom out", "Zoom out": "Zoom out",
"as of today": "as of today", "as of today": "as of today",
"booking.accommodatesUpTo": "Accommodates up to {nrOfGuests, plural, one {# person} other {# people}}",
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}", "booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}",
"booking.children": "{totalChildren, plural, one {# child} other {# children}}", "booking.children": "{totalChildren, plural, one {# child} other {# children}}",
"booking.confirmation.text": "Thank you for booking with us! We look forward to welcoming you and hope you have a pleasant stay. If you have any questions or need to make changes to your reservation, please <emailLink>email us.</emailLink>", "booking.confirmation.text": "Thank you for booking with us! We look forward to welcoming you and hope you have a pleasant stay. If you have any questions or need to make changes to your reservation, please <emailLink>email us.</emailLink>",
@@ -362,6 +373,7 @@
"booking.nights": "{totalNights, plural, one {# night} other {# nights}}", "booking.nights": "{totalNights, plural, one {# night} other {# nights}}",
"booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}", "booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}",
"booking.terms": "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsLink>Terms & Conditions</termsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyLink>Scandic's Privacy policy</privacyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.", "booking.terms": "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsLink>Terms & Conditions</termsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyLink>Scandic's Privacy policy</privacyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.",
"booking.thisRoomIsEquippedWith": "This room is equipped with",
"by": "by", "by": "by",
"characters": "characters", "characters": "characters",
"from": "from", "from": "from",
@@ -384,6 +396,7 @@
"spendable points expiring by": "{points} spendable points expiring by {date}", "spendable points expiring by": "{points} spendable points expiring by {date}",
"to": "to", "to": "to",
"uppercase letter": "uppercase letter", "uppercase letter": "uppercase letter",
"{amount} out of {total}": "{amount} out of {total}",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{card} ending with {cardno}": "{card} ending with {cardno}", "{card} ending with {cardno}": "{card} ending with {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",

View File

@@ -115,8 +115,10 @@
"From": "From", "From": "From",
"Get inspired": "Inspiroidu", "Get inspired": "Inspiroidu",
"Get member benefits & offers": "Hanki jäsenetuja ja -tarjouksia", "Get member benefits & offers": "Hanki jäsenetuja ja -tarjouksia",
"Gift(s) added to your benefits": "{amount, plural, one {Lahja} other {Lahjat}} lisätty etuusi",
"Go back to edit": "Palaa muokkaamaan", "Go back to edit": "Palaa muokkaamaan",
"Go back to overview": "Palaa yleiskatsaukseen", "Go back to overview": "Palaa yleiskatsaukseen",
"Go to My Benefits": "Siirry kohtaan Omat edut",
"Guest information": "Vieraan tiedot", "Guest information": "Vieraan tiedot",
"Guests & Rooms": "Vieraat & Huoneet", "Guests & Rooms": "Vieraat & Huoneet",
"Hi": "Hi", "Hi": "Hi",
@@ -128,6 +130,7 @@
"Hotels": "Hotellit", "Hotels": "Hotellit",
"How do you want to sleep?": "Kuinka haluat nukkua?", "How do you want to sleep?": "Kuinka haluat nukkua?",
"How it works": "Kuinka se toimii", "How it works": "Kuinka se toimii",
"Hurry up and use them before they expire!": "Ole nopea ja käytä ne ennen kuin ne vanhenevat!",
"I would like to get my booking confirmation via sms": "Haluan saada varauksen vahvistuksen SMS-viestillä", "I would like to get my booking confirmation via sms": "Haluan saada varauksen vahvistuksen SMS-viestillä",
"Image gallery": "Kuvagalleria", "Image gallery": "Kuvagalleria",
"In adults bed": "Aikuisten vuoteessa", "In adults bed": "Aikuisten vuoteessa",
@@ -199,6 +202,7 @@
"OTHER PAYMENT METHODS": "MUISE KORT", "OTHER PAYMENT METHODS": "MUISE KORT",
"On your journey": "Matkallasi", "On your journey": "Matkallasi",
"Open": "Avata", "Open": "Avata",
"Open gift(s)": "{amount, plural, one {Avoin lahja} other {Avoimet lahjat}}",
"Open language menu": "Avaa kielivalikko", "Open language menu": "Avaa kielivalikko",
"Open menu": "Avaa valikko", "Open menu": "Avaa valikko",
"Open my pages menu": "Avaa omat sivut -valikko", "Open my pages menu": "Avaa omat sivut -valikko",
@@ -219,6 +223,7 @@
"Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.", "Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.",
"Points needed to level up": "Tarvitset vielä", "Points needed to level up": "Tarvitset vielä",
"Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet", "Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet",
"Previous": "Aikaisempi",
"Previous victories": "Edelliset voitot", "Previous victories": "Edelliset voitot",
"Proceed to login": "Jatka kirjautumiseen", "Proceed to login": "Jatka kirjautumiseen",
"Proceed to payment method": "Siirry maksutavalle", "Proceed to payment method": "Siirry maksutavalle",
@@ -280,6 +285,7 @@
"Street": "Katu", "Street": "Katu",
"Successfully updated profile!": "Profiilin päivitys onnistui!", "Successfully updated profile!": "Profiilin päivitys onnistui!",
"Summary": "Yhteenveto", "Summary": "Yhteenveto",
"Surprise!": "Yllätys!",
"TUI Points": "TUI Points", "TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
"Terms and conditions": "Käyttöehdot", "Terms and conditions": "Käyttöehdot",
@@ -299,12 +305,14 @@
"Use bonus cheque": "Käytä bonussekkiä", "Use bonus cheque": "Käytä bonussekkiä",
"Use code/voucher": "Käytä koodia/voucheria", "Use code/voucher": "Käytä koodia/voucheria",
"User information": "Käyttäjän tiedot", "User information": "Käyttäjän tiedot",
"Valid through": "Voimassa läpi",
"View as list": "Näytä listana", "View as list": "Näytä listana",
"View as map": "Näytä kartalla", "View as map": "Näytä kartalla",
"View your booking": "Näytä varauksesi", "View your booking": "Näytä varauksesi",
"Visiting address": "Käyntiosoite", "Visiting address": "Käyntiosoite",
"We could not add a card right now, please try again later.": "Emme voineet lisätä korttia juuri nyt. Yritä myöhemmin uudelleen.", "We could not add a card right now, please try again later.": "Emme voineet lisätä korttia juuri nyt. Yritä myöhemmin uudelleen.",
"We couldn't find a matching location for your search.": "Emme löytäneet hakuasi vastaavaa sijaintia.", "We couldn't find a matching location for your search.": "Emme löytäneet hakuasi vastaavaa sijaintia.",
"We have a special gift waiting for you!": "Meillä on erityinen lahja odottamassa sinua!",
"We have sent a detailed confirmation of your booking to your email:": "Olemme lähettäneet yksityiskohtaisen varausvahvistuksen sähköpostiisi:", "We have sent a detailed confirmation of your booking to your email:": "Olemme lähettäneet yksityiskohtaisen varausvahvistuksen sähköpostiisi:",
"We look forward to your visit!": "Odotamme innolla vierailuasi!", "We look forward to your visit!": "Odotamme innolla vierailuasi!",
"Weekdays": "Arkisin", "Weekdays": "Arkisin",
@@ -322,8 +330,10 @@
"Yes, remove my card": "Kyllä, poista korttini", "Yes, remove my card": "Kyllä, poista korttini",
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.", "You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
"You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.", "You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.",
"You have <b>#</b> gifts waiting for you!": "Sinulla on <b>{amount}</b> lahjaa odottamassa sinua!",
"You have no previous stays.": "Sinulla ei ole aiempia majoituksia.", "You have no previous stays.": "Sinulla ei ole aiempia majoituksia.",
"You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.", "You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.",
"You'll find all your gifts in 'My benefits'": "Löydät kaikki lahjasi kohdasta Omat edut",
"Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!",
"Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!", "Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!",
"Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!",
@@ -337,12 +347,14 @@
"Zoom in": "Lähennä", "Zoom in": "Lähennä",
"Zoom out": "Loitonna", "Zoom out": "Loitonna",
"as of today": "tänään", "as of today": "tänään",
"booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}",
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}", "booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}",
"booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}", "booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}",
"booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}", "booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}",
"booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}",
"booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}", "booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}",
"booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset <termsLink>ehdot ja ehtoja</termsLink>, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti <privacyLink>Scandicin tietosuojavaltuuden</privacyLink> mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.", "booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset <termsLink>ehdot ja ehtoja</termsLink>, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti <privacyLink>Scandicin tietosuojavaltuuden</privacyLink> mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.",
"booking.thisRoomIsEquippedWith": "Tämä huone on varustettu",
"by": "mennessä", "by": "mennessä",
"characters": "hahmoja", "characters": "hahmoja",
"guest": "Vieras", "guest": "Vieras",
@@ -363,6 +375,7 @@
"spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä",
"to": "to", "to": "to",
"uppercase letter": "iso kirjain", "uppercase letter": "iso kirjain",
"{amount} out of {total}": "{amount}/{total}",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm" "{width} cm × {length} cm": "{width} cm × {length} cm"

View File

@@ -114,8 +114,10 @@
"From": "Fra", "From": "Fra",
"Get inspired": "Bli inspirert", "Get inspired": "Bli inspirert",
"Get member benefits & offers": "Få medlemsfordeler og tilbud", "Get member benefits & offers": "Få medlemsfordeler og tilbud",
"Gift(s) added to your benefits": "{amount, plural, one {Gave} other {Gaver}} lagt til fordelene dine",
"Go back to edit": "Gå tilbake til redigering", "Go back to edit": "Gå tilbake til redigering",
"Go back to overview": "Gå tilbake til oversikten", "Go back to overview": "Gå tilbake til oversikten",
"Go to My Benefits": "Gå til Mine fordeler",
"Guest information": "Informasjon til gjester", "Guest information": "Informasjon til gjester",
"Guests & Rooms": "Gjester & rom", "Guests & Rooms": "Gjester & rom",
"Hi": "Hei", "Hi": "Hei",
@@ -127,6 +129,7 @@
"Hotels": "Hoteller", "Hotels": "Hoteller",
"How do you want to sleep?": "Hvordan vil du sove?", "How do you want to sleep?": "Hvordan vil du sove?",
"How it works": "Hvordan det fungerer", "How it works": "Hvordan det fungerer",
"Hurry up and use them before they expire!": "Skynd deg og bruk dem før de utløper!",
"Image gallery": "Bildegalleri", "Image gallery": "Bildegalleri",
"In adults bed": "i voksnes seng", "In adults bed": "i voksnes seng",
"In crib": "i sprinkelseng", "In crib": "i sprinkelseng",
@@ -197,6 +200,7 @@
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På reisen din", "On your journey": "På reisen din",
"Open": "Åpen", "Open": "Åpen",
"Open gift(s)": "{amount, plural, one {Åpen gave} other {Åpnen gaver}}",
"Open language menu": "Åpne språkmenyen", "Open language menu": "Åpne språkmenyen",
"Open menu": "Åpne menyen", "Open menu": "Åpne menyen",
"Open my pages menu": "Åpne mine sider menyen", "Open my pages menu": "Åpne mine sider menyen",
@@ -217,6 +221,7 @@
"Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.", "Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.",
"Points needed to level up": "Poeng som trengs for å komme opp i nivå", "Points needed to level up": "Poeng som trengs for å komme opp i nivå",
"Points needed to stay on level": "Poeng som trengs for å holde seg på nivå", "Points needed to stay on level": "Poeng som trengs for å holde seg på nivå",
"Previous": "Tidligere",
"Previous victories": "Tidligere seire", "Previous victories": "Tidligere seire",
"Proceed to login": "Fortsett til innlogging", "Proceed to login": "Fortsett til innlogging",
"Proceed to payment method": "Fortsett til betalingsmetode", "Proceed to payment method": "Fortsett til betalingsmetode",
@@ -277,6 +282,7 @@
"Street": "Gate", "Street": "Gate",
"Successfully updated profile!": "Vellykket oppdatert profil!", "Successfully updated profile!": "Vellykket oppdatert profil!",
"Summary": "Sammendrag", "Summary": "Sammendrag",
"Surprise!": "Overraskelse!",
"TUI Points": "TUI Points", "TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
"Terms and conditions": "Vilkår og betingelser", "Terms and conditions": "Vilkår og betingelser",
@@ -296,12 +302,14 @@
"Use bonus cheque": "Bruk bonussjekk", "Use bonus cheque": "Bruk bonussjekk",
"Use code/voucher": "Bruk kode/voucher", "Use code/voucher": "Bruk kode/voucher",
"User information": "Brukerinformasjon", "User information": "Brukerinformasjon",
"Valid through": "Gyldig gjennom",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kart", "View as map": "Vis som kart",
"View your booking": "Se din bestilling", "View your booking": "Se din bestilling",
"Visiting address": "Besøksadresse", "Visiting address": "Besøksadresse",
"We could not add a card right now, please try again later.": "Vi kunne ikke legge til et kort akkurat nå. Prøv igjen senere.", "We could not add a card right now, please try again later.": "Vi kunne ikke legge til et kort akkurat nå. Prøv igjen senere.",
"We couldn't find a matching location for your search.": "Vi finner ikke et sted som samsvarer for søket ditt.", "We couldn't find a matching location for your search.": "Vi finner ikke et sted som samsvarer for søket ditt.",
"We have a special gift waiting for you!": "Vi har en spesiell gave som venter på deg!",
"We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljert bekreftelse av din bestilling til din e-post:", "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljert bekreftelse av din bestilling til din e-post:",
"We look forward to your visit!": "Vi ser frem til ditt besøk!", "We look forward to your visit!": "Vi ser frem til ditt besøk!",
"Weekdays": "Hverdager", "Weekdays": "Hverdager",
@@ -319,8 +327,10 @@
"Yes, remove my card": "Ja, fjern kortet mitt", "Yes, remove my card": "Ja, fjern kortet mitt",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.", "You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
"You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.", "You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.",
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> gaver som venter på deg!",
"You have no previous stays.": "Du har ingen tidligere opphold.", "You have no previous stays.": "Du har ingen tidligere opphold.",
"You have no upcoming stays.": "Du har ingen kommende opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.",
"You'll find all your gifts in 'My benefits'": "Du finner alle gavene dine i Mine fordeler",
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
"Your card was successfully removed!": "Kortet ditt ble fjernet!", "Your card was successfully removed!": "Kortet ditt ble fjernet!",
"Your card was successfully saved!": "Kortet ditt ble lagret!", "Your card was successfully saved!": "Kortet ditt ble lagret!",
@@ -334,11 +344,13 @@
"Zoom in": "Zoom inn", "Zoom in": "Zoom inn",
"Zoom out": "Zoom ut", "Zoom out": "Zoom ut",
"as of today": "per i dag", "as of today": "per i dag",
"booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
"booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}",
"booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}",
"booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}", "booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}",
"booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med",
"by": "innen", "by": "innen",
"characters": "tegn", "characters": "tegn",
"guest": "gjest", "guest": "gjest",
@@ -359,6 +371,7 @@
"spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}",
"to": "til", "to": "til",
"uppercase letter": "stor bokstav", "uppercase letter": "stor bokstav",
"{amount} out of {total}": "{amount} av {total}",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm" "{width} cm × {length} cm": "{width} cm × {length} cm"

View File

@@ -114,8 +114,10 @@
"From": "Från", "From": "Från",
"Get inspired": "Bli inspirerad", "Get inspired": "Bli inspirerad",
"Get member benefits & offers": "Ta del av medlemsförmåner och erbjudanden", "Get member benefits & offers": "Ta del av medlemsförmåner och erbjudanden",
"Gift(s) added to your benefits": "{amount, plural, one {Gåva} other {Gåvor}} läggs till dina förmåner",
"Go back to edit": "Gå tillbaka till redigeringen", "Go back to edit": "Gå tillbaka till redigeringen",
"Go back to overview": "Gå tillbaka till översikten", "Go back to overview": "Gå tillbaka till översikten",
"Go to My Benefits": "Gå till Mina förmåner",
"Guest information": "Information till gästerna", "Guest information": "Information till gästerna",
"Guests & Rooms": "Gäster & rum", "Guests & Rooms": "Gäster & rum",
"Hi": "Hej", "Hi": "Hej",
@@ -127,6 +129,7 @@
"Hotels": "Hotell", "Hotels": "Hotell",
"How do you want to sleep?": "Hur vill du sova?", "How do you want to sleep?": "Hur vill du sova?",
"How it works": "Hur det fungerar", "How it works": "Hur det fungerar",
"Hurry up and use them before they expire!": "Skynda dig och använd dem innan de går ut!",
"Image gallery": "Bildgalleri", "Image gallery": "Bildgalleri",
"In adults bed": "I vuxens säng", "In adults bed": "I vuxens säng",
"In crib": "I spjälsäng", "In crib": "I spjälsäng",
@@ -197,6 +200,7 @@
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På din resa", "On your journey": "På din resa",
"Open": "Öppna", "Open": "Öppna",
"Open gift(s)": "Öppna {amount, plural, one {gåva} other {gåvor}}",
"Open language menu": "Öppna språkmenyn", "Open language menu": "Öppna språkmenyn",
"Open menu": "Öppna menyn", "Open menu": "Öppna menyn",
"Open my pages menu": "Öppna mina sidor menyn", "Open my pages menu": "Öppna mina sidor menyn",
@@ -217,6 +221,7 @@
"Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.", "Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.",
"Points needed to level up": "Poäng som behövs för att gå upp i nivå", "Points needed to level up": "Poäng som behövs för att gå upp i nivå",
"Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå", "Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå",
"Previous": "Föregående",
"Previous victories": "Tidigare segrar", "Previous victories": "Tidigare segrar",
"Proceed to login": "Fortsätt till inloggning", "Proceed to login": "Fortsätt till inloggning",
"Proceed to payment method": "Gå vidare till betalningsmetod", "Proceed to payment method": "Gå vidare till betalningsmetod",
@@ -277,6 +282,7 @@
"Street": "Gata", "Street": "Gata",
"Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!", "Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!",
"Summary": "Sammanfattning", "Summary": "Sammanfattning",
"Surprise!": "Överraskning!",
"TUI Points": "TUI Points", "TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
"Terms and conditions": "Allmänna villkor", "Terms and conditions": "Allmänna villkor",
@@ -296,12 +302,14 @@
"Use bonus cheque": "Använd bonuscheck", "Use bonus cheque": "Använd bonuscheck",
"Use code/voucher": "Använd kod/voucher", "Use code/voucher": "Använd kod/voucher",
"User information": "Användarinformation", "User information": "Användarinformation",
"Valid through": "Giltig t.o.m.",
"View as list": "Visa som lista", "View as list": "Visa som lista",
"View as map": "Visa som karta", "View as map": "Visa som karta",
"View your booking": "Visa din bokning", "View your booking": "Visa din bokning",
"Visiting address": "Besöksadress", "Visiting address": "Besöksadress",
"We could not add a card right now, please try again later.": "Vi kunde inte lägga till ett kort just nu, vänligen försök igen senare.", "We could not add a card right now, please try again later.": "Vi kunde inte lägga till ett kort just nu, vänligen försök igen senare.",
"We couldn't find a matching location for your search.": "Vi kunde inte hitta en plats som matchade din sökning.", "We couldn't find a matching location for your search.": "Vi kunde inte hitta en plats som matchade din sökning.",
"We have a special gift waiting for you!": "Vi har en speciell present som väntar på dig!",
"We have sent a detailed confirmation of your booking to your email:": "Vi har skickat en detaljerad bekräftelse av din bokning till din e-post:", "We have sent a detailed confirmation of your booking to your email:": "Vi har skickat en detaljerad bekräftelse av din bokning till din e-post:",
"We look forward to your visit!": "Vi ser fram emot ditt besök!", "We look forward to your visit!": "Vi ser fram emot ditt besök!",
"Weekdays": "Vardagar", "Weekdays": "Vardagar",
@@ -319,8 +327,10 @@
"Yes, remove my card": "Ja, ta bort mitt kort", "Yes, remove my card": "Ja, ta bort mitt kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.", "You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
"You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.", "You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.",
"You have <b>#</b> gifts waiting for you!": "Du har <b>{amount}</b> presenter som väntar på dig!",
"You have no previous stays.": "Du har inga tidigare vistelser.", "You have no previous stays.": "Du har inga tidigare vistelser.",
"You have no upcoming stays.": "Du har inga planerade resor.", "You have no upcoming stays.": "Du har inga planerade resor.",
"You'll find all your gifts in 'My benefits'": "Du hittar alla dina gåvor i Mina förmåner",
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
"Your card was successfully removed!": "Ditt kort har tagits bort!", "Your card was successfully removed!": "Ditt kort har tagits bort!",
"Your card was successfully saved!": "Ditt kort har sparats!", "Your card was successfully saved!": "Ditt kort har sparats!",
@@ -334,12 +344,14 @@
"Zoom in": "Zooma in", "Zoom in": "Zooma in",
"Zoom out": "Zooma ut", "Zoom out": "Zooma ut",
"as of today": "från och med idag", "as of today": "från och med idag",
"booking.accommodatesUpTo": "Rymmer {nrOfGuests, plural, one {# person} other {upp till # personer}}",
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}", "booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}",
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
"booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}", "booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}",
"booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}",
"booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}", "booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}",
"booking.terms": "Genom att betala med någon av de tillgängliga betalningsmetoderna accepterar jag villkoren för denna bokning och de generella <termsLink>Villkoren och villkoren</termsLink>, och förstår att Scandic kommer att behandla min personliga data i samband med denna bokning i enlighet med <privacyLink>Scandics integritetspolicy</privacyLink>. Jag accepterar att Scandic kräver ett giltigt kreditkort under min besök i fall att något är tillbaka betalt.", "booking.terms": "Genom att betala med någon av de tillgängliga betalningsmetoderna accepterar jag villkoren för denna bokning och de generella <termsLink>Villkoren och villkoren</termsLink>, och förstår att Scandic kommer att behandla min personliga data i samband med denna bokning i enlighet med <privacyLink>Scandics integritetspolicy</privacyLink>. Jag accepterar att Scandic kräver ett giltigt kreditkort under min besök i fall att något är tillbaka betalt.",
"booking.thisRoomIsEquippedWith": "Detta rum är utrustat med",
"by": "innan", "by": "innan",
"characters": "tecken", "characters": "tecken",
"guest": "gäst", "guest": "gäst",
@@ -360,6 +372,7 @@
"spendable points expiring by": "{points} poäng förfaller {date}", "spendable points expiring by": "{points} poäng förfaller {date}",
"to": "till", "to": "till",
"uppercase letter": "stor bokstav", "uppercase letter": "stor bokstav",
"{amount} out of {total}": "{amount} av {total}",
"{amount} {currency}": "{amount} {currency}", "{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm" "{width} cm × {length} cm": "{width} cm × {length} cm"

View File

@@ -1,5 +1,7 @@
import { cache } from "react" import { cache } from "react"
import { Lang } from "@/constants/languages"
import { serverClient } from "../server" import { serverClient } from "../server"
export const getLocations = cache(async function getMemoizedLocations() { export const getLocations = cache(async function getMemoizedLocations() {
@@ -10,6 +12,10 @@ export const getProfile = cache(async function getMemoizedProfile() {
return serverClient().user.get() return serverClient().user.get()
}) })
export const getName = cache(async function getMemoizedName() {
return serverClient().user.name()
})
export const getProfileSafely = cache( export const getProfileSafely = cache(
async function getMemoizedProfileSafely() { async function getMemoizedProfileSafely() {
return serverClient().user.getSafely() return serverClient().user.getSafely()
@@ -22,6 +28,28 @@ export const getCreditCardsSafely = cache(
} }
) )
export const getMembershipLevel = cache(
async function getMemoizedMembershipLevel() {
return serverClient().user.membershipLevel()
}
)
export const getMembershipLevelSafely = cache(
async function getMemoizedMembershipLevelSafely() {
return serverClient().user.safeMembershipLevel()
}
)
export const getMembershipCards = cache(
async function getMemoizedMembershipCards() {
return serverClient().user.membershipCards()
}
)
export const getUserTracking = cache(async function getMemoizedUserTracking() {
return serverClient().user.tracking()
})
export const getHotelData = cache(async function getMemoizedHotelData( export const getHotelData = cache(async function getMemoizedHotelData(
hotelId: string, hotelId: string,
language: string language: string
@@ -40,6 +68,18 @@ export const getHeader = cache(async function getMemoizedHeader() {
return serverClient().contentstack.base.header() return serverClient().contentstack.base.header()
}) })
export const getCurrentHeader = cache(async function getMemoizedCurrentHeader(
lang: Lang
) {
return serverClient().contentstack.base.currentHeader({ lang })
})
export const getMyPagesNavigation = cache(
async function getMemoizedMyPagesNavigation() {
return serverClient().contentstack.myPages.navigation.get()
}
)
export const getLanguageSwitcher = cache( export const getLanguageSwitcher = cache(
async function getMemoizedLanguageSwitcher() { async function getMemoizedLanguageSwitcher() {
return serverClient().contentstack.languageSwitcher.get() return serverClient().contentstack.languageSwitcher.get()

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,3 +1,5 @@
import { cache } from "react"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql" import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
import { import {
@@ -92,7 +94,7 @@ import type {
GetSiteConfigRefData, GetSiteConfigRefData,
} from "@/types/trpc/routers/contentstack/siteConfig" } from "@/types/trpc/routers/contentstack/siteConfig"
async function getContactConfig(lang: Lang) { const getContactConfig = cache(async (lang: Lang) => {
getContactConfigCounter.add(1, { lang }) getContactConfigCounter.add(1, { lang })
console.info( console.info(
"contentstack.contactConfig start", "contentstack.contactConfig start",
@@ -153,7 +155,7 @@ async function getContactConfig(lang: Lang) {
JSON.stringify({ query: { lang } }) JSON.stringify({ query: { lang } })
) )
return validatedContactConfigConfig.data.all_contact_config.items[0] return validatedContactConfigConfig.data.all_contact_config.items[0]
} })
export const baseQueryRouter = router({ export const baseQueryRouter = router({
contact: contentstackBaseProcedure.query(async ({ ctx }) => { contact: contentstackBaseProcedure.query(async ({ ctx }) => {

View File

@@ -17,3 +17,7 @@ export const rewardsCurrentInput = z.object({
cursor: z.number().optional().default(0), cursor: z.number().optional().default(0),
lang: z.nativeEnum(Lang).optional(), lang: z.nativeEnum(Lang).optional(),
}) })
export const rewardsUpdateInput = z.object({
id: z.string(),
})

View File

@@ -2,24 +2,64 @@ import { z } from "zod"
import { MembershipLevelEnum } from "@/constants/membershipLevels" import { MembershipLevelEnum } from "@/constants/membershipLevels"
export const validateApiRewardSchema = z.object({ const Coupon = z.object({
data: z.array( code: z.string().optional(),
z status: z.string().optional(),
.object({ createdAt: z.string().datetime({ offset: true }).optional(),
title: z.string().optional(), customer: z.object({
id: z.string().optional(), id: z.string().optional(),
type: z.string().optional(), }),
status: z.string().optional(), name: z.string().optional(),
rewardId: z.string().optional(), claimedAt: z.string().datetime({ offset: true }).optional(),
redeemLocation: z.string().optional(), redeemedAt: z
autoApplyReward: z.boolean().default(false), .date({ coerce: true })
rewardType: z.string().optional(), .optional()
rewardTierLevel: z.string().optional(), .transform((value) => {
}) if (value?.getFullYear() === 1) {
.optional() return null
), }
return value
}),
type: z.string().optional(),
value: z.number().optional(),
pool: z.string().optional(),
cfUnwrapped: z.boolean().default(false),
}) })
const SurpriseReward = z.object({
title: z.string().optional(),
id: z.string().optional(),
type: z.literal("coupon"),
status: z.string().optional(),
rewardId: z.string().optional(),
redeemLocation: z.string().optional(),
autoApplyReward: z.boolean().default(false),
rewardType: z.literal("Surprise"),
endsAt: z.string().datetime({ offset: true }).optional(),
coupons: z.array(Coupon).optional(),
})
export const validateApiRewardSchema = z
.object({
data: z.array(
z.discriminatedUnion("type", [
z.object({
title: z.string().optional(),
id: z.string().optional(),
type: z.literal("custom"),
status: z.string().optional(),
rewardId: z.string().optional(),
redeemLocation: z.string().optional(),
autoApplyReward: z.boolean().default(false),
rewardType: z.string().optional(),
rewardTierLevel: z.string().optional(),
}),
SurpriseReward,
])
),
})
.transform((data) => data.data)
enum TierKey { enum TierKey {
tier1 = MembershipLevelEnum.L1, tier1 = MembershipLevelEnum.L1,
tier2 = MembershipLevelEnum.L2, tier2 = MembershipLevelEnum.L2,
@@ -37,19 +77,17 @@ export const validateApiTierRewardsSchema = z.record(
return TierKey[data as unknown as Key] return TierKey[data as unknown as Key]
}), }),
z.array( z.array(
z z.object({
.object({ title: z.string().optional(),
title: z.string().optional(), id: z.string().optional(),
id: z.string().optional(), type: z.string().optional(),
type: z.string().optional(), status: z.string().optional(),
status: z.string().optional(), rewardId: z.string().optional(),
rewardId: z.string().optional(), redeemLocation: z.string().optional(),
redeemLocation: z.string().optional(), autoApplyReward: z.boolean().default(false),
autoApplyReward: z.boolean().default(false), rewardType: z.string().optional(),
rewardType: z.string().optional(), rewardTierLevel: z.string().optional(),
rewardTierLevel: z.string().optional(), })
})
.optional()
) )
) )
@@ -77,6 +115,10 @@ export const validateCmsRewardsSchema = z
}) })
.transform((data) => data.data.all_reward.items) .transform((data) => data.data.all_reward.items)
export type ApiReward = z.output<typeof validateApiRewardSchema>[0]
export type SurpriseReward = z.output<typeof SurpriseReward>
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema> export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
export type Reward = z.output<typeof validateCmsRewardsSchema>[0] export type Reward = z.output<typeof validateCmsRewardsSchema>[0]

View File

@@ -19,15 +19,19 @@ import {
rewardsAllInput, rewardsAllInput,
rewardsByLevelInput, rewardsByLevelInput,
rewardsCurrentInput, rewardsCurrentInput,
rewardsUpdateInput,
} from "./input" } from "./input"
import { import {
CmsRewardsResponse, CmsRewardsResponse,
Reward, Reward,
SurpriseReward,
validateApiRewardSchema, validateApiRewardSchema,
validateApiTierRewardsSchema, validateApiTierRewardsSchema,
validateCmsRewardsSchema, validateCmsRewardsSchema,
} from "./output" } from "./output"
import { Surprise } from "@/types/components/blocks/surprises"
const meter = metrics.getMeter("trpc.reward") const meter = metrics.getMeter("trpc.reward")
// OpenTelemetry metrics: Reward // OpenTelemetry metrics: Reward
@@ -242,10 +246,10 @@ export const rewardQueryRouter = router({
return null return null
} }
const rewardIds = validatedApiRewards.data.data const rewardIds = validatedApiRewards.data
.map((reward) => reward?.rewardId) .map((reward) => reward?.rewardId)
.filter(Boolean) .filter((rewardId): rewardId is string => !!rewardId)
.sort() as string[] .sort()
const slicedData = rewardIds.slice(cursor, limit + cursor) const slicedData = rewardIds.slice(cursor, limit + cursor)
@@ -258,9 +262,21 @@ export const rewardQueryRouter = router({
const nextCursor = const nextCursor =
limit + cursor < rewardIds.length ? limit + cursor : undefined limit + cursor < rewardIds.length ? limit + cursor : undefined
const surprisesIds = validatedApiRewards.data
.filter(
({ type, rewardType }) =>
type === "coupon" && rewardType === "Surprise"
)
.map(({ rewardId }) => rewardId)
const rewards = cmsRewards.filter(
(reward) => !surprisesIds.includes(reward.reward_id)
)
getCurrentRewardSuccessCounter.add(1) getCurrentRewardSuccessCounter.add(1)
return { return {
rewards: cmsRewards, rewards,
nextCursor, nextCursor,
} }
}), }),
@@ -374,4 +390,112 @@ export const rewardQueryRouter = router({
getAllRewardSuccessCounter.add(1) getAllRewardSuccessCounter.add(1)
return levelsWithRewards return levelsWithRewards
}), }),
surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => {
getCurrentRewardCounter.add(1)
const apiResponse = await api.get(api.endpoints.v1.rewards, {
cache: undefined, // override defaultOptions
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
next: { revalidate: 60 * 60 },
})
if (!apiResponse.ok) {
const text = await apiResponse.text()
getCurrentRewardFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.reward error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const data = await apiResponse.json()
const validatedApiRewards = validateApiRewardSchema.safeParse(data)
if (!validatedApiRewards.success) {
getCurrentRewardFailCounter.add(1, {
locale: ctx.lang,
error_type: "validation_error",
error: JSON.stringify(validatedApiRewards.error),
})
console.error(validatedApiRewards.error)
console.error(
"contentstack.surprises validation error",
JSON.stringify({
query: { locale: ctx.lang },
error: validatedApiRewards.error,
})
)
return null
}
const rewardIds = validatedApiRewards.data
.map((reward) => reward?.rewardId)
.filter((rewardId): rewardId is string => !!rewardId)
.sort()
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
if (!cmsRewards) {
return null
}
getCurrentRewardSuccessCounter.add(1)
const surprises =
validatedApiRewards.data
.filter(
(reward): reward is SurpriseReward =>
reward?.type === "coupon" && reward?.rewardType === "Surprise"
)
.map((surprise) => {
const reward = cmsRewards.find(
({ reward_id }) => surprise.rewardId === reward_id
)
if (!reward) {
return null
}
return {
...reward,
id: surprise.id,
endsAt: surprise.endsAt,
}
})
.filter((surprise): surprise is Surprise => !!surprise) ?? []
return surprises
}),
update: contentStackBaseWithProtectedProcedure
.input(rewardsUpdateInput)
.mutation(async ({ input, ctx }) => {
const response = await Promise.resolve({ ok: true })
// const response = await api.post(api.endpoints.v1.rewards, {
// body: {
// ids: [input.id],
// },
// })
if (!response.ok) {
return false
}
return true
}),
}) })

View File

@@ -92,7 +92,7 @@ export function transform(data: Data) {
system: data.system, system: data.system,
title: data.title, title: data.title,
url: data.web.original_url url: data.web.original_url
? removeMultipleSlashes(data.web.original_url) ? data.web.original_url
: removeMultipleSlashes(`/${data.system.locale}/${data.url}`), : removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
web: data.web, web: data.web,
} }

View File

@@ -89,5 +89,6 @@ export const roomSchema = z
roomSize: data.attributes.roomSize, roomSize: data.attributes.roomSize,
sortOrder: data.attributes.sortOrder, sortOrder: data.attributes.sortOrder,
type: data.type, type: data.type,
roomFacilities: data.attributes.roomFacilities,
} }
}) })

View File

@@ -1,4 +1,5 @@
import { metrics } from "@opentelemetry/api" import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { import {
@@ -80,86 +81,87 @@ const getCreditCardsFailCounter = meter.createCounter(
"trpc.user.creditCards-fail" "trpc.user.creditCards-fail"
) )
export async function getVerifiedUser({ session }: { session: Session }) { export const getVerifiedUser = cache(
const now = Date.now() async ({ session }: { session: Session }) => {
const now = Date.now()
if (session.token.expires_at && session.token.expires_at < now) { if (session.token.expires_at && session.token.expires_at < now) {
return { error: true, cause: "token_expired" } as const return { error: true, cause: "token_expired" } as const
} }
getVerifiedUserCounter.add(1) getVerifiedUserCounter.add(1)
console.info("api.user.profile getVerifiedUser start", JSON.stringify({})) console.info("api.user.profile getVerifiedUser start", JSON.stringify({}))
const apiResponse = await api.get(api.endpoints.v1.profile, { const apiResponse = await api.get(api.endpoints.v1.profile, {
headers: { headers: {
Authorization: `Bearer ${session.token.access_token}`, Authorization: `Bearer ${session.token.access_token}`,
}, },
})
if (!apiResponse.ok) {
const text = await apiResponse.text()
getVerifiedUserFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
}) })
console.error(
"api.user.profile getVerifiedUser error", if (!apiResponse.ok) {
JSON.stringify({ const text = await apiResponse.text()
error: { getVerifiedUserFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status, status: apiResponse.status,
statusText: apiResponse.statusText, statusText: apiResponse.statusText,
text, text,
}, }),
}) })
) console.error(
if (apiResponse.status === 401) { "api.user.profile getVerifiedUser error",
return { error: true, cause: "unauthorized" } as const JSON.stringify({
} else if (apiResponse.status === 403) { error: {
return { error: true, cause: "forbidden" } as const status: apiResponse.status,
} else if (apiResponse.status === 404) { statusText: apiResponse.statusText,
return { error: true, cause: "notfound" } as const text,
},
})
)
if (apiResponse.status === 401) {
return { error: true, cause: "unauthorized" } as const
} else if (apiResponse.status === 403) {
return { error: true, cause: "forbidden" } as const
} else if (apiResponse.status === 404) {
return { error: true, cause: "notfound" } as const
}
return {
error: true,
cause: "unknown",
status: apiResponse.status,
} as const
} }
return {
error: true,
cause: "unknown",
status: apiResponse.status,
} as const
}
const apiJson = await apiResponse.json() const apiJson = await apiResponse.json()
if (!apiJson.data?.attributes) { if (!apiJson.data?.attributes) {
getVerifiedUserFailCounter.add(1, { getVerifiedUserFailCounter.add(1, {
error_type: "data_error", error_type: "data_error",
})
console.error(
"api.user.profile getVerifiedUser data error",
JSON.stringify({
apiResponse: apiJson,
}) })
) console.error(
return null "api.user.profile getVerifiedUser data error",
} JSON.stringify({
apiResponse: apiJson,
})
)
return null
}
const verifiedData = getUserSchema.safeParse(apiJson) const verifiedData = getUserSchema.safeParse(apiJson)
if (!verifiedData.success) { if (!verifiedData.success) {
getVerifiedUserFailCounter.add(1, { getVerifiedUserFailCounter.add(1, {
error_type: "validation_error", error_type: "validation_error",
error: JSON.stringify(verifiedData.error), error: JSON.stringify(verifiedData.error),
})
console.error(
"api.user.profile validation error",
JSON.stringify({
errors: verifiedData.error,
}) })
) console.error(
return null "api.user.profile validation error",
JSON.stringify({
errors: verifiedData.error,
})
)
return null
}
getVerifiedUserSuccessCounter.add(1)
console.info("api.user.profile getVerifiedUser success", JSON.stringify({}))
return verifiedData
} }
getVerifiedUserSuccessCounter.add(1) )
console.info("api.user.profile getVerifiedUser success", JSON.stringify({}))
return verifiedData
}
function parsedUser(data: User, isMFA: boolean) { function parsedUser(data: User, isMFA: boolean) {
const country = countries.find((c) => c.code === data.address.countryCode) const country = countries.find((c) => c.code === data.address.countryCode)

View File

@@ -0,0 +1,14 @@
import {
Reward,
SurpriseReward,
} from "@/server/routers/contentstack/reward/output"
export interface Surprise extends Reward {
endsAt: SurpriseReward["endsAt"]
id: SurpriseReward["id"]
}
export interface SurprisesProps {
surprises: Surprise[]
membershipNumber?: string
}

View File

@@ -8,17 +8,27 @@ import type { Locations } from "@/types/trpc/routers/hotel/locations"
export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema> export type BookingWidgetSchema = z.output<typeof bookingWidgetSchema>
export type BookingWidgetSearchParams = {
city?: string
hotel?: string
fromDate?: string
toDate?: string
room?: string
}
export type BookingWidgetType = VariantProps< export type BookingWidgetType = VariantProps<
typeof bookingWidgetVariants typeof bookingWidgetVariants
>["type"] >["type"]
export interface BookingWidgetProps { export interface BookingWidgetProps {
type?: BookingWidgetType type?: BookingWidgetType
searchParams?: BookingWidgetSearchParams
} }
export interface BookingWidgetClientProps { export interface BookingWidgetClientProps {
locations: Locations locations: Locations
type?: BookingWidgetType type?: BookingWidgetType
searchParams?: BookingWidgetSearchParams
} }
export interface BookingWidgetToggleButtonProps { export interface BookingWidgetToggleButtonProps {

View File

@@ -1,8 +1,10 @@
import type { PointOfInterest } from "@/types/hotel" import type { PointOfInterest } from "@/types/hotel"
import type { Coordinates } from "../../maps/coordinates"
export interface SidebarProps { export interface SidebarProps {
hotelName: string hotelName: string
pointsOfInterest: PointOfInterest[] pointsOfInterest: PointOfInterest[]
activePoi: PointOfInterest["name"] | null activePoi: PointOfInterest["name"] | null
onActivePoiChange: (poi: PointOfInterest["name"] | null) => void onActivePoiChange: (poi: PointOfInterest["name"] | null) => void
coordinates: Coordinates
} }

View File

@@ -0,0 +1,17 @@
interface Child {
bed: string
age: number
}
interface Room {
adults: number
child?: Child[]
}
export interface SelectHotelSearchParams {
city: string
fromDate: string
toDate: string
room: Room[]
[key: string]: string | string[] | Room[]
}

View File

@@ -0,0 +1,8 @@
import { RoomConfiguration } from "@/server/routers/hotels/output"
import { RoomData } from "@/types/hotel"
export type RoomSidePeekProps = {
roomConfiguration: RoomConfiguration
selectedRoom?: RoomData
}

Some files were not shown because too many files have changed in this diff Show More