feat(SW-572): Added support for logged in and logged out variants of the top link inside the header
This commit is contained in:
@@ -106,7 +106,7 @@
|
||||
--max-width-navigation: 89.5rem;
|
||||
|
||||
--main-menu-mobile-height: 75px;
|
||||
--main-menu-desktop-height: 129px;
|
||||
--main-menu-desktop-height: 125px;
|
||||
--booking-widget-mobile-height: 75px;
|
||||
--booking-widget-desktop-height: 77px;
|
||||
--hotel-page-map-desktop-width: 23.75rem;
|
||||
|
||||
@@ -2,5 +2,16 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
}
|
||||
|
||||
.headerLink:hover {
|
||||
color: var(--Base-Text-High-contrast);
|
||||
}
|
||||
|
||||
.headerLink .icon * {
|
||||
fill: var(--Base-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.headerLink:hover .icon * {
|
||||
fill: var(--Base-Text-High-contrast);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Link from "next/link"
|
||||
|
||||
import { getIconByIconName } from "@/components/Icons/get-icon-by-icon-name"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import styles from "./headerLink.module.css"
|
||||
|
||||
@@ -6,16 +9,19 @@ import type { HeaderLinkProps } from "@/types/components/header/headerLink"
|
||||
|
||||
export default function HeaderLink({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
href,
|
||||
iconName,
|
||||
iconSize = 20,
|
||||
}: HeaderLinkProps) {
|
||||
const Icon = getIconByIconName(iconName)
|
||||
return (
|
||||
<Link
|
||||
color="burgundy"
|
||||
className={`${styles.headerLink} ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
<Caption type="regular" color="textMediumContrast" asChild>
|
||||
<Link href={href} className={styles.headerLink}>
|
||||
{Icon ? (
|
||||
<Icon className={styles.icon} width={iconSize} height={iconSize} />
|
||||
) : null}
|
||||
{children}
|
||||
</Link>
|
||||
</Caption>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,21 +7,23 @@ import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons"
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
|
||||
import HeaderLink from "../../HeaderLink"
|
||||
import TopLink from "../../TopLink"
|
||||
|
||||
import styles from "./mobileMenu.module.css"
|
||||
|
||||
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
|
||||
import type { MobileMenuProps } from "@/types/components/header/mobileMenu"
|
||||
import { IconName } from "@/types/components/icon"
|
||||
|
||||
export default function MobileMenu({
|
||||
children,
|
||||
languageUrls,
|
||||
topLink,
|
||||
isLoggedIn,
|
||||
}: React.PropsWithChildren<MobileMenuProps>) {
|
||||
const intl = useIntl()
|
||||
const {
|
||||
@@ -77,18 +79,11 @@ export default function MobileMenu({
|
||||
>
|
||||
<Suspense fallback={"Loading nav"}>{children}</Suspense>
|
||||
<footer className={styles.footer}>
|
||||
<HeaderLink href="#">
|
||||
<SearchIcon width={20} height={20} color="burgundy" />
|
||||
<HeaderLink href="#" iconName={IconName.Search}>
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</HeaderLink>
|
||||
{topLink.link ? (
|
||||
<HeaderLink href={topLink.link.url}>
|
||||
<GiftIcon width={20} height={20} color="burgundy" />
|
||||
{topLink.title}
|
||||
</HeaderLink>
|
||||
) : null}
|
||||
<HeaderLink href="#">
|
||||
<ServiceIcon width={20} height={20} color="burgundy" />
|
||||
<TopLink isLoggedIn={isLoggedIn} topLink={topLink} iconSize={20} />
|
||||
<HeaderLink href="#" iconName={IconName.Service}>
|
||||
{intl.formatMessage({ id: "Customer service" })}
|
||||
</HeaderLink>
|
||||
<LanguageSwitcher type="mobileHeader" urls={languageUrls} />
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import {
|
||||
getHeader,
|
||||
getLanguageSwitcher,
|
||||
getName,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import MobileMenu from "../MobileMenu"
|
||||
|
||||
@@ -8,13 +12,18 @@ export default async function MobileMenuWrapper({
|
||||
// preloaded
|
||||
const languages = await getLanguageSwitcher()
|
||||
const header = await getHeader()
|
||||
const user = await getName()
|
||||
|
||||
if (!languages || !header) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileMenu languageUrls={languages.urls} topLink={header.data.topLink}>
|
||||
<MobileMenu
|
||||
languageUrls={languages.urls}
|
||||
topLink={header.data.topLink}
|
||||
isLoggedIn={!!user}
|
||||
>
|
||||
{children}
|
||||
</MobileMenu>
|
||||
)
|
||||
|
||||
26
components/Header/TopLink/index.tsx
Normal file
26
components/Header/TopLink/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import HeaderLink from "../HeaderLink"
|
||||
|
||||
import type { TopLinkProps } from "@/types/components/header/topLink"
|
||||
import { IconName } from "@/types/components/icon"
|
||||
|
||||
export default function TopLink({
|
||||
isLoggedIn,
|
||||
topLink,
|
||||
iconSize = 16,
|
||||
}: TopLinkProps) {
|
||||
const linkData = isLoggedIn ? topLink.logged_in : topLink.logged_out
|
||||
|
||||
if (!linkData?.link?.url || !linkData?.title) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderLink
|
||||
href={linkData.link.url}
|
||||
iconName={linkData.icon || IconName.Gift}
|
||||
iconSize={iconSize}
|
||||
>
|
||||
{linkData.title}
|
||||
</HeaderLink>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import {
|
||||
getHeader,
|
||||
getLanguageSwitcher,
|
||||
getName,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { GiftIcon, SearchIcon } from "@/components/Icons"
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import HeaderLink from "../HeaderLink"
|
||||
import TopLink from "../TopLink"
|
||||
|
||||
import styles from "./topMenu.module.css"
|
||||
|
||||
import { IconName } from "@/types/components/icon"
|
||||
|
||||
export default async function TopMenu() {
|
||||
// cached
|
||||
const intl = await getIntl()
|
||||
// both preloaded
|
||||
const languages = await getLanguageSwitcher()
|
||||
const header = await getHeader()
|
||||
const user = await getName()
|
||||
|
||||
if (!languages || !header) {
|
||||
return null
|
||||
@@ -24,28 +30,15 @@ export default async function TopMenu() {
|
||||
return (
|
||||
<div className={styles.topMenu}>
|
||||
<div className={styles.content}>
|
||||
{header.data.topLink.link ? (
|
||||
<Caption type="regular" color="textMediumContrast" asChild>
|
||||
<Link
|
||||
href={header.data.topLink.link.url}
|
||||
color="peach80"
|
||||
variant="icon"
|
||||
>
|
||||
<GiftIcon width={20} height={20} />
|
||||
{header.data.topLink.title}
|
||||
</Link>
|
||||
</Caption>
|
||||
) : null}
|
||||
<TopLink isLoggedIn={!!user} topLink={header.data.topLink} />
|
||||
<div className={styles.options}>
|
||||
<LanguageSwitcher type="desktopHeader" urls={languages.urls} />
|
||||
|
||||
<Caption type="regular" color="textMediumContrast" asChild>
|
||||
<Link href="#" color="peach80" variant="icon">
|
||||
<SearchIcon width={20} height={20} />
|
||||
<HeaderLink href="#" iconName={IconName.Search}>
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</Link>
|
||||
</HeaderLink>
|
||||
</Caption>
|
||||
<HeaderLink href="#"></HeaderLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import {
|
||||
getHeader,
|
||||
getLanguageSwitcher,
|
||||
getName,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import MainMenu from "./MainMenu"
|
||||
import TopMenu from "./TopMenu"
|
||||
@@ -10,6 +14,8 @@ import styles from "./header.module.css"
|
||||
export default function Header() {
|
||||
void getHeader()
|
||||
void getLanguageSwitcher()
|
||||
void getName()
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Suspense fallback="Loading top menu">
|
||||
|
||||
@@ -77,6 +77,7 @@ import {
|
||||
PhoneIcon,
|
||||
PlusCircleIcon,
|
||||
PlusIcon,
|
||||
PriceTagIcon,
|
||||
RestaurantIcon,
|
||||
RoomServiceIcon,
|
||||
SaunaIcon,
|
||||
@@ -101,7 +102,9 @@ import {
|
||||
|
||||
import { IconName, IconProps } from "@/types/components/icon"
|
||||
|
||||
export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
export function getIconByIconName(
|
||||
icon: IconName | null = null
|
||||
): FC<IconProps> | null {
|
||||
switch (icon) {
|
||||
case IconName.Accesories:
|
||||
return AccesoriesIcon
|
||||
@@ -253,6 +256,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return PlusIcon
|
||||
case IconName.PlusCircle:
|
||||
return PlusCircleIcon
|
||||
case IconName.PriceTag:
|
||||
return PriceTagIcon
|
||||
case IconName.Restaurant:
|
||||
return RestaurantIcon
|
||||
case IconName.RoomService:
|
||||
|
||||
@@ -39,6 +39,7 @@ export default function LanguageSwitcher({
|
||||
const languageSwitcherRef = useRef<HTMLDivElement>(null)
|
||||
const isFooter = type === LanguageSwitcherTypesEnum.Footer
|
||||
const isHeader = !isFooter
|
||||
const globeIconSize = type === "desktopHeader" ? 16 : 20
|
||||
|
||||
const position = isFooter ? "footer" : "header"
|
||||
|
||||
@@ -87,7 +88,7 @@ export default function LanguageSwitcher({
|
||||
})}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<GlobeIcon width={20} height={20} />
|
||||
<GlobeIcon width={globeIconSize} height={globeIconSize} />
|
||||
<Caption className={styles.buttonText} type="regular" asChild>
|
||||
<span>{languages[currentLanguage]}</span>
|
||||
</Caption>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#import "../Fragments/System.graphql"
|
||||
|
||||
#import "../Fragments/PageLink/AccountPageLink.graphql"
|
||||
#import "../Fragments/PageLink/CollectionPageLink.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/AccountPage/Ref.graphql"
|
||||
#import "../Fragments/CollectionPage/Ref.graphql"
|
||||
#import "../Fragments/ContentPage/Ref.graphql"
|
||||
#import "../Fragments/HotelPage/Ref.graphql"
|
||||
#import "../Fragments/LoyaltyPage/Ref.graphql"
|
||||
@@ -14,14 +18,35 @@ query GetHeader($locale: String!) {
|
||||
all_header(limit: 1, locale: $locale) {
|
||||
items {
|
||||
top_link {
|
||||
title
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...ContentPageLink
|
||||
...HotelPageLink
|
||||
...LoyaltyPageLink
|
||||
logged_in {
|
||||
icon
|
||||
title
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageLink
|
||||
...CollectionPageLink
|
||||
...ContentPageLink
|
||||
...HotelPageLink
|
||||
...LoyaltyPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logged_out {
|
||||
icon
|
||||
title
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageLink
|
||||
...CollectionPageLink
|
||||
...ContentPageLink
|
||||
...HotelPageLink
|
||||
...LoyaltyPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,13 +109,31 @@ query GetHeaderRef($locale: String!) {
|
||||
all_header(limit: 1, locale: $locale) {
|
||||
items {
|
||||
top_link {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...ContentPageRef
|
||||
...HotelPageRef
|
||||
...LoyaltyPageRef
|
||||
logged_in {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageRef
|
||||
...CollectionPageRef
|
||||
...ContentPageRef
|
||||
...HotelPageRef
|
||||
...LoyaltyPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logged_out {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageRef
|
||||
...CollectionPageRef
|
||||
...ContentPageRef
|
||||
...HotelPageRef
|
||||
...LoyaltyPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
import { IconName } from "@/types/components/icon"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { Image } from "@/types/image"
|
||||
|
||||
@@ -514,6 +515,11 @@ const menuItemsRefsSchema = z.intersection(
|
||||
})
|
||||
)
|
||||
|
||||
const topLinkRefsSchema = z.object({
|
||||
logged_in: linkRefsSchema.nullable(),
|
||||
logged_out: linkRefsSchema.nullable(),
|
||||
})
|
||||
|
||||
export const headerRefsSchema = z
|
||||
.object({
|
||||
all_header: z.object({
|
||||
@@ -522,7 +528,7 @@ export const headerRefsSchema = z
|
||||
z.object({
|
||||
menu_items: z.array(menuItemsRefsSchema),
|
||||
system: systemSchema,
|
||||
top_link: linkRefsSchema,
|
||||
top_link: topLinkRefsSchema,
|
||||
})
|
||||
)
|
||||
.max(1),
|
||||
@@ -636,6 +642,32 @@ export const menuItemSchema = z
|
||||
}
|
||||
})
|
||||
|
||||
const topLinkItemSchema = z.intersection(
|
||||
linkAndTitleSchema,
|
||||
z.object({
|
||||
icon: z
|
||||
.enum(["loyalty", "info", "offer"])
|
||||
.nullable()
|
||||
.transform((icon) => {
|
||||
switch (icon) {
|
||||
case "loyalty":
|
||||
return IconName.Gift
|
||||
case "info":
|
||||
return IconName.InfoCircle
|
||||
case "offer":
|
||||
return IconName.PriceTag
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
export const topLinkSchema = z.object({
|
||||
logged_in: topLinkItemSchema.nullable(),
|
||||
logged_out: topLinkItemSchema.nullable(),
|
||||
})
|
||||
|
||||
export const headerSchema = z
|
||||
.object({
|
||||
all_header: z.object({
|
||||
@@ -643,7 +675,7 @@ export const headerSchema = z
|
||||
.array(
|
||||
z.object({
|
||||
menu_items: z.array(menuItemSchema),
|
||||
top_link: linkAndTitleSchema,
|
||||
top_link: topLinkSchema,
|
||||
})
|
||||
)
|
||||
.max(1),
|
||||
|
||||
@@ -14,8 +14,13 @@ import type { ContactConfig } from "./output"
|
||||
export function getConnections({ header }: HeaderRefs) {
|
||||
const connections: System["system"][] = [header.system]
|
||||
|
||||
if (header.top_link?.link) {
|
||||
connections.push(header.top_link.link)
|
||||
if (header.top_link) {
|
||||
if (header.top_link.logged_in?.link) {
|
||||
connections.push(header.top_link.logged_in.link)
|
||||
}
|
||||
if (header.top_link.logged_out?.link) {
|
||||
connections.push(header.top_link.logged_out.link)
|
||||
}
|
||||
}
|
||||
|
||||
if (header.menu_items.length) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import type { LinkProps } from "@/components/TempDesignSystem/Link/link"
|
||||
import type { LinkProps } from "next/link"
|
||||
|
||||
export interface HeaderLinkProps extends React.PropsWithChildren<LinkProps> {}
|
||||
import type { IconName } from "../icon"
|
||||
|
||||
export interface HeaderLinkProps extends React.PropsWithChildren {
|
||||
href: LinkProps["href"]
|
||||
iconName: IconName | null
|
||||
iconSize?: number
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ import type { Header } from "@/types/trpc/routers/contentstack/header"
|
||||
export interface MobileMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
topLink: Header["header"]["topLink"]
|
||||
isLoggedIn: boolean
|
||||
}
|
||||
|
||||
7
types/components/header/topLink.ts
Normal file
7
types/components/header/topLink.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Header } from "@/types/trpc/routers/contentstack/header"
|
||||
|
||||
export interface TopLinkProps {
|
||||
isLoggedIn: boolean
|
||||
topLink: Header["header"]["topLink"]
|
||||
iconSize?: number
|
||||
}
|
||||
@@ -82,6 +82,7 @@ export enum IconName {
|
||||
Phone = "Phone",
|
||||
Plus = "Plus",
|
||||
PlusCircle = "PlusCircle",
|
||||
PriceTag = "PriceTag",
|
||||
Restaurant = "Restaurant",
|
||||
RoomService = "RoomService",
|
||||
Sauna = "Sauna",
|
||||
|
||||
Reference in New Issue
Block a user