diff --git a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx
index cef74e464..3170c0dfd 100644
--- a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx
+++ b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx
@@ -4,8 +4,6 @@ import { serverClient } from "@/lib/trpc/server"
import Blocks from "@/components/Blocks"
import SectionHeader from "@/components/Section/Header"
-import Preamble from "@/components/TempDesignSystem/Text/Preamble"
-import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n"
import { setLang } from "@/i18n/serverContext"
diff --git a/components/Blocks/DynamicContent/SAS/LinkedAccounts/index.tsx b/components/Blocks/DynamicContent/SAS/LinkedAccounts/index.tsx
index 09054b527..6be7b3565 100644
--- a/components/Blocks/DynamicContent/SAS/LinkedAccounts/index.tsx
+++ b/components/Blocks/DynamicContent/SAS/LinkedAccounts/index.tsx
@@ -1,5 +1,4 @@
import { Suspense } from "react"
-import { setTimeout } from "timers/promises"
import { TIER_TO_FRIEND_MAP } from "@/constants/membershipLevels"
import { env } from "@/env/server"
@@ -8,6 +7,7 @@ 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 { timeout } from "@/utils/timeout"
import { TierLevelCard, TierLevelCardSkeleton } from "./Card/TierLevelCard"
import { LevelUpgradeButton } from "./LevelUpgradeButton"
@@ -60,7 +60,7 @@ function TierLevelCardsSkeleton() {
async function TierLevelCards() {
console.log("[SAS] Fetching tier level cards")
- await setTimeout(2_000)
+ await timeout(2_000)
console.log("[SAS] AFTER Fetching tier level cards")
const user = await getProfile()
diff --git a/components/Current/Header/MainMenu/index.tsx b/components/Current/Header/MainMenu/index.tsx
index 79435abad..5fcdf188b 100644
--- a/components/Current/Header/MainMenu/index.tsx
+++ b/components/Current/Header/MainMenu/index.tsx
@@ -112,7 +112,7 @@ export function MainMenu({
className={`${styles.listWrapper} ${isHamburgerMenuOpen ? styles.isOpen : ""}`}
>
- {!isThreeStaticPagesPathnames && !!user ? (
+ {user ? (
-
>
+import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
+import type { MyPagesLink } from "@/components/MyPages/menuItems"
export default function MyPagesMobileDropdown({
- navigation,
+ primaryLinks,
+ secondaryLinks,
}: {
- navigation: Navigation
+ primaryLinks: MyPagesLink[]
+ secondaryLinks: MyPagesLink[]
}) {
const intl = useIntl()
- const lang = useLang()
const { toggleDropdown, isMyPagesMobileMenuOpen } = useDropdownStore()
- if (!navigation) {
+ if (primaryLinks.length === 0 && secondaryLinks.length === 0) {
return null
}
+ const handleOnClick = () => toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
+
return (
)
}
+
+function List({ children }: { children: ReactNode }) {
+ return (
+ <>
+
+
+ >
+ )
+}
+
+function PrimaryLinks({
+ primaryLinks,
+ handleOnClick,
+}: {
+ primaryLinks: MyPagesLink[]
+ handleOnClick: () => void
+}) {
+ return (
+ <>
+ {primaryLinks.map((link, i) => (
+
-
+
+ {link.text}
+
+
+ ))}
+ >
+ )
+}
+function SecondaryLinks({
+ secondaryLinks,
+ handleOnClick,
+}: {
+ secondaryLinks: MyPagesLink[]
+ handleOnClick: () => void
+}) {
+ const intl = useIntl()
+ const lang = useLang()
+
+ return (
+ <>
+ {secondaryLinks.map((link, i) => (
+ -
+
+ {link.text}
+
+
+ ))}
+ -
+
+ {intl.formatMessage({ id: "Log out" })}
+
+
+ >
+ )
+}
diff --git a/components/Current/Header/index.tsx b/components/Current/Header/index.tsx
index 26fe4b654..7c294520e 100644
--- a/components/Current/Header/index.tsx
+++ b/components/Current/Header/index.tsx
@@ -3,10 +3,13 @@ import { env } from "@/env/server"
import {
getCurrentHeader,
getLanguageSwitcher,
- getMyPagesNavigation,
getName,
} from "@/lib/trpc/memoizedRequests"
+import {
+ getPrimaryLinks,
+ getSecondaryLinks,
+} from "@/components/MyPages/menuItems"
import { getLang } from "@/i18n/serverContext"
import LanguageSwitcher from "./LanguageSwitcher"
@@ -18,14 +21,17 @@ import TopMenu from "./TopMenu"
import styles from "./header.module.css"
export default async function Header() {
- const [data, user, languages, navigation] = await Promise.all([
- getCurrentHeader(getLang()),
- getName(),
- getLanguageSwitcher(),
- getMyPagesNavigation(),
- ])
+ const lang = getLang()
+ const [data, user, languages, primaryLinks, secondaryLinks] =
+ await Promise.all([
+ getCurrentHeader(lang),
+ getName(),
+ getLanguageSwitcher(),
+ getPrimaryLinks({ lang }),
+ getSecondaryLinks({ lang }),
+ ])
- if (!navigation || !languages || !data?.header) {
+ if (!languages || !data?.header) {
return null
}
@@ -53,7 +59,10 @@ export default async function Header() {
topMenuMobileLinks={topMenuMobileLinks}
languageSwitcher={}
myPagesMobileDropdown={
-
+
}
bookingHref={homeHref}
user={user}
diff --git a/components/Header/MainMenu/MyPagesMenu/index.tsx b/components/Header/MainMenu/MyPagesMenu/index.tsx
index c29b836d6..1d3830c45 100644
--- a/components/Header/MainMenu/MyPagesMenu/index.tsx
+++ b/components/Header/MainMenu/MyPagesMenu/index.tsx
@@ -10,7 +10,7 @@ import SkeletonShimmer from "@/components/SkeletonShimmer"
import Body from "@/components/TempDesignSystem/Text/Body"
import useClickOutside from "@/hooks/useClickOutside"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
-import { getInitials } from "@/utils/user"
+import {type FriendsMembership,getInitials } from "@/utils/user"
import Avatar from "../Avatar"
import MainMenuButton from "../MainMenuButton"
@@ -19,11 +19,22 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
import styles from "./myPagesMenu.module.css"
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
-import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
+import type { User } from "@/types/user"
+import type { MyPagesLink } from "@/components/MyPages/menuItems"
+import type { LoyaltyLevel } from "@/server/routers/contentstack/loyaltyLevel/output"
+
+export type MyPagesMenuProps = {
+ primaryLinks: MyPagesLink[]
+ secondaryLinks: MyPagesLink[]
+ user: Pick
+ membership?: FriendsMembership | null
+ membershipLevel: LoyaltyLevel | null
+}
export default function MyPagesMenu({
membership,
- navigation,
+ primaryLinks,
+ secondaryLinks,
user,
membershipLevel,
}: MyPagesMenuProps) {
@@ -65,7 +76,8 @@ export default function MyPagesMenu({
diff --git a/components/Header/MainMenu/MyPagesMenuContent/index.tsx b/components/Header/MainMenu/MyPagesMenuContent/index.tsx
index bc21dfe9e..6f155075c 100644
--- a/components/Header/MainMenu/MyPagesMenuContent/index.tsx
+++ b/components/Header/MainMenu/MyPagesMenuContent/index.tsx
@@ -14,17 +14,19 @@ import { useTrapFocus } from "@/hooks/useTrapFocus"
import styles from "./myPagesMenuContent.module.css"
-import type { MyPagesMenuContentProps } from "@/types/components/header/myPagesMenu"
+import type { MyPagesMenuProps } from "../MyPagesMenu"
+
+type Props = MyPagesMenuProps & { toggleOpenStateFn: () => void }
export default function MyPagesMenuContent({
membership,
- navigation,
+ primaryLinks,
+ secondaryLinks,
toggleOpenStateFn,
user,
membershipLevel,
-}: MyPagesMenuContentProps) {
+}: Props) {
const intl = useIntl()
- const lang = useLang()
const myPagesMenuContentRef = useTrapFocus()
const membershipPoints = membership?.currentPoints
@@ -32,7 +34,8 @@ export default function MyPagesMenuContent({
membershipLevel && membershipPoints
? `${styles.intro}`
: `${styles.intro} ${styles.noMembership}`
- if (!navigation) {
+
+ if (primaryLinks.length === 0 && secondaryLinks.length === 0) {
return null
}
@@ -59,40 +62,81 @@ export default function MyPagesMenuContent({
- {navigation.menuItems.map((menuItem, idx) => (
- -
-
-
- {menuItem.links.map((link) => (
- -
-
- {link.linkText}
-
-
-
- ))}
- {menuItem.display_sign_out_link ? (
- -
-
- {intl.formatMessage({ id: "Log out" })}
-
-
- ) : null}
-
-
- ))}
+ -
+
+
+
+
+
+
+
)
}
+
+function PrimaryLinks({
+ primaryLinks,
+ toggleOpenStateFn,
+}: Pick & { toggleOpenStateFn: () => void }) {
+ return (
+
+ {primaryLinks.map((link, i) => (
+ -
+
+ {link.text}
+
+
+
+ ))}
+
+ )
+}
+
+function SecondaryLinks({
+ secondaryLinks,
+ toggleOpenStateFn,
+}: Pick & { toggleOpenStateFn: () => void }) {
+ const intl = useIntl()
+ const lang = useLang()
+
+ return (
+
+ {secondaryLinks.map((link, i) => (
+ -
+
+ {link.text}
+
+
+
+ ))}
+ -
+
+ {intl.formatMessage({ id: "Log out" })}
+
+
+
+ )
+}
diff --git a/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx b/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx
index de5a5ab53..2e1adadcb 100644
--- a/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx
+++ b/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx
@@ -1,13 +1,14 @@
import { MembershipLevelEnum } from "@/constants/membershipLevels"
-import {
- getMembershipLevelSafely,
- getMyPagesNavigation,
- getName,
-} from "@/lib/trpc/memoizedRequests"
+import { getMembershipLevelSafely, getName } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server"
import LoginButton from "@/components/LoginButton"
+import {
+ getPrimaryLinks,
+ getSecondaryLinks,
+} from "@/components/MyPages/menuItems"
import { getIntl } from "@/i18n"
+import { getLang } from "@/i18n/serverContext"
import Avatar from "../Avatar"
import MyPagesMenu, { MyPagesMenuSkeleton } from "../MyPagesMenu"
@@ -18,12 +19,15 @@ import MyPagesMobileMenu, {
import styles from "./myPagesMenuWrapper.module.css"
export default async function MyPagesMenuWrapper() {
- const [intl, myPagesNavigation, user, membership] = await Promise.all([
- getIntl(),
- getMyPagesNavigation(),
- getName(),
- getMembershipLevelSafely(),
- ])
+ const lang = getLang()
+ const [intl, user, membership, primaryLinks, secondaryLinks] =
+ await Promise.all([
+ getIntl(),
+ getName(),
+ getMembershipLevelSafely(),
+ getPrimaryLinks({ lang }),
+ getSecondaryLinks({ lang }),
+ ])
const membershipLevel = membership?.membershipLevel
? await serverClient().contentstack.loyaltyLevels.byLevel({
@@ -38,13 +42,15 @@ export default async function MyPagesMenuWrapper() {
>
diff --git a/components/Header/MainMenu/MyPagesMobileMenu/index.tsx b/components/Header/MainMenu/MyPagesMobileMenu/index.tsx
index 50afd17df..62c949724 100644
--- a/components/Header/MainMenu/MyPagesMobileMenu/index.tsx
+++ b/components/Header/MainMenu/MyPagesMobileMenu/index.tsx
@@ -17,12 +17,13 @@ import MyPagesMenuContent from "../MyPagesMenuContent"
import styles from "./myPagesMobileMenu.module.css"
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
-import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
+import type { MyPagesMenuProps } from "../MyPagesMenu"
export default function MyPagesMobileMenu({
membershipLevel,
membership,
- navigation,
+ primaryLinks,
+ secondaryLinks,
user,
}: MyPagesMenuProps) {
const intl = useIntl()
@@ -65,7 +66,8 @@ export default function MyPagesMobileMenu({
toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
diff --git a/components/MyPages/Sidebar/index.tsx b/components/MyPages/Sidebar/index.tsx
index 0dd8d36a2..12d42e6a0 100644
--- a/components/MyPages/Sidebar/index.tsx
+++ b/components/MyPages/Sidebar/index.tsx
@@ -1,8 +1,9 @@
-import { Fragment } from "react"
-
import { logout } from "@/constants/routes/handleAuth"
-import { getMyPagesNavigation } from "@/lib/trpc/memoizedRequests"
+import {
+ getPrimaryLinks,
+ getSecondaryLinks,
+} from "@/components/MyPages/menuItems"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
@@ -12,50 +13,85 @@ import { getLang } from "@/i18n/serverContext"
import styles from "./sidebar.module.css"
export default async function SidebarMyPages() {
- const navigation = await getMyPagesNavigation()
const intl = await getIntl()
return (
)
}
+
+async function PrimaryLinks() {
+ const lang = getLang()
+ const links = await getPrimaryLinks({ lang })
+
+ return (
+ <>
+
+
+ {links.map((link) => (
+ -
+
+ {link.text}
+
+
+ ))}
+
+ >
+ )
+}
+
+async function SecondaryLinks() {
+ const lang = getLang()
+ const links = await getSecondaryLinks({ lang })
+ const intl = await getIntl()
+
+ return (
+ <>
+
+
+ {links.map((link) => (
+ -
+
+ {link.text}
+
+
+ ))}
+ -
+
+ {intl.formatMessage({ id: "Log out" })}
+
+
+
+ >
+ )
+}
diff --git a/components/MyPages/menuItems.ts b/components/MyPages/menuItems.ts
new file mode 100644
index 000000000..53fed9dae
--- /dev/null
+++ b/components/MyPages/menuItems.ts
@@ -0,0 +1,101 @@
+import * as routes from "@/constants/routes/myPages"
+import { env } from "@/env/server"
+
+import { getIntl } from "@/i18n"
+import { safeTry } from "@/utils/safeTry"
+
+import type { ReactNode } from "react"
+
+import type { Lang } from "../../constants/languages"
+
+export type MyPagesLink = {
+ text: ReactNode
+ href: string
+}
+
+type Args = {
+ lang: Lang
+}
+
+export async function getPrimaryLinks({ lang }: Args): Promise {
+ const intl = await getIntl()
+ const scandicSasPromise = safeTry(isScandicXSASActive())
+ const teamMemberPromise = safeTry(showTeamMemberCard())
+
+ const [showSASLink] = await scandicSasPromise
+ const [showTeamMemberLink] = await teamMemberPromise
+
+ const menuItems: MyPagesLink[] = [
+ {
+ text: intl.formatMessage({ id: "Overview" }),
+ href: routes.overview[lang],
+ },
+ {
+ text: intl.formatMessage({ id: "My Points" }),
+ href: routes.points[lang],
+ },
+ {
+ text: intl.formatMessage({ id: "My Stays" }),
+ href: routes.stays[lang],
+ },
+ {
+ text: intl.formatMessage({ id: "My Benefits" }),
+ href: routes.benefits[lang],
+ },
+ ]
+
+ if (showSASLink) {
+ menuItems.push({
+ text: intl.formatMessage({ id: "Scandic ♥ SAS" }),
+ href: routes.scandicXSAS[lang],
+ })
+ }
+
+ if (showTeamMemberLink) {
+ menuItems.push({
+ text: intl.formatMessage({ id: "Team Member Card" }),
+ href: "#",
+ })
+ }
+
+ return menuItems
+}
+
+export async function getSecondaryLinks({
+ lang,
+}: Args): Promise {
+ const intl = await getIntl()
+ const menuItems: MyPagesLink[] = [
+ {
+ text: intl.formatMessage({ id: "About Scandic Friends" }),
+ href: routes.scandicFriends[lang],
+ },
+ {
+ text: intl.formatMessage({ id: "My Profile" }),
+ href: routes.profile[lang],
+ },
+ ]
+
+ return menuItems
+}
+
+async function isScandicXSASActive() {
+ async function checkIfLinked() {
+ // TODO: Implement this check
+ return true
+ }
+
+ const isLinked = await checkIfLinked()
+
+ return env.SAS_ENABLED && isLinked
+}
+
+async function showTeamMemberCard() {
+ async function getIsTeamMember() {
+ // TODO: Implement this check
+ return false
+ }
+
+ const isTeamMember = await getIsTeamMember()
+ return isTeamMember
+}
diff --git a/constants/routes/myPages.js b/constants/routes/myPages.js
index 69386344a..e8c8b6855 100644
--- a/constants/routes/myPages.js
+++ b/constants/routes/myPages.js
@@ -7,14 +7,24 @@
* These are routes that define code entries for My pages
*/
+/** @type {import('@/types/routes').LangRoute} */
+export const scandicFriends = {
+ da: "/da/scandic-friends",
+ de: "/de/scandic-friends",
+ en: "/en/scandic-friends",
+ fi: "/fi/scandic-friends",
+ no: "/no/scandic-friends",
+ sv: "/sv/scandic-friends",
+}
+
/** @type {import('@/types/routes').LangRoute} */
export const myPages = {
- da: "/da/scandic-friends/mine-sider",
- de: "/de/scandic-friends/mein-bereich",
- en: "/en/scandic-friends/my-pages",
- fi: "/fi/scandic-friends/omat-sivut",
- no: "/no/scandic-friends/mine-sider",
- sv: "/sv/scandic-friends/mina-sidor",
+ da: `${scandicFriends.da}/mine-sider`,
+ de: `${scandicFriends.de}/mein-bereich`,
+ en: `${scandicFriends.en}/my-pages`,
+ fi: `${scandicFriends.fi}/omat-sivut`,
+ no: `${scandicFriends.no}/mine-sider`,
+ sv: `${scandicFriends.sv}/mina-sidor`,
}
/** @type {import('@/types/routes').LangRoute} */
@@ -77,3 +87,13 @@ export const stays = {
no: `${myPages.no}/opphold`,
sv: `${myPages.sv}/vistelser`,
}
+
+/** @type {import('@/types/routes').LangRoute} */
+export const scandicXSAS = {
+ da: `${myPages.da}/scandic-x-sas`,
+ de: `${myPages.de}/scandic-x-sas`,
+ en: `${myPages.en}/scandic-x-sas`,
+ fi: `${myPages.fi}/scandic-x-sas`,
+ no: `${myPages.no}/scandic-x-sas`,
+ sv: `${myPages.sv}/scandic-x-sas`,
+}
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json
index 36c87c313..3a00d8557 100644
--- a/i18n/dictionaries/da.json
+++ b/i18n/dictionaries/da.json
@@ -6,6 +6,7 @@
"A photo of the room": "Et foto af værelset",
"ACCE": "Tilgængelighed",
"ALLG": "Allergi",
+ "About Scandic Friends": "Om Scandic Friends",
"About accessibility": "Om tilgængelighed",
"About meetings & conferences": "Om møder og konferencer",
"About parking": "Om parkering",
@@ -344,6 +345,10 @@
"Multiroom with voucher error": "Det er ikke muligt at booke flere værelser med gavekort.",
"Museum": "Museum",
"Museums": "Museer",
+ "My Benefits": "Mine Fordele",
+ "My Points": "Mine Point",
+ "My Profile": "Min profil",
+ "My Stays": "Mine Ophold",
"My communication preferences": "Mine kommunikationspræferencer",
"My membership cards": "Mine medlemskort",
"My pages": "Mine sider",
@@ -486,6 +491,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Søge",
"See all": "Se alle",
"See all photos": "Se alle billeder",
diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json
index ae508501e..ddac1282b 100644
--- a/i18n/dictionaries/de.json
+++ b/i18n/dictionaries/de.json
@@ -6,6 +6,7 @@
"A photo of the room": "Ein Foto des Zimmers",
"ACCE": "Zugänglichkeit",
"ALLG": "Allergie",
+ "About Scandic Friends": "Über Scandic Friends",
"About accessibility": "Über Barrierefreiheit",
"About meetings & conferences": "Über Meetings & Konferenzen",
"About parking": "Über das Parken",
@@ -345,6 +346,10 @@
"Multiroom with voucher error": "Die Buchung mehrerer Zimmer ist nicht mit einem Gutschein möglich",
"Museum": "Museum",
"Museums": "Museen",
+ "My Benefits": "Meine Vorteile",
+ "My Points": "Meine Punkte",
+ "My Profile": "Mein Profil",
+ "My Stays": "Meine Aufenthalte",
"My communication preferences": "Meine Kommunikationseinstellungen",
"My membership cards": "Meine Mitgliedskarten",
"My pages": "Meine Seiten",
@@ -487,6 +492,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Suchen",
"See all": "Alle anzeigen",
"See all photos": "Alle Fotos ansehen",
diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json
index d1cec4411..ba20c246b 100644
--- a/i18n/dictionaries/en.json
+++ b/i18n/dictionaries/en.json
@@ -7,6 +7,7 @@
"A photo of the room": "A photo of the room",
"ACCE": "Accessibility",
"ALLG": "Allergy",
+ "About Scandic Friends": "About Scandic Friends",
"About accessibility": "About accessibility",
"About meetings & conferences": "About meetings & conferences",
"About parking": "About parking",
@@ -349,6 +350,10 @@
"Multiroom with voucher error": "Multiroom booking is not compatible with a code or voucher",
"Museum": "Museum",
"Museums": "Museums",
+ "My Benefits": "My Benefits",
+ "My Points": "My points",
+ "My Profile": "My Profile",
+ "My Stays": "My Stays",
"My communication preferences": "My communication preferences",
"My membership cards": "My membership cards",
"My pages": "My pages",
@@ -492,6 +497,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Search",
"See all": "See all",
"See all photos": "See all photos",
diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json
index 0e02fa8bb..c2675b55d 100644
--- a/i18n/dictionaries/fi.json
+++ b/i18n/dictionaries/fi.json
@@ -6,6 +6,7 @@
"A photo of the room": "Kuva huoneesta",
"ACCE": "Saavutettavuus",
"ALLG": "Allergia",
+ "About Scandic Friends": "Scandic Friends -ohjelmasta",
"About accessibility": "Tietoja saavutettavuudesta",
"About meetings & conferences": "Tietoja kokouksista ja konferensseista",
"About parking": "Tietoja pysäköinnistä",
@@ -344,6 +345,10 @@
"Multiroom with voucher error": "Lahjakortilla ei voi varata monta huonetta",
"Museum": "Museo",
"Museums": "Museot",
+ "My Benefits": "Etuni",
+ "My Points": "Pisteeni",
+ "My Profile": "Profiilini",
+ "My Stays": "Varaukseni",
"My communication preferences": "Viestintämieltymykseni",
"My membership cards": "Jäsenkorttini",
"My pages": "Omat sivut",
@@ -487,6 +492,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Haku",
"See all": "Katso kaikki",
"See all photos": "Katso kaikki kuvat",
diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json
index 23af774e5..4913a0847 100644
--- a/i18n/dictionaries/no.json
+++ b/i18n/dictionaries/no.json
@@ -6,6 +6,7 @@
"A photo of the room": "Et bilde av rommet",
"ACCE": "Tilgjengelighet",
"ALLG": "Allergi",
+ "About Scandic Friends": "Om Scandic Friends",
"About accessibility": "Om tilgjengelighet",
"About meetings & conferences": "Om møter og konferanser",
"About parking": "Om parkering",
@@ -343,6 +344,10 @@
"Multiroom with voucher error": "Bestilling av flere rom er ikke mulig når du bestiller med voucher",
"Museum": "Museum",
"Museums": "Museums",
+ "My Benefits": "Mine fordeler",
+ "My Points": "Mine poeng",
+ "My Profile": "Min profil",
+ "My Stays": "Mine opphold",
"My communication preferences": "Mine kommunikasjonspreferanser",
"My membership cards": "Mine medlemskort",
"My pages": "Mine sider",
@@ -485,6 +490,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Søk",
"See all": "Se alle",
"See all photos": "Se alle bilder",
diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json
index e12206a48..1bda25195 100644
--- a/i18n/dictionaries/sv.json
+++ b/i18n/dictionaries/sv.json
@@ -6,6 +6,7 @@
"A photo of the room": "Ett foto av rummet",
"ACCE": "Tillgänglighet",
"ALLG": "Allergi",
+ "About Scandic Friends": "Om Scandic Friends",
"About accessibility": "Om tillgänglighet",
"About meetings & conferences": "Om möten & konferenser",
"About parking": "Om parkering",
@@ -343,6 +344,10 @@
"Multiroom with voucher error": "Flera rum kan inte bokas samtidigt med presentkort",
"Museum": "Museum",
"Museums": "Museer",
+ "My Benefits": "Mina förmåner",
+ "My Points": "Mina poäng",
+ "My Profile": "Min profil",
+ "My Stays": "Mina vistelser",
"My communication preferences": "Mina kommunikationspreferenser",
"My membership cards": "Mina medlemskort",
"My pages": "Mina sidor",
@@ -485,6 +490,7 @@
"Save card to profile": "Save card to profile",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
+ "Scandic ♥ SAS": "Scandic ♥ SAS",
"Search": "Sök",
"See all": "Se alla",
"See all photos": "Se alla foton",
diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts
index 4c6eabc5e..1f4fbd466 100644
--- a/lib/trpc/memoizedRequests/index.ts
+++ b/lib/trpc/memoizedRequests/index.ts
@@ -120,12 +120,6 @@ export const getCurrentFooter = cache(async function getMemoizedCurrentFooter(
return serverClient().contentstack.base.currentFooter({ lang })
})
-export const getMyPagesNavigation = cache(
- async function getMemoizedMyPagesNavigation() {
- return serverClient().contentstack.myPages.navigation.get()
- }
-)
-
export const getLanguageSwitcher = cache(
async function getMemoizedLanguageSwitcher() {
return serverClient().contentstack.languageSwitcher.get()
diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts
index df5749f5d..d671d0686 100644
--- a/server/routers/contentstack/index.ts
+++ b/server/routers/contentstack/index.ts
@@ -14,7 +14,6 @@ import { languageSwitcherRouter } from "./languageSwitcher"
import { loyaltyLevelRouter } from "./loyaltyLevel"
import { loyaltyPageRouter } from "./loyaltyPage"
import { metadataRouter } from "./metadata"
-import { myPagesRouter } from "./myPages"
import { partnerRouter } from "./partner"
import { rewardRouter } from "./reward"
import { startPageRouter } from "./startPage"
@@ -32,7 +31,6 @@ export const contentstackRouter = router({
destinationOverviewPage: destinationOverviewPageRouter,
destinationCountryPage: destinationCountryPageRouter,
destinationCityPage: destinationCityPageRouter,
- myPages: myPagesRouter,
metadata: metadataRouter,
rewards: rewardRouter,
loyaltyLevels: loyaltyLevelRouter,
diff --git a/server/routers/contentstack/myPages/index.ts b/server/routers/contentstack/myPages/index.ts
deleted file mode 100644
index 2b1e07f4e..000000000
--- a/server/routers/contentstack/myPages/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { router } from "@/server/trpc"
-
-import { navigationRouter } from "./navigation"
-
-export const myPagesRouter = router({
- navigation: navigationRouter,
-})
diff --git a/server/routers/contentstack/myPages/navigation/index.ts b/server/routers/contentstack/myPages/navigation/index.ts
deleted file mode 100644
index d2635b00c..000000000
--- a/server/routers/contentstack/myPages/navigation/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { mergeRouters } from "@/server/trpc"
-
-import { navigationQueryRouter } from "./query"
-
-export const navigationRouter = mergeRouters(navigationQueryRouter)
diff --git a/server/routers/contentstack/myPages/navigation/output.ts b/server/routers/contentstack/myPages/navigation/output.ts
deleted file mode 100644
index f4ab428d7..000000000
--- a/server/routers/contentstack/myPages/navigation/output.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { z } from "zod"
-
-import { Lang } from "@/constants/languages"
-
-import { linkRefsUnionSchema, linkUnionSchema } from "../../schemas/pageLinks"
-import { systemSchema } from "../../schemas/system"
-
-const pageConnection = z.object({
- edges: z.array(
- z.object({
- node: linkUnionSchema,
- })
- ),
-})
-
-const pageConnectionRefs = z.object({
- edges: z.array(
- z.object({
- node: linkRefsUnionSchema,
- })
- ),
-})
-
-export const navigationRefsPayloadSchema = z.object({
- all_navigation_my_pages: z.object({
- items: z.array(
- z.object({
- menu_items: z.array(
- z.object({
- links: z.array(
- z.object({
- page: pageConnectionRefs,
- })
- ),
- })
- ),
- system: systemSchema,
- })
- ),
- }),
-})
-
-export type GetNavigationMyPagesRefsData = z.infer<
- typeof navigationRefsPayloadSchema
->
-
-const menuItems = z.array(
- z.object({
- display_sign_out_link: z.boolean(),
- links: z.array(
- z.object({
- link_text: z.string().default(""),
- page: pageConnection,
- })
- ),
- })
-)
-
-export type MenuItems = z.infer
-
-export const navigationPayloadSchema = z.object({
- all_navigation_my_pages: z.object({
- items: z.array(
- z.object({
- menu_items: menuItems,
- title: z.string(),
- })
- ),
- }),
-})
-
-export type GetNavigationMyPagesData = z.infer
-
-const link = z.object({
- lang: z.nativeEnum(Lang),
- linkText: z.string(),
- uid: z.string(),
- url: z.string(),
- originalUrl: z.string().optional(),
-})
-
-export const getNavigationSchema = z.object({
- menuItems: z.array(
- z.object({
- display_sign_out_link: z.boolean(),
- links: z.array(link),
- })
- ),
- title: z.string(),
-})
diff --git a/server/routers/contentstack/myPages/navigation/query.ts b/server/routers/contentstack/myPages/navigation/query.ts
deleted file mode 100644
index 2079e3681..000000000
--- a/server/routers/contentstack/myPages/navigation/query.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import { metrics } from "@opentelemetry/api"
-
-import {
- GetNavigationMyPages,
- GetNavigationMyPagesRefs,
-} from "@/lib/graphql/Query/AccountPage/Navigation.graphql"
-import { request } from "@/lib/graphql/request"
-import { notFound } from "@/server/errors/trpc"
-import { contentstackBaseProcedure, router } from "@/server/trpc"
-
-import {
- generateRefsResponseTag,
- generateTag,
- generateTags,
-} from "@/utils/generateTag"
-
-import {
- type GetNavigationMyPagesData,
- type GetNavigationMyPagesRefsData,
- getNavigationSchema,
- navigationPayloadSchema,
- navigationRefsPayloadSchema,
-} from "./output"
-import { getConnections, mapMenuItems } from "./utils"
-
-const meter = metrics.getMeter("trpc.navigationMyPages")
-const getNavigationMyPagesRefsCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.refs.get"
-)
-const getNavigationMyPagesRefsSuccessCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.refs.get-success"
-)
-const getNavigationMyPagesRefsFailCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.refs.get-fail"
-)
-const getNavigationMyPagesCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.get"
-)
-const getNavigationMyPagesSuccessCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.get-success"
-)
-const getNavigationMyPagesFailCounter = meter.createCounter(
- "trpc.contentstack.navigationMyPages.get-fail"
-)
-
-export const navigationQueryRouter = router({
- get: contentstackBaseProcedure.query(async function ({ ctx }) {
- const { lang } = ctx
- getNavigationMyPagesRefsCounter.add(1, { lang })
- console.info(
- "contentstack.myPages.navigation.refs start",
- JSON.stringify({ query: { lang } })
- )
- const refsResponse = await request(
- GetNavigationMyPagesRefs,
- { locale: lang },
- {
- cache: "force-cache",
- next: {
- tags: [generateRefsResponseTag(lang, "navigation_my_pages")],
- },
- }
- )
-
- if (!refsResponse.data) {
- const notFoundError = notFound(refsResponse)
- getNavigationMyPagesRefsFailCounter.add(1, {
- lang,
- error_type: "not_found",
- error: JSON.stringify({ code: notFoundError.code }),
- })
- console.error(
- "contentstack.myPages.navigation.refs not found error",
- JSON.stringify({
- query: {
- lang,
- },
- error: { code: notFoundError.code },
- })
- )
- throw notFoundError
- }
-
- const validatedMyPagesNavigationRefs =
- navigationRefsPayloadSchema.safeParse(refsResponse.data)
- if (!validatedMyPagesNavigationRefs.success) {
- getNavigationMyPagesRefsFailCounter.add(1, {
- lang,
- error_type: "validation_error",
- error: JSON.stringify(validatedMyPagesNavigationRefs.error),
- })
- console.error(
- "contentstack.myPages.navigation.refs validation error",
- JSON.stringify({
- query: {
- lang,
- },
- error: validatedMyPagesNavigationRefs.error,
- })
- )
- return null
- }
- getNavigationMyPagesRefsSuccessCounter.add(1, { lang })
- console.info(
- "contentstack.myPages.navigation.refs success",
- JSON.stringify({ query: { lang } })
- )
- const connections = getConnections(validatedMyPagesNavigationRefs.data)
-
- const tags = [
- generateTags(lang, connections),
- generateTag(
- lang,
- validatedMyPagesNavigationRefs.data.all_navigation_my_pages.items[0]
- .system.uid
- ),
- ].flat()
- getNavigationMyPagesCounter.add(1)
- console.info(
- "contentstack.myPages.navigation start",
- JSON.stringify({ query: { lang } })
- )
- const response = await request(
- GetNavigationMyPages,
- { locale: lang },
- {
- cache: "force-cache",
- next: { tags },
- }
- )
-
- if (!response.data) {
- const notFoundError = notFound(response)
- getNavigationMyPagesFailCounter.add(1, {
- lang,
- error_type: "not_found",
- error: JSON.stringify({ code: notFoundError.code }),
- })
- console.error("contentstack.myPages.navigation not found error", {
- query: {
- lang,
- },
- error: { code: notFoundError.code },
- })
- throw notFoundError
- }
-
- const validatedMyPagesNavigation = navigationPayloadSchema.safeParse(
- response.data
- )
- if (!validatedMyPagesNavigation.success) {
- getNavigationMyPagesFailCounter.add(1, {
- lang,
- error_type: "validation_error",
- error: JSON.stringify(validatedMyPagesNavigation.error),
- })
- console.error(
- "contentstack.myPages.navigation.payload validation error",
- JSON.stringify({
- query: { lang },
- error: validatedMyPagesNavigation.error,
- })
- )
- return null
- }
-
- const menuItem =
- validatedMyPagesNavigation.data.all_navigation_my_pages.items[0]
-
- const nav = {
- menuItems: mapMenuItems(menuItem.menu_items),
- title: menuItem.title,
- }
-
- const validatedNav = getNavigationSchema.safeParse(nav)
- if (!validatedNav.success) {
- getNavigationMyPagesFailCounter.add(1, {
- lang,
- error_type: "validation_error",
- error: JSON.stringify(validatedNav.error),
- })
- console.error(
- "contentstack.myPages.navigation validation error",
- JSON.stringify({
- query: { lang },
- error: validatedNav.error,
- })
- )
-
- console.error(validatedNav.error)
- return null
- }
- getNavigationMyPagesSuccessCounter.add(1, { lang })
- console.info(
- "contentstack.myPages.navigation success",
- JSON.stringify({ query: { lang } })
- )
- return validatedNav.data
- }),
-})
diff --git a/server/routers/contentstack/myPages/navigation/utils.ts b/server/routers/contentstack/myPages/navigation/utils.ts
deleted file mode 100644
index 6c6e1cdd5..000000000
--- a/server/routers/contentstack/myPages/navigation/utils.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { removeMultipleSlashes } from "@/utils/url"
-
-import { ContentEnum } from "@/types/enums/content"
-import type { Edges } from "@/types/requests/utils/edges"
-import type { NodeRefs } from "@/types/requests/utils/refs"
-import type { GetNavigationMyPagesRefsData, MenuItems } from "./output"
-
-export function getConnections(refs: GetNavigationMyPagesRefsData) {
- const connections: Edges[] = []
- refs.all_navigation_my_pages.items.forEach((ref) => {
- ref.menu_items.forEach((menuItem) => {
- menuItem.links.map((link) => {
- connections.push(link.page)
- })
- })
- })
-
- return connections
-}
-
-export function mapMenuItems(menuItems: MenuItems) {
- return menuItems.map((menuItem) => {
- return {
- ...menuItem,
- links: menuItem.links
- .filter((link) => {
- // If content is unpublished or in other way inaccessible in Contentstack
- // there will be no edges, filter out those links.
- return !!link.page.edges[0]
- })
- .map((link) => {
- const page = link.page.edges[0].node
- let originalUrl = undefined
- if (
- page.__typename === ContentEnum.blocks.ContentPage ||
- page.__typename === ContentEnum.blocks.LoyaltyPage
- ) {
- if (page.web.original_url) {
- originalUrl = page.web.original_url
- }
- }
- return {
- lang: page.system.locale,
- linkText: link.link_text ? link.link_text : page.title,
- uid: page.system.uid,
- url: removeMultipleSlashes(`/${page.system.locale}/${page.url}`),
- originalUrl,
- }
- }),
- }
- })
-}
diff --git a/server/routers/partners/sas/linkAccount.ts b/server/routers/partners/sas/linkAccount.ts
index f838dac52..d878a88c5 100644
--- a/server/routers/partners/sas/linkAccount.ts
+++ b/server/routers/partners/sas/linkAccount.ts
@@ -1,8 +1,9 @@
-import { setTimeout } from "timers/promises"
import { z } from "zod"
import { protectedProcedure } from "@/server/trpc"
+import { timeout } from "@/utils/timeout"
+
import { getSasToken } from "./getSasToken"
const outputSchema = z.object({
@@ -15,7 +16,7 @@ export const linkAccount = protectedProcedure
const sasAuthToken = getSasToken()
console.log("[SAS] link account")
- await setTimeout(1000)
+ await timeout(1000)
//TODO: Call actual API here
console.log("[SAS] link account done")
diff --git a/server/routers/partners/sas/performLevelUpgrade.ts b/server/routers/partners/sas/performLevelUpgrade.ts
index 5280a5d46..7e2a9f2e0 100644
--- a/server/routers/partners/sas/performLevelUpgrade.ts
+++ b/server/routers/partners/sas/performLevelUpgrade.ts
@@ -1,16 +1,17 @@
import { TRPCError } from "@trpc/server"
-import { setTimeout } from "timers/promises"
import { z } from "zod"
import { protectedProcedure } from "@/server/trpc"
+import { timeout } from "@/utils/timeout"
+
const outputSchema = z.object({})
export const performLevelUpgrade = protectedProcedure
.output(outputSchema)
.mutation(async function ({ ctx, input }) {
console.log("[SAS] perform upgrade")
- await setTimeout(1000)
+ await timeout(1000)
//TODO: Call actual API here
throw new TRPCError({
diff --git a/server/routers/partners/sas/unlinkAccount.ts b/server/routers/partners/sas/unlinkAccount.ts
index 6a4438734..12d0da187 100644
--- a/server/routers/partners/sas/unlinkAccount.ts
+++ b/server/routers/partners/sas/unlinkAccount.ts
@@ -1,9 +1,10 @@
import { TRPCError } from "@trpc/server"
-import { setTimeout } from "timers/promises"
import { z } from "zod"
import { protectedProcedure } from "@/server/trpc"
+import { timeout } from "@/utils/timeout"
+
const outputSchema = z.object({
// unlinked: z.boolean(),
})
@@ -12,7 +13,7 @@ export const unlinkAccount = protectedProcedure
.output(outputSchema)
.mutation(async function ({ ctx, input }) {
console.log("[SAS] unlink account")
- await setTimeout(1000)
+ await timeout(1000)
//TODO: Call actual API here
throw new TRPCError({
diff --git a/types/components/header/myPagesMenu.ts b/types/components/header/myPagesMenu.ts
deleted file mode 100644
index 054dc41df..000000000
--- a/types/components/header/myPagesMenu.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { navigationQueryRouter } from "@/server/routers/contentstack/myPages/navigation/query"
-
-import { FriendsMembership } from "@/utils/user"
-
-import type { User } from "@/types/user"
-import type { LoyaltyLevel } from "@/server/routers/contentstack/loyaltyLevel/output"
-
-type MyPagesNavigation = Awaited<
- ReturnType<(typeof navigationQueryRouter)["get"]>
->
-
-export interface MyPagesMenuProps {
- navigation: MyPagesNavigation
- user: Pick
- membership?: FriendsMembership | null
- membershipLevel: LoyaltyLevel | null
-}
-
-export interface MyPagesMenuContentProps extends MyPagesMenuProps {
- toggleOpenStateFn: () => void
-}
diff --git a/utils/timeout.ts b/utils/timeout.ts
new file mode 100644
index 000000000..9451938ca
--- /dev/null
+++ b/utils/timeout.ts
@@ -0,0 +1,3 @@
+export function timeout(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms))
+}