Merge branch 'develop' into feat/SW-185-implement-footer-navigation
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
export default async function ContentPage() {
|
||||
return null
|
||||
}
|
||||
19
components/ContentType/ContentPage/contentPage.module.css
Normal file
19
components/ContentType/ContentPage/contentPage.module.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.contentPage {
|
||||
padding-bottom: var(--Spacing-x9);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2);
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.innerContent {
|
||||
width: 100%;
|
||||
max-width: var(--max-width-content);
|
||||
}
|
||||
46
components/ContentType/ContentPage/index.tsx
Normal file
46
components/ContentType/ContentPage/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import Hero from "@/components/Hero"
|
||||
import Intro from "@/components/Intro"
|
||||
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
|
||||
import styles from "./contentPage.module.css"
|
||||
|
||||
export default async function ContentPage() {
|
||||
const contentPageRes = await serverClient().contentstack.contentPage.get()
|
||||
|
||||
if (!contentPageRes) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { tracking, contentPage } = contentPageRes
|
||||
const heroImage = contentPage.heroImage
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.contentPage}>
|
||||
<header className={styles.header}>
|
||||
<Intro>
|
||||
<Title as="h2">{contentPage.header.heading}</Title>
|
||||
<Preamble>{contentPage.header.preamble}</Preamble>
|
||||
</Intro>
|
||||
</header>
|
||||
|
||||
<main className={styles.content}>
|
||||
<div className={styles.innerContent}>
|
||||
{heroImage ? (
|
||||
<Hero
|
||||
alt={heroImage.meta.alt || heroImage.meta.caption || ""}
|
||||
src={heroImage.url}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<TrackingSDK pageData={tracking} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import Hero from "@/components/Hero"
|
||||
import Intro from "@/components/Intro"
|
||||
import { Blocks } from "@/components/Loyalty/Blocks"
|
||||
import Sidebar from "@/components/Loyalty/Sidebar"
|
||||
import MaxWidth from "@/components/MaxWidth"
|
||||
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
|
||||
@@ -16,7 +19,7 @@ export default async function LoyaltyPage() {
|
||||
}
|
||||
|
||||
const { tracking, loyaltyPage } = loyaltyPageRes
|
||||
|
||||
const heroImage = loyaltyPage.heroImage
|
||||
return (
|
||||
<>
|
||||
<section className={styles.content}>
|
||||
@@ -24,8 +27,22 @@ export default async function LoyaltyPage() {
|
||||
<Sidebar blocks={loyaltyPage.sidebar} />
|
||||
) : null}
|
||||
|
||||
<MaxWidth className={styles.blocks} tag="main">
|
||||
<Title>{loyaltyPage.heading}</Title>
|
||||
<MaxWidth className={styles.blocks}>
|
||||
<header className={styles.header}>
|
||||
<Intro>
|
||||
<Title as="h2">{loyaltyPage.heading}</Title>
|
||||
{loyaltyPage.preamble ? (
|
||||
<Preamble>{loyaltyPage.preamble}</Preamble>
|
||||
) : null}
|
||||
</Intro>
|
||||
|
||||
{heroImage ? (
|
||||
<Hero
|
||||
alt={heroImage.meta.alt || heroImage.meta.caption || ""}
|
||||
src={heroImage.url}
|
||||
/>
|
||||
) : null}
|
||||
</header>
|
||||
{loyaltyPage.blocks ? <Blocks blocks={loyaltyPage.blocks} /> : null}
|
||||
</MaxWidth>
|
||||
</section>
|
||||
@@ -15,6 +15,11 @@
|
||||
padding-right: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.content {
|
||||
gap: var(--Spacing-x5);
|
||||
|
||||
@@ -44,6 +44,7 @@ export default function LoginButton({
|
||||
id={trackingId}
|
||||
color={color}
|
||||
href={`${login[lang]}?redirectTo=${encodeURIComponent(pathName)}`}
|
||||
prefetch={false}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function Header({
|
||||
/**
|
||||
* ToDo: Create logic to get this info from ContentStack based on page
|
||||
* */
|
||||
const hideBookingWidget = false
|
||||
const hideBookingWidget = true
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
|
||||
14
components/Hero/hero.module.css
Normal file
14
components/Hero/hero.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.hero {
|
||||
height: 400px;
|
||||
margin-bottom: var(--Spacing-x2);
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: var(--Corner-radius-xLarge);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero {
|
||||
height: 480px;
|
||||
}
|
||||
}
|
||||
4
components/Hero/hero.ts
Normal file
4
components/Hero/hero.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface HeroProps {
|
||||
alt: string
|
||||
src: string
|
||||
}
|
||||
17
components/Hero/index.tsx
Normal file
17
components/Hero/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import Image from "@/components/Image"
|
||||
|
||||
import { HeroProps } from "./hero"
|
||||
|
||||
import styles from "./hero.module.css"
|
||||
|
||||
export default async function Hero({ alt, src }: HeroProps) {
|
||||
return (
|
||||
<Image
|
||||
className={styles.hero}
|
||||
alt={alt}
|
||||
height={480}
|
||||
width={1196}
|
||||
src={src}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./introSection.module.css"
|
||||
|
||||
import { IntroSectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
|
||||
export default async function IntroSection({ email }: IntroSectionProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<div>
|
||||
<Title textAlign="center" as="h2">
|
||||
{intl.formatMessage({ id: "Thank you" })}
|
||||
</Title>
|
||||
<Subtitle textAlign="center" textTransform="uppercase">
|
||||
{intl.formatMessage({ id: "We look forward to your visit!" })}
|
||||
</Subtitle>
|
||||
</div>
|
||||
<Body color="burgundy" textAlign="center">
|
||||
{intl.formatMessage({
|
||||
id: "We have sent a detailed confirmation of your booking to your email: ",
|
||||
})}
|
||||
{email}
|
||||
</Body>
|
||||
<div className={styles.buttons}>
|
||||
<Button
|
||||
asChild
|
||||
size="small"
|
||||
theme="base"
|
||||
intent="secondary"
|
||||
className={styles.button}
|
||||
>
|
||||
<Link href="#" color="none">
|
||||
{intl.formatMessage({ id: "Download the Scandic app" })}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="small"
|
||||
theme="base"
|
||||
intent="secondary"
|
||||
className={styles.button}
|
||||
>
|
||||
<Link href="#" color="none">
|
||||
{intl.formatMessage({ id: "View your booking" })}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.buttons {
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./staySection.module.css"
|
||||
|
||||
import { StaySectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
|
||||
export default async function StaySection({ hotel, stay }: StaySectionProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const nightsText =
|
||||
stay.nights > 1
|
||||
? intl.formatMessage({ id: "nights" })
|
||||
: intl.formatMessage({ id: "night" })
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.card}>
|
||||
<Image
|
||||
src={hotel.image}
|
||||
alt=""
|
||||
height={400}
|
||||
width={200}
|
||||
className={styles.image}
|
||||
/>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.hotel}>
|
||||
<ScandicLogoIcon color="red" />
|
||||
<Title as="h5" textTransform="capitalize">
|
||||
{hotel.name}
|
||||
</Title>
|
||||
<Caption color="burgundy" className={styles.caption}>
|
||||
<span>{hotel.address}</span>
|
||||
<span>{hotel.phone}</span>
|
||||
</Caption>
|
||||
</div>
|
||||
<Body className={styles.stay}>
|
||||
<span>{`${stay.nights} ${nightsText}`}</span>
|
||||
<span className={styles.dates}>
|
||||
<span>{stay.start}</span>
|
||||
<ArrowRightIcon height={15} width={15} />
|
||||
<span>{stay.end}</span>
|
||||
</span>
|
||||
</Body>
|
||||
</div>
|
||||
</section>
|
||||
<section className={styles.table}>
|
||||
<div className={styles.breakfast}>
|
||||
<Body color="burgundy">
|
||||
{intl.formatMessage({ id: "Breakfast" })}
|
||||
</Body>
|
||||
<Caption className={styles.caption}>
|
||||
<span>{`${intl.formatMessage({ id: "Weekdays" })} ${hotel.breakfast.start}-${hotel.breakfast.end}`}</span>
|
||||
<span>{`${intl.formatMessage({ id: "Weekends" })} ${hotel.breakfast.start}-${hotel.breakfast.end}`}</span>
|
||||
</Caption>
|
||||
</div>
|
||||
<div className={styles.checkIn}>
|
||||
<Body color="burgundy">{intl.formatMessage({ id: "Check in" })}</Body>
|
||||
<Caption className={styles.caption}>
|
||||
<span>{intl.formatMessage({ id: "From" })}</span>
|
||||
<span>{hotel.checkIn}</span>
|
||||
</Caption>
|
||||
</div>
|
||||
<div className={styles.checkOut}>
|
||||
<Body color="burgundy">
|
||||
{intl.formatMessage({ id: "Check out" })}
|
||||
</Body>
|
||||
<Caption className={styles.caption}>
|
||||
<span>{intl.formatMessage({ id: "At latest" })}</span>
|
||||
<span>{hotel.checkOut}</span>
|
||||
</Caption>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
.card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 100%;
|
||||
width: 105px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: var(--Spacing-x1);
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.hotel,
|
||||
.stay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.caption {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dates {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--Spacing-x2);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
background-color: var(--Base-Surface-Primary-dark-Normal);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.breakfast,
|
||||
.checkIn,
|
||||
.checkOut {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.card {
|
||||
flex-direction: column;
|
||||
}
|
||||
.image {
|
||||
width: 100%;
|
||||
max-height: 195px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hotel,
|
||||
.stay {
|
||||
width: 100%;
|
||||
max-width: 230px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./summarySection.module.css"
|
||||
|
||||
import { SummarySectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
|
||||
export default async function SummarySection({ summary }: SummarySectionProps) {
|
||||
const intl = await getIntl()
|
||||
const roomType = `${intl.formatMessage({ id: "Type of room" })}: ${summary.roomType}`
|
||||
const bedType = `${intl.formatMessage({ id: "Type of bed" })}: ${summary.bedType}`
|
||||
const breakfast = `${intl.formatMessage({ id: "Breakfast" })}: ${summary.breakfast}`
|
||||
const flexibility = `${intl.formatMessage({ id: "Flexibility" })}: ${summary.flexibility}`
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<Title as="h4" textAlign="center">
|
||||
{intl.formatMessage({ id: "Summary" })}
|
||||
</Title>
|
||||
<Caption className={styles.summary}>
|
||||
<span>{roomType}</span>
|
||||
<span>1648 SEK</span>
|
||||
</Caption>
|
||||
<Caption className={styles.summary}>
|
||||
<span>{bedType}</span>
|
||||
<span>0 SEK</span>
|
||||
</Caption>
|
||||
<Caption className={styles.summary}>
|
||||
<span>{breakfast}</span>
|
||||
<span>198 SEK</span>
|
||||
</Caption>
|
||||
<Caption className={styles.summary}>
|
||||
<span>{flexibility}</span>
|
||||
<span>200 SEK</span>
|
||||
</Caption>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
}
|
||||
|
||||
.summary span {
|
||||
padding: var(--Spacing-x2) var(--Spacing-x0);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BookingConfirmation } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||
|
||||
export const tempConfirmationData: BookingConfirmation = {
|
||||
email: "lisa.andersson@outlook.com",
|
||||
hotel: {
|
||||
name: "Helsinki Hub",
|
||||
address: "Kaisaniemenkatu 7, Helsinki",
|
||||
location: "Helsinki",
|
||||
phone: "+358 300 870680",
|
||||
image:
|
||||
"https://test3.scandichotels.com/imagevault/publishedmedia/i11isd60bh119s9486b7/downtown-camper-by-scandic-lobby-reception-desk-ch.jpg?w=640",
|
||||
checkIn: "15.00",
|
||||
checkOut: "12.00",
|
||||
breakfast: { start: "06:30", end: "10:00" },
|
||||
},
|
||||
stay: {
|
||||
nights: 1,
|
||||
start: "2024.03.09",
|
||||
end: "2024.03.10",
|
||||
},
|
||||
summary: {
|
||||
roomType: "Standard Room",
|
||||
bedType: "King size",
|
||||
breakfast: "Yes",
|
||||
flexibility: "Yes",
|
||||
},
|
||||
}
|
||||
11
components/Intro/index.tsx
Normal file
11
components/Intro/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { PropsWithChildren } from "react"
|
||||
|
||||
import styles from "./intro.module.css"
|
||||
|
||||
export default async function Intro({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className={styles.intro}>
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
16
components/Intro/intro.module.css
Normal file
16
components/Intro/intro.module.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.intro {
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
max-width: var(--max-width-text-block);
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.content {
|
||||
gap: var(--Spacing-x3);
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,9 @@ export const renderOptions: RenderOptions = {
|
||||
const image = insertResponseToImageVaultAsset(attrs)
|
||||
const alt = image.meta.alt ?? image.title
|
||||
|
||||
const width = parseInt(attrs.width.replaceAll("px", ""))
|
||||
const width = attrs.width
|
||||
? parseInt(attrs.width.replaceAll("px", ""))
|
||||
: image.dimensions.width
|
||||
const props = extractPossibleAttributes(attrs)
|
||||
return (
|
||||
<section key={node.uid}>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"requirement": "0 Punkte",
|
||||
"description": "Dies ist der Beginn von etwas Wunderbarem: Als New Friend können Sie sich auf eine Reise voller herrlicher Scandic-Entdeckungen freuen.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
@@ -78,7 +78,7 @@
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"requirement": "5 000 Punkte",
|
||||
"description": "Sie waren in letzter Zeit viel bei uns! Und ehrlich gesagt haben wir das Gefühl, dass wir auf einer Wellenlänge sind – die vielen angenehmen Aufenthalte und lustigen Überraschungen sprechen für sich.",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
@@ -153,7 +153,7 @@
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"requirement": "10 000 Punkte",
|
||||
"description": "Jetzt wird es ernst: Wir lernen uns wirklich besser kennen, was bedeutet, dass Ihre Zeit mit Scandic noch viel persönlicher wird.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
@@ -229,7 +229,7 @@
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"requirement": "25 000 Punkte",
|
||||
"description": "Ein Hoch auf uns! Unser Verhältnis scheint sich in Richtung Freunde fürs Leben zu entwickeln – was auch bedeutet, dass Sie Zugang zu einer ganzen Menge mehr Scandic bekommen.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
@@ -306,7 +306,7 @@
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"requirement": "100 000 Punkte",
|
||||
"description": "Sie haben uns während zahlreicher Aufenthalte, Happy Hours und Workouts im Fitnessstudio die Treue gehalten – deshalb wollen wir uns mit einigen unserer großartigsten Belohnungen bei Ihnen revanchieren.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
@@ -383,7 +383,7 @@
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"requirement": "250 000 Punkte",
|
||||
"description": "Es spielt keine Rolle, ob Haupt- oder Nebensaison: Sie sind immer für uns da. Genießen Sie noch mehr individuelle Vorteile – genau nach Ihrem Geschmack.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
@@ -460,7 +460,7 @@
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p oder 100 nächte",
|
||||
"requirement": "400 000 Punkte oder 100 Nächte",
|
||||
"description": "Für eine Freundschaft wie diese gibt es im Grunde keine passenden Worte, aber wir versuchen es trotzdem: Denn es könnte gar nichts Besseres geben, wenn es um sehr, sehr exklusive Erlebnisse geht!",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
|
||||
@@ -3,29 +3,29 @@
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"description": "Olemme uuden ja upean kynnyksellä: New Friend -ystävänä pääset nauttimaan kaikesta ihanasta, mitä Scandic tarjoaa.",
|
||||
"requirement": "0 p",
|
||||
"description": "Ystävänämme pääset nauttimaan kaikesta ihanasta, mitä Scandic tarjoaa.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Mikä herkullinen etu! Hyödynnä 10 %:n alennus hotelliemme ravintoloissa ja shopissa viikonloppuisin. Tarjous on voimassa niin majoittujille kuin hotellitunnelmaa hetkeksi etsiville. Hemmottele siis itseäsi ja löydä tie lähimpään Scandiciin.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
"value": "10 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
@@ -39,38 +39,38 @@
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -78,29 +78,29 @@
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"description": "Kiva, että olet vieraillut meillä, ja tuntuu, että ystävyytemme on hyvässä nosteessa. Tästä on hyvä jatkaa, yksi yöpyminen ja iloinen yllätys kerrallaan!",
|
||||
"requirement": "5 000 p",
|
||||
"description": "Tästä on hyvä jatkaa, yksi yöpyminen ja iloinen yllätys kerrallaan!",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
@@ -114,38 +114,38 @@
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -153,29 +153,29 @@
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"description": "Onpa kiva, että olet vieraillut meillä näin usein! Nyt etusi vain paranevat, sillä olemmehan jo enemmän kuin hyvän päivän tuttuja.",
|
||||
"requirement": "10 000 p",
|
||||
"description": "Nyt etusi vain paranevat, sillä olemmehan jo enemmän kuin hyvän päivän tuttuja.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
@@ -190,38 +190,38 @@
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -229,29 +229,29 @@
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"requirement": "25 000 p",
|
||||
"description": "Kippis syventyvälle ystävyydellemme. Nyt pääset nauttimaan liudasta uusia etuja.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
@@ -264,41 +264,41 @@
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Tässä lisäboostia sinulle: saat 25 % enemmän pisteitä joka kerta kun ansaitset pisteitä! Pistä tuulemaan ja haali pisteet yöpymisistä, aterioista ja muusta, niin pääset nauttimaan palkintoyöstä tuossa tuokiossa.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
"value": "25 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -306,29 +306,29 @@
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"description": "Kiva, että olemme saaneet jakaa paljon yhteisiä hetkiä. Olet tosiaan nimesi arvoinen Loyal Friend! Haluamme panostaa ystävyyteemme myös jatkossa ja annammekin sinulle kasan uusia, ihania etuja.",
|
||||
"requirement": "100 000 p",
|
||||
"description": "Haluamme panostaa ystävyyteemme myös jatkossa ja annammekin sinulle kasan uusia, ihania etuja.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
@@ -341,41 +341,41 @@
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Tässä lisäboostia sinulle: saat 25 % enemmän pisteitä joka kerta kun ansaitset pisteitä! Pistä tuulemaan ja haali pisteet yöpymisistä, aterioista ja muusta, niin pääset nauttimaan palkintoyöstä tuossa tuokiossa.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
"value": "25 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -383,29 +383,29 @@
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"description": "Onpa ollut ihana nähdä sinua näin paljon viime aikoina. Tosiystävän tapaan haluamme palkita sinua entistä yksilöllisemmillä eduilla.",
|
||||
"requirement": "250 000 p",
|
||||
"description": "Tosiystävän tapaan haluamme palkita sinua entistä yksilöllisemmillä eduilla.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
@@ -416,43 +416,43 @@
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Tässä extra-boostia sinulle: saat 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä! Pistä tuulemaan ja haali pisteet yöpymisistä, aterioista ja muusta, niin pääset nauttimaan palkintoyöstä tuossa tuokiossa. ",
|
||||
"description": "Saat 25 % tai 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä!.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
@@ -460,76 +460,76 @@
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p tai 100 yötä",
|
||||
"description": "Ystävyytemme on vailla vertaa. Koska sanat eivät riitä kiittämään ystävyydestämme, pääset nyt käsiksi kaikkein eksklusiivisimpiin elämyksiin.",
|
||||
"requirement": "400 000 p tai 100 yötä",
|
||||
"description": "Koska sanat eivät riitä kiittämään ystävyydestämme, pääset nyt käsiksi kaikkein eksklusiivisimpiin elämyksiin.",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Ystävähinnat",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan. Ei salaisia koodeja tai kädenpuristuksia – sen kuin vain hyppäät varaamaan huoletta.",
|
||||
"description": "Ystävänämme saat aina parhaan hinnan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Maukas etu sinulle! Ystävänämme saat 10-15% alennusta ruoasta hotelliemme ravintoloissa ja shopissa. Tarjous on voimassa viikonloppuisin ja valittuina loma-aikoina, majoitut meillä tai et. Hemmottele siis itseäsi – olet sen ansainnut!",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille yöpymisen yhteydessä maksutta",
|
||||
"description": "Lapset mukana matkassa? Meillä lapset ovat superstaroja ja siksi he saavat raikkaan mocktailin majoittumisen yhteydessä meidän piikkiin.",
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen varaustilanteen mukaan",
|
||||
"description": "Joskus myöhäinen uloskirjautuminen pelastaa päivän. Nyt ystävyteemme on jo sillä tasolla, että sinulla ei ole kiire hypätä sängystä aamuvarhaisella. Kirjaudu ulos tuntia myöhemmin ilman lisämaksua ja ota kaikki irti extra-ajasta.",
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Parhaana ystävänämme saat 20 € ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä. Illallinen hotellin ravintolassa tai kasa herkkuja huoneeseen – mihin sinä sen käyttäisit?",
|
||||
"description": "Ystävänämme saat ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä.",
|
||||
"unlocked": true,
|
||||
"value": "20 €"
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Tässä extra-boostia sinulle: saat 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä! Pistä tuulemaan ja haali pisteet yöpymisistä, aterioista ja muusta, niin pääset nauttimaan palkintoyöstä tuossa tuokiossa. ",
|
||||
"description": "Saat 25 % tai 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä!.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen varaustilanteen mukaan",
|
||||
"description": "Ota varaslähtö yöpymiseen! Kirjaudu sisään tuntia aiemmin ilman lisämaksua ja ota oikotie rentoutumiseen.",
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus varaustilanteen mukaan",
|
||||
"description": "Ystävyytemme on seuraavalla vaihteella ja nyt saat huoneluokan korotuksen ilman lisämaksua aina kun siihen on mahdollisuus. Luvassa siis entistä mukavampi majoitus!",
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Herkkuja kaksin kerroin! Nyt aamiaiselle kannattaa saapua kaverin kanssa, sillä saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et. Valmistaudu aamutreffeihin huolella tutustumalla lisätietoihin.",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"description": "Uniikki etu harvoille ja valituille – eli sinulle! Ystävänäsi lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi. Aika taikuruutta, vai mitä?",
|
||||
"description": "Lupaamme sinulle huoneen, vaikka hotelli olisi täyteen buukattu, kunhan varaat sen vähintään 48 tuntia ennen saapumistasi.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen aina maksutta",
|
||||
"description": "Haluaisitko aloittaa aamusi herkullisesti? Tarjoamme sinulle valmiin hotelliaamiaisen maksutta vaikka joka aamu. Etu on käytettävissäsi riippumatta siitä, majoitutko meillä vai et.",
|
||||
"description": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Best Friend -ystävänämme ansaitset kuninkaallista kohtelua. Siksi palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella lahjalla. Mikä se on? No se on tietysti yllätys.",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Ystävyytemme ulottuu myös mukana matkaaviin lapsiin. Muistamme pikkuväkeä pienellä lahjalla joka kerta kun majoitutte meillä. Pienet VIP-vieraat ovat sen arvoisia, vai mitä!",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Nam! Nyt en smakfull 10 % rabatt i restauranten og shoppen vår i helgene. Dette tilbudet gjelder enten du er gjesten vår over natten eller bare kommer innom for en matbit. Så, sett i gang, unn deg selv noe godt.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
"value": "10 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -34,7 +34,7 @@
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
@@ -89,9 +89,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du har opphold hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn rabatt? Som vår Friend får du 10-15 % rabatt på mat i vår restaurant og shop i helger og utvalgte ferier og helligdager. Det gjelder uansett om du bor hos oss eller ikke. Så kom igjen, skjem deg bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -109,7 +109,7 @@
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
@@ -164,9 +164,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du bor hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15 % rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du bor hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -185,7 +185,7 @@
|
||||
"value": "50 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
@@ -240,9 +240,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du har opphold hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn rabatt? Som vår Friend får du 10-15 % rabatt på mat i vår restaurant og shop i helger og utvalgte ferier og helligdager. Det gjelder uansett om du bor hos oss eller ikke. Så kom igjen, skjem deg bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -261,10 +261,10 @@
|
||||
"value": "75 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"name": "Friendsboost",
|
||||
"description": "Her har du noe veldig bra: Hver gang du øker antall vennskapspoeng, får du 25 % ekstra – ekstra på det ekstra! Så, begynn å samle poeng på opphold, måltider og mer, og du vil veldig snart få et gratis opphold.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
"value": "25 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
@@ -317,9 +317,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du har opphold hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn rabatt? Som vår Friend får du 10-15 % rabatt på mat i vår restaurant og shop i helger og utvalgte ferier og helligdager. Det gjelder uansett om du bor hos oss eller ikke. Så kom igjen, skjem deg bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -338,10 +338,10 @@
|
||||
"value": "100 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"name": "Friendsboost",
|
||||
"description": "Her har du noe veldig bra: Hver gang du øker antall vennskapspoeng, får du 25 % ekstra – ekstra på det ekstra! Så, begynn å samle poeng på opphold, måltider og mer, og du vil veldig snart få et gratis opphold.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
"value": "25 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
@@ -394,9 +394,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du har opphold hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn rabatt? Som vår Friend får du 10-15 % rabatt på mat i vår restaurant og shop i helger og utvalgte ferier og helligdager. Det gjelder uansett om du bor hos oss eller ikke. Så kom igjen, skjem deg bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -415,10 +415,10 @@
|
||||
"value": "150 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"description": "Du kan virkelig glede deg. Hver gang du øker antall vennskapspoeng, får du 50 % ekstra – ekstra på det ekstra! Så, få flere poeng på opphold, måltider og mer, og du vil få et gratis opphold lynraskt",
|
||||
"name": "Friendsboost",
|
||||
"description": "Gled deg! Hver gang du tjener nye Friends-poeng får du 25 % eller 50 % ekstra poeng – som en superboost! Begynn å tjene poeng ved å bo og spise hos oss, og du vil få en bonusnatt før du aner det.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
@@ -471,9 +471,9 @@
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Hva er bedre enn en rabatt? Som vår venn får du 10-15% rabatt på mat i restauranten og shoppen vår i helger og på utvalgte helligdager – og det gjelder både når du har opphold hos oss og når du ikke har det. Så kom igjen, skjem deg selv bort.",
|
||||
"description": "Hva er bedre enn rabatt? Som vår Friend får du 10-15 % rabatt på mat i vår restaurant og shop i helger og utvalgte ferier og helligdager. Det gjelder uansett om du bor hos oss eller ikke. Så kom igjen, skjem deg bort.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
@@ -492,10 +492,10 @@
|
||||
"value": "200 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Ekstra vennskap",
|
||||
"description": "Du kan virkelig glede deg. Hver gang du øker antall vennskapspoeng, får du 50 % ekstra – ekstra på det ekstra! Så, få flere poeng på opphold, måltider og mer, og du vil få et gratis opphold lynraskt",
|
||||
"name": "Friendsboost",
|
||||
"description": "Gled deg! Hver gang du tjener nye Friends-poeng får du 25 % eller 50 % ekstra poeng – som en superboost! Begynn å tjene poeng ved å bo og spise hos oss, og du vil få en bonusnatt før du aner det.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
|
||||
13
components/Loyalty/Sidebar/MyPagesNavigation/index.tsx
Normal file
13
components/Loyalty/Sidebar/MyPagesNavigation/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import MyPagesSidebar from "@/components/MyPages/Sidebar"
|
||||
|
||||
export async function MyPagesNavigation() {
|
||||
const user = await serverClient().user.name()
|
||||
|
||||
// Check if we have user, that means we are logged in andt the My Pages menu can show.
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
return <MyPagesSidebar />
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import JsonToHtml from "@/components/JsonToHtml"
|
||||
import SidebarMyPages from "@/components/MyPages/Sidebar"
|
||||
|
||||
import JoinLoyaltyContact from "./JoinLoyalty"
|
||||
import { MyPagesNavigation } from "./MyPagesNavigation"
|
||||
|
||||
import styles from "./sidebar.module.css"
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function SidebarLoyalty({ blocks }: SidebarProps) {
|
||||
case SidebarTypenameEnum.LoyaltyPageSidebarDynamicContent:
|
||||
switch (block.dynamic_content.component) {
|
||||
case LoyaltySidebarDynamicComponentEnum.my_pages_navigation:
|
||||
return <SidebarMyPages key={`${block.__typename}-${idx}`} />
|
||||
return <MyPagesNavigation key={`${block.__typename}-${idx}`} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default async function Friend({
|
||||
{formatMessage(
|
||||
isHighestLevel
|
||||
? { id: "Highest level" }
|
||||
: { id: "Your current level" }
|
||||
: { id: `Level ${membershipLevels[membership.membershipLevel]}` }
|
||||
)}
|
||||
</Body>
|
||||
{membership ? (
|
||||
|
||||
@@ -7,9 +7,8 @@ import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
import DesktopTable from "./Desktop"
|
||||
import MobileTable from "./Mobile"
|
||||
import Pagination from "./Pagination"
|
||||
import Table from "./Table"
|
||||
|
||||
import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
@@ -40,8 +39,7 @@ export default function TransactionTable({
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
<MobileTable transactions={data?.data.transactions || []} />
|
||||
<DesktopTable transactions={data?.data.transactions || []} />
|
||||
<Table transactions={data?.data.transactions || []} />
|
||||
{data && data.meta.totalPages > 1 ? (
|
||||
<Pagination
|
||||
handlePageChange={setPage}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import { awardPointsVariants } from "./awardPointsVariants"
|
||||
|
||||
import type {
|
||||
AwardPointsProps,
|
||||
AwardPointsVariantProps,
|
||||
} from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
export default function AwardPoints({ awardPoints }: AwardPointsProps) {
|
||||
let variant: AwardPointsVariantProps["variant"] = undefined
|
||||
if (awardPoints > 0) {
|
||||
variant = "addition"
|
||||
} else if (awardPoints < 0) {
|
||||
variant = "negation"
|
||||
awardPoints = Math.abs(awardPoints)
|
||||
}
|
||||
|
||||
const classNames = awardPointsVariants({
|
||||
variant,
|
||||
})
|
||||
|
||||
// sv hardcoded to force space on thousands
|
||||
const formatter = new Intl.NumberFormat(Lang.sv)
|
||||
return <td className={classNames}>{formatter.format(awardPoints)} pts</td>
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import AwardPoints from "./AwardPoints"
|
||||
|
||||
import styles from "./row.module.css"
|
||||
|
||||
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
export default function Row({ transaction }: RowProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const description =
|
||||
transaction.hotelName && transaction.city
|
||||
? `${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(lang)
|
||||
.format("DD MMM YYYY")
|
||||
return (
|
||||
<tr className={styles.tr}>
|
||||
<td className={styles.td}>{arrival}</td>
|
||||
<td className={styles.td}>{description}</td>
|
||||
<td className={styles.td}>{transaction.confirmationNumber}</td>
|
||||
<td className={styles.td}>{departure}</td>
|
||||
<AwardPoints awardPoints={transaction.awardPoints} />
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./mobile.module.css"
|
||||
|
||||
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
export default function MobileTable({ transactions }: TableProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<table className={styles.table}>
|
||||
<thead className={styles.thead}>
|
||||
<tr>
|
||||
<Body asChild>
|
||||
<th className={styles.th}>
|
||||
{intl.formatMessage({ id: "Transactions" })}
|
||||
</th>
|
||||
</Body>
|
||||
<Body asChild>
|
||||
<th className={styles.th}>
|
||||
{intl.formatMessage({ id: "Points" })}
|
||||
</th>
|
||||
</Body>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.length ? (
|
||||
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(lang)
|
||||
.format("DD MMM YYYY")}
|
||||
</span>
|
||||
{transaction.hotelName && transaction.city ? (
|
||||
<span>{`${transaction.hotelName}, ${transaction.city}`}</span>
|
||||
) : null}
|
||||
<span>
|
||||
{`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`}
|
||||
</span>
|
||||
</td>
|
||||
<AwardPoints awardPoints={transaction.awardPoints} />
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td className={styles.placeholder} colSpan={2}>
|
||||
{intl.formatMessage({
|
||||
id: "There are no transactions to display",
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
.table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.thead {
|
||||
background-color: var(--Main-Grey-10);
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.tr {
|
||||
border-top: 1px solid var(--Main-Grey-10);
|
||||
}
|
||||
|
||||
.td {
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.transactionDetails {
|
||||
display: grid;
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
}
|
||||
|
||||
.transactionDate {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
text-align: center;
|
||||
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 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import { awardPointsVariants } from "./awardPointsVariants"
|
||||
|
||||
import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
export default function AwardPoints({
|
||||
awardPoints,
|
||||
isCalculated,
|
||||
}: {
|
||||
awardPoints: number
|
||||
isCalculated: boolean
|
||||
}) {
|
||||
let variant: AwardPointsVariantProps["variant"] = undefined
|
||||
const intl = useIntl()
|
||||
|
||||
if (isCalculated) {
|
||||
if (awardPoints > 0) {
|
||||
variant = "addition"
|
||||
} else if (awardPoints < 0) {
|
||||
variant = "negation"
|
||||
awardPoints = Math.abs(awardPoints)
|
||||
}
|
||||
}
|
||||
const classNames = awardPointsVariants({
|
||||
variant,
|
||||
})
|
||||
|
||||
// sv hardcoded to force space on thousands
|
||||
const formatter = new Intl.NumberFormat(Lang.sv)
|
||||
return (
|
||||
<td className={classNames}>
|
||||
{isCalculated
|
||||
? formatter.format(awardPoints)
|
||||
: intl.formatMessage({ id: "Points being calculated" })}
|
||||
</td>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import AwardPoints from "./AwardPoints"
|
||||
|
||||
import styles from "./row.module.css"
|
||||
|
||||
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums"
|
||||
|
||||
export default function Row({ transaction }: RowProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const nightString = `${transaction.nights} ${transaction.nights === 1 ? intl.formatMessage({ id: "night" }) : intl.formatMessage({ id: "nights" })}`
|
||||
|
||||
let description =
|
||||
transaction.hotelName && transaction.city
|
||||
? `${transaction.hotelName}, ${transaction.city} ${nightString}`
|
||||
: `${nightString}`
|
||||
|
||||
switch (transaction.type) {
|
||||
case RewardTransactionTypes.stay:
|
||||
if (transaction.hotelId === "ORS")
|
||||
description = intl.formatMessage({ id: "Former Scandic Hotel" })
|
||||
break
|
||||
case RewardTransactionTypes.ancillary:
|
||||
description = intl.formatMessage({ id: "Extras to your booking" })
|
||||
break
|
||||
case RewardTransactionTypes.enrollment:
|
||||
description = intl.formatMessage({ id: "Sign up bonus" })
|
||||
break
|
||||
case RewardTransactionTypes.mastercard_points:
|
||||
description = intl.formatMessage({ id: "Scandic Friends Mastercard" })
|
||||
break
|
||||
case RewardTransactionTypes.tui_points:
|
||||
description = intl.formatMessage({ id: "TUI Points" })
|
||||
case RewardTransactionTypes.stayAdj:
|
||||
if (transaction.confirmationNumber === "BALFWD")
|
||||
description = intl.formatMessage({
|
||||
id: "Points earned prior to May 1, 2021",
|
||||
})
|
||||
break
|
||||
case RewardTransactionTypes.pointShop:
|
||||
description = intl.formatMessage({ id: "Scandic Friends Point Shop" })
|
||||
break
|
||||
}
|
||||
|
||||
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
|
||||
const transactionDate = dt(transaction.transactionDate)
|
||||
.locale(lang)
|
||||
.format("DD MMM YYYY")
|
||||
|
||||
return (
|
||||
<tr className={styles.tr}>
|
||||
<AwardPoints
|
||||
awardPoints={transaction.awardPoints}
|
||||
isCalculated={transaction.pointsCalculated}
|
||||
/>
|
||||
<td className={`${styles.td} ${styles.description}`}>{description}</td>
|
||||
<td className={styles.td}>
|
||||
{transaction.type === RewardTransactionTypes.stay &&
|
||||
transaction.bookingUrl ? (
|
||||
<Link variant="underscored" href={transaction.bookingUrl}>
|
||||
{transaction.confirmationNumber}
|
||||
</Link>
|
||||
) : (
|
||||
transaction.confirmationNumber
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.td}>
|
||||
{transaction.checkinDate ? arrival : transactionDate}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
.tr {
|
||||
border: 1px solid #e6e9ec;
|
||||
border-bottom: 1px solid var(--Scandic-Brand-Pale-Peach);
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.td {
|
||||
background-color: #fff;
|
||||
color: var(--UI-Text-High-contrast);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x4);
|
||||
padding: var(--Spacing-x2);
|
||||
position: relative;
|
||||
text-align: left;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
||||
}
|
||||
|
||||
.addition {
|
||||
@@ -17,8 +25,7 @@
|
||||
.addition::before {
|
||||
color: var(--Secondary-Light-On-Surface-Accent);
|
||||
content: "+";
|
||||
left: var(--Spacing-x2);
|
||||
position: absolute;
|
||||
margin-right: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.negation {
|
||||
@@ -28,6 +35,11 @@
|
||||
.negation::before {
|
||||
color: var(--Base-Text-Accent);
|
||||
content: "-";
|
||||
left: var(--Spacing-x2);
|
||||
position: absolute;
|
||||
margin-right: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.td {
|
||||
padding: var(--Spacing-x3);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
import Row from "./Row"
|
||||
|
||||
import styles from "./desktop.module.css"
|
||||
import styles from "./table.module.css"
|
||||
|
||||
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||
|
||||
const tableHeadings = [
|
||||
"Arrival date",
|
||||
"Points",
|
||||
"Description",
|
||||
"Booking number",
|
||||
"Transaction date",
|
||||
"Points",
|
||||
"Arrival date",
|
||||
]
|
||||
|
||||
export default function DesktopTable({ transactions }: TableProps) {
|
||||
export default function Table({ 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}
|
||||
/>
|
||||
<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>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.map((transaction, index) => (
|
||||
<Row
|
||||
key={`${transaction.confirmationNumber}-${index}`}
|
||||
transaction={transaction}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<table className={styles.table}>
|
||||
<thead className={styles.thead}>
|
||||
@@ -1,5 +1,8 @@
|
||||
.container {
|
||||
display: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
}
|
||||
|
||||
.table {
|
||||
@@ -17,7 +20,8 @@
|
||||
|
||||
.th {
|
||||
text-align: left;
|
||||
padding: 20px 32px;
|
||||
text-wrap: nowrap;
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
@@ -49,9 +53,10 @@
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow-x: auto;
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
}
|
||||
|
||||
.th {
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export default async function Breadcrumbs() {
|
||||
if (!breadcrumbs?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const homeBreadcrumb = breadcrumbs.shift()
|
||||
return (
|
||||
<nav className={styles.breadcrumbs}>
|
||||
|
||||
@@ -1,64 +1,81 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useRef } 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 { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./addCreditCardButton.module.css"
|
||||
|
||||
import { type AddCreditCardButtonProps } from "@/types/components/myPages/myProfile/addCreditCardButton"
|
||||
|
||||
let hasRunOnce = false
|
||||
|
||||
function useAddCardResultToast() {
|
||||
const hasRunOnce = useRef(false)
|
||||
const intl = useIntl()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
if (hasRunOnce) return
|
||||
if (hasRunOnce.current) return
|
||||
|
||||
const success = searchParams.get("success")
|
||||
const failure = searchParams.get("failure")
|
||||
const cancel = searchParams.get("cancel")
|
||||
const error = searchParams.get("error")
|
||||
|
||||
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!" }))
|
||||
})
|
||||
toast.success(
|
||||
intl.formatMessage({ id: "Your card was successfully saved!" })
|
||||
)
|
||||
} else if (cancel) {
|
||||
toast.warning(
|
||||
intl.formatMessage({
|
||||
id: "You canceled adding a new credit card.",
|
||||
})
|
||||
)
|
||||
} else if (failure || error) {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "Something went wrong and we couldn't add your card. Please try again later.",
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
router.replace(pathname)
|
||||
hasRunOnce = true
|
||||
hasRunOnce.current = true
|
||||
}, [intl, pathname, router, searchParams])
|
||||
}
|
||||
|
||||
export default function AddCreditCardButton({
|
||||
redirectUrl,
|
||||
}: AddCreditCardButtonProps) {
|
||||
export default function AddCreditCardButton() {
|
||||
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!" })),
|
||||
const initiateAddCard = trpc.user.creditCard.add.useMutation({
|
||||
onSuccess: (result) => {
|
||||
if (result?.attribute.link) {
|
||||
router.push(result.attribute.link)
|
||||
} else {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "We could not add a card right now, please try again later.",
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "An error occurred when adding a credit card, please try again later.",
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -70,8 +87,6 @@ export default function AddCreditCardButton({
|
||||
onClick={() =>
|
||||
initiateAddCard.mutate({
|
||||
language: lang,
|
||||
mobileToken: false,
|
||||
redirectUrl,
|
||||
})
|
||||
}
|
||||
wrapping
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.cardContainer {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
31
components/Profile/CreditCardList/index.tsx
Normal file
31
components/Profile/CreditCardList/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import CreditCardRow from "../CreditCardRow"
|
||||
|
||||
import styles from "./CreditCardList.module.css"
|
||||
|
||||
import type { CreditCard } from "@/types/user"
|
||||
|
||||
export default function CreditCardList({
|
||||
initialData,
|
||||
}: {
|
||||
initialData?: CreditCard[] | null
|
||||
}) {
|
||||
const creditCards = trpc.user.creditCards.useQuery(undefined, { initialData })
|
||||
|
||||
if (!creditCards.data || !creditCards.data.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.cardContainer}>
|
||||
{creditCards.data.map((card) => (
|
||||
<CreditCardRow key={card.id} card={card} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
10
components/Profile/CreditCardRow/creditCardRow.module.css
Normal file
10
components/Profile/CreditCardRow/creditCardRow.module.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.card {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
column-gap: var(--Spacing-x1);
|
||||
grid-template-columns: auto auto auto 1fr;
|
||||
justify-items: flex-end;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half,);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
}
|
||||
22
components/Profile/CreditCardRow/index.tsx
Normal file
22
components/Profile/CreditCardRow/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CreditCard } from "@/components/Icons"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import DeleteCreditCardConfirmation from "../DeleteCreditCardConfirmation"
|
||||
|
||||
import styles from "./creditCardRow.module.css"
|
||||
|
||||
import type { CreditCardRowProps } from "@/types/components/myPages/myProfile/creditCards"
|
||||
|
||||
export default function CreditCardRow({ card }: CreditCardRowProps) {
|
||||
const maskedCardNumber = `**** ${card.truncatedNumber.slice(-4)}`
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<CreditCard color="black" />
|
||||
<Body textTransform="bold">{card.type}</Body>
|
||||
<Caption color="textMediumContrast">{maskedCardNumber}</Caption>
|
||||
<DeleteCreditCardConfirmation card={card} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
components/Profile/DeleteCreditCardButton/index.tsx
Normal file
40
components/Profile/DeleteCreditCardButton/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import { Delete } from "@/components/Icons"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
|
||||
export default function DeleteCreditCardButton({
|
||||
creditCardId,
|
||||
}: {
|
||||
creditCardId: string
|
||||
}) {
|
||||
const { formatMessage } = useIntl()
|
||||
const trpcUtils = trpc.useUtils()
|
||||
|
||||
const deleteCreditCardMutation = trpc.user.creditCard.delete.useMutation({
|
||||
onSuccess() {
|
||||
trpcUtils.user.creditCards.invalidate()
|
||||
toast.success(formatMessage({ id: "Credit card deleted successfully" }))
|
||||
},
|
||||
onError() {
|
||||
toast.error(
|
||||
formatMessage({
|
||||
id: "Failed to delete credit card, please try again later.",
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
async function handleDelete() {
|
||||
deleteCreditCardMutation.mutate({ creditCardId })
|
||||
}
|
||||
return (
|
||||
<Button variant="icon" theme="base" intent="text" onClick={handleDelete}>
|
||||
<Delete color="burgundy" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: var(--visual-viewport-height);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
|
||||
&[data-entering] {
|
||||
animation: modal-fade 200ms;
|
||||
}
|
||||
|
||||
&[data-exiting] {
|
||||
animation: modal-fade 150ms reverse ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.modal section {
|
||||
background: var(--Main-Grey-White);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
padding: var(--Spacing-x4);
|
||||
padding-bottom: var(--Spacing-x6);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
font-family: var(--typography-Body-Regular-fontFamily);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: var(--typography-Subtitle-1-fontFamily);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.bodyText {
|
||||
text-align: center;
|
||||
max-width: 425px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--Spacing-x2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.buttonContainer button {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@keyframes modal-fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
99
components/Profile/DeleteCreditCardConfirmation/index.tsx
Normal file
99
components/Profile/DeleteCreditCardConfirmation/index.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
} from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import { Delete } from "@/components/Icons"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
|
||||
import styles from "./deleteCreditCardConfirmation.module.css"
|
||||
|
||||
import type { DeleteCreditCardConfirmationProps } from "@/types/components/myPages/myProfile/creditCards"
|
||||
|
||||
export default function DeleteCreditCardConfirmation({
|
||||
card,
|
||||
}: DeleteCreditCardConfirmationProps) {
|
||||
const intl = useIntl()
|
||||
const trpcUtils = trpc.useUtils()
|
||||
|
||||
const deleteCard = trpc.user.creditCard.delete.useMutation({
|
||||
onSuccess() {
|
||||
trpcUtils.user.creditCards.invalidate()
|
||||
|
||||
toast.success(
|
||||
intl.formatMessage({ id: "Your card was successfully removed!" })
|
||||
)
|
||||
},
|
||||
onError() {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "Something went wrong and we couldn't remove your card. Please try again later.",
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const lastFourDigits = card.truncatedNumber.slice(-4)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DialogTrigger>
|
||||
<Button variant="icon" theme="base" intent="text">
|
||||
<Delete color="burgundy" />
|
||||
</Button>
|
||||
<ModalOverlay className={styles.overlay} isDismissable>
|
||||
<Modal className={styles.modal}>
|
||||
<Dialog role="alertdialog">
|
||||
{({ close }) => (
|
||||
<div className={styles.container}>
|
||||
<Heading slot="title" className={styles.title}>
|
||||
{intl.formatMessage({
|
||||
id: "Remove card from member profile",
|
||||
})}
|
||||
</Heading>
|
||||
<p className={styles.bodyText}>
|
||||
{`${intl.formatMessage({
|
||||
id: "Are you sure you want to remove the card ending with",
|
||||
})} ${lastFourDigits} ${intl.formatMessage({ id: "from your member profile?" })}`}
|
||||
</p>
|
||||
|
||||
{deleteCard.isPending ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button intent="secondary" theme="base" onClick={close}>
|
||||
{intl.formatMessage({ id: "No, keep card" })}
|
||||
</Button>
|
||||
<Button
|
||||
intent="primary"
|
||||
theme="base"
|
||||
onClick={() => {
|
||||
deleteCard.mutate(
|
||||
{ creditCardId: card.id },
|
||||
{ onSettled: close }
|
||||
)
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({ id: "Yes, remove my card" })}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,20 @@
|
||||
import { buttonVariants } from "./variants"
|
||||
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import type { ButtonProps as ReactAriaButtonProps } from "react-aria-components"
|
||||
|
||||
export interface ButtonProps
|
||||
export interface ButtonPropsRAC
|
||||
extends Omit<ReactAriaButtonProps, "isDisabled">,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: false | undefined | never
|
||||
disabled?: ReactAriaButtonProps["isDisabled"]
|
||||
onClick?: ReactAriaButtonProps["onPress"]
|
||||
}
|
||||
|
||||
export interface ButtonPropsSlot
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
asChild: true
|
||||
}
|
||||
|
||||
export type ButtonProps = ButtonPropsSlot | ButtonPropsRAC
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { Button as ButtonRAC } from "react-aria-components"
|
||||
|
||||
import { buttonVariants } from "./variants"
|
||||
|
||||
import type { ButtonProps } from "./button"
|
||||
|
||||
export default function Button({
|
||||
asChild = false,
|
||||
theme,
|
||||
className,
|
||||
disabled,
|
||||
intent,
|
||||
size,
|
||||
variant,
|
||||
wrapping,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
export default function Button(props: ButtonProps) {
|
||||
const { className, intent, size, theme, wrapping, variant, ...restProps } =
|
||||
props
|
||||
|
||||
const classNames = buttonVariants({
|
||||
className,
|
||||
intent,
|
||||
@@ -26,5 +19,19 @@ export default function Button({
|
||||
wrapping,
|
||||
variant,
|
||||
})
|
||||
return <Comp className={classNames} disabled={disabled} {...props} />
|
||||
|
||||
if (restProps.asChild) {
|
||||
const { asChild, ...slotProps } = restProps
|
||||
return <Slot className={classNames} {...slotProps} />
|
||||
}
|
||||
|
||||
const { asChild, onClick, disabled, ...racProps } = restProps
|
||||
return (
|
||||
<ButtonRAC
|
||||
className={classNames}
|
||||
isDisabled={disabled}
|
||||
onPress={onClick}
|
||||
{...racProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { loyaltyCardVariants } from "./variants"
|
||||
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
import { ImageVaultAsset } from "@/types/components/imageVaultImage"
|
||||
import { ImageVaultAsset } from "@/types/components/imageVault"
|
||||
|
||||
export interface LoyaltyCardProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { toastVariants } from "./variants"
|
||||
import styles from "./toasts.module.css"
|
||||
|
||||
export function ToastHandler() {
|
||||
return <Toaster />
|
||||
return <Toaster position="bottom-right" />
|
||||
}
|
||||
|
||||
function getIcon(variant: ToastsProps["variant"]) {
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
|
||||
.iconContainer {
|
||||
display: flex;
|
||||
background-color: var(--icon-background-color);
|
||||
padding: var(--Spacing-x2);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--icon-background-color);
|
||||
padding: var(--Spacing-x2);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user