feat(SW-186): implement cms data into new header
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}`}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
]
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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å",
|
||||
|
||||
@@ -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å",
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { MenuItem } from "@/types/header"
|
||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export interface MainMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
menuItems: MenuItem[] | null
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Header } from "@/types/header"
|
||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export interface TopMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
topLink: Header["topLink"]
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user