feat(WEB-304): remaning UI from design system primitives

This commit is contained in:
Simon Emanuelsson
2024-06-07 10:36:23 +02:00
parent 6737970f54
commit 7c4b8401e9
228 changed files with 3516 additions and 3237 deletions

View File

@@ -188,24 +188,3 @@ export const validateAccountPageRefsSchema = z.object({
export type AccountPageRefsDataRaw = z.infer<
typeof validateAccountPageRefsSchema
>
export const validateLanguageSwitcherData = z.object({
en: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
da: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
de: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
fi: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
sv: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
no: z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable(),
})

View File

@@ -4,7 +4,7 @@ import {
} from "@/lib/graphql/Query/AccountPage.graphql"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackProcedure, router } from "@/server/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
generateRefsResponseTag,
@@ -27,7 +27,7 @@ import { Edges } from "@/types/requests/utils/edges"
import { RTEDocument } from "@/types/rte/node"
export const accountPageQueryRouter = router({
get: contentstackProcedure.query(async ({ ctx }) => {
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const refsResponse = await request<AccountPageRefsDataRaw>(

View File

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

View File

@@ -254,12 +254,3 @@ const validateFooterRefConfigSchema = z.object({
})
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
export const validateLanguageSwitcherData = z.object({
en: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
da: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
de: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
fi: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
sv: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
no: z.object({ url: z.string(), isExternal: z.boolean() }).optional(),
})

View File

@@ -0,0 +1,123 @@
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
import {
GetCurrentFooter,
GetCurrentFooterRef,
} from "@/lib/graphql/Query/CurrentFooter.graphql"
import {
GetCurrentHeader,
GetCurrentHeaderRef,
} from "@/lib/graphql/Query/CurrentHeader.graphql"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import {
type ContactConfigData,
FooterDataRaw,
FooterRefDataRaw,
HeaderData,
HeaderDataRaw,
HeaderRefDataRaw,
validateContactConfigSchema,
validateFooterConfigSchema,
validateHeaderConfigSchema,
} from "./output"
export const baseQueryRouter = router({
contact: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
const response = await request<ContactConfigData>(GetContactConfig, {
locale: lang,
})
if (!response.data) {
throw notFound(response)
}
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
response.data
)
if (!validatedContactConfigConfig.success) {
throw internalServerError(validatedContactConfigConfig.error)
}
return validatedContactConfigConfig.data.all_contact_config.items[0]
}),
header: contentstackBaseProcedure.query(async ({ ctx }) => {
const responseRef = await request<HeaderRefDataRaw>(GetCurrentHeaderRef, {
locale: ctx.lang,
})
const response = await request<HeaderDataRaw>(
GetCurrentHeader,
{ locale: ctx.lang },
{
next: {
tags: [
generateTag(
ctx.lang,
responseRef.data.all_current_header.items[0].system.uid
),
],
},
}
)
if (!response.data) {
throw notFound(response)
}
const validatedHeaderConfig = validateHeaderConfigSchema.safeParse(
response.data
)
if (!validatedHeaderConfig.success) {
throw internalServerError(validatedHeaderConfig.error)
}
const logo =
validatedHeaderConfig.data.all_current_header.items[0].logoConnection
.edges?.[0]?.node
return {
...validatedHeaderConfig.data.all_current_header.items[0],
logo,
} as HeaderData
}),
footer: contentstackBaseProcedure.query(async ({ ctx }) => {
const responseRef = await request<FooterRefDataRaw>(GetCurrentFooterRef, {
locale: ctx.lang,
})
const response = await request<FooterDataRaw>(
GetCurrentFooter,
{
locale: ctx.lang,
},
{
next: {
tags: [
generateTag(
ctx.lang,
responseRef.data.all_current_footer.items[0].system.uid
),
],
},
}
)
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
response.data
)
if (!validatedFooterConfig.success) {
throw internalServerError(validatedFooterConfig.error)
}
return validatedFooterConfig.data.all_current_footer.items[0]
}),
})

View File

@@ -48,10 +48,18 @@ export const validateMyPagesBreadcrumbsRefsContentstackSchema = z.object({
all_account_page: breadcrumbsRefsItems,
})
export type GetMyPagesBreadcrumbsRefsData = z.infer<
typeof validateMyPagesBreadcrumbsRefsContentstackSchema
>
export const validateLoyaltyPageBreadcrumbsRefsContentstackSchema = z.object({
all_loyalty_page: breadcrumbsRefsItems,
})
export type GetLoyaltyPageBreadcrumbsRefsData = z.infer<
typeof validateLoyaltyPageBreadcrumbsContentstackSchema
>
const page = z.object({
web: z.object({
breadcrumbs: z.object({
@@ -91,6 +99,14 @@ export const validateMyPagesBreadcrumbsContentstackSchema = z.object({
all_account_page: breadcrumbsItems,
})
export type GetMyPagesBreadcrumbsData = z.infer<
typeof validateMyPagesBreadcrumbsContentstackSchema
>
export const validateLoyaltyPageBreadcrumbsContentstackSchema = z.object({
all_loyalty_page: breadcrumbsItems,
})
export type GetLoyaltyPageBreadcrumbsData = z.infer<
typeof validateLoyaltyPageBreadcrumbsContentstackSchema
>

View File

@@ -7,9 +7,13 @@ import {
GetMyPagesBreadcrumbsRefs,
} from "@/lib/graphql/Query/BreadcrumbsMyPages.graphql"
import { internalServerError } from "@/server/errors/trpc"
import { contentstackProcedure, router } from "@/server/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
type GetLoyaltyPageBreadcrumbsData,
type GetLoyaltyPageBreadcrumbsRefsData,
type GetMyPagesBreadcrumbsData,
type GetMyPagesBreadcrumbsRefsData,
validateLoyaltyPageBreadcrumbsContentstackSchema,
validateLoyaltyPageBreadcrumbsRefsContentstackSchema,
validateMyPagesBreadcrumbsContentstackSchema,
@@ -23,12 +27,6 @@ import {
Variables,
} from "./utils"
import type {
GetLoyaltyPageBreadcrumbsData,
GetLoyaltyPageBreadcrumbsRefsData,
GetMyPagesBreadcrumbsData,
GetMyPagesBreadcrumbsRefsData,
} from "@/types/requests/myPages/breadcrumbs"
import { PageTypeEnum } from "@/types/requests/pageType"
async function getLoyaltyPageBreadcrumbs(variables: Variables) {
@@ -111,7 +109,7 @@ async function getMyPagesBreadcrumbs(variables: Variables) {
}
export const breadcrumbsQueryRouter = router({
get: contentstackProcedure.query(async ({ ctx }) => {
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const variables = {
locale: ctx.lang,
url: ctx.pathname,

View File

@@ -9,13 +9,12 @@ import {
} from "@/utils/generateTag"
import { removeMultipleSlashes } from "@/utils/url"
import { BreadcrumbsRefsItems, getBreadcrumbsSchema, Page } from "./output"
import { type BreadcrumbsRefsItems, getBreadcrumbsSchema, Page } from "./output"
import type { GetBreadcrumbsItems } from "@/types/requests/myPages/breadcrumbs"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
export function getConnections(refs: GetBreadcrumbsItems) {
export function getConnections(refs: BreadcrumbsRefsItems) {
const connections: Edges<NodeRefs>[] = []
refs.items.forEach((ref) => {

View File

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

View File

@@ -1,258 +0,0 @@
import { Lang } from "@/constants/languages"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetDaDeEnUrlsAccountPage,
GetFiNoSvUrlsAccountPage,
} from "@/lib/graphql/Query/AccountPage.graphql"
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
import {
GetCurrentFooter,
GetCurrentFooterRef,
} from "@/lib/graphql/Query/CurrentFooter.graphql"
import {
GetCurrentHeader,
GetCurrentHeaderRef,
} from "@/lib/graphql/Query/CurrentHeader.graphql"
import {
GetDaDeEnUrlsCurrentBlocksPage,
GetFiNoSvUrlsCurrentBlocksPage,
} from "@/lib/graphql/Query/LanguageSwitcherCurrent.graphql"
import {
GetDaDeEnUrlsLoyaltyPage,
GetFiNoSvUrlsLoyaltyPage,
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackProcedure, publicProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import {
type ContactConfigData,
FooterDataRaw,
FooterRefDataRaw,
HeaderData,
HeaderDataRaw,
HeaderRefDataRaw,
validateContactConfigSchema,
validateFooterConfigSchema,
validateHeaderConfigSchema,
validateLanguageSwitcherData,
} from "./output"
import { languageSwitcherAffix } from "./utils"
import {
LanguageSwitcherData,
LanguageSwitcherQueryDataRaw,
} from "@/types/requests/languageSwitcher"
import { PageTypeEnum } from "@/types/requests/pageType"
export const configQueryRouter = router({
contact: contentstackProcedure.query(async ({ ctx }) => {
const { lang } = ctx
const response = await request<ContactConfigData>(GetContactConfig, {
locale: lang,
})
if (!response.data) {
throw notFound(response)
}
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
response.data
)
if (!validatedContactConfigConfig.success) {
throw internalServerError(validatedContactConfigConfig.error)
}
return validatedContactConfigConfig.data.all_contact_config.items[0]
}),
header: publicProcedure.query(async ({ ctx }) => {
const responseRef = await request<HeaderRefDataRaw>(GetCurrentHeaderRef, {
locale: ctx.lang,
})
const response = await request<HeaderDataRaw>(
GetCurrentHeader,
{ locale: ctx.lang },
{
next: {
tags: [
generateTag(
ctx.lang,
responseRef.data.all_current_header.items[0].system.uid
),
],
},
}
)
if (!response.data) {
throw notFound(response)
}
const validatedHeaderConfig = validateHeaderConfigSchema.safeParse(
response.data
)
if (!validatedHeaderConfig.success) {
throw internalServerError(validatedHeaderConfig.error)
}
const logo =
validatedHeaderConfig.data.all_current_header.items[0].logoConnection
.edges?.[0]?.node
return {
...validatedHeaderConfig.data.all_current_header.items[0],
logo,
} as HeaderData
}),
footer: contentstackProcedure.query(async ({ ctx }) => {
const responseRef = await request<FooterRefDataRaw>(GetCurrentFooterRef, {
locale: ctx.lang,
})
const response = await request<FooterDataRaw>(
GetCurrentFooter,
{
locale: ctx.lang,
},
{
next: {
tags: [
generateTag(
ctx.lang,
responseRef.data.all_current_footer.items[0].system.uid
),
],
},
}
)
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
response.data
)
if (!validatedFooterConfig.success) {
throw internalServerError(validatedFooterConfig.error)
}
return validatedFooterConfig.data.all_current_footer.items[0]
}),
languageSwitcher: contentstackProcedure.query(async ({ ctx }) => {
const variables = { uid: ctx.uid, locale: ctx.lang }
let urls: LanguageSwitcherData
const tagsDaDeEn = [
generateTag(Lang.da, ctx.uid, languageSwitcherAffix),
generateTag(Lang.de, ctx.uid, languageSwitcherAffix),
generateTag(Lang.en, ctx.uid, languageSwitcherAffix),
]
const tagsFiNoSv = [
generateTag(Lang.fi, ctx.uid, languageSwitcherAffix),
generateTag(Lang.no, ctx.uid, languageSwitcherAffix),
generateTag(Lang.sv, ctx.uid, languageSwitcherAffix),
]
switch (ctx.contentType) {
case PageTypeEnum.accountPage:
const accountPageRes = await batchRequest<LanguageSwitcherQueryDataRaw>(
[
{
document: GetDaDeEnUrlsAccountPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsAccountPage,
variables,
tags: tagsFiNoSv,
},
]
)
const accountPageUrls = Object.keys(
accountPageRes.data
).reduce<LanguageSwitcherData>((acc, key) => {
const item = accountPageRes.data[key as Lang]?.items?.[0]
const url = item
? { url: `/${key}${item.url}`, isExternal: false }
: undefined
return { ...acc, [key]: url }
}, {} as LanguageSwitcherData)
const validatedAccountLanguageSwitcherData =
validateLanguageSwitcherData.safeParse(accountPageUrls)
if (!validatedAccountLanguageSwitcherData.success) {
throw internalServerError(validatedAccountLanguageSwitcherData.error)
}
urls = validatedAccountLanguageSwitcherData.data
break
case PageTypeEnum.loyaltyPage:
const loyaltyPageRes = await batchRequest<LanguageSwitcherQueryDataRaw>(
[
{
document: GetDaDeEnUrlsLoyaltyPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsLoyaltyPage,
variables,
tags: tagsFiNoSv,
},
]
)
const loyaltyPageUrls = Object.keys(
loyaltyPageRes.data
).reduce<LanguageSwitcherData>((acc, key) => {
const item = loyaltyPageRes.data[key as Lang]?.items?.[0]
const url = item
? {
url: item.web?.original_url || `/${key}${item.url}`,
isExternal: !!item?.web?.original_url,
}
: undefined
return {
...acc,
[key]: url,
}
}, {} as LanguageSwitcherData)
const validatedLoyaltyLanguageSwitcherData =
validateLanguageSwitcherData.safeParse(loyaltyPageUrls)
if (!validatedLoyaltyLanguageSwitcherData.success) {
throw internalServerError(validatedLoyaltyLanguageSwitcherData.error)
}
urls = validatedLoyaltyLanguageSwitcherData.data
break
case PageTypeEnum.currentBlocksPage:
const { data } = await batchRequest<LanguageSwitcherData>([
{
document: GetDaDeEnUrlsCurrentBlocksPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsCurrentBlocksPage,
variables,
tags: tagsFiNoSv,
},
])
urls = data
break
default:
urls = [] as unknown as LanguageSwitcherData
}
return { urls, lang: ctx.lang }
}),
})

View File

@@ -1,15 +1,17 @@
import { router } from "@/server/trpc"
import { accountPageRouter } from "./accountPage"
import { baseRouter } from "./base"
import { breadcrumbsRouter } from "./breadcrumbs"
import { configRouter } from "./config"
import { languageSwitcherRouter } from "./languageSwitcher"
import { loyaltyPageRouter } from "./loyaltyPage"
import { myPagesRouter } from "./myPages"
export const contentstackRouter = router({
breadcrumbs: breadcrumbsRouter,
accountPage: accountPageRouter,
config: configRouter,
base: baseRouter,
breadcrumbs: breadcrumbsRouter,
languageSwitcher: languageSwitcherRouter,
loyaltyPage: loyaltyPageRouter,
myPages: myPagesRouter,
})

View File

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

View File

@@ -0,0 +1,14 @@
import { z } from "zod"
const link = z
.object({ url: z.string().optional(), isExternal: z.boolean() })
.nullable()
export const validateLanguageSwitcherData = z.object({
da: link,
de: link,
en: link,
fi: link,
no: link,
sv: link,
})

View File

@@ -0,0 +1,127 @@
import { Lang } from "@/constants/languages"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetDaDeEnUrlsAccountPage,
GetFiNoSvUrlsAccountPage,
} from "@/lib/graphql/Query/AccountPage.graphql"
import {
GetDaDeEnUrlsCurrentBlocksPage,
GetFiNoSvUrlsCurrentBlocksPage,
} from "@/lib/graphql/Query/LanguageSwitcherCurrent.graphql"
import {
GetDaDeEnUrlsLoyaltyPage,
GetFiNoSvUrlsLoyaltyPage,
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
import { internalServerError } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { validateLanguageSwitcherData } from "./output"
import { languageSwitcherAffix } from "./utils"
import type {
LanguageSwitcherData,
LanguageSwitcherQueryDataRaw,
} from "@/types/requests/languageSwitcher"
import { PageTypeEnum } from "@/types/requests/pageType"
interface LanguageSwitcherVariables {
contentType: string
uid: string
}
async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
const variables = { uid: options.uid }
const tagsDaDeEn = [
generateTag(Lang.da, options.uid, languageSwitcherAffix),
generateTag(Lang.de, options.uid, languageSwitcherAffix),
generateTag(Lang.en, options.uid, languageSwitcherAffix),
]
const tagsFiNoSv = [
generateTag(Lang.fi, options.uid, languageSwitcherAffix),
generateTag(Lang.no, options.uid, languageSwitcherAffix),
generateTag(Lang.sv, options.uid, languageSwitcherAffix),
]
switch (options.contentType) {
case PageTypeEnum.accountPage:
return await batchRequest<LanguageSwitcherQueryDataRaw>([
{
document: GetDaDeEnUrlsAccountPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsAccountPage,
variables,
tags: tagsFiNoSv,
},
])
case PageTypeEnum.currentBlocksPage:
return await batchRequest<LanguageSwitcherQueryDataRaw>([
{
document: GetDaDeEnUrlsCurrentBlocksPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsCurrentBlocksPage,
variables,
tags: tagsFiNoSv,
},
])
case PageTypeEnum.loyaltyPage:
return await batchRequest<LanguageSwitcherQueryDataRaw>([
{
document: GetDaDeEnUrlsLoyaltyPage,
variables,
tags: tagsDaDeEn,
},
{
document: GetFiNoSvUrlsLoyaltyPage,
variables,
tags: tagsFiNoSv,
},
])
default:
console.info(`type: [${options.contentType}]`)
console.error(`Trying to get a content type that is not supported`)
throw internalServerError()
}
}
export const languageSwitcherQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const res = await getLanguageSwitcher({
contentType: ctx.contentType!,
uid: ctx.uid,
})
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
(acc, key) => {
const item = res.data[key as Lang]?.items[0]
const url = item
? item.web?.original_url || `/${key}${item.url}`
: undefined
return {
...acc,
[key]: { url, isExternal: !!item?.web?.original_url },
}
},
{} as LanguageSwitcherData
)
const validatedLanguageSwitcherData =
validateLanguageSwitcherData.safeParse(urls)
if (!validatedLanguageSwitcherData.success) {
throw internalServerError(validatedLanguageSwitcherData.error)
}
return {
lang: ctx.lang,
urls,
}
}),
})

View File

@@ -23,9 +23,8 @@ const loyaltyPageDynamicContent = z.object({
component: z.nativeEnum(LoyaltyComponentEnum),
link: z
.object({
text: z.string().nullable(),
text: z.string(),
href: z.string(),
title: z.string(),
})
.optional(),
}),

View File

@@ -4,7 +4,7 @@ import {
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackProcedure, router } from "@/server/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
generateRefsResponseTag,
@@ -35,16 +35,16 @@ function makeButtonObject(button: any) {
href:
button.is_contentstack_link && button.linkConnection.edges.length
? button.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
)
removeMultipleSlashes(
`/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
)
: button.external_link.href,
isExternal: !button.is_contentstack_link,
}
}
export const loyaltyPageQueryRouter = router({
get: contentstackProcedure.query(async ({ ctx }) => {
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const refsResponse = await request<LoyaltyPageRefsDataRaw>(
@@ -99,66 +99,66 @@ export const loyaltyPageQueryRouter = router({
const blocks = response.data.loyalty_page.blocks
? response.data.loyalty_page.blocks.map((block: any) => {
switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
return {
...block,
dynamic_content: {
...block.dynamic_content,
link: block.dynamic_content.link.pageConnection.edges.length
? {
text: block.dynamic_content.link.text,
href: removeMultipleSlashes(
`/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}/${block.dynamic_content.link.pageConnection.edges[0].node.url}`
),
title:
block.dynamic_content.link.pageConnection.edges[0]
.node.title,
}
: undefined,
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
return {
...block,
shortcuts: {
...block.shortcuts,
shortcuts: block.shortcuts.shortcuts.map((shortcut: any) => ({
text: shortcut.text,
openInNewTab: shortcut.open_in_new_tab,
...shortcut.linkConnection.edges[0].node,
url:
shortcut.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${shortcut.linkConnection.edges[0].node.system.locale}/${shortcut.linkConnection.edges[0].node.url}`
),
})),
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }: { node: any }) => {
return {
...card,
primaryButton: card.has_primary_button
? makeButtonObject(card.primary_button)
: undefined,
secondaryButton: card.has_secondary_button
? makeButtonObject(card.secondary_button)
: undefined,
switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent:
return {
...block,
dynamic_content: {
...block.dynamic_content,
link: block.dynamic_content.link.pageConnection.edges.length
? {
text: block.dynamic_content.link.text,
href: removeMultipleSlashes(
`/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}/${block.dynamic_content.link.pageConnection.edges[0].node.url}`
),
title:
block.dynamic_content.link.pageConnection.edges[0]
.node.title,
}
: undefined,
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
return {
...block,
shortcuts: {
...block.shortcuts,
shortcuts: block.shortcuts.shortcuts.map((shortcut: any) => ({
text: shortcut.text,
openInNewTab: shortcut.open_in_new_tab,
...shortcut.linkConnection.edges[0].node,
url:
shortcut.linkConnection.edges[0].node.web?.original_url ||
removeMultipleSlashes(
`/${shortcut.linkConnection.edges[0].node.system.locale}/${shortcut.linkConnection.edges[0].node.url}`
),
})),
},
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return {
...block,
cards_grid: {
...block.cards_grid,
cards: block.cards_grid.cardConnection.edges.map(
({ node: card }: { node: any }) => {
return {
...card,
primaryButton: card.has_primary_button
? makeButtonObject(card.primary_button)
: undefined,
secondaryButton: card.has_secondary_button
? makeButtonObject(card.secondary_button)
: undefined,
}
}
}
),
},
}
default:
return block
}
})
),
},
}
default:
return block
}
})
: null
const loyaltyPage = {

View File

@@ -4,18 +4,47 @@ import { Lang } from "@/constants/languages"
import { PageLinkEnum } from "@/types/requests/pageLinks"
const node = z.object({
system: z.object({
locale: z.nativeEnum(Lang),
uid: z.string(),
}),
title: z.string(),
url: z.string(),
})
const web = z.object({
original_url: z.string().optional(),
})
const accountPageLink = z
.object({
__typename: z.literal(PageLinkEnum.AccountPage),
})
.merge(node)
const contentPageLink = z
.object({
__typename: z.literal(PageLinkEnum.ContentPage),
web,
})
.merge(node)
const loyaltyPageLink = z
.object({
__typename: z.literal(PageLinkEnum.LoyaltyPage),
web,
})
.merge(node)
const pageConnection = z.object({
edges: z.array(
z.object({
node: z.object({
__typename: z.nativeEnum(PageLinkEnum),
system: z.object({
locale: z.nativeEnum(Lang),
uid: z.string(),
}),
title: z.string(),
url: z.string(),
}),
node: z.discriminatedUnion("__typename", [
accountPageLink,
contentPageLink,
loyaltyPageLink,
]),
})
),
})
@@ -36,78 +65,58 @@ const pageConnectionRefs = z.object({
export const navigationRefsPayloadSchema = z.object({
all_navigation_my_pages: z.object({
items: z
.array(
z.object({
items: z.array(
z.object({
__typename: z.string(),
item: z.object({
sub_items: z.array(
z.object({
__typename: z.string(),
item: z.object({
pageConnection: pageConnectionRefs,
}),
})
),
pageConnection: pageConnectionRefs,
}),
})
),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
)
.refine(
(input) => {
return input.length === 1
},
{
message: `Expected all_navigation_my_pages items to only contain 1 in navigationRefsPayloadSchema`,
}
),
items: z.array(
z.object({
menu_items: z.array(
z.object({
links: z.array(
z.object({
page: pageConnectionRefs,
})
),
})
),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
),
}),
})
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({
items: z.array(
z.object({
item: z.object({
link_text: z.string().default(""),
pageConnection,
sub_items: z.array(
z.object({
item: z.object({
link_text: z.string().default(""),
pageConnection,
}),
})
),
}),
})
),
title: z.string(),
})
)
.refine(
(input) => {
return input.length === 1
},
{
message: `Expected all_navigation_my_pages items to only contain 1 in navigationPayloadSchema`,
}
),
items: z.array(
z.object({
menu_items: menuItems,
title: z.string(),
})
),
}),
})
const baseMenuItem = z.object({
export type GetNavigationMyPagesData = z.infer<typeof navigationPayloadSchema>
const link = z.object({
lang: z.nativeEnum(Lang),
linkText: z.string(),
uid: z.string(),
@@ -116,12 +125,11 @@ const baseMenuItem = z.object({
})
export const getNavigationSchema = z.object({
items: z.array(
z
.object({
subItems: z.array(baseMenuItem),
})
.merge(baseMenuItem)
menuItems: z.array(
z.object({
display_sign_out_link: z.boolean(),
links: z.array(link),
})
),
title: z.string(),
})

View File

@@ -4,7 +4,7 @@ import {
} from "@/lib/graphql/Query/NavigationMyPages.graphql"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackProcedure, publicProcedure, router } from "@/server/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import {
generateRefsResponseTag,
@@ -14,40 +14,46 @@ import {
import { removeMultipleSlashes } from "@/utils/url"
import {
type GetNavigationMyPagesData,
type GetNavigationMyPagesRefsData,
getNavigationSchema,
type MenuItems,
navigationPayloadSchema,
navigationRefsPayloadSchema,
} from "./output"
import { getConnections } from "./utils"
import type {
GetNavigationMyPagesData,
GetNavigationMyPagesRefsData,
MenuItem,
NavigationItem,
} from "@/types/requests/myPages/navigation"
import { PageLinkEnum } from "@/types/requests/pageLinks"
export function mapMenuItems(navigationItems: NavigationItem[]) {
return navigationItems.map(({ item }): MenuItem => {
const { node } = item.pageConnection.edges[0]
const menuItem: MenuItem = {
lang: node.system.locale,
linkText: item.link_text || node.title,
uid: node.system.uid,
url: removeMultipleSlashes(`/${node.system.locale}/${node.url}`),
export function mapMenuItems(menuItems: MenuItems) {
return menuItems.map((menuItem) => {
return {
...menuItem,
links: menuItem.links.map((link) => {
const page = link.page.edges[0].node
let originalUrl = undefined
if (
page.__typename === PageLinkEnum.ContentPage ||
page.__typename === PageLinkEnum.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,
}
}),
}
if ("sub_items" in item) {
menuItem.subItems = mapMenuItems(item.sub_items)
}
return menuItem
})
}
export const navigationQueryRouter = router({
get: contentstackProcedure.query(async function ({ ctx }) {
get: contentstackBaseProcedure.query(async function ({ ctx }) {
const { lang } = ctx
const refsResponse = await request<GetNavigationMyPagesRefsData>(
@@ -102,7 +108,7 @@ export const navigationQueryRouter = router({
validatedMyPagesNavigation.data.all_navigation_my_pages.items[0]
const nav = {
items: mapMenuItems(menuItem.items),
menuItems: mapMenuItems(menuItem.menu_items),
title: menuItem.title,
}

View File

@@ -1,15 +1,13 @@
import type { GetNavigationMyPagesRefsData } from "@/types/requests/myPages/navigation"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
import type { GetNavigationMyPagesRefsData } from "./output"
export function getConnections(refs: GetNavigationMyPagesRefsData) {
const connections: Edges<NodeRefs>[] = []
refs.all_navigation_my_pages.items.forEach((ref) => {
ref.items.forEach(({ item }) => {
connections.push(item.pageConnection)
item.sub_items.forEach(({ item: subItem }) => {
connections.push(subItem.pageConnection)
ref.menu_items.forEach((menuItem) => {
menuItem.links.map((link) => {
connections.push(link.page)
})
})
})

View File

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

View File

@@ -1,28 +0,0 @@
import { protectedProcedure, publicProcedure, router } from "@/server/trpc"
import { allLevels } from "./temp"
function fakingRequest<T>(payload: T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(payload)
}, 1500)
})
}
export const lotaltyQueryRouter = router({
levels: router({
all: publicProcedure.query(async function ({ ctx }) {
// TODO: Make request to get user data from Scandic API
return await fakingRequest<typeof allLevels>(allLevels)
}),
current: protectedProcedure.query(async function (opts) {
// TODO: Make request to get user data from Scandic API
return await fakingRequest<(typeof allLevels)[number]>(allLevels[1])
}),
next: protectedProcedure.query(async function (opts) {
// TODO: Make request to get user data from Scandic API
return await fakingRequest<(typeof allLevels)[number]>(allLevels[2])
}),
}),
})

View File

@@ -1,86 +0,0 @@
export const allLevels = [
{
tier: 1,
name: "New Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/new-friend.svg",
},
{
tier: 2,
name: "Good Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/good-friend.svg",
},
{
tier: 3,
name: "Close Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/close-friend.svg",
},
{
tier: 4,
name: "Dear Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/dear-friend.svg",
},
{
tier: 5,
name: "Loyal Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/loyal-friend.svg",
},
{
tier: 6,
name: "True Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/true-friend.svg",
},
{
tier: 7,
name: "Best Friend",
requiredPoints: 50000,
requiredNights: "X",
benefits: [
"15% on food on weekends",
"Always best price",
"Book reward nights with points",
],
logo: "/_static/icons/loyaltyLevels/best-friend.svg",
},
]