feat(SW-186): implement cms data into new header

This commit is contained in:
Erik Tiekstra
2024-09-03 15:41:49 +02:00
parent bf7d22c728
commit 52fdc1daac
25 changed files with 123 additions and 154 deletions

View File

@@ -4,9 +4,17 @@ import styles from "./headerLink.module.css"
import type { HeaderLinkProps } from "@/types/components/header/headerLink"
export default function HeaderLink({ children, ...props }: HeaderLinkProps) {
export default function HeaderLink({
children,
className,
...props
}: HeaderLinkProps) {
return (
<Link color="burgundy" className={styles.headerLink} {...props}>
<Link
color="burgundy"
className={`${styles.headerLink} ${className}`}
{...props}
>
{children}
</Link>
)

View File

@@ -17,7 +17,7 @@ import styles from "./mobileMenu.module.css"
import type { MobileMenuProps } from "@/types/components/header/mobileMenu"
export default function MobileMenu({
mainNavigation,
menuItems,
languageUrls,
}: MobileMenuProps) {
const intl = useIntl()
@@ -65,7 +65,7 @@ export default function MobileMenu({
className={styles.dialog}
aria-label={intl.formatMessage({ id: "Menu" })}
>
<NavigationMenu isMobile={true} items={mainNavigation} />
<NavigationMenu isMobile={true} items={menuItems} />
<footer className={styles.footer}>
<HeaderLink href="#">
<SearchIcon width={20} height={20} color="burgundy" />

View File

@@ -12,14 +12,18 @@ import styles from "./navigationMenuItem.module.css"
import type { NavigationMenuItemProps } from "@/types/components/header/navigationMenuItem"
export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
const { children, title, href, seeAllLinkText, infoCard } = item
const { submenu, title, link, seeAllLink, card } = item
const [isExpanded, setIsExpanded] = useState(false)
function handleButtonClick() {
setIsExpanded((prev) => !prev)
}
return children?.length ? (
if (!submenu.length && !link) {
return null
}
return submenu.length ? (
<MainMenuButton
onClick={handleButtonClick}
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`}
@@ -36,7 +40,7 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
</MainMenuButton>
) : (
<Link
href={href}
href={link!.href}
color="burgundy"
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`}
>

View File

@@ -8,12 +8,20 @@ export default function NavigationMenu({
items,
isMobile,
}: NavigationMenuProps) {
const filteredItems = items.filter(
({ link, submenu }) => submenu.length || link
)
if (!filteredItems.length) {
return null
}
return (
<ul
className={`${styles.navigationMenu} ${isMobile ? styles.mobile : styles.desktop}`}
>
{items.map((item) => (
<li key={item.id} className={styles.item}>
{filteredItems.map((item) => (
<li key={item.title} className={styles.item}>
<NavigationMenuItem isMobile={isMobile} item={item} />
</li>
))}

View File

@@ -8,7 +8,6 @@ import Link from "@/components/TempDesignSystem/Link"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { navigationMenuItems } from "../tempHeaderData"
import Avatar from "./Avatar"
import MobileMenu from "./MobileMenu"
import MyPagesMenu from "./MyPagesMenu"
@@ -19,7 +18,10 @@ import styles from "./mainMenu.module.css"
import type { MainMenuProps } from "@/types/components/header/mainMenu"
export default async function MainMenu({ languageUrls }: MainMenuProps) {
export default async function MainMenu({
languageUrls,
menuItems,
}: MainMenuProps) {
const intl = await getIntl()
const lang = getLang()
const myPagesNavigation =
@@ -44,7 +46,9 @@ export default async function MainMenu({ languageUrls }: MainMenuProps) {
/>
</NextLink>
<div className={styles.menus}>
<NavigationMenu items={navigationMenuItems} isMobile={false} />
{menuItems ? (
<NavigationMenu items={menuItems} isMobile={false} />
) : null}
{user ? (
<>
<MyPagesMenu
@@ -70,12 +74,34 @@ export default async function MainMenu({ languageUrls }: MainMenuProps) {
</span>
</Link>
)}
<MobileMenu
languageUrls={languageUrls}
mainNavigation={navigationMenuItems}
/>
{menuItems ? (
<MobileMenu languageUrls={languageUrls} menuItems={menuItems} />
) : null}
</div>
</nav>
</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

@@ -8,17 +8,19 @@ import styles from "./topMenu.module.css"
import type { TopMenuProps } from "@/types/components/header/topMenu"
export default async function TopMenu({ languageUrls }: TopMenuProps) {
export default async function TopMenu({ languageUrls, topLink }: TopMenuProps) {
const intl = await getIntl()
return (
<div className={styles.topMenu}>
<div className={styles.content}>
<HeaderLink href="#">
<GiftIcon width={20} height={20} color="burgundy" />
{intl.formatMessage({ id: "Join Scandic Friends" })}
</HeaderLink>
<div className={styles.right}>
{topLink ? (
<HeaderLink className={styles.topLink} href={topLink.href}>
<GiftIcon width={20} height={20} color="burgundy" />
{topLink.title}
</HeaderLink>
) : null}
<div className={styles.options}>
<LanguageSwitcher type="desktopHeader" urls={languageUrls} />
<HeaderLink href="#">
<SearchIcon width={20} height={20} color="burgundy" />

View File

@@ -8,12 +8,12 @@
.content {
max-width: var(--max-width-navigation);
margin: 0 auto;
display: flex;
display: grid;
justify-content: space-between;
gap: var(--Spacing-x3);
}
.right {
.options {
display: flex;
gap: var(--Spacing-x2);
align-items: center;
@@ -23,4 +23,15 @@
.topMenu {
display: block;
}
.content {
grid-template-areas: "topLink options";
}
.topLink {
grid-area: topLink;
}
.options {
grid-area: options;
}
}

View File

@@ -2,6 +2,5 @@
position: relative;
font-family: var(--typography-Body-Regular-fontFamily);
color: var(--Base-Text-High-contrast);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.08);
z-index: var(--header-z-index);
}

View File

@@ -7,15 +7,19 @@ import styles from "./header.module.css"
export default async function Header() {
const languages = await serverClient().contentstack.languageSwitcher.get()
const headerData = await serverClient().contentstack.base.header()
if (!languages) {
if (!languages || !headerData) {
return null
}
return (
<header className={styles.header}>
<TopMenu languageUrls={languages.urls} />
<MainMenu languageUrls={languages.urls} />
<TopMenu languageUrls={languages.urls} topLink={headerData.topLink} />
<MainMenu
languageUrls={languages.urls}
menuItems={headerData.menuItems}
/>
</header>
)
}

View File

@@ -1,75 +0,0 @@
import type { MainNavigationItem } from "@/types/components/header/mainNavigationItem"
export const navigationMenuItems: MainNavigationItem[] = [
{
id: "hotels",
title: "Hotels",
href: "/hotels",
children: [],
},
{
id: "business",
title: "Business",
href: "/business",
children: [
{
groupTitle: "Top conference venues",
children: [
{
id: "stockholm",
title: "Stockholm",
href: "/stockholm",
},
{
id: "bergen",
title: "Bergen",
href: "/bergen",
},
{
id: "copenhagen",
title: "Copenhagen",
href: "/copenhagen",
},
],
},
{
groupTitle: "Scandic for business",
children: [
{
id: "book-a-venue",
title: "Book a venue",
href: "/book-a-venue",
},
{
id: "conference-packages",
title: "Conference packages",
href: "/conference-packages",
},
{
id: "co-working",
title: "Co-working",
href: "/co-working",
},
],
},
],
seeAllLinkText: "See all conference & meeting venues",
infoCard: {
scriptedTitle: "Stockholm",
title: "Meeting venues in Stockholm",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et felis metus. Sed et felis metus.",
ctaLink: "/stockholm",
},
},
{
id: "offers",
title: "Offers",
href: "/offers",
},
{
id: "restaurants",
title: "Restaurants",
href: "/restaurants",
},
]

View File

@@ -74,7 +74,6 @@
"Hotel facilities": "Hotel faciliteter",
"Hotel surroundings": "Hotel omgivelser",
"How it works": "Hvordan det virker",
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
"km to city center": "km til byens centrum",
"Language": "Sprog",
"Level": "Niveau",

View File

@@ -73,7 +73,6 @@
"Hotel facilities": "Hotel-Infos",
"Hotel surroundings": "Umgebung des Hotels",
"How it works": "Wie es funktioniert",
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
"km to city center": "km bis zum Stadtzentrum",
"Language": "Sprache",
"Level": "Level",

View File

@@ -79,7 +79,6 @@
"hotelPages.rooms.roomCard.persons": "persons",
"hotelPages.rooms.roomCard.seeRoomDetails": "See room details",
"How it works": "How it works",
"Join Scandic Friends": "Join Scandic Friends",
"km to city center": "km to city center",
"Language": "Language",
"Level": "Level",

View File

@@ -74,7 +74,6 @@
"Hotel facilities": "Hotellin palvelut",
"Hotel surroundings": "Hotellin ympäristö",
"How it works": "Kuinka se toimii",
"Join Scandic Friends": "Liity jäseneksi",
"km to city center": "km keskustaan",
"Language": "Kieli",
"Level": "Level",

View File

@@ -74,7 +74,6 @@
"Hotel facilities": "Hotelfaciliteter",
"Hotel surroundings": "Hotellomgivelser",
"How it works": "Hvordan det fungerer",
"Join Scandic Friends": "Bli med i Scandic Friends",
"km to city center": "km til sentrum",
"Language": "Språk",
"Level": "Nivå",

View File

@@ -76,7 +76,6 @@
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"How it works": "Hur det fungerar",
"Join Scandic Friends": "Gå med i Scandic Friends",
"km to city center": "km till stadens centrum",
"Language": "Språk",
"Level": "Nivå",

View File

@@ -301,11 +301,11 @@ const linkConnectionNodeSchema = z
const linkWithTitleSchema = z
.object({
title: z.string(),
title: z.string().nullable(),
linkConnection: linkConnectionNodeSchema,
})
.transform((rawData) => {
return rawData.linkConnection
return rawData.linkConnection && rawData.title
? {
title: rawData.title,
href: rawData.linkConnection.href,
@@ -378,7 +378,7 @@ const cardConnectionSchema = z
}
})
const menuItemSchema = z
export const menuItemSchema = z
.object({
title: z.string(),
linkConnection: linkConnectionNodeSchema,
@@ -391,22 +391,25 @@ const menuItemSchema = z
see_all_link: linkWithTitleSchema,
cardConnection: cardConnectionSchema,
})
.transform(({ submenu, linkConnection, cardConnection, see_all_link }) => {
return {
link: submenu.length ? null : linkConnection,
seeAllLink: submenu.length ? see_all_link : null,
submenu,
card: cardConnection,
.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,
}
}
})
)
export const getHeaderSchema = z
.object({
all_header: z.object({
items: z.array(
z.object({
top_link: linkWithTitleSchema,
menu_items: z.array(menuItemSchema),
top_link: linkWithTitleSchema.nullable(),
menu_items: z.array(menuItemSchema).nullable(),
})
),
}),

View File

@@ -183,12 +183,12 @@ export const baseQueryRouter = router({
const response = await request<HeaderResponse>(
GetHeader,
{ locale: lang },
{
tags: [
generateTag(lang, responseRef.data.all_header.items[0].system.uid),
],
}
{ locale: lang }
// {
// tags: [
// generateTag(lang, responseRef.data.all_header.items[0].system.uid),
// ],
// }
)
if (!response.data) {

View File

@@ -1,5 +1,7 @@
import { MenuItem } from "@/types/header"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
export interface MainMenuProps {
languageUrls: LanguageSwitcherData
menuItems: MenuItem[] | null
}

View File

@@ -1,20 +0,0 @@
export interface MainNavigationItem {
id: string
title: string
href: string
children?: {
groupTitle: string
children: {
id: string
title: string
href: string
}[]
}[]
seeAllLinkText?: string
infoCard?: {
scriptedTitle: string
title: string
description: string
ctaLink: string
}
}

View File

@@ -1,8 +1,7 @@
import { MainNavigationItem } from "./mainNavigationItem"
import { MenuItem } from "@/types/header"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
export interface MobileMenuProps {
languageUrls: LanguageSwitcherData
mainNavigation: MainNavigationItem[]
menuItems: MenuItem[]
}

View File

@@ -1,6 +1,6 @@
import type { MainNavigationItem } from "@/types/components/header/mainNavigationItem"
import { MenuItem } from "@/types/header"
export interface NavigationMenuProps {
items: MainNavigationItem[]
items: MenuItem[]
isMobile: boolean
}

View File

@@ -1,6 +1,6 @@
import type { MainNavigationItem } from "@/types/components/header/mainNavigationItem"
import { MenuItem } from "@/types/header"
export interface NavigationMenuItemProps {
item: MainNavigationItem
item: MenuItem
isMobile: boolean
}

View File

@@ -1,5 +1,7 @@
import { Header } from "@/types/header"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
export interface TopMenuProps {
languageUrls: LanguageSwitcherData
topLink: Header["topLink"]
}

View File

@@ -3,8 +3,10 @@ import { z } from "zod"
import {
getHeaderRefSchema,
getHeaderSchema,
menuItemSchema,
} from "@/server/routers/contentstack/base/output"
export type HeaderRefResponse = z.input<typeof getHeaderRefSchema>
export type HeaderResponse = z.input<typeof getHeaderSchema>
export type Header = z.output<typeof getHeaderSchema>
export type MenuItem = z.output<typeof menuItemSchema>