feat(SW-186): Added refs and some extra querys

This commit is contained in:
Erik Tiekstra
2024-09-05 09:25:21 +02:00
parent 52fdc1daac
commit 55fdbc527b
11 changed files with 388 additions and 303 deletions

View File

@@ -13,16 +13,12 @@ import type { NavigationMenuItemProps } from "@/types/components/header/navigati
export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
const { submenu, title, link, seeAllLink, card } = item
const [isExpanded, setIsExpanded] = useState(false)
const [isExpanded, setIsExpanded] = useState(false) // TODO: Use store to manage this state when adding the menu itself.
function handleButtonClick() {
setIsExpanded((prev) => !prev)
}
if (!submenu.length && !link) {
return null
}
return submenu.length ? (
<MainMenuButton
onClick={handleButtonClick}

View File

@@ -46,7 +46,7 @@ export default async function MainMenu({
/>
</NextLink>
<div className={styles.menus}>
{menuItems ? (
{menuItems?.length ? (
<NavigationMenu items={menuItems} isMobile={false} />
) : null}
{user ? (
@@ -74,7 +74,7 @@ export default async function MainMenu({
</span>
</Link>
)}
{menuItems ? (
{menuItems?.length ? (
<MobileMenu languageUrls={languageUrls} menuItems={menuItems} />
) : null}
</div>
@@ -82,26 +82,3 @@ export default async function MainMenu({
</div>
)
}
const error = {
query: { lang: "sv" },
error: {
issues: [
{
code: "invalid_type",
expected: "string",
received: "null",
path: ["all_header", "items", 0, "top_link", "title"],
message: "Expected string, received null",
},
{
code: "invalid_type",
expected: "array",
received: "null",
path: ["all_header", "items", 0, "menu_items"],
message: "Expected array, received null",
},
],
name: "ZodError",
},
}

View File

@@ -1,16 +1,13 @@
.topMenu {
display: none;
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Spacing-x2) var(--Spacing-x-one-and-half);
padding: var(--Spacing-x2);
border-bottom: 1px solid var(--Base-Border-Subtle);
}
.content {
max-width: var(--max-width-navigation);
margin: 0 auto;
display: grid;
justify-content: space-between;
gap: var(--Spacing-x3);
}
.options {
@@ -24,7 +21,10 @@
display: block;
}
.content {
display: grid;
grid-template-areas: "topLink options";
justify-content: space-between;
gap: var(--Spacing-x3);
}
.topLink {

View File

@@ -1,22 +0,0 @@
#import "../PageLink/ContentPageLink.graphql"
#import "../PageLink/LoyaltyPageLink.graphql"
fragment InternalOrExternalLink on InternalOrExternalLink {
is_external_link
open_in_new_tab
external_link {
href
title
}
page_link {
linkConnection {
edges {
node {
...ContentPageLink
...LoyaltyPageLink
}
}
}
link_title
}
}

View File

@@ -5,4 +5,8 @@ fragment HotelPageLink on HotelPage {
}
title
url
# TODO: Might need to add this if this is needed for hotel pages.
# web {
# original_url
# }
}

View File

@@ -0,0 +1,48 @@
#import "../System.graphql"
fragment HotelPageBreadcrumbsRefs on HotelPage {
web {
breadcrumbs {
title
parentsConnection {
edges {
node {
... on ContentPage {
web {
breadcrumbs {
title
}
}
system {
...System
}
}
... on HotelPage {
web {
breadcrumbs {
title
}
}
system {
...System
}
}
... on LoyaltyPage {
web {
breadcrumbs {
title
}
}
system {
...System
}
}
}
}
}
}
}
system {
...System
}
}

View File

@@ -0,0 +1,7 @@
#import "../System.graphql"
fragment HotelPageRef on HotelPage {
system {
...System
}
}

View File

@@ -1,11 +1,17 @@
#import "../Fragments/Refs/System.graphql"
#import "../Fragments/Header/InternalOrExternalLink.graphql"
#import "../Fragments/PageLink/ContentPageLink.graphql"
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
#import "../Fragments/PageLink/AccountPageLink.graphql"
#import "../Fragments/PageLink/ContentPageLink.graphql"
#import "../Fragments/PageLink/HotelPageLink.graphql"
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
#import "../Fragments/Blocks/Card.graphql"
#import "../Fragments/Blocks/Refs/Card.graphql"
#import "../Fragments/Refs/ContentPage/ContentPage.graphql"
#import "../Fragments/Refs/HotelPage/HotelPage.graphql"
#import "../Fragments/Refs/LoyaltyPage/LoyaltyPage.graphql"
#import "../Fragments/Refs/MyPages/AccountPage.graphql"
query GetHeader($locale: String!) {
all_header(limit: 1, locale: $locale) {
items {
@@ -15,6 +21,7 @@ query GetHeader($locale: String!) {
edges {
node {
...ContentPageLink
...HotelPageLink
...LoyaltyPageLink
}
}
@@ -26,6 +33,7 @@ query GetHeader($locale: String!) {
edges {
node {
...ContentPageLink
...HotelPageLink
...LoyaltyPageLink
}
}
@@ -36,6 +44,7 @@ query GetHeader($locale: String!) {
edges {
node {
...ContentPageLink
...HotelPageLink
...LoyaltyPageLink
}
}
@@ -49,6 +58,7 @@ query GetHeader($locale: String!) {
edges {
node {
...ContentPageLink
...HotelPageLink
...LoyaltyPageLink
}
}
@@ -70,6 +80,59 @@ query GetHeader($locale: String!) {
query GetHeaderRef($locale: String!) {
all_header(limit: 1, locale: $locale) {
items {
top_link {
linkConnection {
edges {
node {
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
menu_items {
linkConnection {
edges {
node {
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
see_all_link {
linkConnection {
edges {
node {
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
submenu {
links {
linkConnection {
edges {
node {
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
}
cardConnection {
edges {
node {
...CardBlockRef
}
}
}
}
system {
...System
}

View File

@@ -268,24 +268,26 @@ export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
const linkConnectionNodeSchema = z
.object({
edges: z.array(
z.object({
node: z.object({
system: z.object({
uid: z.string(),
locale: z.nativeEnum(Lang),
edges: z
.array(
z.object({
node: z.object({
system: z.object({
uid: z.string(),
locale: z.nativeEnum(Lang),
}),
url: z.string(),
title: z.string(),
web: z.object({
original_url: z.string(),
}),
}),
url: z.string(),
title: z.string(),
web: z.object({
original_url: z.string().nullable().optional(),
}),
}),
})
),
})
)
.max(1),
})
.transform((rawData) => {
const node = rawData.edges[0]?.node
.transform((data) => {
const node = data.edges[0]?.node
if (!node) {
return null
}
@@ -301,15 +303,14 @@ const linkConnectionNodeSchema = z
const linkWithTitleSchema = z
.object({
title: z.string().nullable(),
title: z.string(),
linkConnection: linkConnectionNodeSchema,
})
.transform((rawData) => {
return rawData.linkConnection && rawData.title
? {
...rawData.linkConnection,
title: rawData.title,
href: rawData.linkConnection.href,
isExternal: rawData.linkConnection.isExternal,
}
: null
})
@@ -325,21 +326,18 @@ const cardButtonSchema = z
linkConnection: linkConnectionNodeSchema,
open_in_new_tab: z.boolean(),
})
.transform((rawData) => {
if (!rawData) {
return null
}
const linkConnectionData = rawData.linkConnection
const isContentstackLink = rawData.is_contentstack_link
const externalLink = rawData.external_link
.transform((data) => {
const linkConnectionData = data.linkConnection
const isContentstackLink = data.is_contentstack_link
const externalLink = data.external_link
const href =
isContentstackLink && externalLink.href
? externalLink.href
: linkConnectionData?.href || ""
return {
openInNewTab: rawData.open_in_new_tab,
title: rawData.cta_text,
openInNewTab: data.open_in_new_tab,
title: data.cta_text,
href,
isExternal: !isContentstackLink || linkConnectionData?.isExternal,
}
@@ -347,23 +345,25 @@ const cardButtonSchema = z
const cardConnectionSchema = z
.object({
edges: z.array(
z.object({
node: z.object({
heading: z.string(),
body_text: z.string(),
background_image: imageVaultAssetTransformedSchema,
has_primary_button: z.boolean(),
has_secondary_button: z.boolean(),
scripted_top_title: z.string(),
primary_button: cardButtonSchema,
secondary_button: cardButtonSchema,
}),
})
),
edges: z
.array(
z.object({
node: z.object({
heading: z.string(),
body_text: z.string(),
background_image: imageVaultAssetTransformedSchema,
has_primary_button: z.boolean(),
has_secondary_button: z.boolean(),
scripted_top_title: z.string(),
primary_button: cardButtonSchema.nullable(),
secondary_button: cardButtonSchema.nullable(),
}),
})
)
.max(1),
})
.transform((rawData) => {
const node = rawData.edges[0]?.node
.transform((data) => {
const node = data.edges[0]?.node
if (!node) {
return null
}
@@ -391,31 +391,33 @@ export const menuItemSchema = z
see_all_link: linkWithTitleSchema,
cardConnection: cardConnectionSchema,
})
.transform(
({ submenu, linkConnection, cardConnection, see_all_link, title }) => {
return {
title,
link: submenu.length ? null : linkConnection,
seeAllLink: submenu.length ? see_all_link : null,
submenu,
card: cardConnection,
}
.transform((data) => {
const { submenu, linkConnection, cardConnection, see_all_link, title } =
data
return {
title,
link: submenu.length ? null : linkConnection,
seeAllLink: submenu.length ? see_all_link : null,
submenu,
card: cardConnection,
}
)
})
export const getHeaderSchema = z
.object({
all_header: z.object({
items: z.array(
z.object({
top_link: linkWithTitleSchema.nullable(),
menu_items: z.array(menuItemSchema).nullable(),
})
),
items: z
.array(
z.object({
top_link: linkWithTitleSchema,
menu_items: z.array(menuItemSchema),
})
)
.length(1),
}),
})
.transform((rawData) => {
const { top_link, menu_items } = rawData.all_header.items[0]
.transform((data) => {
const { top_link, menu_items } = data.all_header.items[0]
return {
topLink: top_link,
@@ -423,15 +425,78 @@ export const getHeaderSchema = z
}
})
export const getHeaderRefSchema = z.object({
all_header: z.object({
items: z.array(
const linkConnectionRefs = z.object({
edges: z
.array(
z.object({
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
node: z.object({
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
),
)
.max(1),
})
const cardConnectionRefs = z.object({
primary_button: z
.object({
linkConnection: linkConnectionRefs,
})
.nullable(),
secondary_button: z
.object({
linkConnection: linkConnectionRefs,
})
.nullable(),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
export const getHeaderRefSchema = z.object({
all_header: z.object({
items: z
.array(
z.object({
top_link: z
.object({
linkConnection: linkConnectionRefs,
})
.nullable(),
menu_items: z.array(
z.object({
linkConnection: linkConnectionRefs,
see_all_link: z.object({
linkConnection: linkConnectionRefs,
}),
cardConnection: z.object({
edges: z
.array(
z.object({
node: cardConnectionRefs,
})
)
.max(1),
}),
submenu: z.array(
z.object({
links: z.array(
z.object({ linkConnection: linkConnectionRefs })
),
})
),
})
),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
)
.length(1),
}),
})

View File

@@ -14,7 +14,11 @@ import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { generateRefsResponseTag, generateTag } from "@/utils/generateTag"
import {
generateRefsResponseTag,
generateTag,
generateTags,
} from "@/utils/generateTag"
import { langInput } from "./input"
import {
@@ -24,11 +28,13 @@ import {
CurrentHeaderRefDataRaw,
FooterDataRaw,
FooterRefDataRaw,
getHeaderRefSchema,
getHeaderSchema,
validateContactConfigSchema,
validateCurrentHeaderConfigSchema,
validateFooterConfigSchema,
} from "./output"
import { getConnections } from "./utils"
import { HeaderRefResponse, HeaderResponse } from "@/types/header"
@@ -64,13 +70,13 @@ const getCurrentHeaderFailCounter = meter.createCounter(
)
// OpenTelemetry metrics: Header
const getHeaderRefCounter = meter.createCounter(
const getHeaderRefsCounter = meter.createCounter(
"trpc.contentstack.header.ref.get"
)
const getHeaderRefSuccessCounter = meter.createCounter(
const getHeaderRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-success"
)
const getHeaderRefFailCounter = meter.createCounter(
const getHeaderRefsFailCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-fail"
)
const getHeaderCounter = meter.createCounter("trpc.contentstack.header.get")
@@ -164,31 +170,90 @@ export const baseQueryRouter = router({
}),
header: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getHeaderRefCounter.add(1, { lang })
getHeaderRefsCounter.add(1, { lang })
console.info(
"contentstack.header.ref start",
"contentstack.header.refs start",
JSON.stringify({ query: { lang } })
)
// TODO: Add better ref types and error handling for responseRef
const responseRef = await request<HeaderRefResponse>(GetHeaderRef, {
locale: lang,
})
const responseRef = await request<HeaderRefResponse>(
GetHeaderRef,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(lang, "header")],
},
}
)
getCurrentHeaderCounter.add(1, { lang })
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getHeaderRefsFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.header.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedHeaderRefs = getHeaderRefSchema.safeParse(responseRef.data)
if (!validatedHeaderRefs.success) {
getHeaderRefsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedHeaderRefs.error),
})
console.error(
"contentstack.header.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedHeaderRefs.error,
})
)
return null
}
getHeaderRefsSuccessCounter.add(1, { lang })
console.info(
"contentstack.header.refs success",
JSON.stringify({ query: { lang } })
)
const connections = getConnections(validatedHeaderRefs.data)
getHeaderCounter.add(1, { lang })
console.info(
"contentstack.header start",
JSON.stringify({ query: { lang } })
)
const tags = [
generateTags(lang, connections),
generateTag(
lang,
validatedHeaderRefs.data.all_header.items[0].system.uid
),
].flat()
const response = await request<HeaderResponse>(
GetHeader,
{ locale: lang }
// {
// tags: [
// generateTag(lang, responseRef.data.all_header.items[0].system.uid),
// ],
// }
{ locale: lang },
{ cache: "force-cache", next: { tags } }
)
if (!response.data) {
@@ -422,161 +487,3 @@ export const baseQueryRouter = router({
return validatedFooterConfig.data.all_current_footer.items[0]
}),
})
const json = {
query: { lang: "en" },
error: {
issues: [
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: ["all_header", "items", 0, "top_link", "link"],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: ["all_header", "items", 0, "menu_items", 0, "link"],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
0,
"submenu",
0,
"links",
0,
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
0,
"submenu",
0,
"links",
1,
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
0,
"submenu",
1,
"links",
0,
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
0,
"see_all_link",
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: ["all_header", "items", 0, "menu_items", 1, "link"],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
1,
"see_all_link",
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: ["all_header", "items", 0, "menu_items", 2, "link"],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
2,
"see_all_link",
"link",
],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: ["all_header", "items", 0, "menu_items", 3, "link"],
message: "Required",
},
{
code: "invalid_type",
expected: "object",
received: "undefined",
path: [
"all_header",
"items",
0,
"menu_items",
3,
"see_all_link",
"link",
],
message: "Required",
},
],
name: "ZodError",
},
}

View File

@@ -0,0 +1,40 @@
import { HeaderRefResponse } from "@/types/header"
import { Edges } from "@/types/requests/utils/edges"
import { NodeRefs } from "@/types/requests/utils/refs"
export function getConnections(refs: HeaderRefResponse) {
const connections: Edges<NodeRefs>[] = []
const headerData = refs.all_header.items[0]
const topLink = headerData.top_link
if (topLink) {
connections.push(topLink.linkConnection)
}
headerData.menu_items.forEach(
({ linkConnection, see_all_link, cardConnection, submenu }) => {
const card = cardConnection.edges[0]?.node
connections.push(linkConnection)
if (see_all_link) {
connections.push(see_all_link.linkConnection)
}
if (card) {
if (card.primary_button) {
connections.push(card.primary_button.linkConnection)
}
if (card.secondary_button) {
connections.push(card.secondary_button.linkConnection)
}
}
submenu.forEach(({ links }) => {
links.forEach(({ linkConnection }) => {
connections.push(linkConnection)
})
})
}
)
return connections
}