Merged in feature/hardcoded-mypages-links (pull request #1325)

Feature/hardcoded mypages links

* feat: wip use hardcoded links

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/hardcoded-mypages-links

* feat: use hardcoded links for my pages to support dynamic links

* cleanup

* code fixes

* refactor: restructure MyPagesMobileDropdown component for improved readability

* use util timeout function


Approved-by: Christian Andolf
Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2025-02-13 09:28:30 +00:00
parent eabe45b73c
commit 6f51130e48
30 changed files with 500 additions and 556 deletions

View File

@@ -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,

View File

@@ -1,7 +0,0 @@
import { router } from "@/server/trpc"
import { navigationRouter } from "./navigation"
export const myPagesRouter = router({
navigation: navigationRouter,
})

View File

@@ -1,5 +0,0 @@
import { mergeRouters } from "@/server/trpc"
import { navigationQueryRouter } from "./query"
export const navigationRouter = mergeRouters(navigationQueryRouter)

View File

@@ -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<typeof menuItems>
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<typeof navigationPayloadSchema>
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(),
})

View File

@@ -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<GetNavigationMyPagesRefsData>(
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<GetNavigationMyPagesData>(
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
}),
})

View File

@@ -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<NodeRefs>[] = []
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,
}
}),
}
})
}

View File

@@ -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")

View File

@@ -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({

View File

@@ -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({