feat(SW-353): dynamic rewards
This commit is contained in:
103
app/api/web/revalidate/loyaltyConfig/route.ts
Normal file
103
app/api/web/revalidate/loyaltyConfig/route.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { revalidateTag } from "next/cache"
|
||||
import { headers } from "next/headers"
|
||||
import { NextRequest } from "next/server"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { env } from "@/env/server"
|
||||
import { internalServerError } from "@/server/errors/next"
|
||||
|
||||
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
|
||||
|
||||
enum LoyaltyConfigContentTypes {
|
||||
loyalty_level = "loyalty_level",
|
||||
rewards = "rewards",
|
||||
}
|
||||
|
||||
const validateJsonBody = z.object({
|
||||
data: z.object({
|
||||
content_type: z.object({
|
||||
uid: z.nativeEnum(LoyaltyConfigContentTypes),
|
||||
}),
|
||||
entry: z.object({
|
||||
reward_id: z.string().optional(),
|
||||
level_id: z.string().optional(),
|
||||
system: z.object({
|
||||
locale: z.nativeEnum(Lang),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headersList = headers()
|
||||
const secret = headersList.get("x-revalidate-secret")
|
||||
|
||||
if (secret !== env.REVALIDATE_SECRET) {
|
||||
console.error(`Invalid Secret`)
|
||||
console.error({ secret })
|
||||
return Response.json(
|
||||
{
|
||||
now: Date.now(),
|
||||
revalidated: false,
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const data = await request.json()
|
||||
const validatedData = validateJsonBody.safeParse(data)
|
||||
if (!validatedData.success) {
|
||||
console.error(
|
||||
"Bad validation for `validatedData` in loyaltyConfig revalidation"
|
||||
)
|
||||
console.error(validatedData.error)
|
||||
return internalServerError({ revalidated: false, now: Date.now() })
|
||||
}
|
||||
|
||||
const {
|
||||
data: {
|
||||
data: { content_type, entry },
|
||||
},
|
||||
} = validatedData
|
||||
|
||||
let tag = ""
|
||||
if (
|
||||
content_type.uid === LoyaltyConfigContentTypes.loyalty_level &&
|
||||
entry.level_id
|
||||
) {
|
||||
tag = generateLoyaltyConfigTag(
|
||||
entry.system.locale,
|
||||
content_type.uid,
|
||||
entry.level_id
|
||||
)
|
||||
} else if (
|
||||
content_type.uid === LoyaltyConfigContentTypes.rewards &&
|
||||
entry.reward_id
|
||||
) {
|
||||
tag = generateLoyaltyConfigTag(
|
||||
entry.system.locale,
|
||||
content_type.uid,
|
||||
entry.reward_id
|
||||
)
|
||||
} else {
|
||||
console.error("Invalid content_type")
|
||||
return Response.json(
|
||||
{ revalidated: false, now: Date.now() },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
console.info(`Revalidating loyalty config tag: ${tag}`)
|
||||
revalidateTag(tag)
|
||||
|
||||
return Response.json({ revalidated: true, now: Date.now() })
|
||||
} catch (error) {
|
||||
console.error("Failed to revalidate tag(s) for loyalty config")
|
||||
console.error(error)
|
||||
return internalServerError({ revalidated: false, now: Date.now() })
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
|
||||
import styles from "./current.module.css"
|
||||
|
||||
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
|
||||
|
||||
export default async function CurrentBenefitsBlock({
|
||||
title,
|
||||
subtitle,
|
||||
link,
|
||||
}: AccountPageComponentProps) {
|
||||
const user = await getProfile()
|
||||
// TAKE NOTE: we need clarification on how benefits stack from different levels
|
||||
// in order to determine if a benefit is specific to a level or if it is a cumulative benefit
|
||||
// we might have to add a new boolean property "exclusive" or similar
|
||||
if (!user || "error" in user || !user.membership) {
|
||||
return null
|
||||
}
|
||||
|
||||
const currentLevel = getMembershipLevelObject(
|
||||
user.membership.membershipLevel as MembershipLevelEnum,
|
||||
getLang()
|
||||
)
|
||||
if (!currentLevel) {
|
||||
// TODO: handle this case?
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader title={title} link={link} preamble={subtitle} />
|
||||
<Grids.Stackable>
|
||||
{currentLevel.benefits.map((benefit, idx) => (
|
||||
<article className={styles.card} key={`${currentLevel}-${idx}`}>
|
||||
<Title
|
||||
as="h4"
|
||||
level="h3"
|
||||
textAlign="center"
|
||||
textTransform="regular"
|
||||
>
|
||||
{benefit.title}
|
||||
</Title>
|
||||
</article>
|
||||
))}
|
||||
</Grids.Stackable>
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,88 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import { notFound, useParams } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import { CheckIcon } from "@/components/Icons"
|
||||
import {
|
||||
BestFriend,
|
||||
CloseFriend,
|
||||
DearFriend,
|
||||
GoodFriend,
|
||||
LoyalFriend,
|
||||
NewFriend,
|
||||
TrueFriend,
|
||||
} from "@/components/Levels"
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import levelsData from "@/data/loyaltyLevels"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import SectionWrapper from "../SectionWrapper"
|
||||
|
||||
import styles from "./loyaltyLevels.module.css"
|
||||
|
||||
import type { LoyaltyLevelsProps } from "@/types/components/blocks/dynamicContent"
|
||||
import type { Level, LevelCardProps } from "@/types/components/overviewTable"
|
||||
import { LoyaltyLevelsProps } from "@/types/components/blocks/dynamicContent"
|
||||
import type { LevelCardProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function LoyaltyLevels({
|
||||
export default async function LoyaltyLevels({
|
||||
dynamic_content,
|
||||
firstItem,
|
||||
}: LoyaltyLevelsProps) {
|
||||
const params = useParams()
|
||||
const lang = params.lang as Lang
|
||||
const { formatMessage } = useIntl()
|
||||
const uniqueLevels = await serverClient().contentstack.rewards.all({
|
||||
unique: true,
|
||||
})
|
||||
|
||||
const { levels } = levelsData[lang]
|
||||
return (
|
||||
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
|
||||
<section className={styles.cardContainer}>
|
||||
{levels.map((level: Level) => (
|
||||
<LevelCard
|
||||
key={level.level}
|
||||
formatMessage={formatMessage}
|
||||
lang={lang}
|
||||
level={level}
|
||||
/>
|
||||
{uniqueLevels.map((level) => (
|
||||
<LevelCard key={level.level_id} level={level} />
|
||||
))}
|
||||
</section>
|
||||
</SectionWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
|
||||
let Level = null
|
||||
switch (level.level) {
|
||||
case 1:
|
||||
Level = NewFriend
|
||||
break
|
||||
case 2:
|
||||
Level = GoodFriend
|
||||
break
|
||||
case 3:
|
||||
Level = CloseFriend
|
||||
break
|
||||
case 4:
|
||||
Level = DearFriend
|
||||
break
|
||||
case 5:
|
||||
Level = LoyalFriend
|
||||
break
|
||||
case 6:
|
||||
Level = TrueFriend
|
||||
break
|
||||
case 7:
|
||||
Level = BestFriend
|
||||
break
|
||||
default: {
|
||||
const loyaltyLevel = level.level as never
|
||||
console.error(`Unsupported loyalty level given: ${loyaltyLevel}`)
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
const pointsString = `${level.requiredPoints.toLocaleString(lang)} ${formatMessage({ id: "points" })} `
|
||||
async function LevelCard({ level }: LevelCardProps) {
|
||||
const lang = getLang()
|
||||
const intl = await getIntl()
|
||||
const pointsString = `${level.required_points.toLocaleString(lang)} ${intl.formatMessage({ id: "points" })} `
|
||||
|
||||
return (
|
||||
<article className={styles.card}>
|
||||
@@ -92,24 +47,24 @@ function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
|
||||
color="primaryLightOnSurfaceAccent"
|
||||
tilted="large"
|
||||
>
|
||||
{formatMessage({ id: "Level" })} {level.level}
|
||||
{intl.formatMessage({ id: "Level" })} {level.user_facing_tag}
|
||||
</BiroScript>
|
||||
<Level color="red" />
|
||||
<MembershipLevelIcon level={level.level_id} color="red" />
|
||||
</header>
|
||||
<Title textAlign="center" level="h5">
|
||||
{pointsString}
|
||||
{level.requiredNights ? (
|
||||
{level.required_nights ? (
|
||||
<span className={styles.redText}>
|
||||
{formatMessage({ id: "or" })} {level.requiredNights}{" "}
|
||||
{formatMessage({ id: "nights" })}
|
||||
{intl.formatMessage({ id: "or" })} {level.required_nights}{" "}
|
||||
{intl.formatMessage({ id: "nights" })}
|
||||
</span>
|
||||
) : null}
|
||||
</Title>
|
||||
<div className={styles.textContainer}>
|
||||
{level.benefits.map((benefit) => (
|
||||
{level.rewards.map((reward) => (
|
||||
<Caption
|
||||
className={styles.levelText}
|
||||
key={benefit.title}
|
||||
key={reward.reward_id}
|
||||
textAlign="center"
|
||||
color="textMediumContrast"
|
||||
>
|
||||
@@ -117,7 +72,7 @@ function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
|
||||
className={styles.checkIcon}
|
||||
color="primaryLightOnSurfaceAccent"
|
||||
/>
|
||||
{benefit.title}
|
||||
{reward.label}
|
||||
</Caption>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
|
||||
import {
|
||||
BestFriend,
|
||||
CloseFriend,
|
||||
DearFriend,
|
||||
GoodFriend,
|
||||
LoyalFriend,
|
||||
NewFriend,
|
||||
TrueFriend,
|
||||
} from "@/components/Levels"
|
||||
|
||||
import styles from "./membershipLevel.module.css"
|
||||
|
||||
import type { MembershipLevelProps } from "@/types/components/myPages/membership"
|
||||
|
||||
export default function MembershipLevel({ level }: MembershipLevelProps) {
|
||||
switch (level) {
|
||||
case membershipLevels.L1:
|
||||
return <NewFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L2:
|
||||
return <GoodFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L3:
|
||||
return <CloseFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L4:
|
||||
return <DearFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L5:
|
||||
return <LoyalFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L6:
|
||||
return <TrueFriend className={styles.level} color="pale" />
|
||||
case membershipLevels.L7:
|
||||
return <BestFriend className={styles.level} color="pale" />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.level {
|
||||
height: 105px;
|
||||
width: 219px;
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
import {
|
||||
MembershipLevelEnum,
|
||||
membershipLevels,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { isHighestMembership } from "@/utils/user"
|
||||
|
||||
import MembershipLevel from "./MembershipLevel"
|
||||
|
||||
import styles from "./friend.module.css"
|
||||
|
||||
import type { FriendProps } from "@/types/components/myPages/friend"
|
||||
@@ -20,7 +22,6 @@ export default async function Friend({
|
||||
if (!membership?.membershipLevel) {
|
||||
return null
|
||||
}
|
||||
// @ts-expect-error: membershiplevel needs proper fix
|
||||
const isHighestLevel = isHighestMembership(membership.membershipLevel)
|
||||
|
||||
return (
|
||||
@@ -30,16 +31,14 @@ export default async function Friend({
|
||||
{formatMessage(
|
||||
isHighestLevel
|
||||
? { id: "Highest level" }
|
||||
: // @ts-expect-error: membershiplevel needs proper fix
|
||||
{ id: `Level ${membershipLevels[membership.membershipLevel]}` }
|
||||
: { id: `Level ${membershipLevels[membership.membershipLevel]}` }
|
||||
)}
|
||||
</Body>
|
||||
{membership ? (
|
||||
<MembershipLevel
|
||||
// @ts-expect-error: membershiplevel needs proper fix
|
||||
level={membershipLevels[membership.membershipLevel]}
|
||||
/>
|
||||
) : null}
|
||||
<MembershipLevelIcon
|
||||
level={MembershipLevelEnum[membership.membershipLevel]}
|
||||
height="110"
|
||||
width="220"
|
||||
/>
|
||||
</header>
|
||||
<div className={styles.membership}>
|
||||
<Title className={styles.name} color="pale" level="h3">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import PointsContainer from "./Container"
|
||||
@@ -14,10 +13,12 @@ export default async function Points({ user }: UserProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
|
||||
const membership = getMembership(user.memberships)
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
membership?.nextLevel as MembershipLevelEnum,
|
||||
getLang()
|
||||
)
|
||||
|
||||
const nextLevel = membership?.nextLevel
|
||||
? await serverClient().contentstack.loyaltyLevels.byLevel({
|
||||
level: MembershipLevelEnum[membership.nextLevel],
|
||||
})
|
||||
: null
|
||||
|
||||
return (
|
||||
<PointsContainer>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
|
||||
import BenefitValue from "../BenefitValue"
|
||||
|
||||
import styles from "./benefitCard.module.css"
|
||||
|
||||
import type { BenefitCardProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function BenefitCard({
|
||||
comparedValues,
|
||||
title,
|
||||
description,
|
||||
}: BenefitCardProps) {
|
||||
return (
|
||||
<div className={styles.benefitCard}>
|
||||
<div className={styles.benefitInfo}>
|
||||
<details className={styles.details}>
|
||||
<summary className={styles.summary}>
|
||||
<hgroup className={styles.benefitCardHeader}>
|
||||
<Title
|
||||
as="h5"
|
||||
level="h2"
|
||||
textTransform={"regular"}
|
||||
className={styles.benefitCardTitle}
|
||||
>
|
||||
{title}
|
||||
</Title>
|
||||
<span className={styles.chevron}>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</hgroup>
|
||||
</summary>
|
||||
<p
|
||||
className={styles.benefitCardDescription}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
<div className={styles.benefitComparison}>
|
||||
{comparedValues.map((benefit, idx) => (
|
||||
<div
|
||||
key={`${benefit.valueDetails}-${idx}`}
|
||||
className={styles.comparisonItem}
|
||||
>
|
||||
<BenefitValue benefit={benefit} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { findBenefit, getUnlockedBenefits } from "@/utils/loyaltyTable"
|
||||
|
||||
import BenefitCard from "../BenefitCard"
|
||||
|
||||
import styles from "./benefitList.module.css"
|
||||
|
||||
import type { BenefitListProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function BenefitList({ levels }: BenefitListProps) {
|
||||
return getUnlockedBenefits(levels).map((benefit) => {
|
||||
const levelBenefits = levels.map((level) => {
|
||||
return findBenefit(benefit, level)
|
||||
})
|
||||
return (
|
||||
<div key={benefit.name} className={styles.benefitCardWrapper}>
|
||||
<BenefitCard
|
||||
title={benefit.name}
|
||||
description={benefit.description}
|
||||
comparedValues={levelBenefits.map((benefit, idx) => {
|
||||
return {
|
||||
key: `${benefit.name}-${idx}`,
|
||||
value: benefit.value,
|
||||
unlocked: benefit.unlocked,
|
||||
valueDetails: benefit.valueDetails,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Minus } from "react-feather"
|
||||
|
||||
import CheckCircle from "@/components/Icons/CheckCircle"
|
||||
|
||||
import styles from "./benefitValue.module.css"
|
||||
|
||||
import type { BenefitValueProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function BenefitValue({ benefit }: BenefitValueProps) {
|
||||
if (!benefit.unlocked) {
|
||||
return <Minus color="var(--UI-Grey-40)" />
|
||||
}
|
||||
if (!benefit.value) {
|
||||
return <CheckCircle height={32} width={32} color="green" />
|
||||
}
|
||||
return (
|
||||
<div className={styles.benefitValueContainer}>
|
||||
<span className={styles.benefitValue}>{benefit.value}</span>
|
||||
{benefit.valueDetails && (
|
||||
<span className={styles.benefitValueDetails}>
|
||||
{benefit.valueDetails}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,23 +3,18 @@
|
||||
import { useReducer } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
import {
|
||||
MembershipLevel,
|
||||
MembershipLevelEnum,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
import Select from "@/components/TempDesignSystem/Select"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getMembership } from "@/utils/user"
|
||||
import { getSteppedUpLevel } from "@/utils/user"
|
||||
|
||||
import DA from "./data/DA.json"
|
||||
import DE from "./data/DE.json"
|
||||
import EN from "./data/EN.json"
|
||||
import FI from "./data/FI.json"
|
||||
import NO from "./data/NO.json"
|
||||
import SV from "./data/SV.json"
|
||||
import BenefitList from "./BenefitList"
|
||||
import LargeTable from "./LargeTable"
|
||||
import LevelSummary from "./LevelSummary"
|
||||
import RewardList from "./RewardList"
|
||||
import YourLevel from "./YourLevelScript"
|
||||
|
||||
import styles from "./overviewTable.module.css"
|
||||
@@ -29,94 +24,91 @@ import type { Key } from "react-aria-components"
|
||||
import {
|
||||
ComparisonLevel,
|
||||
DesktopSelectColumns,
|
||||
LevelWithRewards,
|
||||
type MobileColumnHeaderProps,
|
||||
overviewTableActionsEnum,
|
||||
OverviewTableActionsEnum,
|
||||
type OverviewTableClientProps,
|
||||
OverviewTableReducerAction,
|
||||
} from "@/types/components/overviewTable"
|
||||
import type { User } from "@/types/user"
|
||||
|
||||
const levelsTranslations = {
|
||||
[Lang.en]: EN,
|
||||
[Lang.sv]: SV,
|
||||
[Lang.no]: NO,
|
||||
[Lang.da]: DA,
|
||||
[Lang.fi]: FI,
|
||||
[Lang.de]: DE,
|
||||
function getLevelNamesForSelect(level: MembershipLevel, levelName: string) {
|
||||
const levelToNumber = MembershipLevelEnum[level]
|
||||
return [levelToNumber, levelName].join(" - ")
|
||||
}
|
||||
|
||||
function getTranslatedLevel(membershipLevel: membershipLevels, lang: Lang) {
|
||||
return levelsTranslations[lang].levels.find(
|
||||
(level) => level.level === membershipLevel
|
||||
) as ComparisonLevel
|
||||
function getLevel(
|
||||
membershipLevel: MembershipLevel,
|
||||
levels: LevelWithRewards[]
|
||||
) {
|
||||
return levels.find((level) => level.level_id === membershipLevel)!
|
||||
}
|
||||
|
||||
function getInitialState({ user, lang }: { user?: User; lang: Lang }) {
|
||||
const membership = user?.memberships ? getMembership(user.memberships) : null
|
||||
if (!membership?.membershipLevel) {
|
||||
function getInitialState({
|
||||
activeMembership,
|
||||
levels,
|
||||
}: OverviewTableClientProps) {
|
||||
if (!activeMembership) {
|
||||
return {
|
||||
selectedLevelAMobile: getTranslatedLevel(1, lang),
|
||||
selectedLevelBMobile: getTranslatedLevel(2, lang),
|
||||
selectedLevelADesktop: getTranslatedLevel(1, lang),
|
||||
selectedLevelBDesktop: getTranslatedLevel(2, lang),
|
||||
selectedLevelCDesktop: getTranslatedLevel(3, lang),
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L1, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L2, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L1, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L2, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L3, levels),
|
||||
}
|
||||
}
|
||||
if (!membership.membershipLevel) return null
|
||||
// @ts-expect-error: membership levels needs proper fix
|
||||
const level = membershipLevels[membership.membershipLevel]
|
||||
const level = MembershipLevelEnum[activeMembership]
|
||||
|
||||
switch (level) {
|
||||
case 6:
|
||||
case MembershipLevelEnum.L6:
|
||||
return {
|
||||
selectedLevelAMobile: getTranslatedLevel(6, lang),
|
||||
selectedLevelBMobile: getTranslatedLevel(7, lang),
|
||||
selectedLevelADesktop: getTranslatedLevel(5, lang),
|
||||
selectedLevelBDesktop: getTranslatedLevel(6, lang),
|
||||
selectedLevelCDesktop: getTranslatedLevel(7, lang),
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L5, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L7, levels),
|
||||
}
|
||||
case 7:
|
||||
case MembershipLevelEnum.L7:
|
||||
return {
|
||||
selectedLevelAMobile: getTranslatedLevel(6, lang),
|
||||
selectedLevelBMobile: getTranslatedLevel(7, lang),
|
||||
selectedLevelADesktop: getTranslatedLevel(6, lang),
|
||||
selectedLevelBDesktop: getTranslatedLevel(7, lang),
|
||||
selectedLevelCDesktop: getTranslatedLevel(1, lang),
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L1, levels),
|
||||
}
|
||||
default:
|
||||
return {
|
||||
selectedLevelAMobile: getTranslatedLevel(level, lang),
|
||||
selectedLevelBMobile: getTranslatedLevel(level + 1, lang),
|
||||
selectedLevelADesktop: getTranslatedLevel(level, lang),
|
||||
selectedLevelBDesktop: getTranslatedLevel(level + 1, lang),
|
||||
selectedLevelCDesktop: getTranslatedLevel(level + 2, lang),
|
||||
selectedLevelAMobile: getLevel(level, levels),
|
||||
selectedLevelBMobile: getLevel(getSteppedUpLevel(level, 1), levels),
|
||||
selectedLevelADesktop: getLevel(level, levels),
|
||||
selectedLevelBDesktop: getLevel(getSteppedUpLevel(level, 1), levels),
|
||||
selectedLevelCDesktop: getLevel(getSteppedUpLevel(level, 2), levels),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reducer(state: any, action: OverviewTableReducerAction) {
|
||||
switch (action.type) {
|
||||
case overviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE:
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelAMobile: action.payload,
|
||||
}
|
||||
case overviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE:
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelBMobile: action.payload,
|
||||
}
|
||||
case overviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP:
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelADesktop: action.payload,
|
||||
}
|
||||
case overviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP:
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelBDesktop: action.payload,
|
||||
}
|
||||
case overviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP:
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelCDesktop: action.payload,
|
||||
@@ -126,51 +118,45 @@ function reducer(state: any, action: OverviewTableReducerAction) {
|
||||
}
|
||||
}
|
||||
|
||||
export default function OverviewTableClient({
|
||||
export default function OverviewTable({
|
||||
activeMembership,
|
||||
levels,
|
||||
}: OverviewTableClientProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const levelsData = levelsTranslations[lang]
|
||||
|
||||
const [selectionState, dispatch] = useReducer(
|
||||
reducer,
|
||||
{ activeMembership, lang },
|
||||
{ activeMembership, levels },
|
||||
getInitialState
|
||||
)
|
||||
|
||||
function handleSelectChange(actionType: overviewTableActionsEnum) {
|
||||
function handleSelectChange(actionType: OverviewTableActionsEnum) {
|
||||
return (key: Key) => {
|
||||
if (typeof key === "number") {
|
||||
dispatch({
|
||||
payload: getTranslatedLevel(key, lang),
|
||||
type: actionType,
|
||||
})
|
||||
}
|
||||
dispatch({
|
||||
payload: getLevel(key as MembershipLevel, levels),
|
||||
type: actionType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const levelOptions = levelsData.levels.map((level) => ({
|
||||
label: [level.level, level.name].join(" "),
|
||||
value: level.level,
|
||||
const levelOptions = levels.map((level) => ({
|
||||
label: getLevelNamesForSelect(level.level_id, level.name),
|
||||
value: level.level_id,
|
||||
}))
|
||||
|
||||
let activeMembershipLevel: membershipLevels | null = null
|
||||
if (activeMembership?.membershipLevel) {
|
||||
// @ts-expect-error: membershiplevel needs proper fix
|
||||
activeMembershipLevel = membershipLevels[activeMembership?.membershipLevel]
|
||||
}
|
||||
const activeMembershipLevel = activeMembership ?? null
|
||||
|
||||
function MobileColumnHeader({ column }: MobileColumnHeaderProps) {
|
||||
let selectedLevelMobile: ComparisonLevel
|
||||
let actionEnumMobile: overviewTableActionsEnum
|
||||
let actionEnumMobile: OverviewTableActionsEnum
|
||||
switch (column) {
|
||||
case "A":
|
||||
selectedLevelMobile = selectionState.selectedLevelAMobile
|
||||
actionEnumMobile = overviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE
|
||||
actionEnumMobile = OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE
|
||||
break
|
||||
case "B":
|
||||
selectedLevelMobile = selectionState.selectedLevelBMobile
|
||||
actionEnumMobile = overviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE
|
||||
actionEnumMobile = OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE
|
||||
break
|
||||
default:
|
||||
return null
|
||||
@@ -178,30 +164,30 @@ export default function OverviewTableClient({
|
||||
return (
|
||||
<div className={styles.columnHeader}>
|
||||
<div className={styles.icon}>
|
||||
{activeMembershipLevel === selectedLevelMobile.level ? (
|
||||
{activeMembershipLevel === selectedLevelMobile.level_id ? (
|
||||
<YourLevel />
|
||||
) : null}
|
||||
<Image
|
||||
src={selectedLevelMobile.icon}
|
||||
alt={selectedLevelMobile.name}
|
||||
height={50}
|
||||
width={100}
|
||||
<MembershipLevelIcon
|
||||
level={selectedLevelMobile.level_id}
|
||||
color="red"
|
||||
height="50"
|
||||
width="100"
|
||||
/>
|
||||
</div>
|
||||
<LevelSummary
|
||||
level={
|
||||
levelsData.levels.find(
|
||||
(level) => level.level === selectedLevelMobile.level
|
||||
) as ComparisonLevel
|
||||
levels.find(
|
||||
(level) => level.level_id === selectedLevelMobile.level_id
|
||||
)!
|
||||
}
|
||||
showDescription={false}
|
||||
/>
|
||||
<Select
|
||||
aria-label={intl.formatMessage({ id: "Level" })}
|
||||
name={`benefit` + column}
|
||||
name={`reward` + column}
|
||||
label={intl.formatMessage({ id: "Level" })}
|
||||
items={levelOptions}
|
||||
value={selectedLevelMobile.level}
|
||||
value={selectedLevelMobile.level_id}
|
||||
onSelect={handleSelectChange(actionEnumMobile)}
|
||||
/>
|
||||
</div>
|
||||
@@ -210,22 +196,22 @@ export default function OverviewTableClient({
|
||||
|
||||
function SelectDesktop({ column }: DesktopSelectColumns) {
|
||||
let selectedLevelDesktop: ComparisonLevel
|
||||
let actionEnumDesktop: overviewTableActionsEnum
|
||||
let actionEnumDesktop: OverviewTableActionsEnum
|
||||
switch (column) {
|
||||
case "A":
|
||||
selectedLevelDesktop = selectionState.selectedLevelADesktop
|
||||
actionEnumDesktop =
|
||||
overviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP
|
||||
OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP
|
||||
break
|
||||
case "B":
|
||||
selectedLevelDesktop = selectionState.selectedLevelBDesktop
|
||||
actionEnumDesktop =
|
||||
overviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP
|
||||
OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP
|
||||
break
|
||||
case "C":
|
||||
selectedLevelDesktop = selectionState.selectedLevelCDesktop
|
||||
actionEnumDesktop =
|
||||
overviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP
|
||||
OverviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP
|
||||
break
|
||||
default:
|
||||
return null
|
||||
@@ -233,15 +219,14 @@ export default function OverviewTableClient({
|
||||
return (
|
||||
<Select
|
||||
aria-label={intl.formatMessage({ id: "Level" })}
|
||||
name={`benefit` + column}
|
||||
name={`reward` + column}
|
||||
label={intl.formatMessage({ id: "Level" })}
|
||||
items={levelOptions}
|
||||
value={selectedLevelDesktop.level}
|
||||
value={selectedLevelDesktop.level_id}
|
||||
onSelect={handleSelectChange(actionEnumDesktop)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.mobileColumns}>
|
||||
@@ -249,7 +234,7 @@ export default function OverviewTableClient({
|
||||
<MobileColumnHeader column={"A"} />
|
||||
<MobileColumnHeader column={"B"} />
|
||||
</div>
|
||||
<BenefitList
|
||||
<RewardList
|
||||
levels={[
|
||||
selectionState.selectedLevelAMobile,
|
||||
selectionState.selectedLevelBMobile,
|
||||
@@ -268,11 +253,7 @@ export default function OverviewTableClient({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.largeTableContainer}>
|
||||
{/* Remove `as` once we have real data */}
|
||||
<LargeTable
|
||||
levels={levelsData.levels as ComparisonLevel[]}
|
||||
activeLevel={activeMembershipLevel}
|
||||
/>
|
||||
<LargeTable levels={levels} activeLevel={activeMembershipLevel} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Image from "@/components/Image"
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
|
||||
import LevelSummary from "../../LevelSummary"
|
||||
import YourLevel from "../../YourLevelScript"
|
||||
@@ -21,13 +21,13 @@ export default function DesktopHeader({
|
||||
<th className={styles.verticalTableHeader} />
|
||||
{levels.map((level, idx) => {
|
||||
return (
|
||||
<th key={"image" + level.level + idx} className={styles.iconTh}>
|
||||
{activeLevel === level.level ? <YourLevel /> : null}
|
||||
<Image
|
||||
height={50}
|
||||
width={100}
|
||||
alt={level.name}
|
||||
src={level.icon}
|
||||
<th key={"image" + level.level_id + idx} className={styles.iconTh}>
|
||||
{activeLevel === level.level_id ? <YourLevel /> : null}
|
||||
<MembershipLevelIcon
|
||||
color="red"
|
||||
level={level.level_id}
|
||||
height="50"
|
||||
width="100"
|
||||
/>
|
||||
</th>
|
||||
)
|
||||
@@ -38,7 +38,7 @@ export default function DesktopHeader({
|
||||
{levels.map((level, idx) => {
|
||||
return (
|
||||
<th
|
||||
key={"summary" + level.level + idx}
|
||||
key={"summary" + level.level_id + idx}
|
||||
className={styles.summaryTh}
|
||||
>
|
||||
<LevelSummary level={level} />
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { findBenefit, getUnlockedBenefits } from "@/utils/loyaltyTable"
|
||||
import {
|
||||
findAvailableRewards,
|
||||
getGroupedLabelAndDescription,
|
||||
getGroupedRewards,
|
||||
} from "@/utils/loyaltyTable"
|
||||
|
||||
import BenefitValue from "../BenefitValue"
|
||||
import RewardValue from "../RewardValue"
|
||||
import DesktopHeader from "./DesktopHeader"
|
||||
|
||||
import styles from "./largeTable.module.css"
|
||||
|
||||
import type {
|
||||
BenefitTableHeaderProps,
|
||||
LargeTableProps,
|
||||
RewardTableHeaderProps,
|
||||
} from "@/types/components/overviewTable"
|
||||
|
||||
export default function LargeTable({
|
||||
@@ -18,6 +22,8 @@ export default function LargeTable({
|
||||
activeLevel,
|
||||
Select,
|
||||
}: LargeTableProps) {
|
||||
const groupedRewards = getGroupedRewards(levels)
|
||||
|
||||
return (
|
||||
<table className={styles.table}>
|
||||
<DesktopHeader
|
||||
@@ -26,19 +32,21 @@ export default function LargeTable({
|
||||
Select={Select}
|
||||
/>
|
||||
<tbody className={styles.tbody}>
|
||||
{getUnlockedBenefits(levels).map((benefit) => {
|
||||
{Object.entries(groupedRewards).map(([key, groupedRewards], idx) => {
|
||||
const { label, description } =
|
||||
getGroupedLabelAndDescription(groupedRewards)
|
||||
|
||||
return (
|
||||
<tr key={benefit.name} className={styles.tr}>
|
||||
<th scope={"row"} className={styles.benefitTh}>
|
||||
<BenefitTableHeader
|
||||
name={benefit.name}
|
||||
description={benefit.description}
|
||||
/>
|
||||
<tr key={key + idx} className={styles.tr}>
|
||||
<th scope={"row"} className={styles.rewardTh}>
|
||||
<RewardTableHeader name={label} description={description} />
|
||||
</th>
|
||||
{levels.map((level, idx) => {
|
||||
const rewardIdsInGroup = groupedRewards.map((b) => b.reward_id)
|
||||
const reward = findAvailableRewards(rewardIdsInGroup, level)
|
||||
return (
|
||||
<td key={"icon" + level.level + idx} className={styles.td}>
|
||||
<BenefitValue benefit={findBenefit(benefit, level)} />
|
||||
<td key={`${reward?.reward_id}-${idx}`} className={styles.td}>
|
||||
<RewardValue reward={reward} />
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
@@ -50,17 +58,12 @@ export default function LargeTable({
|
||||
)
|
||||
}
|
||||
|
||||
function BenefitTableHeader({ name, description }: BenefitTableHeaderProps) {
|
||||
function RewardTableHeader({ name, description }: RewardTableHeaderProps) {
|
||||
return (
|
||||
<details className={styles.details}>
|
||||
<summary className={styles.summary}>
|
||||
<hgroup className={styles.benefitHeader}>
|
||||
<Title
|
||||
as="h5"
|
||||
level="h2"
|
||||
textTransform={"regular"}
|
||||
className={styles.benefitTitle}
|
||||
>
|
||||
<hgroup className={styles.rewardHeader}>
|
||||
<Title as="h5" level="h2" textTransform={"regular"}>
|
||||
{name}
|
||||
</Title>
|
||||
<span className={styles.chevron}>
|
||||
@@ -69,7 +72,7 @@ function BenefitTableHeader({ name, description }: BenefitTableHeaderProps) {
|
||||
</hgroup>
|
||||
</summary>
|
||||
<p
|
||||
className={styles.benefitDescription}
|
||||
className={styles.rewardDescription}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</details>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.benefitTh {
|
||||
.rewardTh {
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
||||
@@ -29,14 +29,14 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.benefitHeader {
|
||||
.rewardHeader {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: 1fr auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.benefitDescription {
|
||||
.rewardDescription {
|
||||
margin: 0;
|
||||
padding-top: var(--Spacing-x1);
|
||||
text-align: start;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./levelSummary.module.css"
|
||||
|
||||
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
||||
@@ -6,9 +10,17 @@ export default function LevelSummary({
|
||||
level,
|
||||
showDescription = true,
|
||||
}: LevelSummaryProps) {
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<div className={styles.levelSummary}>
|
||||
<span className={styles.levelRequirements}>{level.requirement}</span>
|
||||
<span className={styles.levelRequirements}>
|
||||
{level.required_points.toLocaleString(lang)}
|
||||
{"p "}
|
||||
{level.required_nights
|
||||
? `${intl.formatMessage({ id: "or" })} ${level.required_nights} ${intl.formatMessage({ id: "nights" })}`
|
||||
: ""}
|
||||
</span>
|
||||
{showDescription && (
|
||||
<p className={styles.levelSummaryText}>{level.description}</p>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
|
||||
import RewardValue from "../../RewardValue"
|
||||
|
||||
import styles from "./rewardCard.module.css"
|
||||
|
||||
import type { RewardCardProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardCard({
|
||||
comparedValues,
|
||||
title,
|
||||
description,
|
||||
}: RewardCardProps) {
|
||||
return (
|
||||
<div className={styles.rewardCard}>
|
||||
<div className={styles.rewardInfo}>
|
||||
<details className={styles.details}>
|
||||
<summary className={styles.summary}>
|
||||
<hgroup className={styles.rewardCardHeader}>
|
||||
<Title as="h5" level="h2" textTransform={"regular"}>
|
||||
{title}
|
||||
</Title>
|
||||
<span className={styles.chevron}>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</hgroup>
|
||||
</summary>
|
||||
<p
|
||||
className={styles.rewardCardDescription}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
<div className={styles.rewardComparison}>
|
||||
{comparedValues.map((reward, idx) => (
|
||||
<div
|
||||
key={`${reward?.reward_id}-${idx}`}
|
||||
className={styles.comparisonItem}
|
||||
>
|
||||
<RewardValue reward={reward} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
.benefitCard {
|
||||
.rewardCard {
|
||||
padding-bottom: var(--Spacing-x-one-and-half);
|
||||
z-index: 2;
|
||||
grid-column: 1/3;
|
||||
}
|
||||
|
||||
.benefitCardHeader {
|
||||
.rewardCardHeader {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.benefitCardDescription {
|
||||
.rewardCardDescription {
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
line-height: 150%;
|
||||
padding-right: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.benefitInfo {
|
||||
.rewardInfo {
|
||||
padding-bottom: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.benefitComparison {
|
||||
.rewardComparison {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.benefitComparison {
|
||||
.rewardComparison {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
findAvailableRewards,
|
||||
getGroupedLabelAndDescription,
|
||||
getGroupedRewards,
|
||||
} from "@/utils/loyaltyTable"
|
||||
|
||||
import RewardCard from "./Card"
|
||||
|
||||
import styles from "./rewardList.module.css"
|
||||
|
||||
import type { RewardListProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardList({ levels }: RewardListProps) {
|
||||
const groupedRewards = getGroupedRewards(levels)
|
||||
|
||||
return Object.values(groupedRewards).map((groupedRewards) => {
|
||||
const rewardIdsInGroup = groupedRewards.map((b) => b.reward_id)
|
||||
|
||||
const { label, description } = getGroupedLabelAndDescription(groupedRewards)
|
||||
|
||||
const levelRewards = levels.map((level) => {
|
||||
return findAvailableRewards(rewardIdsInGroup, level)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
key={levelRewards[0]?.reward_id ?? ""}
|
||||
className={styles.rewardCardWrapper}
|
||||
>
|
||||
<RewardCard
|
||||
title={label}
|
||||
description={description}
|
||||
comparedValues={levelRewards}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
.benefitCardWrapper {
|
||||
.rewardCardWrapper {
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
position: relative;
|
||||
display: grid;
|
||||
@@ -8,12 +8,12 @@
|
||||
margin: var(--Spacing-x1) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.benefitCardWrapper:last-child {
|
||||
.rewardCardWrapper:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.benefitCardWrapper {
|
||||
.rewardCardWrapper {
|
||||
grid-column: 1/4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Minus } from "react-feather"
|
||||
|
||||
import CheckCircle from "@/components/Icons/CheckCircle"
|
||||
|
||||
import styles from "./rewardValue.module.css"
|
||||
|
||||
import type { RewardValueProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardValue({ reward }: RewardValueProps) {
|
||||
if (!reward) {
|
||||
return <Minus color="var(--UI-Grey-40)" />
|
||||
}
|
||||
if (!reward.value) {
|
||||
return <CheckCircle height={32} width={32} color="green" />
|
||||
}
|
||||
return (
|
||||
<div className={styles.rewardValueContainer}>
|
||||
<span className={styles.rewardValue}>{reward.value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
.benefitValueContainer {
|
||||
.rewardValueContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -7,12 +7,12 @@
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.benefitValue {
|
||||
.rewardValue {
|
||||
font-size: var(--typography-Body-Bold-fontSize);
|
||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
||||
}
|
||||
|
||||
.benefitValueDetails {
|
||||
.rewardValueDetails {
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
text-align: center;
|
||||
color: var(--UI-Grey-80);
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"description": "Det her er starten på noget smukt. Som New Friend kan du godt glæde dig til at opdage alt det skønne ved Scandic.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Mums! I weekenderne får du en lækker rabat på 10% i vores restaurant og hotelshop. Tilbuddet gælder, uanset om du overnatter eller bare kigger forbi for at få en bid mad. Så kom i gang med at forkæle dig selv og bestille ekstra roomservice.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnene føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"description": "Du har været her meget på det sidste! Lad os tage venskabet videre ét behageligt ophold ad gangen.",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotel shop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"description": "Vi har lært hinanden bedre at kende, og det gør din oplevelse hos Scandic meget mere personlig.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotel shop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "For hver pointgivende overnatning hos os giver vi dig en restaurantvoucher på DKK 50,-.",
|
||||
"unlocked": true,
|
||||
"value": "DKK 50,-"
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"description": "Her er vist grobund for et livslangt venskab, og det betyder, at du får adgang til meget mere med Scandic.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotelshop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "Vores venskab blev lige en smule bedre! Få en restaurantvoucher på DKK 75,- for hver pointgivende overnatning.",
|
||||
"unlocked": true,
|
||||
"value": "DKK 75,-"
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "Hver gang du booster dine venskabspoint, får du 25% ekstra! Så kom i gang med at optjene point på ophold, måltider m.m., og pludselig har du en gratis overnatning.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"description": "Du har været hos os på mange ophold, så derfor vil vi gerne give dig nogle af vores bedste fordele.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotel shop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "Vi giver dig en restaurantvoucher på DKK 100,- for hver pointgivende overnatning. Det kan blive til mange croissanter!",
|
||||
"unlocked": true,
|
||||
"value": "DKK 100,-"
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "Hver gang du booster dine venskabspoint, får du 25% ekstra! Så kom i gang med at optjene point på ophold, måltider m.m., og pludselig har du en gratis overnatning.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"description": "Vi er glade for, at du besøger os, uanset om det er høj- eller lavsæson. Derfor får du endnu flere skræddersyede fordele.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotel shop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "Det går ret godt, så nu giver hver pointgivende overnatning en restaurantvoucher. Den er helt sikkert god at have ved hånden, næste gang du vil spise en god middag!",
|
||||
"unlocked": true,
|
||||
"value": "DKK 150,-"
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "Du kan godt glæde dig. Hver gang du booster dine venskabspoint, får du 25-50% ekstra! Så kom i gang med at optjene point på ophold, måltider m.m., og pludselig har du en gratis overnatning.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p eller 100 nætter",
|
||||
"description": "Det bliver simpelthen ikke bedre end det her, når det kommer til de helt eksklusive oplevelser!",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Venlige priser",
|
||||
"description": "Som vores ven får du altid de bedste tilbud, uanset tid og sted. Her er der ingen hemmelige aftaler eller bookingkoder – du skal bare booke som normalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabat på mad",
|
||||
"description": "Hvad er mere lækkert end en rabat? Som vores ven får du i weekenderne og på udvalgte helligdage 10-15% rabat i vores restaurant og på maden i vores hotel shop – og det er uanset om du overnatter hos os eller ej. Så kom i gang med at forkæle dig selv: Bestil noget roomservice!",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Gratis mocktail til børn under opholdet",
|
||||
"description": "Vi vil så gerne have, at børnenes føler sig som de VIPs, de er, så vi giver en forfriskende mocktail under hvert ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen check ud, når tilgængeligt",
|
||||
"description": "Vi ved godt, at muligheden for at checke sent ud virkelig kan redde turen, og nu skal du ikke længere skynde dig ud af sengen. Check ud en time senere uden ekstra omkostninger, og nyd at kunne sove lidt længere.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantvoucher",
|
||||
"description": "Nu giver hver pointgivende overnatning en restaurantvoucher. Forestil dig lige, hvor mange croissanter du kan få!",
|
||||
"unlocked": true,
|
||||
"value": "DKK 200,-"
|
||||
},
|
||||
{
|
||||
"name": "Venskabsboost",
|
||||
"description": "Du kan godt glæde dig. Hver gang du booster dine venskabspoint, får du 25-50% ekstra! Så kom i gang med at optjene point på ophold, måltider m.m., og pludselig har du en gratis overnatning.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig check ind, når tilgængeligt",
|
||||
"description": "Vil du tage hul på opholdet lidt hurtigere? Intet problem. Check ind en time før uden ekstra omkostninger, og nyd at kunne slappe en smule mere af.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis opgraderinger, når tilgængelige",
|
||||
"description": "På det her venskabsniveau opgraderer vi dit værelse, hvis det er muligt, så du får et endnu mere behageligt ophold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 morgenmad",
|
||||
"description": "Uanset om du overnatter hos os eller ej, kan du invitere en ven på morgenmad, for I kan spise for to personer og kun betale for én! Men husk, at tjekke detaljerne først, så alt er klar til et hyggeligt morgenmøde.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-timers værelsesgaranti",
|
||||
"description": "Kun få af jer får denne særlige fordel! Selv hvis vi er fuldt bookede, er du garanteret et værelse, så længe du booker mindst 48 timer i forvejen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis morgenmad – altid",
|
||||
"description": "Klar til noget lækkert om morgenen? Nu kan du starte dagen med gratis morgenmad – og ved du hvad? Det gælder uanset om du overnatter hos os eller ej.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "En fantastisk gave hvert år",
|
||||
"description": "Som vores Best Friend fortjener du virkelig en særlig behandling, og derfor har vi en eksklusiv og ret fantastisk gave klar til dig en gang om året. Hvad det er? Det er jo en overraskelse!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Børneboost",
|
||||
"description": "Dit barn er også vores ven, og det betyder en særlig boostgave, når I overnatter hos os. Hvorfor? Fordi børn er de bedste!",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Mmmh, lecker! Genießen Sie an den Wochenenden einen verlockenden Preisnachlass von 10 % in unserem Restaurant und Hotelshop. Dieses Angebot gilt unabhängig davon, ob Sie bei uns übernachten oder nur auf einen Happen zu essen bei uns vorbeischauen. Worauf warten Sie also noch? Gönnen Sie sich etwas und bestellen Sie den zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "Und jetzt kommt der Clou: Für jede Übernachtung, mit der Sie bei uns Freundschaftspunkte sammeln, schenken wir Ihnen zusätzlich einen Restaurantgutschein über 5 €.",
|
||||
"unlocked": true,
|
||||
"value": "5 €"
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "Unsere Freundschaft wird jetzt noch schöner! Genießen Sie einen Restaurantgutschein im Wert von 7,50 € für jede Übernachtung, mit der Sie bei uns Punkte sammeln. Ist das nicht aufregend?",
|
||||
"unlocked": true,
|
||||
"value": "7,50 €"
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "Hier haben wir etwas wirklich Großartiges für Sie: Jedes Mal, wenn Sie einen neuen Schub an Freundschaftspunkten erhalten, gibt es von uns 25 % dazu – ein ordentlicher Extra-Schub! Wenn Sie also anfangen, diese Punkte für Aufenthalte, Mahlzeiten und anderes zu sammeln, erhalten Sie im Handumdrehen eine kostenlose Übernachtung.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "Wir schenken Ihnen einen zusätzlichen Restaurantgutschein über 10 € für jede Übernachtung, mit der Sie bei uns Freundschaftspunkte sammeln. Dafür bekommen Sie schon einen ganzen Berg von Croissants! Klingt das nicht verlockend?",
|
||||
"unlocked": true,
|
||||
"value": "10 €"
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "Hier haben wir etwas wirklich Großartiges für Sie: Jedes Mal, wenn Sie einen neuen Schub an Freundschaftspunkten erhalten, gibt es von uns 25 % dazu – ein ordentlicher Extra-Schub! Wenn Sie also anfangen, diese Punkte für Aufenthalte, Mahlzeiten und anderes zu sammeln, erhalten Sie im Handumdrehen eine kostenlose Übernachtung.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "Zwischen uns läuft es inzwischen so gut, dass Sie für jede Nacht, in der Sie bei uns Freundschaftspunkte sammeln, zusätzlich noch einen Restaurantgutschein im Wert von 15 € erhalten. Perfekt für Ihr nächstes köstliches Frühstück oder Abendessen!",
|
||||
"unlocked": true,
|
||||
"value": "15 €"
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "Was für ein Spaß! Jedes Mal, wenn Sie einen neuen Schub an Freundschaftspunkten erhalten, gibt es von uns 25-50 % dazu – ein ordentlicher Extraschub! Je früher Sie also anfangen, Punkte für Ihre Aufenthalte, Mahlzeiten und vieles mehr zu sammeln, desto schneller erhalten Sie Ihre erste kostenlose Übernachtung.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"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": [
|
||||
{
|
||||
"name": "Freundschaftspreise",
|
||||
"description": "Als unser Freund erhalten Sie immer das beste Angebot: an jedem Tag und an jedem Ort. Und dafür ist weder ein Handschlag noch ein Buchungscode erforderlich – stürzen Sie sich einfach ins Buchungserlebnis und überzeugen Sie sich selbst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt auf Speisen",
|
||||
"description": "Was gibt es Schöneres als einen Preisnachlass? Als Freund erhalten Sie an Wochenenden und ausgewählten Feiertagen 10-15% Rabatt auf die Speisen in unseren Restaurants und Hotelshops – und zwar unabhängig davon, ob Sie bei uns übernachten oder nicht. Also kommen Sie vorbei und tun Sie sich etwas Gutes! Zum Beispiel mit unserem zusätzlichen Zimmerservice.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Kostenloser Kinder-Mocktail während des Aufenthalts",
|
||||
"description": "Oh, Sie haben ja die Kinder dabei! Wie schön – hallo junger Freund! Wir möchten, dass sich Kinder bei uns wie VIPs fühlen, und deshalb bekommen sie von uns bei jedem Aufenthalt einen erfrischenden Mocktail spendiert.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Später Check-out, wenn verfügbar",
|
||||
"description": "Wir wissen, dass ein später Check-out manchmal eine große Erleichterung sein kann. Und an diesem Punkt unserer Freundschaft gibt es keinen Grund mehr, sich aus dem Bett zu quälen: Wir kommen Ihnen gerne entgegen. Checken Sie kostenlos 1 Stunde später aus und schlafen Sie einfach etwas länger.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantgutschein",
|
||||
"description": "Für jede Nacht, in der Sie bei uns Freundschaftspunkte sammeln, erhalten Sie einen Restaurantgutschein in Höhe von 20 €. Stellen Sie sich vor, wie viele Croissants Sie dafür essen könnten! Wenn das nicht verlockend ist?",
|
||||
"unlocked": true,
|
||||
"value": "20 €"
|
||||
},
|
||||
{
|
||||
"name": "Freundschaftsbonus",
|
||||
"description": "Was für ein Spaß! Jedes Mal, wenn Sie einen neuen Schub an Freundschaftspunkten erhalten, gibt es von uns 25-50 % dazu – ein ordentlicher Extraschub! Je früher Sie also anfangen, Punkte für Ihre Aufenthalte, Mahlzeiten und vieles mehr zu sammeln, desto schneller erhalten Sie Ihre erste kostenlose Übernachtung.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Früher Check-in, wenn verfügbar",
|
||||
"description": "Sie treffen schon früher bei uns ein? Kein Problem! Checken Sie einfach kostenlos eine Stunde früher ein – so können Sie sich gleich ein wenig ausruhen und haben noch mehr von Ihrem Aufenthalt.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kostenlose Upgrades wenn verfügbar",
|
||||
"description": "Um auf den Punkt zu kommen – wir setzen noch einen drauf. Auf dieser Stufe unserer Freundschaft bieten wir Ihnen, wann immer es möglich ist, ein Zimmer-Upgrade an – so wird Ihr Aufenthalt noch komfortabler. Wenn das nicht großartig klingt …",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frühstück für zwei zum Preis von einem",
|
||||
"description": "Wenn das kein delikater Deal ist: Egal, ob Sie bei uns übernachten oder nicht, schnappen Sie sich einen Frühstückspartner, denn jetzt können Sie zu zweit zum Preis von einem bei uns essen! Informieren Sie sich einfach vorher über alle Details und genießen Sie ein gemütliches Frühstück zu zweit.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48-Stunden-Zimmergarantie",
|
||||
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48 Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ein kostenloses Frühstück – jederzeit",
|
||||
"description": "Lust auf einen leckeren Start in den Tag? Dann schauen Sie doch einfach bei uns vorbei! Denn jetzt können Sie Ihren Tag mit einem kostenlosen Frühstück beginnen – und zwar ganz unabhängig davon, ob Sie bei uns übernachten oder nicht.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ein exklusives Geschenk pro Jahr",
|
||||
"description": "Als Best Friend haben Sie natürlich eine königliche Behandlung verdient – deshalb haben wir einmal im Jahr ein exklusives und ziemlich großartiges Geschenk für Sie. Neugierig? Tja, das bleibt leider eine Überraschung. Es wird noch nichts verraten!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ein Geschenk für Kinder",
|
||||
"description": "An diesem Punkt unserer Freundschaft betrachten wir natürlich auch Ihr Kind als Freund – was bedeutet, dass es bei jeder Übernachtung ein ganz spezielles Geschenk von uns erhält. Und warum? Weil Kinder einfach cool sind! Sie haben eine VIP-Behandlung verdient.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"description": "This is the start of something beautiful: as a New Friend, get ready for a journey of delightful Scandic discoveries.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "Yumm! Enjoy a savory 10% or more off in our restaurant and hotel shop on weekends. This offer stands whether you're our guest for the night or just dropping by for a bite. So, go ahead, treat yourself and order that extra room service.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"description": "You've been around a lot lately! And honestly, we feel like we're vibing - one enjoyable stay at a time.",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"description": "It's serious now: we're really getting to know each other which makes your Scandic experiences a lot more personal.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": true,
|
||||
"value": "€5"
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"description": "Cheers to us! This seems to be going in the direction of friends for life - and that comes with access to a-whole-lotta-more of Scandic.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": true,
|
||||
"value": "€7.5"
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": true,
|
||||
"value": "+25%"
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"description": "You've stuck with us through stays, after works and gym sessions - so we'll stick with you through some of our very best rewards.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": true,
|
||||
"value": "€10"
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": true,
|
||||
"value": "+25%"
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"description": "It doesn't matter if it's peak or off season, you're always there for us. Enjoy even more tailored perks - just the way you like them.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": true,
|
||||
"value": "€15"
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": true,
|
||||
"value": "+50%"
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p or 100 nights",
|
||||
"description": "There are no words for a bond like this, but here's a few anyway: It simply doesn't get any better when it comes to very, very exclusive experiences!",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendly rates",
|
||||
"description": "Being our friend means you’re always scoring the best deal: any day, any place. No secret handshake or booking code needed – just dive into the booking and see for yourself.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Discount on food",
|
||||
"description": "What’s more delicious than a discount? As our friend, you get 10-15% off on our restaurant and hotel shop food on weekends and selected holidays – and that goes for when you’re staying with us and when you’re not. So come on, spoil yourself! Get some room service.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Free kids mocktail during stay",
|
||||
"description": "Got the kiddos with you? That’s amazing – hello there, buddy! We wanna make sure that kids feel like the VIPs they really are, so every stay they get a refreshing mocktail on us.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Late checkout when available",
|
||||
"description": "We get it – late checkout can be a total trip-saver at times. At this point in our friendship, there’s no need to rush out of bed: we’ve got you covered. Check out 1 hour later at no cost and get those last, extra refreshing minutes of sleep.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurant voucher",
|
||||
"description": "Yeah, that’s right: for each friendship point-boosting night, you get a voucher to redeem when you dine or drink at our restaurants and bars.",
|
||||
"unlocked": true,
|
||||
"value": "€20"
|
||||
},
|
||||
{
|
||||
"name": "Friendship point boost",
|
||||
"description": "Now here’s something pretty sweet: every time you get a boost of friendship points, you get 25% or 50% extra – a boost! So you know, start racking up those points on stays, meals and more and you’ll get a free night in no time.",
|
||||
"unlocked": true,
|
||||
"value": "+50%"
|
||||
},
|
||||
{
|
||||
"name": "Early check-in when available",
|
||||
"description": "Wanna get a head start on your stay? No problem, no problem. Check in 1 hour earlier at no cost and take the highway to relaxation and bliss.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free upgrades when available",
|
||||
"description": "Let's cut to the chase – we’re taking things up a notch. At this level in our friendship, we'll upgrade your room whenever possible for an even comfier stay. Sounds good, right?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 breakfast",
|
||||
"description": "Now, this is a delicious deal: regardless if you’re staying with us or not, go grab a breakfast buddy because you're eating two for the price of one! Just make sure to check the details first so everything is in place for your yummy get-together.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48hr room guarantee",
|
||||
"description": "Hush – this one's a special treat just for a few of you! So listen up: even if we're fully booked, you're guaranteed a room as long as you book 48 hours in advance. Pretty incredible!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Free breakfast – always",
|
||||
"description": "Care for a tasty morning treat? Come on over! You now get to kickstart your day with free breakfast – and guess what: that goes regardless if you’re staying with us or not.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Yearly awesome gift",
|
||||
"description": "As our Best Friend, you totally deserve princess treatment – so we’ve got an exclusive, annual and pretty awesome gift all lined up. What it is? Well, that’s a surprise. No peeking!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "At this friendship level, your kid is our friend, too – and that means a special kid’s boost gift when you stay with us. Why? Because kids are cool! They totally deserve VIP treatment.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"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.",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Tässä sinulle makoisa herkku: saat 5 € ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä. <a href=''>Katso tästä lisätiedot</a>.",
|
||||
"unlocked": true,
|
||||
"value": "5 €"
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Ystävyytemme on entistä makeampaa! Saat 7,50 € ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä. Huippua, vai mitä?",
|
||||
"unlocked": true,
|
||||
"value": "7,50 €"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Saat 10 € ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä. Se tuo varmasti iloa seuraavaan herkutteluhetkeen.",
|
||||
"unlocked": true,
|
||||
"value": "10 €"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Ystävyytemme vain vahvistuu ja nyt saat 15 € ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä. Kuinkahan paljon herkkuja sillä saisi?",
|
||||
"unlocked": true,
|
||||
"value": "15 €"
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Saat 25 % tai 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä!.",
|
||||
"unlocked": true,
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"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.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alennus ruoasta",
|
||||
"description": "Ystävänämme saat 10-15 % alennusta ruoasta hotelliemme ravintoloissa ja shopissa viikonloppuisin ja valittuina loma-aikoina.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Mocktail lapsille maksutta",
|
||||
"description": "Meillä lapset saavat raikkaan mocktailin majoittumisen yhteydessä.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Myöhäinen uloskirjautuminen",
|
||||
"description": "Kirjaudu ulos tuntia myöhemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Ravintolakuponki",
|
||||
"description": "Ystävänämme saat ravintolakupongin jokaisesta pisteisiin oikeuttavasta yöstä.",
|
||||
"unlocked": true,
|
||||
"value": "20 €"
|
||||
},
|
||||
{
|
||||
"name": "Enemmän pisteitä",
|
||||
"description": "Saat 25 % tai 50 % enemmän pisteitä joka kerta kun ansaitset pisteitä!.",
|
||||
"unlocked": true,
|
||||
"value": "50 %"
|
||||
},
|
||||
{
|
||||
"name": "Aikainen sisäänkirjautuminen",
|
||||
"description": "Kirjaudu sisään tuntia aiemmin ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Maksuton huoneluokan korotus",
|
||||
"description": "Saat huoneluokan korotuksen ilman lisämaksua. Saatavilla varaustilanteen mukaan.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Aamiainen – kaksi yhden hinnalla",
|
||||
"description": "Saat kaksi aamiaista yhden hinnalla, majoitut meillä tai et.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 tunnin huonetakuu",
|
||||
"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": "Tarjoamme sinulle hotelliaamiaisen maksutta, majoitut meillä tai et.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Upea vuotuinen lahja",
|
||||
"description": "Palkitsemme sinut vuosittain mahtavalla ja eksklusiivisella yllätyslahjalla.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Kid’s boost",
|
||||
"description": "Muistamme lapsia pienellä lahjalla majoituksen yhteydessä.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"description": "Dette er starten på noe vakkert: Som en New Friend, gjør deg klar for en reise fylt av fine Scandic-opplevelser.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"description": "Du har vært her mye i det siste! Og ærlig talt føler vi at vi er på bølgelengde – ett behagelig opphold og én morsom overraskelse om gangen.",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"description": "Nå er det seriøst: Vi begynner virkelig å bli kjent med hverandre, noe som gjør Scandic-opplevelsen din mye mer personlig.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"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.",
|
||||
"unlocked": true,
|
||||
"value": "15 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "Så, her er godbiten: For hver vennskapspoenggivende natt du bor hos oss, gir vi deg en restaurantkupong på 50 NOK.",
|
||||
"unlocked": true,
|
||||
"value": "50 NOK"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"description": "Hurra for oss! Dette ser ut til å gå i retning av venner for livet – og det kommer med tilgang til mye mer av Scandic.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "Vennskapet vårt ble nettopp enda bedre! Nyt en restaurantkupong på 75 NOK for hver poenggivende natt. Bra, ikke sant?",
|
||||
"unlocked": true,
|
||||
"value": "75 NOK"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"description": "Du har vært lojal mot oss gjennom opphold, happy hours og treningsøkter – så vi er der for deg med noen av de aller beste fordelene våre.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "Vi gir deg en restaurantkupong på 100 NOK for hver vennskapspoenggivende natt du bor. Det kan gi deg et fjell av croissanter! Herlig, hva?",
|
||||
"unlocked": true,
|
||||
"value": "100 NOK"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"description": "Det spiller ingen rolle om det er høysesong eller lavsesong, du er alltid der for oss. Nyt enda flere skreddersydde fordeler – akkurat slik du liker dem.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "Dette går virkelig flott! Nå vil hver vennskapspoenggivende natt gi deg en restaurantkupong på 150 NOK. Det vil garantert være nyttig for den neste lekre frokosten eller middagsdaten din!",
|
||||
"unlocked": true,
|
||||
"value": "150 NOK"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p eller 100 netter",
|
||||
"description": "Det finnes ikke ord for et bånd som dette, men vi gjør et forsøk allikevel: Det blir bare ikke bedre når det gjelder svært eksklusive opplevelser!",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Vennlige priser",
|
||||
"description": "Å være vennen vår betyr at du alltid får det beste tilbudet: Når som helst, hvor som helst. Det er ikke nødvendig med noe hemmelig håndtrykk eller bestillingskode – bare dykk inn i bestillingene og se selv.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Gratis barne-mocktail under oppholdet",
|
||||
"description": "Har du barna med deg? Det er fantastisk – hei der, kompis! Vi vil sørge for at ungene føler seg som de VIP-ene de faktisk er, og spanderer en forfriskende mocktail på dem ved hvert opphold.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utsjekking når tilgjengelig",
|
||||
"description": "Vi skjønner – sen utsjekkingen kan til tider være redningen for en reise. På dette punktet i vennskapet vårt, trenger du ikke skynde deg ut av sengen: Vi går god for deg, så sjekk ut én time senere kostnadsfritt, og få de siste, ekstra oppfriskende minuttene med søvn!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurantkupong",
|
||||
"description": "Vi gir deg en restaurantkupong på 200 NOK for hver vennskapspoenggivende natt du bor. Det kan gi deg et fjell av croissanter! Herlig, hva?",
|
||||
"unlocked": true,
|
||||
"value": "200 NOK"
|
||||
},
|
||||
{
|
||||
"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 %"
|
||||
},
|
||||
{
|
||||
"name": "Tidlig innsjekk når tilgjengelig",
|
||||
"description": "Vil du få et forsprang på oppholdet ditt? Det er ikke noe problem. Sjekk inn én time tidligere kostnadsfritt, og ta snarveien til avslapning og lykke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis oppgraderinger (når tilgjengelig)",
|
||||
"description": "La oss ikke gå som katten rundt grøten – vi tar ting opp et nivå. På dette nivået i vennskapet vårt vil vi oppgradere rommet ditt når det er mulig, for et enda mer komfortabelt opphold. Høres ikke det bra ut?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "2-for-1 frokost",
|
||||
"description": "Dette er virkelig et deilig tilbud: Uavhengig om du bor hos oss eller ikke, finn deg en frokostkompis, og dere kan spise for prisen av én! Bare sørg for å sjekke detaljene først, slik at alt er på plass for deres smakfulle sammenkomst.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Romgaranti i 48 timer",
|
||||
"description": "Hysj – dette er en spesiell godbit, kun for noen få av dere! Så merk deg: Selv om vi er fullbooket, er du garantert et rom så lenge du bestiller 48 timer på forhånd. Ganske utrolig!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Gratis frokost – alltid",
|
||||
"description": "Har du lyst på en smakfull morgengodbit? Bare kom innom! Nå kan du sparke i gang dagen din med gratis frokost – og gjett hva: Det gjelder uavhengig om du bor hos oss eller ikke.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Årlig fantastisk gave",
|
||||
"description": "Som vår Best Friend fortjener du absolutt spesialbehandling, så vi har en eksklusiv, årlig, og ganske fantastisk gave klar til deg. Hva det er? Vel, det er en overraskelse. Ingen sniktitting!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Boost for barn",
|
||||
"description": "På dette vennskapsnivået er barnet ditt også vår venn – og det betyr en spesiell boost-gave for barn når du bor hos oss. Hvorfor? Fordi barn er kule! De fortjener virkelig VIP-behandling.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requirement": "0p",
|
||||
"description": "Det är nu den börjar, vår härliga resa tillsammans. Som en New Friend kommer du att upptäcka oss om och om igen under dina äventyr med Scandic.",
|
||||
"icon": "/_static/icons/loyaltylevels/new-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10 % rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "10%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Finns det nåt bättre än en bra rabatt? Som vår vän får du rabatt i våra restauranger och hotellshoppen på helger och utvalda helgdagar, oavsett om du övernattar eller inte. Så, passa att unna dig det lilla extra.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requirement": "5 000p",
|
||||
"description": "Vi har fått se ganska mycket av varandra senaste tiden! Helt ärligt, det känns som att vi har nåt på gång, men vi tar det i vår takt, en vistelse i taget.",
|
||||
"icon": "/_static/icons/loyaltylevels/good-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15% rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Finns det nåt bättre än en bra rabatt? Som vår vän får du rabatt i våra restauranger och hotellshoppen på helger och utvalda helgdagar, oavsett om du övernattar eller inte. Så, passa att unna dig det lilla extra.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requirement": "10 000p",
|
||||
"description": "Det börjar bli seriöst nu: vi har verkligen lärt känna varann. Därför tycker vi att det är dags att din upplevelse hos oss blir ännu mer personlig.",
|
||||
"icon": "/_static/icons/loyaltylevels/close-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15% rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Okej, såhär: för varje natt som ger friendspoäng bjuder vi även på en restaurangkupong värd 50 kr.",
|
||||
"unlocked": true,
|
||||
"value": "50 kr"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requirement": "25 000p",
|
||||
"description": "Skål för oss! Vi känner tydliga vänner för livet-vibbar i luften och det öppnar upp för ännu fler Scandicfördelar.",
|
||||
"icon": "/_static/icons/loyaltylevels/dear-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15% rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Vår vänskap har vuxit sig starkare. Njut av en restaurangkupong värd 75 kr för varje natt som ger friendspoäng.",
|
||||
"unlocked": true,
|
||||
"value": "75 kr"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requirement": "100 000p",
|
||||
"description": "Du har bott hos oss otaliga nätter, hängt med oss sena timmar och genom ur och skur. Det är dags att du får några av våra allra bästa förmåner.",
|
||||
"icon": "/_static/icons/loyaltylevels/loyal-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15% rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Vi bjuder dig på en restaurangkupong värd 100 kr för varje natt som ger friendsspoäng. Det motsvarar ett mindre berg av croissanter!",
|
||||
"unlocked": true,
|
||||
"value": "100 kr"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": true,
|
||||
"value": "25%"
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requirement": "250 000p",
|
||||
"description": "Dig kan man lita på, oavsett säsong finns du där för oss. Därför vill vi att du ska få njuta av fler förmåner anpassade efter dig.",
|
||||
"icon": "/_static/icons/loyaltylevels/true-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15% rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Det blir bättre och bättre! Nu får du en restaurangkupong värd 150 kr för varje natt som ge friendsspoäng. Perfekt när du känner för en lyxig frukost eller ska på middagsdate!",
|
||||
"unlocked": true,
|
||||
"value": "150 kr"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requirement": "400 000p eller 100 nätter",
|
||||
"description": "Det finns inga ord för en vänskap som vår, men vi vill ändå säga: Bättre upplevelser än så här går inte att få, nu vi talar om exklusiva upplevelser!",
|
||||
"icon": "/_static/icons/loyaltylevels/best-friend.svg",
|
||||
"benefits": [
|
||||
{
|
||||
"name": "Friendspriser på rum",
|
||||
"description": "Som vår vän får du alltid det bästa erbjudandet: oavsett tid och plats. Inga hemliga deals eller bokningskoder behövs – det är bara att boka.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Rabatt på mat",
|
||||
"description": "Mums! Prova vår välsmakande 10-15 % rabatt i våra restauranger och hotellshoppar på helgerna, oavsett om du övernattar eller bara tittar förbi på en bit mat. Så passa på att unna dig det lilla extra.",
|
||||
"unlocked": true,
|
||||
"value": "15%"
|
||||
},
|
||||
{
|
||||
"name": "Fri mocktail för barn under vistelse",
|
||||
"description": "Har du barnen med dig på resan? Fantastiskt – hallå där, kompis! Hos oss är barn värdefulla små VIP-gäster, så därför bjuder vi på en läskande och god mocktail vid ankomst.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Sen utcheckning om möjligt",
|
||||
"description": "Vi fattar, ibland kan man behöva en sovmorgon. Som de vänner vi är låter vi dig checka ut en timme senare utan kostnad. Lite extra tid kan sätta hela känslan för resten av dagen.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Restaurangkupong",
|
||||
"description": "Det blir bättre och bättre! Nu får du en restaurangkupong för varje natt som ger friendsspoäng. Perfekt när du känner för en lyxig frukost eller ska på middagsdate!",
|
||||
"unlocked": true,
|
||||
"value": "200 kr"
|
||||
},
|
||||
{
|
||||
"name": "Friendsboost",
|
||||
"description": "Sweet! Varje gång du tjänar nya friendspoäng bjuder vi dig på 25-50 % extra poäng – som en superboost! Börja samla poäng genom att bo och äta så har du kirrat en bonusnatt på nolltid.",
|
||||
"unlocked": true,
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Tidig incheckning om möjligt",
|
||||
"description": "Vill du ha ditt rum lite i förväg? Inga problem. Checka in en timme tidigare utan kostnad och få en timmes extra avkoppling.",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Fri uppgradering om möjligt",
|
||||
"description": "Vi steppar upp saker ett snäpp. Vår vänskap har nått nivån där vi kommer uppgradera ditt rum när det är möjligt, så du kan få en ännu skönare vistelse. Bra, eller hur?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Frukost 2 för 1",
|
||||
"description": "Okej, nu snackar vi. Oavsett om du bor hos oss eller inte, ta med en frukostkompis eftersom du äter två frukostar till priset av en! Kom bara ihåg att dubbelkolla detaljerna först så att allt är i sin ordning för en maxad frukost.",
|
||||
"unlocked": false
|
||||
},
|
||||
{
|
||||
"name": "48 timmars rumsgaranti",
|
||||
"description": "Schhh – en hemlis för våra närmaste vänner: vi garanterar dig ett rum, även om vi är fullbokade, så länge du gör bokningen minst 48 timmar i förväg. Bra va?",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Alltid fri frukost",
|
||||
"description": "Kickstarta dagen med en kostnadsfri frukost hos oss – och du, det gäller oavsett om du bor hos oss eller inte. Det är bara att komma förbi!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Årlig spännande present",
|
||||
"description": "Som Best Friend förtjänar du det allra bästa. Därför ska du få en spännande present varje år, men vad det blir är en överraskning. Tjuvkika inte!",
|
||||
"unlocked": true
|
||||
},
|
||||
{
|
||||
"name": "Boost för barn",
|
||||
"description": "Vid det här laget räknar vi även dina barn som våra vänner, vilket betyder att ni får en speciell överraskning när ni bor hos oss. Lite VIP-behandling. För att barn är coola.",
|
||||
"unlocked": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,16 +3,20 @@ import { serverClient } from "@/lib/trpc/server"
|
||||
import SectionWrapper from "../SectionWrapper"
|
||||
import OverviewTableClient from "./Client"
|
||||
|
||||
import type { OverviewTableProps } from "@/types/components/blocks/dynamicContent"
|
||||
import { OverviewTableProps } from "@/types/components/blocks/dynamicContent"
|
||||
|
||||
export default async function OverviewTable({
|
||||
dynamic_content,
|
||||
firstItem,
|
||||
}: OverviewTableProps) {
|
||||
const levels = await serverClient().contentstack.rewards.all()
|
||||
const membershipLevel = await serverClient().user.safeMembershipLevel()
|
||||
return (
|
||||
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
|
||||
<OverviewTableClient activeMembership={membershipLevel} />
|
||||
<OverviewTableClient
|
||||
levels={levels}
|
||||
activeMembership={membershipLevel?.membershipLevel ?? null}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import PointsContainer from "../../../Overview/Stats/Points/Container"
|
||||
@@ -20,11 +20,12 @@ export default async function Points({ user, lang }: UserProps & LangParams) {
|
||||
const { formatMessage } = await getIntl()
|
||||
|
||||
const membership = getMembership(user.memberships)
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
membership?.nextLevel as MembershipLevelEnum,
|
||||
lang
|
||||
)
|
||||
|
||||
if (!membership?.nextLevel) {
|
||||
return null
|
||||
}
|
||||
const nextLevel = await serverClient().contentstack.loyaltyLevels.byLevel({
|
||||
level: MembershipLevelEnum[membership.nextLevel],
|
||||
})
|
||||
return (
|
||||
<PointsContainer>
|
||||
<YourPointsColumn points={membership?.currentPoints} />
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import { Reward } from "@/server/routers/contentstack/reward/output"
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./current.module.css"
|
||||
|
||||
type CurrentRewardsClientProps = {
|
||||
initialCurrentRewards: { rewards: Reward[]; nextCursor: number | undefined }
|
||||
}
|
||||
export default function ClientCurrentRewards({
|
||||
initialCurrentRewards,
|
||||
}: CurrentRewardsClientProps) {
|
||||
const lang = useLang()
|
||||
const { data, isFetching, fetchNextPage, hasNextPage, isLoading } =
|
||||
trpc.contentstack.rewards.current.useInfiniteQuery(
|
||||
{
|
||||
limit: 3,
|
||||
lang,
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage?.nextCursor,
|
||||
initialData: {
|
||||
pageParams: [undefined, 1],
|
||||
pages: [initialCurrentRewards],
|
||||
},
|
||||
}
|
||||
)
|
||||
function loadMoreData() {
|
||||
if (hasNextPage) {
|
||||
fetchNextPage()
|
||||
}
|
||||
}
|
||||
const filteredRewards =
|
||||
data?.pages.filter((page) => page && page.rewards) ?? []
|
||||
const rewards = filteredRewards.flatMap((page) => page?.rewards) as Reward[]
|
||||
return isLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : rewards.length ? (
|
||||
<div>
|
||||
<Grids.Stackable>
|
||||
{rewards.map((reward, idx) => (
|
||||
<article className={styles.card} key={`${reward.reward_id}-${idx}`}>
|
||||
<Title
|
||||
as="h4"
|
||||
level="h3"
|
||||
textAlign="center"
|
||||
textTransform="regular"
|
||||
>
|
||||
{reward.label}
|
||||
</Title>
|
||||
</article>
|
||||
))}
|
||||
</Grids.Stackable>
|
||||
{hasNextPage ? (
|
||||
isFetching ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<ShowMoreButton disabled={isFetching} loadMoreData={loadMoreData} />
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
|
||||
import ClientCurrentRewards from "./Client"
|
||||
|
||||
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
|
||||
|
||||
export default async function CurrentRewardsBlock({
|
||||
title,
|
||||
subtitle,
|
||||
link,
|
||||
}: AccountPageComponentProps) {
|
||||
const initialCurrentRewards =
|
||||
await serverClient().contentstack.rewards.current({
|
||||
limit: 3,
|
||||
})
|
||||
|
||||
if (!initialCurrentRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader title={title} link={link} preamble={subtitle} />
|
||||
<ClientCurrentRewards initialCurrentRewards={initialCurrentRewards} />
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Lock } from "react-feather"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
@@ -11,38 +11,39 @@ import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
|
||||
import styles from "./next.module.css"
|
||||
|
||||
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
|
||||
|
||||
export default async function NextLevelBenefitsBlock({
|
||||
export default async function NextLevelRewardsBlock({
|
||||
title,
|
||||
subtitle,
|
||||
link,
|
||||
}: AccountPageComponentProps) {
|
||||
const intl = await getIntl()
|
||||
const user = await getProfile()
|
||||
if (!user || "error" in user || !user.membership) {
|
||||
const membershipLevel = await serverClient().user.membershipLevel()
|
||||
|
||||
if (!membershipLevel || !membershipLevel?.nextLevel) {
|
||||
return null
|
||||
}
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
user.membership.nextLevel as MembershipLevelEnum,
|
||||
getLang()
|
||||
)
|
||||
if (!nextLevel) {
|
||||
// TODO: handle this case, when missing or when user is top level?
|
||||
|
||||
const nextLevelRewards = await serverClient().contentstack.rewards.byLevel({
|
||||
level_id: MembershipLevelEnum[membershipLevel?.nextLevel],
|
||||
unique: true,
|
||||
})
|
||||
|
||||
// TODO: handle this case, when missing or when user is top level?
|
||||
if (!nextLevelRewards) {
|
||||
return null
|
||||
}
|
||||
// TODO: how to handle different count of unlockable benefits?
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader title={title} preamble={subtitle} link={link} />
|
||||
<Grids.Stackable columns={2}>
|
||||
{nextLevel.benefits.map((benefit) => (
|
||||
<article key={benefit.title} className={styles.card}>
|
||||
{nextLevelRewards.rewards.map((reward) => (
|
||||
<article key={reward.reward_id} className={styles.card}>
|
||||
<Chip>
|
||||
<Lock height={16} />
|
||||
{intl.formatMessage({ id: "Level up to unlock" })}
|
||||
@@ -51,11 +52,11 @@ export default async function NextLevelBenefitsBlock({
|
||||
<Body color="peach50" textAlign="center">
|
||||
{intl.formatMessage(
|
||||
{ id: "As our" },
|
||||
{ level: nextLevel.name }
|
||||
{ level: nextLevelRewards.level?.name }
|
||||
)}
|
||||
</Body>{" "}
|
||||
</Body>
|
||||
<Title level="h4" as="h5" color="pale" textAlign="center">
|
||||
{benefit.title}
|
||||
{reward.label}
|
||||
</Title>
|
||||
</div>
|
||||
</article>
|
||||
@@ -1,5 +1,3 @@
|
||||
import CurrentBenefitsBlock from "@/components/Blocks/DynamicContent/Benefits/CurrentLevel"
|
||||
import NextLevelBenefitsBlock from "@/components/Blocks/DynamicContent/Benefits/NextLevel"
|
||||
import HowItWorks from "@/components/Blocks/DynamicContent/HowItWorks"
|
||||
import LoyaltyLevels from "@/components/Blocks/DynamicContent/LoyaltyLevels"
|
||||
import Overview from "@/components/Blocks/DynamicContent/Overview"
|
||||
@@ -7,6 +5,8 @@ import OverviewTable from "@/components/Blocks/DynamicContent/OverviewTable"
|
||||
import EarnAndBurn from "@/components/Blocks/DynamicContent/Points/EarnAndBurn"
|
||||
import ExpiringPoints from "@/components/Blocks/DynamicContent/Points/ExpiringPoints"
|
||||
import PointsOverview from "@/components/Blocks/DynamicContent/Points/Overview"
|
||||
import CurrentRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/CurrentLevel"
|
||||
import NextLevelRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/NextLevel"
|
||||
import PreviousStays from "@/components/Blocks/DynamicContent/Stays/Previous"
|
||||
import SoonestStays from "@/components/Blocks/DynamicContent/Stays/Soonest"
|
||||
import UpcomingStays from "@/components/Blocks/DynamicContent/Stays/Upcoming"
|
||||
@@ -20,7 +20,7 @@ export default async function DynamicContent({
|
||||
}: DynamicContentProps) {
|
||||
switch (dynamic_content.component) {
|
||||
case DynamicContentEnum.Blocks.components.current_benefits:
|
||||
return <CurrentBenefitsBlock {...dynamic_content} />
|
||||
return <CurrentRewardsBlock {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.earn_and_burn:
|
||||
return <EarnAndBurn {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.expiring_points:
|
||||
@@ -39,7 +39,7 @@ export default async function DynamicContent({
|
||||
case DynamicContentEnum.Blocks.components.membership_overview:
|
||||
return <Overview {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.next_benefits:
|
||||
return <NextLevelBenefitsBlock {...dynamic_content} />
|
||||
return <NextLevelRewardsBlock {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.overview_table:
|
||||
return (
|
||||
<OverviewTable
|
||||
|
||||
@@ -36,11 +36,10 @@ export default function MyPagesMenu({
|
||||
return (
|
||||
<div className={styles.myPagesMenu}>
|
||||
<MainMenuButton
|
||||
className={styles.button}
|
||||
onClick={() => toggleDropdown(DropdownTypeEnum.MyPagesMenu)}
|
||||
>
|
||||
<Avatar initials={getInitials(user.firstName, user.lastName)} />
|
||||
<Subtitle type="two" className={styles.userName}>
|
||||
<Subtitle type="two">
|
||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
||||
</Subtitle>
|
||||
<ChevronDownIcon
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import { logout } from "@/constants/routes/handleAuth"
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import { ArrowRightIcon } from "@/components/Icons"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
@@ -12,7 +13,6 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { useTrapFocus } from "@/hooks/useTrapFocus"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
|
||||
import styles from "./myPagesMenuContent.module.css"
|
||||
|
||||
@@ -28,12 +28,13 @@ export default function MyPagesMenuContent({
|
||||
const lang = useLang()
|
||||
const myPagesMenuContentRef = useTrapFocus()
|
||||
|
||||
const membershipLevel = membership?.membershipLevel
|
||||
? getMembershipLevelObject(
|
||||
membership.membershipLevel as MembershipLevelEnum,
|
||||
lang
|
||||
)
|
||||
: null
|
||||
const membershipLevel = trpc.contentstack.loyaltyLevels.byLevel.useQuery(
|
||||
{
|
||||
level: MembershipLevelEnum[membership?.membershipLevel!],
|
||||
},
|
||||
{ enabled: !!membership?.membershipLevel }
|
||||
).data
|
||||
|
||||
const membershipPoints = membership?.currentPoints
|
||||
const introClassName =
|
||||
membershipLevel && membershipPoints
|
||||
@@ -52,7 +53,7 @@ export default function MyPagesMenuContent({
|
||||
{membershipLevel && membershipPoints ? (
|
||||
<Caption className={styles.friendTypeWrapper}>
|
||||
<span className={styles.friendType}>{membershipLevel.name}</span>
|
||||
<span className={styles.friendPoints}>
|
||||
<span>
|
||||
{membershipPoints} {intl.formatMessage({ id: "points" })}
|
||||
</span>
|
||||
</Caption>
|
||||
@@ -61,10 +62,7 @@ export default function MyPagesMenuContent({
|
||||
|
||||
<ul className={styles.groups}>
|
||||
{navigation.menuItems.map((menuItem, idx) => (
|
||||
<li
|
||||
key={`${menuItem.display_sign_out_link}-${idx}`}
|
||||
className={styles.group}
|
||||
>
|
||||
<li key={`${menuItem.display_sign_out_link}-${idx}`}>
|
||||
<Divider color="subtle" className={styles.divider} />
|
||||
<ul className={styles.menuItems}>
|
||||
{menuItem.links.map((link) => (
|
||||
|
||||
38
components/Levels/Icon.tsx
Normal file
38
components/Levels/Icon.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
import {
|
||||
BestFriend,
|
||||
CloseFriend,
|
||||
DearFriend,
|
||||
GoodFriend,
|
||||
LoyalFriend,
|
||||
NewFriend,
|
||||
TrueFriend,
|
||||
} from "@/components/Levels"
|
||||
|
||||
import type { MembershipLevelIconProps } from "@/types/components/myPages/membership"
|
||||
|
||||
export default function MembershipLevelIcon({
|
||||
level,
|
||||
color = "pale",
|
||||
...props
|
||||
}: MembershipLevelIconProps) {
|
||||
switch (level) {
|
||||
case MembershipLevelEnum.L1:
|
||||
return <NewFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L2:
|
||||
return <GoodFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L3:
|
||||
return <CloseFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L4:
|
||||
return <DearFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L5:
|
||||
return <LoyalFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L6:
|
||||
return <TrueFriend color={color} {...props} />
|
||||
case MembershipLevelEnum.L7:
|
||||
return <BestFriend color={color} {...props} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,13 @@ import { levelVariants } from "../variants"
|
||||
|
||||
import type { LevelProps } from "../levels"
|
||||
|
||||
export default function BestFriend({ className, color, ...props }: LevelProps) {
|
||||
export default function BestFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "159",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
className,
|
||||
color,
|
||||
@@ -11,9 +17,9 @@ export default function BestFriend({ className, color, ...props }: LevelProps) {
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 159 75"
|
||||
width="159"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { LevelProps } from "../levels"
|
||||
export default function CloseFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "158",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
@@ -15,9 +17,9 @@ export default function CloseFriend({
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 158 75"
|
||||
width="158"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,13 @@ import { levelVariants } from "../variants"
|
||||
|
||||
import type { LevelProps } from "../levels"
|
||||
|
||||
export default function DearFriend({ className, color, ...props }: LevelProps) {
|
||||
export default function DearFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "159",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
className,
|
||||
color,
|
||||
@@ -11,9 +17,9 @@ export default function DearFriend({ className, color, ...props }: LevelProps) {
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 159 75"
|
||||
width="159"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,13 @@ import { levelVariants } from "../variants"
|
||||
|
||||
import type { LevelProps } from "../levels"
|
||||
|
||||
export default function GoodFriend({ className, color, ...props }: LevelProps) {
|
||||
export default function GoodFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "159",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
className,
|
||||
color,
|
||||
@@ -11,9 +17,9 @@ export default function GoodFriend({ className, color, ...props }: LevelProps) {
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 159 75"
|
||||
width="159"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { LevelProps } from "../levels"
|
||||
export default function LoyalFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "158",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
@@ -15,9 +17,9 @@ export default function LoyalFriend({
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 158 75"
|
||||
width="158"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,13 @@ import { levelVariants } from "../variants"
|
||||
|
||||
import type { LevelProps } from "../levels"
|
||||
|
||||
export default function NewFriend({ className, color, ...props }: LevelProps) {
|
||||
export default function NewFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "159",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
className,
|
||||
color,
|
||||
@@ -11,9 +17,9 @@ export default function NewFriend({ className, color, ...props }: LevelProps) {
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 159 75"
|
||||
width="159"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,13 @@ import { levelVariants } from "../variants"
|
||||
|
||||
import type { LevelProps } from "../levels"
|
||||
|
||||
export default function TrueFriend({ className, color, ...props }: LevelProps) {
|
||||
export default function TrueFriend({
|
||||
className,
|
||||
color,
|
||||
height = "75",
|
||||
width = "159",
|
||||
...props
|
||||
}: LevelProps) {
|
||||
const classNames = levelVariants({
|
||||
className,
|
||||
color,
|
||||
@@ -11,9 +17,9 @@ export default function TrueFriend({ className, color, ...props }: LevelProps) {
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="75"
|
||||
height={height}
|
||||
viewBox="0 0 159 75"
|
||||
width="159"
|
||||
width={width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -4,4 +4,7 @@ import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
export interface LevelProps
|
||||
extends Omit<React.HTMLAttributes<HTMLOrSVGElement>, "color">,
|
||||
VariantProps<typeof levelVariants> {}
|
||||
VariantProps<typeof levelVariants> {
|
||||
height?: string
|
||||
width?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
32
components/TempDesignSystem/ShowMoreButton/index.tsx
Normal file
32
components/TempDesignSystem/ShowMoreButton/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { ChevronDownIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
import styles from "./button.module.css"
|
||||
|
||||
import type { ShowMoreButtonParams } from "@/types/components/myPages/stays/button"
|
||||
|
||||
export default function ShowMoreButton({
|
||||
disabled,
|
||||
loadMoreData,
|
||||
}: ShowMoreButtonParams) {
|
||||
const { formatMessage } = useIntl()
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={loadMoreData}
|
||||
variant="icon"
|
||||
type="button"
|
||||
theme="base"
|
||||
intent="text"
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
{formatMessage({ id: "Show more" })}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import CurrentBenefitsBlock from "@/components/Blocks/DynamicContent/Benefits/CurrentLevel"
|
||||
import NextLevelBenefitsBlock from "@/components/Blocks/DynamicContent/Benefits/NextLevel"
|
||||
import Overview from "@/components/Blocks/DynamicContent/Overview"
|
||||
import EarnAndBurn from "@/components/Blocks/DynamicContent/Points/EarnAndBurn"
|
||||
import PointsOverview from "@/components/Blocks/DynamicContent/Points/Overview"
|
||||
import CurrentRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/CurrentLevel"
|
||||
import NextLevelRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/NextLevel"
|
||||
import Shortcuts from "@/components/Blocks/Shortcuts"
|
||||
import JsonToHtml from "@/components/JsonToHtml"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
@@ -31,9 +31,9 @@ function DynamicComponent({ dynamic_content }: AccountPageContentProps) {
|
||||
case DynamicContentEnum.Blocks.components.points_overview:
|
||||
return <PointsOverview {...dynamicContent} />
|
||||
case DynamicContentEnum.Blocks.components.current_benefits:
|
||||
return <CurrentBenefitsBlock {...dynamicContent} />
|
||||
return <CurrentRewardsBlock {...dynamicContent} />
|
||||
case DynamicContentEnum.Blocks.components.next_benefits:
|
||||
return <NextLevelBenefitsBlock {...dynamicContent} />
|
||||
return <NextLevelRewardsBlock {...dynamicContent} />
|
||||
case DynamicContentEnum.Blocks.components.expiring_points:
|
||||
// TODO: Add once available
|
||||
// return <ExpiringPoints />
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Prisvenlige værelser"
|
||||
},
|
||||
{
|
||||
"title": "10% weekendrabat på mad"
|
||||
},
|
||||
{
|
||||
"title": "Gratis mocktail til børn under opholdet"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15% weekendrabat på mad"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Sen check ud – 1 time, når tilgængeligt"
|
||||
},
|
||||
{
|
||||
"title": "Voucher på DKK 50,-"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "25% optjeningsrate"
|
||||
},
|
||||
{
|
||||
"title": "Tidlig check ind, når tilgængeligt"
|
||||
},
|
||||
{
|
||||
"title": "Voucher på DKK 75,-"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Gratis opgraderinger, når tilgængelige"
|
||||
},
|
||||
{
|
||||
"title": "Voucher på DKK 100,-"
|
||||
},
|
||||
{
|
||||
"title": "2-for-1 morgenmad"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "50% optjeningsrate"
|
||||
},
|
||||
{
|
||||
"title": "Voucher på DKK 150,-"
|
||||
},
|
||||
{
|
||||
"title": "48-timers værelsesgaranti"
|
||||
},
|
||||
{
|
||||
"title": "Altid gratis morgenmad"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Voucher på DKK 200,-"
|
||||
},
|
||||
{
|
||||
"title": "Årlig eksklusiv gave"
|
||||
},
|
||||
{
|
||||
"title": "Børneboost"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Freundschaftspreise für Hotelzimmer"
|
||||
},
|
||||
{
|
||||
"title": "10 % Rabatt auf Speisen an den Wochenenden"
|
||||
},
|
||||
{
|
||||
"title": "Kostenloser Kinder-Mocktail während des Aufenthalts"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15 % Rabatt auf Speisen an den Wochenenden"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Später Check-Out – 1 Stunde, wenn verfügbar"
|
||||
},
|
||||
{
|
||||
"title": "Gutschein über 5 EUR"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "25 % mehr Punkte"
|
||||
},
|
||||
{
|
||||
"title": "Früher Check-In – 1 Stunde, wenn verfügbar"
|
||||
},
|
||||
{
|
||||
"title": "Gutschein über 7,50 EUR"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Kostenloses Zimmer-Upgrade, wenn verfügbar"
|
||||
},
|
||||
{
|
||||
"title": "Gutschein über 10 EUR"
|
||||
},
|
||||
{
|
||||
"title": "Frühstück für Zwei zum Preis von einem"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "50 % mehr Punkte"
|
||||
},
|
||||
{
|
||||
"title": "Gutschein über 15 EUR"
|
||||
},
|
||||
{
|
||||
"title": "48-Stunden-Zimmergarantie"
|
||||
},
|
||||
{
|
||||
"title": "Jederzeit ein kostenloses Frühstück"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Gutschein über 20 EUR"
|
||||
},
|
||||
{
|
||||
"title": "Ein exklusives Geschenk pro Jahr"
|
||||
},
|
||||
{
|
||||
"title": "Ein Geschenk für Kinder"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Friendly room rates"
|
||||
},
|
||||
{
|
||||
"title": "10% off on food on weekends"
|
||||
},
|
||||
{
|
||||
"title": "Free kids mocktail during stay"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15% on food on weekends"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Late checkout - 1 hour when available"
|
||||
},
|
||||
{
|
||||
"title": "5 EUR voucher"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "25% earn rate"
|
||||
},
|
||||
{
|
||||
"title": "Early check-in - 1 hour when available"
|
||||
},
|
||||
{
|
||||
"title": "7.50 EUR voucher"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Free room upgrade when available"
|
||||
},
|
||||
{
|
||||
"title": "10 EUR voucher"
|
||||
},
|
||||
{
|
||||
"title": "2-for-1 breakfast"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "50% earn rate"
|
||||
},
|
||||
{
|
||||
"title": "15 EUR voucher"
|
||||
},
|
||||
{
|
||||
"title": "48h room guarantee"
|
||||
},
|
||||
{
|
||||
"title": "Always free breakfast"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "20 EUR voucher"
|
||||
},
|
||||
{
|
||||
"title": "Yearly exclusive gift"
|
||||
},
|
||||
{
|
||||
"title": "Kid's boost"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Ystävälliset huonehinnat"
|
||||
},
|
||||
{
|
||||
"title": "10 % alennusta ruoasta viikonloppuisin"
|
||||
},
|
||||
{
|
||||
"title": "Maksuton lasten mocktail majoituksen aikana"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15 % alennusta ruoasta viikonloppuisin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Myöhäinen uloskirjautuminen – 1 tunti lisäaikaa varaustilanteen mukaan"
|
||||
},
|
||||
{
|
||||
"title": "Ravintolakuponki (arvo 5 €)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Ansaintakerroin +25 %"
|
||||
},
|
||||
{
|
||||
"title": "Aikainen sisäänkirjautuminen – 1 tunti lisäaikaa varaustilanteen mukaan"
|
||||
},
|
||||
{
|
||||
"title": "Ravintolakuponki (arvo 7,50 €)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Maksuton huoneluokan korotus varaustilanteen mukaan"
|
||||
},
|
||||
{
|
||||
"title": "Ravintolakuponki (arvo 10 €)"
|
||||
},
|
||||
{
|
||||
"title": "Aamiainen – kaksi yhden hinnalla"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Ansaintakerroin +50 %"
|
||||
},
|
||||
{
|
||||
"title": "Ravintolakuponki (arvo 15 €)"
|
||||
},
|
||||
{
|
||||
"title": "48 tunnin huonetakuu"
|
||||
},
|
||||
{
|
||||
"title": "Aamiainen aina maksutta"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Ravintolakuponki (arvo 20 €)"
|
||||
},
|
||||
{
|
||||
"title": "Henkilökohtainen lahja vuosittain"
|
||||
},
|
||||
{
|
||||
"title": "Tervetuliaislahja lapselle"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Vennlige rompriser"
|
||||
},
|
||||
{
|
||||
"title": "10 % rabatt på mat i helger"
|
||||
},
|
||||
{
|
||||
"title": "Gratis barne-mocktail under oppholdet"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15 % rabatt på mat i helger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Sen utsjekking – 1 time når tilgjengelig"
|
||||
},
|
||||
{
|
||||
"title": "Kupong på 50 NOK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "25 % opptjeningsrate"
|
||||
},
|
||||
{
|
||||
"title": "Tidlig innsjekk – 1 time når tilgjengelig"
|
||||
},
|
||||
{
|
||||
"title": "Kupong på 75 NOK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Gratis romoppgradering når tilgjengelig"
|
||||
},
|
||||
{
|
||||
"title": "Kupong på 100 NOK"
|
||||
},
|
||||
{
|
||||
"title": "2-for-1 frokost"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "50 % opptjeningsrate"
|
||||
},
|
||||
{
|
||||
"title": "Kupong på 150 NOK"
|
||||
},
|
||||
{
|
||||
"title": "Romgaranti i 48 timer"
|
||||
},
|
||||
{
|
||||
"title": "Alltid gratis frokost"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Kupong på 200 NOK"
|
||||
},
|
||||
{
|
||||
"title": "Årlig eksklusiv gave"
|
||||
},
|
||||
{
|
||||
"title": "Boost for barn"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"levels": [
|
||||
{
|
||||
"level": 1,
|
||||
"name": "New Friend",
|
||||
"requiredPoints": 0,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Friendspriser på rum"
|
||||
},
|
||||
{
|
||||
"title": "10 % rabatt på mat under helger"
|
||||
},
|
||||
{
|
||||
"title": "Fri mocktail för barn under vistelse"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"name": "Good Friend",
|
||||
"requiredPoints": 5000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "15 % rabatt på mat under helger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"name": "Close Friend",
|
||||
"requiredPoints": 10000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Sen utcheckning – 1 timme, i mån av plats"
|
||||
},
|
||||
{
|
||||
"title": "Kupong 50 kr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"name": "Dear Friend",
|
||||
"requiredPoints": 25000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "25 % poängboost"
|
||||
},
|
||||
{
|
||||
"title": "Tidig incheckning – 1 timme, i mån av plats"
|
||||
},
|
||||
{
|
||||
"title": "Kupong 75 kr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"name": "Loyal Friend",
|
||||
"requiredPoints": 100000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Kostnadsfri uppgradering av rum, i mån av plats"
|
||||
},
|
||||
{
|
||||
"title": "Kupong 100 kr"
|
||||
},
|
||||
{
|
||||
"title": "Frukost 2 för 1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"name": "True Friend",
|
||||
"requiredPoints": 250000,
|
||||
"requiredNights": 0,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "50 % poängboost"
|
||||
},
|
||||
{
|
||||
"title": "Kupong 150 kr"
|
||||
},
|
||||
{
|
||||
"title": "48 timmars rumsgaranti"
|
||||
},
|
||||
{
|
||||
"title": "Alltid kostnadsfri frukost"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"name": "Best Friend",
|
||||
"requiredPoints": 400000,
|
||||
"requiredNights": 100,
|
||||
"benefits": [
|
||||
{
|
||||
"title": "Kupong 200 kr"
|
||||
},
|
||||
{
|
||||
"title": "Spännande gåva varje år"
|
||||
},
|
||||
{
|
||||
"title": "Boost för barn"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import DA from "./DA.json"
|
||||
import DE from "./DE.json"
|
||||
import EN from "./EN.json"
|
||||
import FI from "./FI.json"
|
||||
import NO from "./NO.json"
|
||||
import SV from "./SV.json"
|
||||
|
||||
const levelsData = {
|
||||
[Lang.en]: EN,
|
||||
[Lang.sv]: SV,
|
||||
[Lang.no]: NO,
|
||||
[Lang.fi]: FI,
|
||||
[Lang.da]: DA,
|
||||
[Lang.de]: DE,
|
||||
}
|
||||
|
||||
export default levelsData
|
||||
@@ -19,6 +19,8 @@ export namespace endpoints {
|
||||
locations = "hotel/v1/Locations",
|
||||
previousStays = "booking/v1/Stays/past",
|
||||
upcomingStays = "booking/v1/Stays/future",
|
||||
rewards = `${profile}/reward`,
|
||||
tierRewards = `${profile}/TierRewards`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
lib/graphql/Query/LoyaltyLevels.graphql
Normal file
25
lib/graphql/Query/LoyaltyLevels.graphql
Normal file
@@ -0,0 +1,25 @@
|
||||
query GetAllLoyaltyLevels($lang: String!, $level_ids: [String]!) {
|
||||
all_loyalty_level(where: { level_id_in: $level_ids }, locale: $lang) {
|
||||
items {
|
||||
description
|
||||
level_id
|
||||
name
|
||||
required_points
|
||||
required_nights
|
||||
user_facing_tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetLoyaltyLevel($lang: String!, $level_id: String!) {
|
||||
all_loyalty_level(where: { level_id: $level_id }, locale: $lang) {
|
||||
items {
|
||||
description
|
||||
level_id
|
||||
name
|
||||
required_points
|
||||
required_nights
|
||||
user_facing_tag
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/graphql/Query/Rewards.graphql
Normal file
15
lib/graphql/Query/Rewards.graphql
Normal file
@@ -0,0 +1,15 @@
|
||||
query GetRewards($locale: String!, $rewardIds: [String!]) {
|
||||
all_reward(locale: $locale, where: { reward_id_in: $rewardIds }) {
|
||||
items {
|
||||
taxonomies {
|
||||
term_uid
|
||||
}
|
||||
label
|
||||
grouped_label
|
||||
description
|
||||
grouped_description
|
||||
value
|
||||
reward_id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@ import { breadcrumbsRouter } from "./breadcrumbs"
|
||||
import { contentPageRouter } from "./contentPage"
|
||||
import { hotelPageRouter } from "./hotelPage"
|
||||
import { languageSwitcherRouter } from "./languageSwitcher"
|
||||
import { loyaltyLevelRouter } from "./loyaltyLevel"
|
||||
import { loyaltyPageRouter } from "./loyaltyPage"
|
||||
import { metaDataRouter } from "./metadata"
|
||||
import { myPagesRouter } from "./myPages"
|
||||
import { rewardRouter } from "./reward"
|
||||
|
||||
export const contentstackRouter = router({
|
||||
accountPage: accountPageRouter,
|
||||
@@ -22,4 +24,6 @@ export const contentstackRouter = router({
|
||||
contentPage: contentPageRouter,
|
||||
myPages: myPagesRouter,
|
||||
metaData: metaDataRouter,
|
||||
rewards: rewardRouter,
|
||||
loyaltyLevels: loyaltyLevelRouter,
|
||||
})
|
||||
|
||||
5
server/routers/contentstack/loyaltyLevel/index.ts
Normal file
5
server/routers/contentstack/loyaltyLevel/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { mergeRouters } from "@/server/trpc"
|
||||
|
||||
import { loyaltyLevelQueryRouter } from "./query"
|
||||
|
||||
export const loyaltyLevelRouter = mergeRouters(loyaltyLevelQueryRouter)
|
||||
7
server/routers/contentstack/loyaltyLevel/input.ts
Normal file
7
server/routers/contentstack/loyaltyLevel/input.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
export const loyaltyLevelInput = z.object({
|
||||
level: z.nativeEnum(MembershipLevelEnum),
|
||||
})
|
||||
24
server/routers/contentstack/loyaltyLevel/output.ts
Normal file
24
server/routers/contentstack/loyaltyLevel/output.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
export const validateLoyaltyLevelsSchema = z
|
||||
.object({
|
||||
all_loyalty_level: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
level_id: z.nativeEnum(MembershipLevelEnum),
|
||||
name: z.string(),
|
||||
user_facing_tag: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
required_nights: z.number().optional().nullable(),
|
||||
required_points: z.number(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((data) => data.all_loyalty_level.items)
|
||||
|
||||
export type LoyaltyLevelsResponse = z.input<typeof validateLoyaltyLevelsSchema>
|
||||
|
||||
export type LoyaltyLevel = z.output<typeof validateLoyaltyLevelsSchema>[0]
|
||||
147
server/routers/contentstack/loyaltyLevel/query.ts
Normal file
147
server/routers/contentstack/loyaltyLevel/query.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import {
|
||||
MembershipLevel,
|
||||
MembershipLevelEnum,
|
||||
} from "@/constants/membershipLevels"
|
||||
import {
|
||||
GetAllLoyaltyLevels,
|
||||
GetLoyaltyLevel,
|
||||
} from "@/lib/graphql/Query/LoyaltyLevels.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { Context } from "@/server/context"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackBaseProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
|
||||
|
||||
import { loyaltyLevelInput } from "./input"
|
||||
import { LoyaltyLevelsResponse, validateLoyaltyLevelsSchema } from "./output"
|
||||
|
||||
const meter = metrics.getMeter("trpc.loyaltyLevel")
|
||||
// OpenTelemetry metrics: Loyalty Level
|
||||
const getAllLoyaltyLevelCounter = meter.createCounter(
|
||||
"trpc.contentstack.loyaltyLevel.all"
|
||||
)
|
||||
|
||||
const getAllLoyaltyLevelSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.loyaltyLevel.all-success"
|
||||
)
|
||||
const getAllLoyaltyLevelFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.loyaltyLevel.all-fail"
|
||||
)
|
||||
|
||||
export async function getAllLoyaltyLevels(ctx: Context) {
|
||||
getAllLoyaltyLevelCounter.add(1)
|
||||
|
||||
// Ideally we should fetch all available tiers from API, but since they
|
||||
// are static, we can just use the enum values. We want to know which
|
||||
// levels we are fetching so that we can use tags to cache them
|
||||
const allLevelIds = Object.values(MembershipLevelEnum)
|
||||
|
||||
const tags = allLevelIds.map((levelId) =>
|
||||
generateLoyaltyConfigTag(ctx.lang, "loyalty_level", levelId)
|
||||
)
|
||||
|
||||
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
|
||||
GetAllLoyaltyLevels,
|
||||
{ lang: ctx.lang, level_ids: allLevelIds },
|
||||
{ next: { tags }, cache: "force-cache" }
|
||||
)
|
||||
|
||||
if (!loyaltyLevelsConfigResponse.data) {
|
||||
getAllLoyaltyLevelFailCounter.add(1)
|
||||
const notFoundError = notFound(loyaltyLevelsConfigResponse)
|
||||
console.error(
|
||||
"contentstack.loyaltyLevels not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang: ctx.lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedLoyaltyLevels = validateLoyaltyLevelsSchema.safeParse(
|
||||
loyaltyLevelsConfigResponse.data
|
||||
)
|
||||
if (!validatedLoyaltyLevels.success) {
|
||||
getAllLoyaltyLevelFailCounter.add(1)
|
||||
console.error(validatedLoyaltyLevels.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang: ctx.lang,
|
||||
},
|
||||
error: validatedLoyaltyLevels.error,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
getAllLoyaltyLevelSuccessCounter.add(1)
|
||||
return validatedLoyaltyLevels.data
|
||||
}
|
||||
|
||||
export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) {
|
||||
getAllLoyaltyLevelCounter.add(1)
|
||||
|
||||
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
|
||||
GetLoyaltyLevel,
|
||||
{ lang: ctx.lang, level_id },
|
||||
{
|
||||
next: {
|
||||
tags: [generateLoyaltyConfigTag(ctx.lang, "loyalty_level", level_id)],
|
||||
},
|
||||
cache: "force-cache",
|
||||
}
|
||||
)
|
||||
if (
|
||||
!loyaltyLevelsConfigResponse.data ||
|
||||
!loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length
|
||||
) {
|
||||
getAllLoyaltyLevelFailCounter.add(1)
|
||||
const notFoundError = notFound(loyaltyLevelsConfigResponse)
|
||||
console.error(
|
||||
"contentstack.loyaltyLevels not found error",
|
||||
JSON.stringify({
|
||||
query: { lang: ctx.lang, level_id },
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedLoyaltyLevels = validateLoyaltyLevelsSchema.safeParse(
|
||||
loyaltyLevelsConfigResponse.data
|
||||
)
|
||||
if (!validatedLoyaltyLevels.success) {
|
||||
getAllLoyaltyLevelFailCounter.add(1)
|
||||
console.error(validatedLoyaltyLevels.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: { lang: ctx.lang, level_id },
|
||||
error: validatedLoyaltyLevels.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getAllLoyaltyLevelSuccessCounter.add(1)
|
||||
return validatedLoyaltyLevels.data[0]
|
||||
}
|
||||
|
||||
export const loyaltyLevelQueryRouter = router({
|
||||
byLevel: contentstackBaseProcedure
|
||||
.input(loyaltyLevelInput)
|
||||
.query(async function ({ ctx, input }) {
|
||||
return getLoyaltyLevel(ctx, input.level)
|
||||
}),
|
||||
all: contentstackBaseProcedure.query(async function ({ ctx }) {
|
||||
return getAllLoyaltyLevels(ctx)
|
||||
}),
|
||||
})
|
||||
5
server/routers/contentstack/reward/index.ts
Normal file
5
server/routers/contentstack/reward/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { mergeRouters } from "@/server/trpc"
|
||||
|
||||
import { rewardQueryRouter } from "./query"
|
||||
|
||||
export const rewardRouter = mergeRouters(rewardQueryRouter)
|
||||
19
server/routers/contentstack/reward/input.ts
Normal file
19
server/routers/contentstack/reward/input.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
export const rewardsByLevelInput = z.object({
|
||||
level_id: z.nativeEnum(MembershipLevelEnum),
|
||||
unique: z.boolean().default(false),
|
||||
})
|
||||
|
||||
export const rewardsAllInput = z
|
||||
.object({ unique: z.boolean() })
|
||||
.default({ unique: false })
|
||||
|
||||
export const rewardsCurrentInput = z.object({
|
||||
limit: z.number().min(0).default(3),
|
||||
cursor: z.number().optional().default(0),
|
||||
lang: z.nativeEnum(Lang).optional(),
|
||||
})
|
||||
82
server/routers/contentstack/reward/output.ts
Normal file
82
server/routers/contentstack/reward/output.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
export const validateApiRewardSchema = z.object({
|
||||
data: z.array(
|
||||
z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
rewardId: z.string().optional(),
|
||||
redeemLocation: z.string().optional(),
|
||||
autoApplyReward: z.boolean().default(false),
|
||||
rewardType: z.string().optional(),
|
||||
rewardTierLevel: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
),
|
||||
})
|
||||
|
||||
enum TierKey {
|
||||
tier1 = MembershipLevelEnum.L1,
|
||||
tier2 = MembershipLevelEnum.L2,
|
||||
tier3 = MembershipLevelEnum.L3,
|
||||
tier4 = MembershipLevelEnum.L4,
|
||||
tier5 = MembershipLevelEnum.L5,
|
||||
tier6 = MembershipLevelEnum.L6,
|
||||
tier7 = MembershipLevelEnum.L7,
|
||||
}
|
||||
|
||||
type Key = keyof typeof TierKey
|
||||
|
||||
export const validateApiTierRewardsSchema = z.record(
|
||||
z.nativeEnum(TierKey).transform((data) => {
|
||||
return TierKey[data as unknown as Key]
|
||||
}),
|
||||
z.array(
|
||||
z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
rewardId: z.string().optional(),
|
||||
redeemLocation: z.string().optional(),
|
||||
autoApplyReward: z.boolean().default(false),
|
||||
rewardType: z.string().optional(),
|
||||
rewardTierLevel: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
)
|
||||
)
|
||||
|
||||
export const validateCmsRewardsSchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
all_reward: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
taxonomies: z.array(
|
||||
z.object({
|
||||
term_uid: z.string().optional(),
|
||||
})
|
||||
),
|
||||
label: z.string().optional(),
|
||||
reward_id: z.string(),
|
||||
grouped_label: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
grouped_description: z.string().optional(),
|
||||
value: z.string().optional(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.transform((data) => data.data.all_reward.items)
|
||||
|
||||
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
|
||||
|
||||
export type Reward = z.output<typeof validateCmsRewardsSchema>[0]
|
||||
371
server/routers/contentstack/reward/query.ts
Normal file
371
server/routers/contentstack/reward/query.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import * as api from "@/lib/api"
|
||||
import { GetRewards } from "@/lib/graphql/Query/Rewards.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { Context } from "@/server/context"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import {
|
||||
contentStackBaseWithProfileServiceProcedure,
|
||||
contentStackBaseWithProtectedProcedure,
|
||||
router,
|
||||
} from "@/server/trpc"
|
||||
|
||||
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
|
||||
|
||||
import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query"
|
||||
import {
|
||||
rewardsAllInput,
|
||||
rewardsByLevelInput,
|
||||
rewardsCurrentInput,
|
||||
} from "./input"
|
||||
import {
|
||||
CmsRewardsResponse,
|
||||
Reward,
|
||||
validateApiRewardSchema,
|
||||
validateApiTierRewardsSchema,
|
||||
validateCmsRewardsSchema,
|
||||
} from "./output"
|
||||
|
||||
const meter = metrics.getMeter("trpc.reward")
|
||||
// OpenTelemetry metrics: Reward
|
||||
|
||||
const getCurrentRewardCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.current"
|
||||
)
|
||||
const getCurrentRewardSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.current-success"
|
||||
)
|
||||
|
||||
const getCurrentRewardFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.current-fail"
|
||||
)
|
||||
|
||||
const getByLevelRewardCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.byLevel"
|
||||
)
|
||||
const getByLevelRewardSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.byLevel-success"
|
||||
)
|
||||
|
||||
const getByLevelRewardFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.byLevel-fail"
|
||||
)
|
||||
|
||||
const getAllRewardCounter = meter.createCounter("trpc.contentstack.reward.all")
|
||||
|
||||
const getAllRewardSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.all-success"
|
||||
)
|
||||
const getAllRewardFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.all-fail"
|
||||
)
|
||||
|
||||
function getUniqueRewardIds(rewardIds: string[]) {
|
||||
const uniqueRewardIds = new Set(rewardIds)
|
||||
return Array.from(uniqueRewardIds)
|
||||
}
|
||||
|
||||
async function getAllApiRewards(ctx: Context & { serviceToken: string }) {
|
||||
const apiResponse = await api.get(api.endpoints.v1.tierRewards, {
|
||||
cache: undefined, // override defaultOptions
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
// One hour. Since the service token is refreshed every hour, this is the longest cache we can have.
|
||||
next: { revalidate: 60 * 60 },
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.rewards.tierRewards error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiTierRewards = validateApiTierRewardsSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiTierRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiTierRewards.error),
|
||||
})
|
||||
console.error(validatedApiTierRewards.error)
|
||||
console.error(
|
||||
"api.rewards validation error",
|
||||
JSON.stringify({
|
||||
error: validatedApiTierRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return validatedApiTierRewards.data
|
||||
}
|
||||
|
||||
async function getCmsRewards(locale: Lang, rewardIds: string[]) {
|
||||
const tags = rewardIds.map((id) =>
|
||||
generateLoyaltyConfigTag(locale, "reward", id)
|
||||
)
|
||||
|
||||
const cmsRewardsResponse = await request<CmsRewardsResponse>(
|
||||
GetRewards,
|
||||
{
|
||||
locale: locale,
|
||||
rewardIds,
|
||||
},
|
||||
{ next: { tags }, cache: "force-cache" }
|
||||
)
|
||||
|
||||
if (!cmsRewardsResponse.data) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
lang: locale,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(cmsRewardsResponse.data),
|
||||
})
|
||||
const notFoundError = notFound(cmsRewardsResponse)
|
||||
console.error(
|
||||
"contentstack.rewards not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
locale,
|
||||
rewardIds,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedCmsRewards =
|
||||
validateCmsRewardsSchema.safeParse(cmsRewardsResponse)
|
||||
|
||||
if (!validatedCmsRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
locale,
|
||||
rewardIds,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedCmsRewards.error),
|
||||
})
|
||||
console.error(validatedCmsRewards.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: { locale, rewardIds },
|
||||
error: validatedCmsRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return validatedCmsRewards.data
|
||||
}
|
||||
|
||||
export const rewardQueryRouter = router({
|
||||
current: contentStackBaseWithProtectedProcedure
|
||||
.input(rewardsCurrentInput)
|
||||
.query(async function ({ input, ctx }) {
|
||||
getCurrentRewardCounter.add(1)
|
||||
|
||||
const { limit, cursor } = input
|
||||
|
||||
const apiResponse = await api.get(api.endpoints.v1.rewards, {
|
||||
cache: undefined, // override defaultOptions
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
next: { revalidate: 60 * 60 },
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
|
||||
const validatedApiRewards = validateApiRewardSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = validatedApiRewards.data.data
|
||||
.map((reward) => reward?.rewardId)
|
||||
.filter(Boolean)
|
||||
.sort() as string[]
|
||||
|
||||
const slicedData = rewardIds.slice(cursor, limit + cursor)
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, slicedData)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nextCursor =
|
||||
limit + cursor < rewardIds.length ? limit + cursor : undefined
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
return {
|
||||
rewards: cmsRewards,
|
||||
nextCursor,
|
||||
}
|
||||
}),
|
||||
byLevel: contentStackBaseWithProfileServiceProcedure
|
||||
.input(rewardsByLevelInput)
|
||||
.query(async function ({ input, ctx }) {
|
||||
getByLevelRewardCounter.add(1)
|
||||
const { level_id } = input
|
||||
|
||||
const allUpcomingApiRewards = await getAllApiRewards(ctx)
|
||||
|
||||
if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) {
|
||||
getByLevelRewardFailCounter.add(1)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
let apiRewards = allUpcomingApiRewards[level_id]!
|
||||
|
||||
if (input.unique) {
|
||||
apiRewards = allUpcomingApiRewards[level_id]!.filter(
|
||||
(reward) => reward?.rewardTierLevel === level_id
|
||||
)
|
||||
}
|
||||
|
||||
const rewardIds = apiRewards
|
||||
.map((reward) => reward?.rewardId)
|
||||
.filter((id): id is string => Boolean(id))
|
||||
|
||||
const contentStackRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
if (!contentStackRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
const loyaltyLevelsConfig = await getLoyaltyLevel(ctx, input.level_id)
|
||||
|
||||
const levelsWithRewards = apiRewards
|
||||
.map((reward) => {
|
||||
const contentStackReward = contentStackRewards.find((r) => {
|
||||
return r.reward_id === reward?.rewardId
|
||||
})
|
||||
|
||||
if (contentStackReward) {
|
||||
return contentStackReward
|
||||
} else {
|
||||
console.error("No contentStackReward found", reward?.rewardId)
|
||||
}
|
||||
})
|
||||
.filter((reward): reward is Reward => Boolean(reward))
|
||||
|
||||
getByLevelRewardSuccessCounter.add(1)
|
||||
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
|
||||
}),
|
||||
all: contentStackBaseWithProfileServiceProcedure
|
||||
.input(rewardsAllInput)
|
||||
.query(async function ({ input, ctx }) {
|
||||
getAllRewardCounter.add(1)
|
||||
const allApiRewards = await getAllApiRewards(ctx)
|
||||
|
||||
if (!allApiRewards) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rewardIds = Object.values(allApiRewards)
|
||||
.flatMap((level) => level.map((reward) => reward?.rewardId))
|
||||
.filter((id): id is string => Boolean(id))
|
||||
|
||||
const contentStackRewards = await getCmsRewards(
|
||||
ctx.lang,
|
||||
getUniqueRewardIds(rewardIds)
|
||||
)
|
||||
|
||||
if (!contentStackRewards) {
|
||||
return []
|
||||
}
|
||||
|
||||
const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx)
|
||||
const levelsWithRewards = Object.entries(allApiRewards).map(
|
||||
([level, rewards]) => {
|
||||
const combinedRewards = rewards
|
||||
.filter((r) => (input.unique ? r?.rewardTierLevel === level : true))
|
||||
.map((reward) => {
|
||||
const contentStackReward = contentStackRewards.find((r) => {
|
||||
return r.reward_id === reward?.rewardId
|
||||
})
|
||||
|
||||
if (contentStackReward) {
|
||||
return contentStackReward
|
||||
} else {
|
||||
console.error("No contentStackReward found", reward?.rewardId)
|
||||
}
|
||||
})
|
||||
.filter((reward): reward is Reward => Boolean(reward))
|
||||
|
||||
const levelConfig = loyaltyLevelsConfig.find(
|
||||
(l) => l.level_id === level
|
||||
)
|
||||
|
||||
if (!levelConfig) {
|
||||
getAllRewardFailCounter.add(1)
|
||||
|
||||
console.error("contentstack.loyaltyLevels level not found")
|
||||
throw notFound()
|
||||
}
|
||||
return { ...levelConfig, rewards: combinedRewards }
|
||||
}
|
||||
)
|
||||
|
||||
getAllRewardSuccessCounter.add(1)
|
||||
return levelsWithRewards
|
||||
}),
|
||||
})
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
} from "@/server/errors/trpc"
|
||||
import { extractHotelImages } from "@/server/routers/utils/hotels"
|
||||
import {
|
||||
contentStackUidWithServiceProcedure,
|
||||
contentStackUidWithHotelServiceProcedure,
|
||||
hotelServiceProcedure,
|
||||
publicProcedure,
|
||||
router,
|
||||
serviceProcedure,
|
||||
} from "@/server/trpc"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
@@ -83,7 +83,7 @@ async function getContentstackData(
|
||||
}
|
||||
|
||||
export const hotelQueryRouter = router({
|
||||
get: contentStackUidWithServiceProcedure
|
||||
get: contentStackUidWithHotelServiceProcedure
|
||||
.input(getHotelInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { lang, uid } = ctx
|
||||
@@ -178,34 +178,34 @@ export const hotelQueryRouter = router({
|
||||
|
||||
const roomCategories = included
|
||||
? included
|
||||
.filter((item) => item.type === "roomcategories")
|
||||
.map((roomCategory) => {
|
||||
const validatedRoom = roomSchema.safeParse(roomCategory)
|
||||
if (!validatedRoom.success) {
|
||||
getHotelFailCounter.add(1, {
|
||||
hotelId,
|
||||
lang,
|
||||
include,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(
|
||||
validatedRoom.error.issues.map(({ code, message }) => ({
|
||||
code,
|
||||
message,
|
||||
}))
|
||||
),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotel validation error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: validatedRoom.error,
|
||||
.filter((item) => item.type === "roomcategories")
|
||||
.map((roomCategory) => {
|
||||
const validatedRoom = roomSchema.safeParse(roomCategory)
|
||||
if (!validatedRoom.success) {
|
||||
getHotelFailCounter.add(1, {
|
||||
hotelId,
|
||||
lang,
|
||||
include,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(
|
||||
validatedRoom.error.issues.map(({ code, message }) => ({
|
||||
code,
|
||||
message,
|
||||
}))
|
||||
),
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
console.error(
|
||||
"api.hotels.hotel validation error",
|
||||
JSON.stringify({
|
||||
query: { hotelId, params },
|
||||
error: validatedRoom.error,
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
|
||||
return validatedRoom.data
|
||||
})
|
||||
return validatedRoom.data
|
||||
})
|
||||
: []
|
||||
|
||||
const activities = contentstackData?.content
|
||||
@@ -233,7 +233,7 @@ export const hotelQueryRouter = router({
|
||||
}
|
||||
}),
|
||||
availability: router({
|
||||
get: serviceProcedure
|
||||
get: hotelServiceProcedure
|
||||
.input(getAvailabilityInputSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const {
|
||||
@@ -395,7 +395,7 @@ export const hotelQueryRouter = router({
|
||||
}),
|
||||
}),
|
||||
hotelData: router({
|
||||
get: serviceProcedure
|
||||
get: hotelServiceProcedure
|
||||
.input(getlHotelDataInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { hotelId, language, include } = input
|
||||
@@ -493,7 +493,7 @@ export const hotelQueryRouter = router({
|
||||
}),
|
||||
}),
|
||||
locations: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
get: hotelServiceProcedure.query(async function ({ ctx }) {
|
||||
const searchParams = new URLSearchParams()
|
||||
searchParams.set("language", toApiLang(ctx.lang))
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import { ServiceTokenResponse } from "@/types/tokens"
|
||||
|
||||
const SERVICE_TOKEN_REVALIDATE_SECONDS = 3599 // 59 minutes and 59 seconds.
|
||||
|
||||
export async function fetchServiceToken(): Promise<ServiceTokenResponse> {
|
||||
export async function fetchServiceToken(
|
||||
scopes: string[]
|
||||
): Promise<ServiceTokenResponse> {
|
||||
try {
|
||||
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
|
||||
method: "POST",
|
||||
@@ -16,7 +18,7 @@ export async function fetchServiceToken(): Promise<ServiceTokenResponse> {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.CURITY_CLIENT_ID_SERVICE,
|
||||
client_secret: env.CURITY_CLIENT_SECRET_SERVICE,
|
||||
scope: ["hotel"].join(","),
|
||||
scope: scopes.join(","),
|
||||
}),
|
||||
next: {
|
||||
revalidate: SERVICE_TOKEN_REVALIDATE_SECONDS,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { type Context, createContext } from "./context"
|
||||
import { fetchServiceToken } from "./tokenManager"
|
||||
import { transformer } from "./transformer"
|
||||
import { langInput } from "./utils"
|
||||
|
||||
import type { Session } from "next-auth"
|
||||
|
||||
@@ -39,7 +40,19 @@ export const { createCallerFactory, mergeRouters, router } = t
|
||||
export const publicProcedure = t.procedure
|
||||
export const contentstackBaseProcedure = t.procedure.use(async function (opts) {
|
||||
if (!opts.ctx.lang) {
|
||||
throw badRequestError("Missing Lang in tRPC context")
|
||||
// When fetching data client side with TRPC we don't pass through middlewares and therefore do not get the lang through headers
|
||||
// We can then pass lang as an input in the request and set it to the context in the procedure
|
||||
const input = await opts.getRawInput()
|
||||
const parsedInput = langInput.safeParse(input)
|
||||
if (!parsedInput.success) {
|
||||
throw badRequestError("Missing Lang in tRPC context")
|
||||
}
|
||||
|
||||
return opts.next({
|
||||
ctx: {
|
||||
lang: parsedInput.data.lang,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return opts.next({
|
||||
@@ -108,10 +121,10 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) {
|
||||
})
|
||||
})
|
||||
|
||||
export const serviceProcedure = t.procedure.use(async (opts) => {
|
||||
const { access_token } = await fetchServiceToken()
|
||||
export const profileServiceProcedure = t.procedure.use(async (opts) => {
|
||||
const { access_token } = await fetchServiceToken(["profile"])
|
||||
if (!access_token) {
|
||||
throw internalServerError("Failed to obtain service token")
|
||||
throw internalServerError("Failed to obtain profile service token")
|
||||
}
|
||||
return opts.next({
|
||||
ctx: {
|
||||
@@ -120,6 +133,17 @@ export const serviceProcedure = t.procedure.use(async (opts) => {
|
||||
})
|
||||
})
|
||||
|
||||
export const hotelServiceProcedure = t.procedure.use(async (opts) => {
|
||||
const { access_token } = await fetchServiceToken(["hotel"])
|
||||
if (!access_token) {
|
||||
throw internalServerError("Failed to obtain hotel service token")
|
||||
}
|
||||
return opts.next({
|
||||
ctx: {
|
||||
serviceToken: access_token,
|
||||
},
|
||||
})
|
||||
})
|
||||
export const serverActionProcedure = t.procedure.experimental_caller(
|
||||
experimental_nextAppDirCaller({
|
||||
createContext,
|
||||
@@ -149,5 +173,11 @@ export const protectedServerActionProcedure = serverActionProcedure.use(
|
||||
|
||||
// NOTE: This is actually save to use, just the implementation could change
|
||||
// in minor version bumps. Please read: https://trpc.io/docs/faq#unstable
|
||||
export const contentStackUidWithServiceProcedure =
|
||||
contentstackExtendedProcedureUID.unstable_concat(serviceProcedure)
|
||||
export const contentStackUidWithHotelServiceProcedure =
|
||||
contentstackExtendedProcedureUID.unstable_concat(hotelServiceProcedure)
|
||||
|
||||
export const contentStackBaseWithProfileServiceProcedure =
|
||||
contentstackBaseProcedure.unstable_concat(profileServiceProcedure)
|
||||
|
||||
export const contentStackBaseWithProtectedProcedure =
|
||||
contentstackBaseProcedure.unstable_concat(protectedProcedure)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { navigationQueryRouter } from "@/server/routers/contentstack/myPages/navigation/query"
|
||||
|
||||
import { MembershipLevel } from "@/utils/user"
|
||||
import { FriendsMembership } from "@/utils/user"
|
||||
|
||||
import type { User } from "@/types/user"
|
||||
|
||||
@@ -11,7 +11,7 @@ type MyPagesNavigation = Awaited<
|
||||
export interface MyPagesMenuProps {
|
||||
navigation: MyPagesNavigation
|
||||
user: Pick<User, "firstName" | "lastName">
|
||||
membership?: MembershipLevel | null
|
||||
membership?: FriendsMembership | null
|
||||
}
|
||||
|
||||
export interface MyPagesMenuContentProps extends MyPagesMenuProps {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
import { MembershipLevel } from "@/constants/membershipLevels"
|
||||
|
||||
export type MembershipLevelProps = {
|
||||
level: membershipLevels
|
||||
}
|
||||
import { LevelProps } from "@/components/Levels/levels"
|
||||
|
||||
export type MembershipLevelIconProps = {
|
||||
level: MembershipLevel
|
||||
} & LevelProps
|
||||
|
||||
export type CopyButtonProps = {
|
||||
membershipNumber: string
|
||||
|
||||
@@ -1,69 +1,36 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
|
||||
import { MembershipLevel } from "@/utils/user"
|
||||
|
||||
import type { IntlFormatters } from "@formatjs/intl"
|
||||
|
||||
type BenefitTitle = { title: string }
|
||||
import { MembershipLevel } from "@/constants/membershipLevels"
|
||||
import { LoyaltyLevel } from "@/server/routers/contentstack/loyaltyLevel/output"
|
||||
import { Reward } from "@/server/routers/contentstack/reward/output"
|
||||
|
||||
export type OverviewTableClientProps = {
|
||||
activeMembership: MembershipLevel | null
|
||||
}
|
||||
|
||||
export type Level = {
|
||||
level: membershipLevels
|
||||
name: string
|
||||
requiredPoints: number
|
||||
requiredNights?: number
|
||||
benefits: BenefitTitle[]
|
||||
levels: ComparisonLevel[]
|
||||
}
|
||||
|
||||
export type LevelCardProps = {
|
||||
formatMessage: IntlFormatters["formatMessage"]
|
||||
lang: Lang
|
||||
level: Level
|
||||
level: LevelWithRewards
|
||||
}
|
||||
|
||||
export type ComparisonLevel = {
|
||||
level: membershipLevels
|
||||
name: string
|
||||
description: string
|
||||
requirement: string
|
||||
icon: string
|
||||
benefits: Benefit[]
|
||||
}
|
||||
export type LevelWithRewards = LoyaltyLevel & { rewards: Reward[] }
|
||||
|
||||
export type Benefit = {
|
||||
name: string
|
||||
description: string
|
||||
unlocked: boolean
|
||||
value?: string
|
||||
valueDetails?: string
|
||||
}
|
||||
export type ComparisonLevel = LevelWithRewards
|
||||
|
||||
export type LevelSummaryProps = {
|
||||
level: ComparisonLevel
|
||||
showDescription?: boolean
|
||||
}
|
||||
|
||||
export type BenefitCardProps = {
|
||||
comparedValues: BenefitValueInformation[]
|
||||
export type RewardCardProps = {
|
||||
comparedValues: (Reward | undefined)[]
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type BenefitValueInformation = {
|
||||
unlocked: boolean
|
||||
value?: string
|
||||
valueDetails?: string
|
||||
export type RewardValueProps = {
|
||||
reward?: Reward
|
||||
}
|
||||
|
||||
export type BenefitValueProps = {
|
||||
benefit: BenefitValueInformation
|
||||
}
|
||||
|
||||
export type BenefitListProps = {
|
||||
export type RewardListProps = {
|
||||
levels: ComparisonLevel[]
|
||||
}
|
||||
|
||||
@@ -77,16 +44,16 @@ export type DesktopSelectColumns = {
|
||||
|
||||
export type LargeTableProps = {
|
||||
levels: ComparisonLevel[]
|
||||
activeLevel: membershipLevels | null
|
||||
activeLevel: MembershipLevel | null
|
||||
Select?: (column: DesktopSelectColumns) => JSX.Element | null
|
||||
}
|
||||
|
||||
export type BenefitTableHeaderProps = {
|
||||
export type RewardTableHeaderProps = {
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export enum overviewTableActionsEnum {
|
||||
export enum OverviewTableActionsEnum {
|
||||
SET_SELECTED_LEVEL_A_MOBILE = "SET_SELECTED_LEVEL_A_MOBILE",
|
||||
SET_SELECTED_LEVEL_B_MOBILE = "SET_SELECTED_LEVEL_B_MOBILE",
|
||||
SET_SELECTED_LEVEL_A_DESKTOP = "SET_SELECTED_LEVEL_A_DESKTOP",
|
||||
@@ -95,6 +62,6 @@ export enum overviewTableActionsEnum {
|
||||
}
|
||||
|
||||
export type OverviewTableReducerAction = {
|
||||
type: overviewTableActionsEnum
|
||||
type: OverviewTableActionsEnum
|
||||
payload: ComparisonLevel
|
||||
}
|
||||
|
||||
@@ -83,3 +83,19 @@ export function generateTagsFromSystem(
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate tags for loyalty configuration models
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param contentTypeUid content_type_uid of reference
|
||||
* @param id system shared identifier, e.g reward_id, level_id
|
||||
* @returns string
|
||||
*/
|
||||
export function generateLoyaltyConfigTag(
|
||||
lang: Lang,
|
||||
contentTypeUid: string,
|
||||
id: string
|
||||
) {
|
||||
return `${lang}:loyalty_config:${contentTypeUid}:${id}`
|
||||
}
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
import type { Benefit, ComparisonLevel } from "@/types/components/overviewTable"
|
||||
import { Reward } from "@/server/routers/contentstack/reward/output"
|
||||
|
||||
export function getUnlockedBenefits(levels: ComparisonLevel[]) {
|
||||
const allBenefits = levels
|
||||
import type { ComparisonLevel } from "@/types/components/overviewTable"
|
||||
|
||||
export function getGroupedRewards(levels: ComparisonLevel[]) {
|
||||
const allRewards = levels
|
||||
.map((level) => {
|
||||
return level.benefits.filter((benefit) => benefit.unlocked)
|
||||
return level.rewards
|
||||
})
|
||||
.flat()
|
||||
|
||||
/* Remove duplicate benefits based on the name property */
|
||||
return Array.from(
|
||||
new Map(allBenefits.map((benefit) => [benefit.name, benefit])).values()
|
||||
const mappedRewards = allRewards.reduce<Record<string, Reward[]>>(
|
||||
(acc, curr) => {
|
||||
const taxonomiTerm = curr.taxonomies.find((tax) => tax.term_uid)?.term_uid
|
||||
|
||||
if (taxonomiTerm) {
|
||||
if (!acc[taxonomiTerm]) {
|
||||
acc[taxonomiTerm] = []
|
||||
}
|
||||
acc[taxonomiTerm].push(curr)
|
||||
} else {
|
||||
if (!acc[curr.reward_id]) {
|
||||
acc[curr.reward_id] = []
|
||||
}
|
||||
acc[curr.reward_id].push(curr)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
return mappedRewards
|
||||
}
|
||||
|
||||
export function findBenefit(benefit: Benefit, level: ComparisonLevel) {
|
||||
return level.benefits.find((b) => b.name === benefit.name) as Benefit
|
||||
export function findAvailableRewards(
|
||||
allRewardIds: string[],
|
||||
level: ComparisonLevel
|
||||
) {
|
||||
return level.rewards.find((r) => allRewardIds.includes(r.reward_id))
|
||||
}
|
||||
|
||||
export function getGroupedLabelAndDescription(rewards: Reward[]) {
|
||||
const reward = rewards.find(
|
||||
(reward) => !!(reward.grouped_label && reward.grouped_label)
|
||||
)
|
||||
return {
|
||||
label: reward?.grouped_label ?? "",
|
||||
description: reward?.grouped_description ?? "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import {
|
||||
MembershipLevelEnum,
|
||||
membershipLevels,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import levelsData from "@/data/loyaltyLevels"
|
||||
|
||||
export function getMembershipLevelObject(
|
||||
membershipLevel: MembershipLevelEnum,
|
||||
lang: Lang
|
||||
) {
|
||||
return levelsData[lang].levels.find(
|
||||
(level) => level.level === membershipLevels[membershipLevel]
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
import {
|
||||
MembershipLevel,
|
||||
MembershipLevelEnum,
|
||||
} from "@/constants/membershipLevels"
|
||||
import { getMembershipCardsSchema } from "@/server/routers/user/output"
|
||||
|
||||
import type { Memberships, User } from "@/types/user"
|
||||
import type { Membership, Memberships, User } from "@/types/user"
|
||||
|
||||
enum scandicMemberships {
|
||||
guestpr = "guestpr",
|
||||
@@ -14,9 +17,16 @@ export function getMembership(memberships: Memberships) {
|
||||
return memberships?.find(
|
||||
(membership) =>
|
||||
membership.membershipType.toLowerCase() === scandicMemberships.guestpr
|
||||
)
|
||||
) as FriendsMembership | undefined
|
||||
}
|
||||
|
||||
export type FriendsMembership = Omit<
|
||||
NonNullable<Membership>,
|
||||
"membershipLevel" | "nextLevel"
|
||||
> & {
|
||||
membershipLevel: MembershipLevel
|
||||
nextLevel: MembershipLevel
|
||||
}
|
||||
export type MembershipLevel = ReturnType<typeof getMembership>
|
||||
|
||||
export function getMembershipCards(
|
||||
memberships: z.infer<typeof getMembershipCardsSchema>
|
||||
@@ -31,7 +41,7 @@ export function getMembershipCards(
|
||||
}
|
||||
|
||||
export function isHighestMembership(
|
||||
membershipLevel: MembershipLevelEnum | undefined
|
||||
membershipLevel: MembershipLevel | undefined
|
||||
) {
|
||||
return membershipLevel == MembershipLevelEnum.L7
|
||||
}
|
||||
@@ -45,3 +55,15 @@ export function getInitials(
|
||||
const lastInitial = lastName.charAt(0).toUpperCase()
|
||||
return `${firstInitial}${lastInitial}`
|
||||
}
|
||||
|
||||
export function getSteppedUpLevel(
|
||||
currentValue: MembershipLevel,
|
||||
stepsUp: number
|
||||
): MembershipLevel {
|
||||
const values = Object.values(MembershipLevelEnum)
|
||||
const currentIndex = values.indexOf(currentValue as MembershipLevelEnum)
|
||||
if (currentIndex === -1 || currentIndex === values.length - 1) {
|
||||
return currentValue
|
||||
}
|
||||
return values[currentIndex + stepsUp]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user