Merge branch 'develop' into feat/SW-185-implement-footer-navigation

# Conflicts:
#	app/[lang]/(live)/layout.tsx
#	components/Icons/get-icon-by-icon-name.ts
#	types/components/icon.ts
This commit is contained in:
Pontus Dreij
2024-08-21 14:21:57 +02:00
87 changed files with 2056 additions and 10893 deletions
@@ -1,21 +1,10 @@
.container {
display: none;
border-top: 1px solid var(--Base-Border-Subtle);
border-bottom: 1px solid var(--Base-Border-Subtle);
}
/**
* Update the styles after mobile UX is ready
*/
@media screen and (min-width: 1367px) {
@media screen and (max-width: 1367px) {
.container {
display: grid;
padding: 0 var(--Spacing-x5);
gap: var(--Spacing-x3);
}
.form {
display: grid;
gap: var(--Spacing-x5);
grid-template-columns: repeat(6, auto);
align-items: center;
display: none;
}
}
+4 -55
View File
@@ -1,62 +1,11 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { dt } from "@/lib/dt"
import Button from "../TempDesignSystem/Button"
import { bookingWidgetSchema } from "./schema"
import Form from "../Forms/BookingWidget"
import styles from "./bookingWidget.module.css"
import { type BookingWidgetSchema } from "@/types/components/bookingWidget"
export function BookingWidget() {
const methods = useForm<BookingWidgetSchema>({
defaultValues: {
search: {
stayType: "",
stayValue: "",
},
nights: {
// 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.
fromDate: dt().utc().format("DD/MM/YYYY"),
toDate: dt().utc().add(1, "day").format("DD/MM/YYYY"),
},
bookingCode: "",
redemption: false,
voucher: false,
rooms: [
{
adults: 1,
childs: [],
},
],
},
mode: "all",
resolver: zodResolver(bookingWidgetSchema),
reValidateMode: "onChange",
})
function onSubmit(data: BookingWidgetSchema) {
console.log(data)
// Parse data and route accordignly to Select hotel or select room-rate page
console.log("to be routing")
}
return (
<div id="booking-widget" className={styles.container}>
<form onSubmit={methods.handleSubmit(onSubmit)} className={styles.form}>
<FormProvider {...methods}>
<div>Search</div>
<div>Nights</div>
<div>Rooms</div>
<div>Bonus code</div>
<div>Bonus cheque or reward nights</div>
<Button type="submit">Search</Button>
</FormProvider>
</form>
</div>
<section className={styles.container}>
<Form />
</section>
)
}
+12 -7
View File
@@ -18,11 +18,16 @@ export default async function HotelPage() {
return null
}
const lang = getLang()
const { attributes, roomCategories } = await serverClient().hotel.getHotel({
const hotelData = await serverClient().hotel.getHotel({
hotelId: hotelPageIdentifierData.hotel_page_id,
language: lang,
include: ["RoomCategories"],
})
if (!hotelData) {
return null
}
const { hotel, roomCategories } = hotelData
return (
<div className={styles.pageContainer}>
@@ -30,14 +35,14 @@ export default async function HotelPage() {
<main className={styles.mainSection}>
<div className={styles.introContainer}>
<IntroSection
hotelName={attributes.name}
hotelDescription={attributes.hotelContent.texts.descriptions.short}
location={attributes.location}
address={attributes.address}
tripAdvisor={attributes.ratings.tripAdvisor}
hotelName={hotel.name}
hotelDescription={hotel.hotelContent.texts.descriptions.short}
location={hotel.location}
address={hotel.address}
tripAdvisor={hotel.ratings?.tripAdvisor}
/>
<SidePeeks />
<AmenitiesList detailedFacilities={attributes.detailedFacilities} />
<AmenitiesList detailedFacilities={hotel.detailedFacilities} />
</div>
<Rooms rooms={roomCategories} />
</main>
@@ -30,10 +30,17 @@ export default async function IntroSection({
)
const lang = getLang()
const formattedLocationText = `${streetAddress}, ${city} (${formattedDistanceText})`
const formattedTripAdvisorText = intl.formatMessage(
{ id: "Tripadvisor reviews" },
{ rating: tripAdvisor.rating, count: tripAdvisor.numberOfReviews }
const hasTripAdvisorData = !!(
tripAdvisor?.rating &&
tripAdvisor?.numberOfReviews &&
tripAdvisor?.webUrl
)
const formattedTripAdvisorText = hasTripAdvisorData
? intl.formatMessage(
{ id: "Tripadvisor reviews" },
{ rating: tripAdvisor.rating, count: tripAdvisor.numberOfReviews }
)
: ""
return (
<section className={styles.introSection}>
@@ -45,17 +52,19 @@ export default async function IntroSection({
<Title level="h2">{hotelName}</Title>
</div>
<Body color="textMediumContrast">{formattedLocationText}</Body>
<Link
className={styles.introLink}
target="_blank"
variant="icon"
textDecoration="underline"
color="peach80"
href={tripAdvisor.webUrl}
>
<TripAdvisorIcon color="peach80" />
{formattedTripAdvisorText}
</Link>
{hasTripAdvisorData && (
<Link
className={styles.introLink}
target="_blank"
variant="icon"
textDecoration="underline"
color="peach80"
href={tripAdvisor.webUrl}
>
<TripAdvisorIcon color="peach80" />
{formattedTripAdvisorText}
</Link>
)}
</div>
<div className={styles.subtitleContent}>
<Preamble>{hotelDescription}</Preamble>
@@ -19,7 +19,7 @@ export function RoomCard({
subtitle,
title,
}: RoomCardProps) {
const { formatMessage } = useIntl()
const intl = useIntl()
const mainImage = images[0]
function handleImageClick() {
@@ -35,11 +35,12 @@ export function RoomCard({
return (
<article className={styles.roomCard}>
<button className={styles.imageWrapper} onClick={handleImageClick}>
{badgeTextTransKey && (
<span className={styles.badge}>
{formatMessage({ id: badgeTextTransKey })}
</span>
)}
{/* TODO: re-enable once we have support for badge text from API team. */}
{/* {badgeTextTransKey && ( */}
{/* <span className={styles.badge}> */}
{/* {intl.formatMessage({ id: badgeTextTransKey })} */}
{/* </span> */}
{/* )} */}
<span className={styles.imageCount}>
<ImageIcon color="white" />
{images.length}
@@ -67,7 +68,7 @@ export function RoomCard({
variant="underscored"
onClick={handleRoomCtaClick}
>
{formatMessage({ id: "hotelPages.rooms.roomCard.seeRoomDetails" })}
{intl.formatMessage({ id: "See room details" })}
</Link>
</div>
</article>
@@ -10,7 +10,7 @@ import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
export default function TabNavigation() {
const hash = useHash()
const { formatMessage } = useIntl()
const intl = useIntl()
const hotelTabLinks: { href: HotelHashValues; text: string }[] = [
{ href: HotelHashValues.overview, text: "Overview" },
{ href: HotelHashValues.rooms, text: "Rooms" },
@@ -35,7 +35,7 @@ export default function TabNavigation() {
color="burgundy"
textDecoration="none"
>
{formatMessage({ id: link.text })}
{intl.formatMessage({ id: link.text })}
</Link>
)
})}
+1
View File
@@ -13,6 +13,7 @@ const facilityToIconMap: { [key: string]: IconName } = {
"Meeting rooms": IconName.People2,
"Meeting / conference facilities": IconName.People2,
"Pet-friendly rooms": IconName.Pets,
Sauna: IconName.Sauna,
Restaurant: IconName.Restaurant,
}
+1 -1
View File
@@ -28,7 +28,7 @@ export default async function Header({
/**
* ToDo: Create logic to get this info from ContentStack based on page
* */
const hideBookingWidget = true
const hideBookingWidget = false
if (!data) {
return null
@@ -0,0 +1,40 @@
.input {
display: flex;
gap: var(--Spacing-x2);
}
.input input[type="text"] {
border: none;
padding: var(--Spacing-x1) var(--Spacing-x0);
}
.where {
width: 100%;
max-width: 280px;
border-right: 1px solid var(--Base-Surface-Subtle-Normal);
}
.when,
.rooms {
width: 100%;
max-width: 240px;
border-right: 1px solid var(--Base-Surface-Subtle-Normal);
}
.vouchers {
width: 100%;
max-width: 200px;
border-right: 1px solid var(--Base-Surface-Subtle-Normal);
}
.options {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
max-width: 158px;
}
.option {
display: flex;
}
@@ -0,0 +1,54 @@
"use client"
import { useIntl } from "react-intl"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./formContent.module.css"
export default function FormContent() {
const intl = useIntl()
const where = intl.formatMessage({ id: "Where to" })
const when = intl.formatMessage({ id: "When" })
const rooms = intl.formatMessage({ id: "Rooms & Guests" })
const vouchers = intl.formatMessage({ id: "Booking codes and vouchers" })
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
const reward = intl.formatMessage({ id: "Book reward night" })
return (
<div className={styles.input}>
<div className={styles.where}>
<Caption color="red">{where}</Caption>
<input type="text" placeholder={where} />
</div>
<div className={styles.when}>
<Caption color="red" textTransform="bold">
{when}
</Caption>
<input type="text" placeholder={when} />
</div>
<div className={styles.rooms}>
<Caption color="red" textTransform="bold">
{rooms}
</Caption>
<input type="text" placeholder={rooms} />
</div>
<div className={styles.vouchers}>
<Caption color="textMediumContrast" textTransform="bold">
{vouchers}
</Caption>
<input type="text" placeholder={vouchers} />
</div>
<div className={styles.options}>
<div className={styles.option}>
<input type="checkbox" />
<Caption color="textMediumContrast">{bonus}</Caption>
</div>
<div className={styles.option}>
<input type="checkbox" />
<Caption color="textMediumContrast">{reward}</Caption>
</div>
</div>
</div>
)
}
@@ -0,0 +1,15 @@
.section {
display: flex;
align-items: center;
max-width: 1432px;
padding: var(--Spacing-x2) var(--Spacing-x5);
}
.form {
width: 100%;
}
.button {
width: 118px;
justify-content: center;
}
+80
View File
@@ -0,0 +1,80 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import FormContent from "./FormContent"
import { bookingWidgetSchema } from "./schema"
import styles from "./form.module.css"
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
const formId = "booking-widget"
export default function Form() {
const intl = useIntl()
const methods = useForm<BookingWidgetSchema>({
defaultValues: {
search: {
stayType: "",
stayValue: "",
},
nights: {
// 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.
fromDate: dt().utc().format("DD/MM/YYYY"),
toDate: dt().utc().add(1, "day").format("DD/MM/YYYY"),
},
bookingCode: "",
redemption: false,
voucher: false,
rooms: [
{
adults: 1,
childs: [],
},
],
},
mode: "all",
resolver: zodResolver(bookingWidgetSchema),
reValidateMode: "onChange",
})
function onSubmit(data: BookingWidgetSchema) {
console.log(data)
// Parse data and route accordignly to Select hotel or select room-rate page
console.log("to be routing")
}
return (
<section className={styles.section}>
<form
onSubmit={methods.handleSubmit(onSubmit)}
className={styles.form}
id={formId}
>
<FormProvider {...methods}>
<FormContent />
</FormProvider>
</form>
<Button
type="submit"
form={formId}
size="small"
theme="base"
intent="primary"
className={styles.button}
>
<Caption color="white" textTransform="bold">
{intl.formatMessage({ id: "Find hotels" })}
</Caption>
</Button>
</section>
)
}
+40
View File
@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CloseLargeIcon({
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_1756_2612"
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_1756_2612)">
<path
d="M12 13.5422L6.34057 19.2016C6.12719 19.415 5.87017 19.5193 5.5695 19.5144C5.26882 19.5096 5.0118 19.4004 4.79842 19.1871C4.59474 18.9737 4.49532 18.7191 4.50017 18.4233C4.50502 18.1274 4.60928 17.8777 4.81297 17.674L10.4578 12L4.81297 6.32606C4.60928 6.12237 4.50744 5.87262 4.50744 5.5768C4.50744 5.28098 4.60928 5.02638 4.81297 4.813C5.01665 4.59961 5.26882 4.4905 5.5695 4.48565C5.87017 4.4808 6.12719 4.58507 6.34057 4.79845L12 10.4579L17.6594 4.79845C17.8728 4.58507 18.1298 4.4808 18.4305 4.48565C18.7312 4.4905 18.9882 4.59961 19.2016 4.813C19.4053 5.02638 19.5047 5.28098 19.4998 5.5768C19.495 5.87262 19.3907 6.12237 19.187 6.32606L13.5422 12L19.187 17.674C19.3907 17.8777 19.4926 18.1274 19.4926 18.4233C19.4926 18.7191 19.3907 18.9737 19.187 19.1871C18.9834 19.4004 18.7312 19.5096 18.4305 19.5144C18.1298 19.5193 17.8728 19.415 17.6594 19.2016L12 13.5422Z"
fill="#57514E"
/>
</g>
</svg>
)
}
+40
View File
@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CrossCircleIcon({
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_1756_2637"
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_1756_2637)">
<path
d="M12 13.3L14.9 16.2C15.075 16.375 15.2917 16.4625 15.55 16.4625C15.8083 16.4625 16.025 16.375 16.2 16.2C16.375 16.025 16.4625 15.8083 16.4625 15.55C16.4625 15.2917 16.375 15.075 16.2 14.9L13.3 12L16.2 9.1C16.375 8.925 16.4625 8.70833 16.4625 8.45C16.4625 8.19167 16.375 7.975 16.2 7.8C16.025 7.625 15.8083 7.5375 15.55 7.5375C15.2917 7.5375 15.075 7.625 14.9 7.8L12 10.7L9.1 7.8C8.925 7.625 8.70833 7.5375 8.45 7.5375C8.19167 7.5375 7.975 7.625 7.8 7.8C7.625 7.975 7.5375 8.19167 7.5375 8.45C7.5375 8.70833 7.625 8.925 7.8 9.1L10.7 12L7.8 14.9C7.625 15.075 7.5375 15.2917 7.5375 15.55C7.5375 15.8083 7.625 16.025 7.8 16.2C7.975 16.375 8.19167 16.4625 8.45 16.4625C8.70833 16.4625 8.925 16.375 9.1 16.2L12 13.3ZM12 21.75C10.6516 21.75 9.38434 21.4936 8.19838 20.9809C7.01239 20.4682 5.98075 19.7724 5.10345 18.8934C4.22615 18.0145 3.53125 16.9826 3.01875 15.7978C2.50625 14.613 2.25 13.3471 2.25 12C2.25 10.6516 2.50636 9.38434 3.01908 8.19838C3.53179 7.01239 4.22762 5.98075 5.10658 5.10345C5.98553 4.22615 7.01739 3.53125 8.20218 3.01875C9.38698 2.50625 10.6529 2.25 12 2.25C13.3484 2.25 14.6157 2.50636 15.8016 3.01908C16.9876 3.53179 18.0193 4.22762 18.8966 5.10658C19.7739 5.98553 20.4688 7.01739 20.9813 8.20217C21.4938 9.38697 21.75 10.6529 21.75 12C21.75 13.3484 21.4936 14.6157 20.9809 15.8016C20.4682 16.9876 19.7724 18.0193 18.8934 18.8966C18.0145 19.7739 16.9826 20.4688 15.7978 20.9813C14.613 21.4938 13.3471 21.75 12 21.75Z"
fill="white"
/>
</g>
</svg>
)
}
+36
View File
@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function SaunaIcon({ 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_83_359"
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_359)">
<path
d="M4 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 16V20H6V18H5V16H4ZM4 14H5C5 13.7167 5.09583 13.4792 5.2875 13.2875C5.47917 13.0958 5.71667 13 6 13V10.5C6 10.0833 6.14583 9.72917 6.4375 9.4375C6.72917 9.14583 7.08333 9 7.5 9H9.5C9.91667 9 10.2708 9.14583 10.5625 9.4375C10.8542 9.72917 11 10.0833 11 10.5V13C11.2833 13 11.5208 13.0958 11.7125 13.2875C11.9042 13.4792 12 13.7167 12 14H20V4H4V14ZM6.5 16.5H10.5V14.5H6.5V16.5ZM8.5 8C8.08333 8 7.72917 7.85417 7.4375 7.5625C7.14583 7.27083 7 6.91667 7 6.5C7 6.08333 7.14583 5.72917 7.4375 5.4375C7.72917 5.14583 8.08333 5 8.5 5C8.91667 5 9.27083 5.14583 9.5625 5.4375C9.85417 5.72917 10 6.08333 10 6.5C10 6.91667 9.85417 7.27083 9.5625 7.5625C9.27083 7.85417 8.91667 8 8.5 8ZM8 20H9V18H8V20ZM11 20H20V16H12V18H11V20ZM12.25 10C12.2833 9.78333 12.3042 9.62083 12.3125 9.5125C12.3208 9.40417 12.325 9.29167 12.325 9.175C12.325 8.99167 12.2875 8.81667 12.2125 8.65C12.1375 8.48333 11.9833 8.25833 11.75 7.975C11.5 7.65833 11.3125 7.34167 11.1875 7.025C11.0625 6.70833 11 6.38333 11 6.05C11 5.91667 11.0083 5.77917 11.025 5.6375L11.1 5H12.6C12.5667 5.18333 12.5417 5.35417 12.525 5.5125C12.5083 5.67083 12.5 5.85 12.5 6.05C12.5 6.23333 12.5375 6.4125 12.6125 6.5875C12.6875 6.7625 12.8167 6.95 13 7.15C13.3 7.51667 13.5167 7.8625 13.65 8.1875C13.7833 8.5125 13.85 8.84167 13.85 9.175C13.85 9.35833 13.8417 9.50833 13.825 9.625C13.8083 9.74167 13.7833 9.86667 13.75 10H12.25ZM14.75 10C14.7833 9.78333 14.8042 9.62083 14.8125 9.5125C14.8208 9.40417 14.825 9.29167 14.825 9.175C14.825 8.99167 14.7875 8.81667 14.7125 8.65C14.6375 8.48333 14.4833 8.25833 14.25 7.975C14 7.65833 13.8125 7.34167 13.6875 7.025C13.5625 6.70833 13.5 6.38333 13.5 6.05C13.5 5.91667 13.5083 5.77917 13.525 5.6375L13.6 5H15.1C15.0667 5.18333 15.0417 5.35417 15.025 5.5125C15.0083 5.67083 15 5.85 15 6.05C15 6.23333 15.0375 6.4125 15.1125 6.5875C15.1875 6.7625 15.3167 6.95 15.5 7.15C15.8 7.51667 16.0167 7.8625 16.15 8.1875C16.2833 8.5125 16.35 8.84167 16.35 9.175C16.35 9.35833 16.3417 9.50833 16.325 9.625C16.3083 9.74167 16.2833 9.86667 16.25 10H14.75ZM17.3 10C17.3333 9.78333 17.3542 9.62083 17.3625 9.5125C17.3708 9.40417 17.375 9.29167 17.375 9.175C17.375 8.99167 17.3375 8.81667 17.2625 8.65C17.1875 8.48333 17.0333 8.25833 16.8 7.975C16.55 7.65833 16.3625 7.34167 16.2375 7.025C16.1125 6.70833 16.05 6.38333 16.05 6.05C16.05 5.91667 16.0583 5.77917 16.075 5.6375L16.15 5H17.65C17.6167 5.18333 17.5917 5.35417 17.575 5.5125C17.5583 5.67083 17.55 5.85 17.55 6.05C17.55 6.23333 17.5875 6.4125 17.6625 6.5875C17.7375 6.7625 17.8667 6.95 18.05 7.15C18.35 7.51667 18.5667 7.8625 18.7 8.1875C18.8333 8.5125 18.9 8.84167 18.9 9.175C18.9 9.35833 18.8917 9.50833 18.875 9.625C18.8583 9.74167 18.8333 9.86667 18.8 10H17.3Z"
fill="#26201E"
/>
</g>
</svg>
)
}
+40
View File
@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function WarningTriangleIcon({
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_1756_2606"
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_1756_2606)">
<path
d="M2.95563 20.775C2.7768 20.775 2.61564 20.7315 2.47216 20.6444C2.32869 20.5573 2.2171 20.4425 2.13738 20.3C2.05405 20.1583 2.0103 20.0063 2.00613 19.8438C2.00196 19.6813 2.04571 19.5208 2.13738 19.3625L11.1874 3.75001C11.279 3.59167 11.3986 3.47501 11.546 3.40001C11.6934 3.32501 11.8447 3.28751 11.9999 3.28751C12.155 3.28751 12.3063 3.32501 12.4538 3.40001C12.6012 3.47501 12.7207 3.59167 12.8124 3.75001L21.8624 19.3625C21.954 19.5208 21.9978 19.6813 21.9936 19.8438C21.9895 20.0063 21.9457 20.1583 21.8624 20.3C21.779 20.4417 21.6663 20.5563 21.524 20.6438C21.3818 20.7313 21.2237 20.775 21.0499 20.775H2.95563ZM11.9973 17.875C12.2657 17.875 12.4915 17.7842 12.6749 17.6026C12.8582 17.4211 12.9499 17.1961 12.9499 16.9276C12.9499 16.6592 12.8591 16.4333 12.6775 16.25C12.4959 16.0667 12.2709 15.975 12.0025 15.975C11.7341 15.975 11.5082 16.0658 11.3249 16.2474C11.1415 16.4289 11.0499 16.6539 11.0499 16.9224C11.0499 17.1908 11.1407 17.4167 11.3223 17.6C11.5038 17.7833 11.7288 17.875 11.9973 17.875ZM12.0124 15C12.2707 15 12.4915 14.9083 12.6749 14.725C12.8582 14.5417 12.9499 14.3208 12.9499 14.0625V11.0125C12.9499 10.7542 12.8582 10.5333 12.6749 10.35C12.4915 10.1667 12.2707 10.075 12.0124 10.075C11.754 10.075 11.5332 10.1667 11.3499 10.35C11.1665 10.5333 11.0749 10.7542 11.0749 11.0125V14.0625C11.0749 14.3208 11.1665 14.5417 11.3499 14.725C11.5332 14.9083 11.754 15 12.0124 15Z"
fill="white"
/>
</g>
</svg>
)
}
+12
View File
@@ -17,8 +17,10 @@ import {
ChevronDownIcon,
ChevronRightIcon,
CloseIcon,
CloseLarge,
CoffeeIcon,
ConciergeIcon,
CrossCircle,
DoorOpenIcon,
ElectricBikeIcon,
EmailIcon,
@@ -36,7 +38,9 @@ import {
PhoneIcon,
PlusCircleIcon,
RestaurantIcon,
SaunaIcon,
TshirtWashIcon,
WarningTriangle,
WifiIcon,
} from "."
@@ -62,6 +66,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return CellphoneIcon
case IconName.Check:
return CheckIcon
case IconName.CrossCircle:
return CrossCircle
case IconName.CheckCircle:
return CheckCircleIcon
case IconName.ChevronDown:
@@ -70,6 +76,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return ChevronRightIcon
case IconName.Close:
return CloseIcon
case IconName.CloseLarge:
return CloseLarge
case IconName.Coffee:
return CoffeeIcon
case IconName.Concierge:
@@ -114,8 +122,12 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return RestaurantIcon
case IconName.Tripadvisor:
return TripAdvisorIcon
case IconName.Sauna:
return SaunaIcon
case IconName.TshirtWash:
return TshirtWashIcon
case IconName.WarningTriangle:
return WarningTriangle
case IconName.Wifi:
return WifiIcon
default:
+1 -1
View File
@@ -44,5 +44,5 @@
.white,
.white * {
fill: var(--Scandic-Opacity-White-100);
fill: var(--UI-Opacity-White-100);
}
+4
View File
@@ -11,9 +11,11 @@ export { default as CheckCircleIcon } from "./CheckCircle"
export { default as ChevronDownIcon } from "./ChevronDown"
export { default as ChevronRightIcon } from "./ChevronRight"
export { default as CloseIcon } from "./Close"
export { default as CloseLarge } from "./CloseLarge"
export { default as CoffeeIcon } from "./Coffee"
export { default as ConciergeIcon } from "./Concierge"
export { default as CreditCard } from "./CreditCard"
export { default as CrossCircle } from "./CrossCircle"
export { default as Delete } from "./Delete"
export { default as DoorOpenIcon } from "./DoorOpen"
export { default as ElectricBikeIcon } from "./ElectricBike"
@@ -32,6 +34,8 @@ export { default as PetsIcon } from "./Pets"
export { default as PhoneIcon } from "./Phone"
export { default as PlusCircleIcon } from "./PlusCircle"
export { default as RestaurantIcon } from "./Restaurant"
export { default as SaunaIcon } from "./Sauna"
export { default as ScandicLogoIcon } from "./ScandicLogo"
export { default as TshirtWashIcon } from "./TshirtWash"
export { default as WarningTriangle } from "./WarningTriangle"
export { default as WifiIcon } from "./Wifi"
@@ -13,7 +13,7 @@
"title": "10 % rabatt på mat under helger"
},
{
"title": "Kostnadsfri mocktail för barn under vistelse"
"title": "Fri mocktail för barn under vistelse"
}
]
},
@@ -1,76 +0,0 @@
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n"
import Row from "./Row"
import styles from "./desktop.module.css"
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
const tableHeadings = [
"Arrival date",
"Description",
"Booking number",
"Transaction date",
"Points",
]
export default async function DesktopTable({ transactions }: TableProps) {
const { formatMessage } = await getIntl()
return (
<div className={styles.container}>
{transactions.length ? (
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
{tableHeadings.map((heading) => (
<th key={heading} className={styles.th}>
<Body textTransform="bold">
{formatMessage({ id: heading })}
</Body>
</th>
))}
</tr>
</thead>
<tbody>
{transactions.map((transaction) => (
<Row
key={transaction.confirmationNumber}
transaction={transaction}
/>
))}
</tbody>
</table>
) : (
// TODO: add once pagination is available through API
// <Button
// disabled={isFetching}
// intent="primary"
// bgcolor="white"
// type="button"
// onClick={loadMoreData}
// >
// {formatMessage({id:"See more transactions"})}
// </Button>
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
{tableHeadings.map((heading) => (
<th key={heading} className={styles.th}>
{heading}
</th>
))}
</tr>
</thead>
<tbody>
<tr>
<td colSpan={tableHeadings.length} className={styles.placeholder}>
{formatMessage({ id: "No transactions available" })}
</td>
</tr>
</tbody>
</table>
)}
</div>
)
}
@@ -0,0 +1,55 @@
"use client"
import { keepPreviousData } from "@tanstack/react-query"
import { useState } from "react"
import { trpc } from "@/lib/trpc/client"
import LoadingSpinner from "@/components/LoadingSpinner"
import DesktopTable from "./Desktop"
import MobileTable from "./Mobile"
import Pagination from "./Pagination"
import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
export default function TransactionTable({
initialJourneyTransactions,
}: {
initialJourneyTransactions: {
data: { transactions: Transactions }
meta: { totalPages: number }
}
}) {
const limit = 5
const [page, setPage] = useState(1)
const { data, isFetching, isLoading } =
trpc.user.transaction.friendTransactions.useQuery(
{
limit,
page,
},
{
// TODO: fix the initial data issues on page load
// initialData: initialJourneyTransactions,
placeholderData: keepPreviousData,
}
)
return isLoading ? (
<LoadingSpinner />
) : (
<>
<MobileTable transactions={data?.data.transactions || []} />
<DesktopTable transactions={data?.data.transactions || []} />
{data && data.meta.totalPages > 1 ? (
<Pagination
handlePageChange={setPage}
pageCount={data.meta.totalPages}
isFetching={isFetching}
currentPage={page}
/>
) : null}
</>
)
}
@@ -1,7 +1,10 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import useLang from "@/hooks/useLang"
import AwardPoints from "./AwardPoints"
@@ -9,17 +12,16 @@ import styles from "./row.module.css"
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
export default async function Row({ transaction }: RowProps) {
const { formatMessage } = await getIntl()
export default function Row({ transaction }: RowProps) {
const intl = useIntl()
const lang = useLang()
const description =
transaction.hotelName && transaction.city
? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${formatMessage({ id: "nights" })}`
: `${transaction.nights} ${formatMessage({ id: "nights" })}`
const arrival = dt(transaction.checkinDate)
.locale(getLang())
.format("DD MMM YYYY")
? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
: `${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
const departure = dt(transaction.checkoutDate)
.locale(getLang())
.locale(lang)
.format("DD MMM YYYY")
return (
<tr className={styles.tr}>
@@ -28,6 +28,25 @@
background-color: #fff;
}
.footer {
background-color: var(--Scandic-Brand-Pale-Peach);
border-left: 1px solid var(--Scandic-Brand-Pale-Peach);
border-right: 1px solid var(--Scandic-Brand-Pale-Peach);
display: flex;
padding: 20px 32px;
justify-content: center;
}
.loadMoreButton {
border: none;
background-color: transparent;
color: var(--Main-Brand-Burgundy);
font-size: var(--typography-Caption-Bold-Desktop-fontSize);
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
cursor: pointer;
}
@media screen and (min-width: 768px) {
.container {
display: flex;
@@ -0,0 +1,70 @@
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Row from "./Row"
import styles from "./desktop.module.css"
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
const tableHeadings = [
"Arrival date",
"Description",
"Booking number",
"Transaction date",
"Points",
]
export default function DesktopTable({ transactions }: TableProps) {
const intl = useIntl()
return (
<div className={styles.container}>
{transactions.length ? (
<div>
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
{tableHeadings.map((heading) => (
<th key={heading} className={styles.th}>
<Body textTransform="bold">
{intl.formatMessage({ id: heading })}
</Body>
</th>
))}
</tr>
</thead>
<tbody>
{transactions.map((transaction, idx) => (
<Row
key={`${transaction.confirmationNumber}-${idx}`}
transaction={transaction}
/>
))}
</tbody>
</table>
</div>
) : (
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
{tableHeadings.map((heading) => (
<th key={heading} className={styles.th}>
{heading}
</th>
))}
</tr>
</thead>
<tbody>
<tr>
<td colSpan={tableHeadings.length} className={styles.placeholder}>
{intl.formatMessage({ id: "No transactions available" })}
</td>
</tr>
</tbody>
</table>
)}
</div>
)
}
@@ -1,16 +1,18 @@
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/AwardPoints"
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import useLang from "@/hooks/useLang"
import styles from "./mobile.module.css"
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
export default async function MobileTable({ transactions }: TableProps) {
const { formatMessage } = await getIntl()
export default function MobileTable({ transactions }: TableProps) {
const intl = useIntl()
const lang = useLang()
return (
<div className={styles.container}>
<table className={styles.table}>
@@ -18,29 +20,34 @@ export default async function MobileTable({ transactions }: TableProps) {
<tr>
<Body asChild>
<th className={styles.th}>
{formatMessage({ id: "Transactions" })}
{intl.formatMessage({ id: "Transactions" })}
</th>
</Body>
<Body asChild>
<th className={styles.th}>{formatMessage({ id: "Points" })}</th>
<th className={styles.th}>
{intl.formatMessage({ id: "Points" })}
</th>
</Body>
</tr>
</thead>
<tbody>
{transactions.length ? (
transactions.map((transaction) => (
<tr className={styles.tr} key={transaction.confirmationNumber}>
transactions.map((transaction, idx) => (
<tr
className={styles.tr}
key={`${transaction.confirmationNumber}-${idx}`}
>
<td className={`${styles.td} ${styles.transactionDetails}`}>
<span className={styles.transactionDate}>
{dt(transaction.checkinDate)
.locale(getLang())
.locale(lang)
.format("DD MMM YYYY")}
</span>
{transaction.hotelName && transaction.city ? (
<span>{`${transaction.hotelName}, ${transaction.city}`}</span>
) : null}
<span>
{`${transaction.nights} ${formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`}
{`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`}
</span>
</td>
<AwardPoints awardPoints={transaction.awardPoints} />
@@ -49,7 +56,9 @@ export default async function MobileTable({ transactions }: TableProps) {
) : (
<tr>
<td className={styles.placeholder} colSpan={2}>
{formatMessage({ id: "There are no transactions to display" })}
{intl.formatMessage({
id: "There are no transactions to display",
})}
</td>
</tr>
)}
@@ -34,6 +34,16 @@
padding: var(--Spacing-x4);
border: 1px solid var(--Main-Grey-10);
}
.loadMoreButton {
background-color: var(--Main-Grey-10);
border: none;
display: flex;
align-items: center;
justify-content: center;
gap: var(--Spacing-x-half);
padding: var(--Spacing-x2);
width: 100%;
}
@media screen and (min-width: 768px) {
.container {
@@ -0,0 +1,68 @@
import { ChevronRightIcon } from "@/components/Icons"
import styles from "./pagination.module.css"
import {
PaginationButtonProps,
PaginationProps,
} from "@/types/components/myPages/myPage/earnAndBurn"
function PaginationButton({
children,
isActive,
handleClick,
disabled,
}: React.PropsWithChildren<PaginationButtonProps>) {
return (
<button
type={"button"}
disabled={disabled}
onClick={handleClick}
className={`${styles.paginationButton} ${isActive ? styles.paginationButtonActive : ""}`}
>
{children}
</button>
)
}
export default function Pagination({
pageCount,
isFetching,
handlePageChange,
currentPage,
}: PaginationProps) {
const isOnFirstPage = currentPage === 1
const isOnLastPage = currentPage === pageCount
return (
<div className={styles.pagination}>
<PaginationButton
disabled={isFetching || isOnFirstPage}
handleClick={() => {
handlePageChange(currentPage - 1)
}}
>
<ChevronRightIcon className={styles.chevronLeft} />
</PaginationButton>
{[...Array(pageCount)].map((_, idx) => (
<PaginationButton
isActive={currentPage === idx + 1}
disabled={isFetching || currentPage === idx + 1}
key={idx}
handleClick={() => {
handlePageChange(idx + 1)
}}
>
{idx + 1}
</PaginationButton>
))}
<PaginationButton
disabled={isFetching || isOnLastPage}
handleClick={() => {
handlePageChange(currentPage + 1)
}}
>
<ChevronRightIcon />
</PaginationButton>
</div>
)
}
@@ -0,0 +1,40 @@
.pagination {
display: flex;
justify-content: left;
padding: var(--Spacing-x2);
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-Rounded);
margin: auto;
gap: var(--Spacing-x5);
max-width: 100%;
overflow-x: auto;
}
.paginationButton {
background-color: transparent;
border: none;
cursor: pointer;
height: 32px;
width: 32px;
font-size: var(--typography-Body-Bold-fontSize);
font-weight: var(--typography-Body-Bold-fontWeight);
padding: 0;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.paginationButton[disabled] {
cursor: not-allowed;
}
.chevronLeft {
transform: rotate(180deg);
height: 100%;
}
.paginationButtonActive {
color: var(--WHITE);
background-color: var(--Base-Text-Accent);
border-radius: var(--Corner-radius-Rounded);
}
@@ -0,0 +1,18 @@
import { serverClient } from "@/lib/trpc/server"
import ClientJourney from "./Client"
export default async function JourneyTable() {
const initialJourneyTransactions =
await serverClient().user.transaction.friendTransactions({
page: 1,
limit: 5,
})
if (!initialJourneyTransactions?.data) {
return null
}
return (
<ClientJourney initialJourneyTransactions={initialJourneyTransactions} />
)
}
@@ -1,4 +0,0 @@
.container {
display: grid;
gap: var(--Spacing-x3);
}
@@ -1,11 +1,8 @@
import { serverClient } from "@/lib/trpc/server"
import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"
import DesktopTable from "./Desktop"
import MobileTable from "./Mobile"
import JourneyTable from "./JourneyTable"
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
@@ -14,16 +11,10 @@ export default async function EarnAndBurn({
subtitle,
title,
}: AccountPageComponentProps) {
const transactions =
await serverClient().user.transaction.friendTransactions()
if (!transactions) {
return null
}
return (
<SectionContainer>
<SectionHeader title={title} link={link} subtitle={subtitle} />
<MobileTable transactions={transactions.data} />
<DesktopTable transactions={transactions.data} />
<JourneyTable />
<SectionLink link={link} variant="mobile" />
</SectionContainer>
)
@@ -0,0 +1,3 @@
.addCreditCardButton {
justify-self: flex-start;
}
@@ -1,31 +1,83 @@
"use client"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useEffect } from "react"
import { useIntl } from "react-intl"
import { toast } from "sonner"
import { trpc } from "@/lib/trpc/client"
import { PlusCircleIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import useLang from "@/hooks/useLang"
export default function AddCreditCardButton() {
const { formatMessage } = useIntl()
import styles from "./addCreditCardButton.module.css"
import { type AddCreditCardButtonProps } from "@/types/components/myPages/myProfile/addCreditCardButton"
let hasRunOnce = false
function useAddCardResultToast() {
const intl = useIntl()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
if (hasRunOnce) return
const success = searchParams.get("success")
const failure = searchParams.get("failure")
if (success) {
// setTimeout is used to make sure DOM is loaded before triggering toast. See documentation for more info: https://sonner.emilkowal.ski/toast#render-toast-on-page-load
setTimeout(() => {
toast.success(
intl.formatMessage({ id: "Your card was successfully saved!" })
)
})
} else if (failure) {
setTimeout(() => {
toast.error(intl.formatMessage({ id: "Something went wrong!" }))
})
}
router.replace(pathname)
hasRunOnce = true
}, [intl, pathname, router, searchParams])
}
export default function AddCreditCardButton({
redirectUrl,
}: AddCreditCardButtonProps) {
const intl = useIntl()
const router = useRouter()
const lang = useLang()
useAddCardResultToast()
const initiateAddCard = trpc.user.initiateSaveCard.useMutation({
onSuccess: (result) => (result ? router.push(result.attribute.link) : null),
onError: () =>
toast.error(intl.formatMessage({ id: "Something went wrong!" })),
})
async function handleAddCreditCard() {
// TODO: initiate add credit card flow and redirect user to planet:
// const { url } = trpc.user.creditCard.add.useMutation()
// router.redirect(url)
console.log("Credit card added!")
}
return (
<Button
className={styles.addCreditCardButton}
variant="icon"
theme="base"
intent="text"
onClick={handleAddCreditCard}
onClick={() =>
initiateAddCard.mutate({
language: lang,
mobileToken: false,
redirectUrl,
})
}
wrapping
>
<PlusCircleIcon color="burgundy" />
{formatMessage({ id: "Add new card" })}
{intl.formatMessage({ id: "Add new card" })}
</Button>
)
}
@@ -63,7 +63,7 @@
}
.textMediumContrast {
color: var(--Base-Text-UI-Medium-contrast);
color: var(--UI-Text-Medium-contrast);
}
.white {
@@ -41,6 +41,14 @@
color: var(--UI-Text-Medium-contrast);
}
.red {
color: var(--Scandic-Brand-Scandic-Red);
}
.white {
color: var(--UI-Opacity-White-100);
}
.center {
text-align: center;
}
@@ -9,6 +9,8 @@ const config = {
burgundy: styles.burgundy,
pale: styles.pale,
textMediumContrast: styles.textMediumContrast,
red: styles.red,
white: styles.white,
},
textTransform: {
bold: styles.bold,
@@ -0,0 +1,96 @@
import { ExternalToast, toast as sonnerToast, Toaster } from "sonner"
import {
CheckCircleIcon,
CloseLarge,
CrossCircle,
InfoCircleIcon,
WarningTriangle,
} from "@/components/Icons"
import Button from "../Button"
import Body from "../Text/Body"
import { ToastsProps } from "./toasts"
import { toastVariants } from "./variants"
import styles from "./toasts.module.css"
export function ToastHandler() {
return <Toaster />
}
function getIcon(variant: ToastsProps["variant"]) {
switch (variant) {
case "error":
return CrossCircle
case "info":
return InfoCircleIcon
case "success":
return CheckCircleIcon
case "warning":
return WarningTriangle
}
}
export function Toast({ message, onClose, variant }: ToastsProps) {
const className = toastVariants({ variant })
const Icon = getIcon(variant)
return (
<div className={className}>
<div className={styles.iconContainer}>
{Icon && <Icon color="white" height={24} width={24} />}
</div>
<Body className={styles.message}>{message}</Body>
<Button onClick={onClose} variant="icon" intent="text">
<CloseLarge />
</Button>
</div>
)
}
export const toast = {
success: (message: string, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
<Toast
variant="success"
message={message}
onClose={() => sonnerToast.dismiss(t)}
/>
),
options
),
info: (message: string, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
<Toast
variant="info"
message={message}
onClose={() => sonnerToast.dismiss(t)}
/>
),
options
),
error: (message: string, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
<Toast
variant="error"
message={message}
onClose={() => sonnerToast.dismiss(t)}
/>
),
options
),
warning: (message: string, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
<Toast
variant="warning"
message={message}
onClose={() => sonnerToast.dismiss(t)}
/>
),
options
),
}
@@ -0,0 +1,38 @@
.toast {
display: grid;
grid-template-columns: auto 1fr auto;
border-radius: var(--Corner-radius-Large);
overflow: hidden;
background: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.08);
align-items: center;
width: var(--width);
}
.message {
padding: var(--Spacing-x2) var(--Spacing-x-one-and-half);
}
.success {
--icon-background-color: var(--UI-Semantic-Success);
}
.error {
--icon-background-color: var(--UI-Semantic-Error);
}
.warning {
--icon-background-color: var(--UI-Semantic-Warning);
}
.info {
--icon-background-color: var(--UI-Semantic-Information);
}
.iconContainer {
display: flex;
background-color: var(--icon-background-color);
padding: var(--Spacing-x2);
align-items: center;
justify-content: center;
}
@@ -0,0 +1,10 @@
import { toastVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
export interface ToastsProps
extends Omit<React.AnchorHTMLAttributes<HTMLDivElement>, "color">,
VariantProps<typeof toastVariants> {
message: string
onClose: () => void
}
@@ -0,0 +1,14 @@
import { cva } from "class-variance-authority"
import styles from "./toasts.module.css"
export const toastVariants = cva(styles.toast, {
variants: {
variant: {
success: styles.success,
info: styles.info,
warning: styles.warning,
error: styles.error,
},
},
})
+5 -1
View File
@@ -5,13 +5,17 @@ import { overview } from "@/constants/routes/webviews"
import Link from "@/components/TempDesignSystem/Link"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { webviewSearchParams } from "@/utils/webviews"
import styles from "./linkToOverview.module.css"
export default async function LinkToOverview() {
const { formatMessage } = await getIntl()
const searchParams = webviewSearchParams()
const overviewHref = `${overview[getLang()]}?${searchParams.toString()}`
return (
<Link className={styles.overviewLink} href={overview[getLang()]}>
<Link className={styles.overviewLink} href={overviewHref}>
<ArrowLeft height={20} width={20} />{" "}
{formatMessage({ id: "Go back to overview" })}
</Link>