Merged in monorepo-step-1 (pull request #1080)
Migrate to a monorepo setup - step 1 * Move web to subfolder /apps/scandic-web * Yarn + transitive deps - Move to yarn - design-system package removed for now since yarn doesn't support the parameter for token (ie project currently broken) - Add missing transitive dependencies as Yarn otherwise prevents these imports - VS Code doesn't pick up TS path aliases unless you open /apps/scandic-web instead of root (will be fixed with monorepo) * Pin framer-motion to temporarily fix typing issue https://github.com/adobe/react-spectrum/issues/7494 * Pin zod to avoid typ error There seems to have been a breaking change in the types returned by zod where error is now returned as undefined instead of missing in the type. We should just handle this but to avoid merge conflicts just pin the dependency for now. * Pin react-intl version Pin version of react-intl to avoid tiny type issue where formatMessage does not accept a generic any more. This will be fixed in a future commit, but to avoid merge conflicts just pin for now. * Pin typescript version Temporarily pin version as newer versions as stricter and results in a type error. Will be fixed in future commit after merge. * Setup workspaces * Add design-system as a monorepo package * Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN * Fix husky for monorepo setup * Update netlify.toml * Add lint script to root package.json * Add stub readme * Fix react-intl formatMessage types * Test netlify.toml in root * Remove root toml * Update netlify.toml publish path * Remove package-lock.json * Update build for branch/preview builds Approved-by: Linus Flood
This commit is contained in:
committed by
Linus Flood
parent
667cab6fb6
commit
80100e7631
@@ -0,0 +1,43 @@
|
||||
.button {
|
||||
background-color: #02838e;
|
||||
color: #fff;
|
||||
padding: 5px 15px;
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 50px;
|
||||
height: 38px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.button:focus,
|
||||
.button:active {
|
||||
box-shadow: 0 0 1px 2px #b4defa;
|
||||
outline: none;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.button {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
height: auto;
|
||||
padding: 12px 32px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import "@scandic-hotels/design-system/current/style.css"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import styles from "./bookingButton.module.css"
|
||||
|
||||
export default function BookingButton({ href }: { href: string }) {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<a className={styles.button} href={href}>
|
||||
{intl.formatMessage({ id: "Book" })}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MainMenuSkeleton } from "../MainMenu"
|
||||
import { TopMenuSkeleton } from "../TopMenu"
|
||||
|
||||
import styles from "../header.module.css"
|
||||
|
||||
export default async function HeaderFallback() {
|
||||
return (
|
||||
<header className={styles.header} role="banner">
|
||||
<TopMenuSkeleton />
|
||||
<MainMenuSkeleton />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
color: #fff;
|
||||
padding: 3px 15px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0 auto;
|
||||
cursor: pointer;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000em;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.caret {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-left: 2px;
|
||||
vertical-align: middle;
|
||||
border-top: 5px dashed;
|
||||
border-right: 5px solid transparent;
|
||||
border-left: 5px solid transparent;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: -4px;
|
||||
margin-right: 3px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
float: left;
|
||||
min-width: 160px;
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown.isOpen {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.link {
|
||||
clear: both;
|
||||
color: grey;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
padding: 3px 20px;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #737373;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.active > .link {
|
||||
background-color: #00838e;
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1366px) {
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
|
||||
import { type Lang, languages } from "@/constants/languages"
|
||||
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./desktop.module.css"
|
||||
|
||||
import type { LanguageSwitcherProps } from "@/types/components/current/languageSwitcher"
|
||||
|
||||
export default function Desktop({ urls }: LanguageSwitcherProps) {
|
||||
const currentLanguage = useLang()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const divRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
function toggleOpen() {
|
||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
||||
}
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [setIsOpen])
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(evt: Event) {
|
||||
const target = evt.target as HTMLElement
|
||||
if (divRef.current && target && !divRef.current.contains(target)) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
if (divRef.current) {
|
||||
document.addEventListener("click", handleClickOutside, false)
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside, false)
|
||||
}
|
||||
}, [close])
|
||||
|
||||
const urlKeys = Object.keys(urls)
|
||||
|
||||
if (urlKeys.length === 1 && urlKeys[0] === currentLanguage) {
|
||||
return (
|
||||
<div className={styles.container} ref={divRef}>
|
||||
<section className={styles.languageSwitcher}>
|
||||
<svg focusable="false" className={styles.icon} viewBox="0 0 32 32">
|
||||
<use xlinkHref="/_static/img/icons/sprites.svg#icon-globe"></use>
|
||||
</svg>
|
||||
{languages[currentLanguage]}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.desktop}>
|
||||
<div className={styles.container} ref={divRef}>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={styles.toggle}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<svg focusable="false" className={styles.icon} viewBox="0 0 32 32">
|
||||
<use xlinkHref="/_static/img/icons/sprites.svg#icon-globe"></use>
|
||||
</svg>
|
||||
{languages[currentLanguage]}
|
||||
<span className={styles.hiddenAccessible}>Choose language</span>
|
||||
<span className={styles.caret}></span>
|
||||
</button>
|
||||
<ul className={`${styles.dropdown} ${isOpen ? styles.isOpen : ""}`}>
|
||||
{urlKeys.map((key) => {
|
||||
const url = urls[key as Lang]?.url
|
||||
if (url) {
|
||||
return (
|
||||
<li
|
||||
key={key}
|
||||
className={`${styles.li} ${currentLanguage === key ? styles.active : ""}`}
|
||||
>
|
||||
{urls[key as Lang]?.isExternal ? (
|
||||
<Link className={styles.link} href={url}>
|
||||
{languages[key as Lang]}
|
||||
</Link>
|
||||
) : (
|
||||
<a className={styles.link} href={url}>
|
||||
{languages[key as Lang]}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
|
||||
import { type Lang, languages } from "@/constants/languages"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./mobile.module.css"
|
||||
|
||||
import type { LanguageSwitcherProps } from "@/types/components/current/languageSwitcher"
|
||||
|
||||
export default function Mobile({ urls }: LanguageSwitcherProps) {
|
||||
const currentLanguage = useLang()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
function toggleOpen() {
|
||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
||||
}
|
||||
const urlKeys = Object.keys(urls)
|
||||
|
||||
if (urlKeys.length === 1 && urlKeys[0] === currentLanguage) {
|
||||
return <div className={styles.toggle}>{languages[currentLanguage]}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.mobile}>
|
||||
<div>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={styles.toggle}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
{languages[currentLanguage]}{" "}
|
||||
<span
|
||||
className={`${styles.arrow} ${isOpen ? styles.open : ""}`}
|
||||
></span>
|
||||
<span className={styles.hiddenAccessible}>Choose language</span>
|
||||
</button>
|
||||
<ul className={`${styles.dropdown} ${isOpen ? styles.isOpen : ""}`}>
|
||||
{urlKeys.map((key) => {
|
||||
const url = urls[key as Lang]?.url
|
||||
if (url) {
|
||||
return (
|
||||
<li key={key} className={styles.li}>
|
||||
<a href={url} className={styles.link}>
|
||||
{languages[key as Lang]}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
.mobile {
|
||||
display: block;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
font-size: 14px;
|
||||
padding: 5px 0;
|
||||
display: block;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0 auto;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000em;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.li {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
line-height: 22.4px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown.isOpen {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
background-image: url("/_static/img/icons/arrows/arrow-down-grey.png");
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.arrow.open {
|
||||
background-image: url("/_static/img/icons/arrows/arrow-up-grey.png");
|
||||
}
|
||||
|
||||
.link {
|
||||
color: grey;
|
||||
display: block;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: #7f7369;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1366px) {
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import Desktop from "./Desktop"
|
||||
import Mobile from "./Mobile"
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const currentLanguage = useLang()
|
||||
const pathName = usePathname()
|
||||
|
||||
const { data: languagesResponse, isLoading } =
|
||||
trpc.contentstack.languageSwitcher.get.useQuery({
|
||||
pathName,
|
||||
lang: currentLanguage,
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonShimmer width="12ch" />
|
||||
}
|
||||
|
||||
if (!languagesResponse?.urls) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Desktop urls={languagesResponse.urls} />
|
||||
<Mobile urls={languagesResponse.urls} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
276
apps/scandic-web/components/Current/Header/MainMenu/index.tsx
Normal file
276
apps/scandic-web/components/Current/Header/MainMenu/index.tsx
Normal file
@@ -0,0 +1,276 @@
|
||||
"use client"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { logout } from "@/constants/routes/handleAuth"
|
||||
import { myPages } from "@/constants/routes/myPages"
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
import LoginButton from "@/components/LoginButton"
|
||||
import Avatar from "@/components/MyPages/Avatar"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { trackClick } from "@/utils/tracking"
|
||||
|
||||
import BookingButton from "../BookingButton"
|
||||
|
||||
import styles from "./mainMenu.module.css"
|
||||
|
||||
import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
|
||||
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
|
||||
|
||||
export function MainMenu({
|
||||
frontpageLinkText,
|
||||
homeHref,
|
||||
links,
|
||||
logo,
|
||||
topMenuMobileLinks,
|
||||
languageSwitcher,
|
||||
myPagesMobileDropdown,
|
||||
bookingHref,
|
||||
user,
|
||||
}: MainMenuProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const pathname = usePathname()
|
||||
|
||||
const isThreeStaticPagesPathnames = [
|
||||
"/de/sponsoring",
|
||||
"/en/sponsoring",
|
||||
"/da/sponsorering",
|
||||
"/fi/sponsorointi",
|
||||
"/no/vi-sponser",
|
||||
"/sv/vi-sponsrar",
|
||||
"/de/scandic-entdecken/wlan",
|
||||
"/en/explore-scandic/wifi",
|
||||
"/da/oplev-scandic/wifi",
|
||||
"/fi/koe-scandic/maksuton-internetyhteys",
|
||||
"/no/utforsk-scandic/wifi",
|
||||
"/sv/utforska-scandic/wi-fi",
|
||||
"/de/kundenbetreuung/haufig-gestellte-fragen/nutzung-der-internetseite",
|
||||
"/en/customer-service/frequently-asked-questions/using-the-website",
|
||||
"/da/kundeservice/sporgsmal-og-svar/om-scandics-website",
|
||||
"/fi/asiakaspalvelu/usein-kysytyt-kysymykset/tietoja-internetsivuista",
|
||||
"/no/kundeservice/sporsmal-og-svar/bruk-av-nettsiden",
|
||||
"/sv/kundservice/fragor-och-svar/om-scandics-webbplats",
|
||||
"/de/current-content-page",
|
||||
"/en/current-content-page",
|
||||
"/da/current-content-page",
|
||||
"/fi/current-content-page",
|
||||
"/no/current-content-page",
|
||||
"/sv/current-content-page",
|
||||
].includes(pathname)
|
||||
|
||||
const { toggleDropdown, isMyPagesMobileMenuOpen, isHamburgerMenuOpen } =
|
||||
useDropdownStore()
|
||||
|
||||
function handleMyPagesMobileMenuClick() {
|
||||
// Only track click when opening it
|
||||
if (!isMyPagesMobileMenuOpen) {
|
||||
trackClick("profile picture icon")
|
||||
}
|
||||
toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.mainMenu}>
|
||||
<div
|
||||
className={styles.container}
|
||||
itemScope
|
||||
itemType="http://schema.org/Organization"
|
||||
>
|
||||
<meta itemProp="name" content="Scandic" />
|
||||
<nav className={styles.navBar}>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={`${styles.expanderBtn} ${isHamburgerMenuOpen ? styles.expanded : ""}`}
|
||||
onClick={() => toggleDropdown(DropdownTypeEnum.HamburgerMenu)}
|
||||
type="button"
|
||||
>
|
||||
<span className={styles.iconBars}></span>
|
||||
<span className={styles.hiddenAccessible}>Menu</span>
|
||||
</button>
|
||||
|
||||
<a className={styles.logoLink} href={homeHref}>
|
||||
<span className={styles.hiddenAccessible}>{frontpageLinkText}</span>
|
||||
<Image
|
||||
alt="Scandic Hotels logo"
|
||||
className={styles.logo}
|
||||
data-js="scandiclogoimg"
|
||||
data-nosvgsrc="/_static/img/scandic-logotype.png"
|
||||
itemProp="logo"
|
||||
height={22}
|
||||
src={logo.url}
|
||||
width={logo.dimension.width}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<ul
|
||||
className={`${styles.listWrapper} ${isHamburgerMenuOpen ? styles.isOpen : ""}`}
|
||||
>
|
||||
<ul className={styles.linkRow}>
|
||||
{user ? (
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<Link
|
||||
className={styles.mobileLinkButton}
|
||||
href={myPages[lang]}
|
||||
>
|
||||
{intl.formatMessage({ id: "My pages" })}
|
||||
</Link>
|
||||
</li>
|
||||
) : (
|
||||
<>
|
||||
<li>
|
||||
<Image
|
||||
src="/_static/img/icon-scandic-friends.svg"
|
||||
alt="Scanidc Friends Logo"
|
||||
height={35}
|
||||
width={35}
|
||||
className={styles.scandicFriendsLogo}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<LoginButton
|
||||
position="hamburger menu"
|
||||
trackingId="loginStartHamburgerMenu"
|
||||
className={styles.mobileLinkButton}
|
||||
>
|
||||
{intl.formatMessage({ id: "Log in" })}
|
||||
</LoginButton>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<span className={styles.mobileSeparator} />
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<a className={styles.mobileLinkButton} href="">
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className={styles.mainLinks}>
|
||||
{links.map((link, i) => (
|
||||
<li className={styles.li} key={link.href + i}>
|
||||
<Link
|
||||
className={styles.link}
|
||||
href={link.href}
|
||||
trackingId={
|
||||
isHamburgerMenuOpen
|
||||
? `hamburger - ${link.title}`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<ul className={styles.mobileList}>
|
||||
{topMenuMobileLinks.map(({ link }, i) => (
|
||||
<li className={styles.mobileLi} key={link.href + i}>
|
||||
<Link
|
||||
className={styles.mobileLink}
|
||||
href={link.href}
|
||||
trackingId={
|
||||
isHamburgerMenuOpen
|
||||
? `hamburger - ${link.title}`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{languageSwitcher ? (
|
||||
<li className={styles.mobileLi}>{languageSwitcher}</li>
|
||||
) : null}
|
||||
{!!user ? (
|
||||
<li className={`${styles.mobileLi} ${styles.logout}`}>
|
||||
<Link
|
||||
href={logout[lang]}
|
||||
className={styles.mobileLink}
|
||||
prefetch={false}
|
||||
>
|
||||
{intl.formatMessage({ id: "Log out" })}
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
<div className={styles.buttonContainer}>
|
||||
<div className={styles.myPagesDesktopLink}>
|
||||
{!isThreeStaticPagesPathnames && user ? (
|
||||
<Link className={styles.link} href={myPages[lang]}>
|
||||
{intl.formatMessage({ id: "My pages" })}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
<BookingButton href={bookingHref} />
|
||||
{!isThreeStaticPagesPathnames && myPagesMobileDropdown && user ? (
|
||||
<div
|
||||
role="button"
|
||||
onClick={handleMyPagesMobileMenuClick}
|
||||
className={styles.avatarButton}
|
||||
>
|
||||
<Avatar firstName={user.firstName} lastName={user.lastName} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{isMyPagesMobileMenuOpen ? myPagesMobileDropdown : null}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function MainMenuSkeleton() {
|
||||
return (
|
||||
<div className={styles.mainMenu}>
|
||||
<div
|
||||
className={styles.container}
|
||||
itemScope
|
||||
itemType="http://schema.org/Organization"
|
||||
>
|
||||
<meta itemProp="name" content="Scandic" />
|
||||
<nav className={styles.navBar}>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={styles.expanderBtn}
|
||||
type="button"
|
||||
>
|
||||
<span className={styles.iconBars}></span>
|
||||
<span className={styles.hiddenAccessible}>Menu</span>
|
||||
</button>
|
||||
|
||||
<a className={styles.logoLink} href={""}>
|
||||
<Image
|
||||
alt="Scandic Hotels logo"
|
||||
className={styles.logo}
|
||||
data-js="scandiclogoimg"
|
||||
itemProp="logo"
|
||||
height={20}
|
||||
src={"/_static/img/scandic-logotype.png"}
|
||||
width={200}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<ul className={styles.listWrapper}>
|
||||
{Array.from({ length: 5 }, () => "").map((_, i) => (
|
||||
<li className={`${styles.li} ${styles.skeletonWrapper}`} key={i}>
|
||||
<SkeletonShimmer height="22px" width="130px" />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className={styles.buttonContainer}>
|
||||
<BookingButton href={""} />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
.mainMenu {
|
||||
background-color: var(--Main-Grey-White);
|
||||
background-image: none;
|
||||
box-shadow: 0px 1.001px 1.001px 0px rgba(0, 0, 0, 0.05);
|
||||
max-height: 100%;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
height: var(--current-mobile-site-header-height);
|
||||
max-width: var(--max-width-navigation);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-sizing: content-box;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mainLinks {
|
||||
background-color: #f3f2f1;
|
||||
}
|
||||
|
||||
.navBar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 80px 1fr;
|
||||
grid-template-areas: "expanderBtn logoLink . buttonContainer";
|
||||
grid-template-rows: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expanderBtn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
justify-self: flex-start;
|
||||
padding: 11px 8px 16px;
|
||||
transition: 0.3s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconBars,
|
||||
.iconBars::after,
|
||||
.iconBars::before {
|
||||
background: #757575;
|
||||
border-radius: 2.3px;
|
||||
display: inline-block;
|
||||
height: 5px;
|
||||
position: relative;
|
||||
transition: 0.3s;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.iconBars::after,
|
||||
.iconBars::before {
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
transform-origin: 2.286px center;
|
||||
}
|
||||
|
||||
.iconBars::after {
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
.iconBars::before {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.expanded .iconBars {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.expanded .iconBars::after,
|
||||
.expanded .iconBars::before {
|
||||
top: 0;
|
||||
transform-origin: 50% 50%;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.expanded .iconBars::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.expanded .iconBars::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
height: 1px;
|
||||
left: -100000em;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.logoLink {
|
||||
/*padding: 16px 0 8px;*/
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e3e0db;
|
||||
display: none;
|
||||
list-style: none;
|
||||
overflow-y: visible;
|
||||
padding-bottom: 20px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.listWrapper.isOpen {
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.li {
|
||||
border-bottom: none;
|
||||
display: block;
|
||||
line-height: 17px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: #7f7369;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.linkRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #e3e0db;
|
||||
background-color: #f3f2f1 !important;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.scandicFriendsLogo {
|
||||
margin-right: 4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.mobileLinkRow {
|
||||
margin: 6px 0;
|
||||
padding: 15px 15px 15px 5px;
|
||||
}
|
||||
|
||||
.mobileLinkButton {
|
||||
font-size: 14px;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif !important;
|
||||
font-weight: 700;
|
||||
background-color: transparent !important;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
outline-color: transparent;
|
||||
}
|
||||
|
||||
.mobileSeparator {
|
||||
border-left: 1px solid #e3e0db;
|
||||
height: 35px;
|
||||
margin-bottom: -12px;
|
||||
margin-left: -1px;
|
||||
margin-top: -12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobileList {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.mobileLi {
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
line-height: 22.4px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.mobileLi.logout {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mobileLink {
|
||||
color: #000;
|
||||
display: block;
|
||||
font-family: Helvetica !important;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: inline-flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
gap: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.myPagesDesktopLink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.skeletonWrapper {
|
||||
padding: 4px 10px;
|
||||
height: 100%;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1367px) {
|
||||
.navBar {
|
||||
grid-template-columns: 140px auto 1fr;
|
||||
height: 82.4px;
|
||||
align-content: center;
|
||||
padding: 0px 0px var(--Spacing-x-quarter) 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logoLink {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 27px 30px 26px 0;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mainMenu {
|
||||
box-shadow: none;
|
||||
background-color: hsla(0, 0%, 100%, 0.95);
|
||||
position: relative;
|
||||
z-index: unset;
|
||||
height: 82.4px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 var(--Spacing-x5) 0 120px;
|
||||
}
|
||||
|
||||
.mainLinks {
|
||||
padding-top: 2.5px;
|
||||
background-color: transparent;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expanderBtn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 102.17px;
|
||||
height: 100%;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
border-top: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 0;
|
||||
position: static;
|
||||
width: 100%;
|
||||
padding-bottom: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.listWrapper.isOpen {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.li {
|
||||
display: inline-grid;
|
||||
float: none;
|
||||
vertical-align: middle;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.link {
|
||||
background-image: none;
|
||||
font-family: var(--typography-Body-Regular-fontFamily);
|
||||
font-size: var(--typography-Body-Regular-fontSize);
|
||||
font-feature-settings:
|
||||
"clig" off,
|
||||
"liga" off;
|
||||
font-weight: 600;
|
||||
line-height: 125%;
|
||||
padding: 30px 15px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-black); /* Design system should return #404040 */
|
||||
}
|
||||
|
||||
.linkRow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobileList {
|
||||
display: none;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.mobileLi {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.avatarButton {
|
||||
display: none;
|
||||
}
|
||||
.myPagesDesktopLink {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { logout } from "@/constants/routes/handleAuth"
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./my-pages-mobile-dropdown.module.css"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
|
||||
|
||||
export default function MyPagesMobileDropdown() {
|
||||
const intl = useIntl()
|
||||
const { toggleDropdown, isMyPagesMobileMenuOpen } = useDropdownStore()
|
||||
|
||||
const handleOnClick = () => toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`${styles.navigationMenu} ${isMyPagesMobileMenuOpen ? styles.navigationMenuIsOpen : ""}`}
|
||||
>
|
||||
<Title textTransform="capitalize" level="h5">
|
||||
<div className={styles.heading}>
|
||||
{intl.formatMessage({ id: "My pages" })}
|
||||
</div>
|
||||
</Title>
|
||||
|
||||
<List>
|
||||
<PrimaryLinks handleOnClick={handleOnClick} />
|
||||
</List>
|
||||
<List>
|
||||
<SecondaryLinks handleOnClick={handleOnClick} />
|
||||
</List>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function List({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.dividerWrapper}>
|
||||
<Divider color="subtle" />
|
||||
</div>
|
||||
<ul className={styles.dropdownWrapper}>
|
||||
<ul className={styles.dropdownLinks}>{children}</ul>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PrimaryLinks({ handleOnClick }: { handleOnClick: () => void }) {
|
||||
const {
|
||||
data: myPagesNavigation,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
} = useMyPagesNavigation()
|
||||
|
||||
const primaryLinks = myPagesNavigation?.primaryLinks ?? []
|
||||
return (
|
||||
<>
|
||||
{isLoading && <Skeletons count={4} />}
|
||||
{isSuccess &&
|
||||
primaryLinks.map((link, i) => (
|
||||
<li key={link.href + i}>
|
||||
<Link
|
||||
href={link.href}
|
||||
partialMatch
|
||||
size={"regular"}
|
||||
variant="myPageMobileDropdown"
|
||||
color="burgundy"
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
function SecondaryLinks({ handleOnClick }: { handleOnClick: () => void }) {
|
||||
const {
|
||||
data: myPagesNavigation,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
} = useMyPagesNavigation()
|
||||
|
||||
const secondaryLinks = myPagesNavigation?.secondaryLinks ?? []
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && <Skeletons count={3} />}
|
||||
{isSuccess &&
|
||||
secondaryLinks.map((link, i) => (
|
||||
<li key={link.href + i}>
|
||||
<Link
|
||||
href={link.href}
|
||||
partialMatch
|
||||
size={"small"}
|
||||
variant="myPageMobileDropdown"
|
||||
color="burgundy"
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<Link
|
||||
href={logout[lang]}
|
||||
prefetch={false}
|
||||
size="small"
|
||||
color="burgundy"
|
||||
variant="myPageMobileDropdown"
|
||||
>
|
||||
{intl.formatMessage({ id: "Log out" })}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Skeletons({ count }: { count: number }) {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<li key={i} className={styles.skeletonItem}>
|
||||
<SkeletonShimmer width="100px" height="20px" />
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function useMyPagesNavigation() {
|
||||
const lang = useLang()
|
||||
return trpc.navigation.myPages.useQuery({ lang })
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
.navigationMenu {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e3e0db;
|
||||
display: none;
|
||||
list-style: none;
|
||||
overflow-y: visible;
|
||||
margin: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.navigationMenu.navigationMenuIsOpen {
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.dropdownWrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20px var(--Spacing-x2);
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
background-color: var(--Main-Grey-White);
|
||||
box-shadow:
|
||||
0px 276px 77px 0px rgba(0, 0, 0, 0),
|
||||
0px 177px 71px 0px rgba(0, 0, 0, 0.01),
|
||||
0px 99px 60px 0px rgba(0, 0, 0, 0.05),
|
||||
0px 44px 44px 0px rgba(0, 0, 0, 0.09),
|
||||
0px 11px 24px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dividerWrapper {
|
||||
background-color: var(--Main-Grey-White);
|
||||
padding: 0 var(--Spacing-x2);
|
||||
margin: auto;
|
||||
place-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: 20px var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.dropdownLinks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-half);
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.navigationMenu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navigationMenu.navigationMenuIsOpen {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.skeletonItem {
|
||||
padding: var(--Spacing-x1);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.banner {
|
||||
align-items: center;
|
||||
background: #606060;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reloadBtn {
|
||||
color: #fff;
|
||||
background-color: #00838e;
|
||||
border: 0;
|
||||
border-radius: 18px;
|
||||
outline: 0 none;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import styles from "./banner.module.css"
|
||||
|
||||
export default function OfflineBanner() {
|
||||
return (
|
||||
<div className={`${styles.banner} ${styles.hidden}`}>
|
||||
You are offline, some content may be out of date.
|
||||
<button className={styles.reloadBtn} type="button">Reload</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
112
apps/scandic-web/components/Current/Header/TopMenu/index.tsx
Normal file
112
apps/scandic-web/components/Current/Header/TopMenu/index.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { logout } from "@/constants/routes/handleAuth"
|
||||
import { overview } from "@/constants/routes/myPages"
|
||||
import { getName } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import LoginButton from "@/components/LoginButton"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import styles from "./topMenu.module.css"
|
||||
|
||||
import type { TopMenuProps } from "@/types/components/current/header/topMenu"
|
||||
|
||||
function capitalize(str: string) {
|
||||
return str.charAt(0).toUpperCase().toUpperCase() + str.slice(1).toLowerCase()
|
||||
}
|
||||
|
||||
export default async function TopMenu({
|
||||
frontpageLinkText,
|
||||
homeHref,
|
||||
links,
|
||||
languageSwitcher,
|
||||
}: TopMenuProps) {
|
||||
const intl = await getIntl()
|
||||
const user = await getName()
|
||||
return (
|
||||
<div className={styles.topMenu}>
|
||||
<div className={styles.container}>
|
||||
<a className={styles.homeLink} href={homeHref}>
|
||||
{frontpageLinkText}
|
||||
</a>
|
||||
|
||||
<ul className={styles.list}>
|
||||
{languageSwitcher ? (
|
||||
<li className={styles.langSwitcher}>{languageSwitcher}</li>
|
||||
) : null}
|
||||
|
||||
{links.map(({ link }, i) => (
|
||||
<li key={link.href + i}>
|
||||
<a className={styles.link} href={link.href}>
|
||||
{link.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.sessionContainer}>
|
||||
{user ? (
|
||||
<>
|
||||
{user ? (
|
||||
<Link
|
||||
href={overview[getLang()]}
|
||||
className={styles.sessionLink}
|
||||
prefetch={false}
|
||||
>
|
||||
<span data-hj-suppress>{capitalize(user.firstName)}</span>
|
||||
</Link>
|
||||
) : null}
|
||||
<div className={styles.loginSeparator} />
|
||||
<Link
|
||||
href={logout[getLang()]}
|
||||
className={styles.sessionLink}
|
||||
prefetch={false}
|
||||
>
|
||||
{intl.formatMessage({ id: "Log out" })}
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<LoginButton
|
||||
position="hamburger menu"
|
||||
trackingId="loginStartTopMenu"
|
||||
className={`${styles.sessionLink} ${styles.loginLink}`}
|
||||
variant="default"
|
||||
size="small"
|
||||
>
|
||||
{intl.formatMessage({ id: "Log in" })}
|
||||
</LoginButton>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export async function TopMenuSkeleton() {
|
||||
const intl = await getIntl()
|
||||
const links = new Array(5).fill("")
|
||||
return (
|
||||
<div className={styles.topMenu}>
|
||||
<div className={styles.container}>
|
||||
<ul className={styles.list}>
|
||||
{links.map((_link, i) => (
|
||||
<li key={i} className={styles.skeletonWrapper}>
|
||||
<SkeletonShimmer width="100px" height="16px" />
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.sessionContainer}>
|
||||
<LoginButton
|
||||
position="hamburger menu"
|
||||
trackingId="loginStartTopMenu"
|
||||
className={`${styles.sessionLink} ${styles.loginLink}`}
|
||||
variant="default"
|
||||
size="small"
|
||||
>
|
||||
{intl.formatMessage({ id: "Log in" })}
|
||||
</LoginButton>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
.topMenu {
|
||||
background-color: #8d3a7c;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.homeLink {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
line-height: 22.4px;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
text-decoration: none;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
.langSwitcher {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.skeletonWrapper {
|
||||
padding: 4px 10px;
|
||||
height: 30px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.container {
|
||||
padding: 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.topMenu {
|
||||
background-color: #3d3835;
|
||||
display: block;
|
||||
}
|
||||
.list {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.loginContainer {
|
||||
margin-left: 10px;
|
||||
background-color: #f3f2f1;
|
||||
}
|
||||
|
||||
.loginLink {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
color: #000;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif !important;
|
||||
}
|
||||
|
||||
.sessionContainer {
|
||||
margin-left: 10px;
|
||||
background-color: #f3f2f1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loginSeparator {
|
||||
height: 15px;
|
||||
border-right: 1px solid #000;
|
||||
}
|
||||
|
||||
.sessionLink {
|
||||
padding: 4px 15px;
|
||||
color: #000;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif !important;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sessionLink.loginLink {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
}
|
||||
14
apps/scandic-web/components/Current/Header/header.module.css
Normal file
14
apps/scandic-web/components/Current/Header/header.module.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.header {
|
||||
display: grid;
|
||||
background-color: var(--Main-Grey-White);
|
||||
position: relative;
|
||||
z-index: var(--header-z-index);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 950px) {
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--header-z-index);
|
||||
}
|
||||
}
|
||||
52
apps/scandic-web/components/Current/Header/index.tsx
Normal file
52
apps/scandic-web/components/Current/Header/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { homeHrefs } from "@/constants/homeHrefs"
|
||||
import { env } from "@/env/server"
|
||||
import { getCurrentHeader, getName } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import LanguageSwitcher from "./LanguageSwitcher"
|
||||
import { MainMenu } from "./MainMenu"
|
||||
import MyPagesMobileDropdown from "./MyPagesMobileDropdown"
|
||||
import OfflineBanner from "./OfflineBanner"
|
||||
import TopMenu from "./TopMenu"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
export default async function Header() {
|
||||
const lang = getLang()
|
||||
const [data, user] = await Promise.all([getCurrentHeader(lang), getName()])
|
||||
|
||||
if (!data?.header) {
|
||||
return null
|
||||
}
|
||||
|
||||
const homeHref = homeHrefs[env.NODE_ENV][getLang()]
|
||||
const { frontpageLinkText, logo, menu, topMenu } = data.header
|
||||
|
||||
const topMenuMobileLinks = topMenu.links
|
||||
.filter((link) => link.show_on_mobile)
|
||||
.sort((a, b) => (a.sort_order_mobile < b.sort_order_mobile ? 1 : -1))
|
||||
|
||||
return (
|
||||
<header className={styles.header} role="banner">
|
||||
<OfflineBanner />
|
||||
<TopMenu
|
||||
frontpageLinkText={frontpageLinkText}
|
||||
homeHref={homeHref}
|
||||
links={topMenu.links}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
/>
|
||||
<MainMenu
|
||||
frontpageLinkText={frontpageLinkText}
|
||||
homeHref={homeHref}
|
||||
links={menu.links}
|
||||
logo={logo}
|
||||
topMenuMobileLinks={topMenuMobileLinks}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
myPagesMobileDropdown={<MyPagesMobileDropdown />}
|
||||
bookingHref={homeHref}
|
||||
user={user}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user