diff --git a/app/[lang]/(live)/@header/[...paths]/@myPagesMobileDropdown/page.tsx b/app/[lang]/(live)/@header/[...paths]/@myPagesMobileDropdown/page.tsx new file mode 100644 index 000000000..1c912e168 --- /dev/null +++ b/app/[lang]/(live)/@header/[...paths]/@myPagesMobileDropdown/page.tsx @@ -0,0 +1,14 @@ +import { logout } from "@/constants/routes/handleAuth" +import { serverClient } from "@/lib/trpc/server" + +import MyPagesMobileDropdown from "@/components/Current/Header/MyPagesMobileDropdown" + +import { LangParams, PageArgs } from "@/types/params" + +export default async function MyPagesMobileDropdownPage({ + params, +}: PageArgs) { + const navigation = await serverClient().contentstack.myPages.navigation.get() + if (!navigation) return null + return +} diff --git a/app/[lang]/(live)/@header/[...paths]/layout.tsx b/app/[lang]/(live)/@header/[...paths]/layout.tsx index 8b88b75e1..73a42092b 100644 --- a/app/[lang]/(live)/@header/[...paths]/layout.tsx +++ b/app/[lang]/(live)/@header/[...paths]/layout.tsx @@ -5,8 +5,15 @@ import { LangParams, PageArgs } from "@/types/params" export default function HeaderLayout({ params, languageSwitcher, + myPagesMobileDropdown, }: PageArgs & { languageSwitcher: React.ReactNode -}) { - return
+} & { myPagesMobileDropdown: React.ReactNode }) { + return ( +
+ ) } diff --git a/app/[lang]/(live)/@header/page.tsx b/app/[lang]/(live)/@header/page.tsx index 53bf8ae11..9e5e41636 100644 --- a/app/[lang]/(live)/@header/page.tsx +++ b/app/[lang]/(live)/@header/page.tsx @@ -1,14 +1,20 @@ import { baseUrls } from "@/constants/routes/baseUrls" +import { serverClient } from "@/lib/trpc/server" import Header from "@/components/Current/Header" import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher" +import MyPagesMobileDropdown from "@/components/Current/Header/MyPagesMobileDropdown" import { LangParams, PageArgs } from "@/types/params" export default async function HeaderPage({ params }: PageArgs) { + const navigation = await serverClient().contentstack.myPages.navigation.get() return (
+ } languageSwitcher={} /> ) diff --git a/app/[lang]/(live-current)/@myPagesMobileDropdown/page.tsx b/app/[lang]/(live-current)/@myPagesMobileDropdown/page.tsx new file mode 100644 index 000000000..c67a198fd --- /dev/null +++ b/app/[lang]/(live-current)/@myPagesMobileDropdown/page.tsx @@ -0,0 +1,15 @@ +import { serverClient } from "@/lib/trpc/server" + +import MyPagesMobileDropdown from "@/components/Current/Header/MyPagesMobileDropdown" + +import { LangParams, PageArgs } from "@/types/params" + +export default async function MyPagesMobileDropdownPage({ + params, +}: PageArgs) { + const navigation = await serverClient().contentstack.myPages.navigation.get() + if (!navigation) { + return null + } + return +} diff --git a/app/[lang]/(live-current)/layout.tsx b/app/[lang]/(live-current)/layout.tsx index ce6ac8a4d..9712b1a86 100644 --- a/app/[lang]/(live-current)/layout.tsx +++ b/app/[lang]/(live-current)/layout.tsx @@ -24,8 +24,11 @@ export default async function RootLayout({ children, params, languageSwitcher, + myPagesMobileDropdown, }: React.PropsWithChildren< - LayoutArgs & { languageSwitcher: React.ReactNode } + LayoutArgs & { languageSwitcher: React.ReactNode } & { + myPagesMobileDropdown: React.ReactNode + } >) { const { defaultLocale, locale, messages } = await getIntl() return ( @@ -82,7 +85,11 @@ export default async function RootLayout({ -
+
{children}
diff --git a/components/Current/Header/BookingButton/bookingButton.module.css b/components/Current/Header/BookingButton/bookingButton.module.css index c19ba0855..797cd377b 100644 --- a/components/Current/Header/BookingButton/bookingButton.module.css +++ b/components/Current/Header/BookingButton/bookingButton.module.css @@ -31,7 +31,7 @@ text-decoration: underline; } -@media screen and (min-width: 950px) { +@media screen and (min-width: 1367px) { .button { font-weight: 600; font-size: 16px; diff --git a/components/Current/Header/MainMenu/index.tsx b/components/Current/Header/MainMenu/index.tsx index 1c84c7e3e..086631277 100644 --- a/components/Current/Header/MainMenu/index.tsx +++ b/components/Current/Header/MainMenu/index.tsx @@ -4,8 +4,10 @@ import { useIntl } from "react-intl" import { login } from "@/constants/routes/handleAuth" import { myPages } from "@/constants/routes/myPages" +import useDropdownStore from "@/stores/main-menu" import Image from "@/components/Image" +import Avatar from "@/components/MyPages/Avatar" import Link from "@/components/TempDesignSystem/Link" import BookingButton from "../BookingButton" @@ -21,16 +23,19 @@ export function MainMenu({ logo, topMenuMobileLinks, languageSwitcher, + myPagesMobileDropdown, bookingHref, - isLoggedIn, + user, lang, }: MainMenuProps) { const intl = useIntl() - const [isOpen, setIsOpen] = useState(false) - function toogleIsOpen() { - setIsOpen((prevIsOpen) => !prevIsOpen) - } + const { + isHamburgerMenuOpen, + isMyPagesMobileMenuOpen, + toggleHamburgerMenu, + toggleMyPagesMobileMenu, + } = useDropdownStore() return (
@@ -43,8 +48,8 @@ export function MainMenu({
diff --git a/components/Current/Header/MainMenu/mainMenu.module.css b/components/Current/Header/MainMenu/mainMenu.module.css index 7a824eb34..ebfdd8197 100644 --- a/components/Current/Header/MainMenu/mainMenu.module.css +++ b/components/Current/Header/MainMenu/mainMenu.module.css @@ -1,21 +1,20 @@ .mainMenu { - background-color: #fff; + background-color: var(--Main-Grey-White); background-image: none; - box-shadow: 0 0 7px rgba(0, 0, 0, 0.75); + box-shadow: 0px 1.001px 1.001px 0px rgba(0, 0, 0, 0.05); max-height: 100%; overflow: visible; position: fixed; top: 0; width: 100%; z-index: 99999; - height: 52.39px; + height: 70.047px; } .container { box-sizing: content-box; height: 100%; margin: 0 auto; - max-width: 1200px; padding: 0; } @@ -26,8 +25,11 @@ .navBar { display: grid; grid-template-columns: 1fr 80px 1fr; + grid-template-columns: auto auto 1fr auto; + grid-template-areas: "expanderBtn logoLink . buttonContainer"; grid-template-rows: 100%; height: 100%; + padding: 0 var(--Spacing-x2); } .expanderBtn { @@ -46,7 +48,7 @@ background: #757575; border-radius: 2.3px; display: inline-block; - height: 5px; + height: 3px; position: relative; transition: 0.3s; width: 32px; @@ -98,16 +100,19 @@ } .logoLink { - display: inline; + /*padding: 16px 0 8px;*/ + display: inline-flex; + align-items: center; height: 100%; width: 80px; - padding: 16px 0 8px; + padding-left: var(--Spacing-x1); } .logo { width: 80px; object-fit: fill; } + .listWrapper { background-color: #fff; border-top: 1px solid #e3e0db; @@ -175,13 +180,8 @@ padding: 15px 15px 15px 5px; } -.loggedInLogo { - height: 35px; - width: 35px; - border-radius: 50px; - background-color: #000; +.avatarWrapper { margin-right: 4px; - margin-left: -4px; } .mobileLinkButton { @@ -228,50 +228,56 @@ } .buttonContainer { - display: flex; + display: inline-flex; justify-content: flex-end; align-items: center; margin-right: 8px; + gap: var(--Spacing-x3); } -@media screen and (min-width: 950px) { +.myPagesDesktopLink { + display: none; +} + +@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: 85.09px; + height: 82.4px; } .container { - padding: 0px 30px; + padding: 0 var(--Spacing-x5) 0 120px; } .mainLinks { padding-top: 2.5px; background-color: transparent; - } - - .navBar { - grid-template-columns: 132.18px 1fr auto; - align-content: center; - padding-bottom: 2px; - - overflow: hidden; + height: 100%; } .expanderBtn { display: none; } - .logoLink { - display: flex; - height: 100%; - padding: 30px 10px 30px 15px; - width: auto; - align-items: center; - } - .logo { width: 102.17px; height: 100%; @@ -282,9 +288,11 @@ border-top: none; display: flex; align-items: center; - padding-bottom: 0; + padding-top: 0; position: static; width: 100%; + padding-bottom: 0px; + height: 100%; } .listWrapper.isOpen { @@ -292,7 +300,7 @@ } .li { - display: table-cell; + display: inline-grid; float: none; vertical-align: middle; line-height: 1.15; @@ -300,10 +308,16 @@ .link { background-image: none; - font-family: Helvetica, Arial, sans-serif; - font-weight: 700; - line-height: 1.15; + 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 { @@ -322,25 +336,11 @@ .buttonContainer { margin-right: 0; } -} -@media (min-width: 1200px) { - .mainMenu { - height: 82.4px; + .avatarButton { + display: none; } - .navBar { - grid-template-columns: 140px auto 1fr; - height: 82.4px; - } - - .logoLink { - display: inline-block; - width: 100%; - padding: 27px 30px 26px 0; - text-align: center; - } - - .listWrapper { - padding-top: 0; + .myPagesDesktopLink { + display: block; } } diff --git a/components/Current/Header/MyPagesMobileDropdown/index.tsx b/components/Current/Header/MyPagesMobileDropdown/index.tsx new file mode 100644 index 000000000..f79865496 --- /dev/null +++ b/components/Current/Header/MyPagesMobileDropdown/index.tsx @@ -0,0 +1,80 @@ +"use client" +import { Fragment } from "react" +import { useIntl } from "react-intl" + +import { Lang } from "@/constants/languages" +import { logout } from "@/constants/routes/handleAuth" +import { navigationQueryRouter } from "@/server/routers/contentstack/myPages/navigation/query" +import useDropdownStore from "@/stores/main-menu" + +import Divider from "@/components/TempDesignSystem/Divider" +import Link from "@/components/TempDesignSystem/Link" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./my-pages-mobile-dropdown.module.css" + +type Navigation = Awaited> + +export default function MyPagesMobileDropdown({ + navigation, + lang, +}: { + navigation: Navigation + lang: Lang | null +}) { + const { formatMessage } = useIntl() + const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = + useDropdownStore() + + if (!navigation) { + return null + } + + return ( + + ) +} diff --git a/components/Current/Header/MyPagesMobileDropdown/my-pages-mobile-dropdown.module.css b/components/Current/Header/MyPagesMobileDropdown/my-pages-mobile-dropdown.module.css new file mode 100644 index 000000000..f42c1dfad --- /dev/null +++ b/components/Current/Header/MyPagesMobileDropdown/my-pages-mobile-dropdown.module.css @@ -0,0 +1,63 @@ +.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; + } +} diff --git a/components/Current/Header/header.module.css b/components/Current/Header/header.module.css index 13035f53a..6e1001420 100644 --- a/components/Current/Header/header.module.css +++ b/components/Current/Header/header.module.css @@ -2,8 +2,8 @@ display: grid; } -@media screen and (max-width: 950px) { +@media screen and (max-width: 1366px) { .header { - height: 50px; + height: 70.047px; } } diff --git a/components/Current/Header/index.tsx b/components/Current/Header/index.tsx index b72164ab4..2f1425902 100644 --- a/components/Current/Header/index.tsx +++ b/components/Current/Header/index.tsx @@ -15,11 +15,18 @@ import { LangParams } from "@/types/params" export default async function Header({ lang, languageSwitcher, -}: LangParams & { languageSwitcher: React.ReactNode }) { - const data = await serverClient().contentstack.base.header({ - lang, - }) - const session = await auth() + myPagesMobileDropdown, +}: LangParams & { languageSwitcher: React.ReactNode } & { + myPagesMobileDropdown: React.ReactNode +}) { + const [data, session] = await Promise.all([ + serverClient().contentstack.base.header({ + lang, + }), + auth(), + ]) + + const user = !!session ? await serverClient().user.get() : null if (!data) { return null @@ -49,8 +56,9 @@ export default async function Header({ logo={logo} topMenuMobileLinks={topMenuMobileLinks} languageSwitcher={languageSwitcher} + myPagesMobileDropdown={myPagesMobileDropdown} bookingHref={homeHref} - isLoggedIn={!!session} + user={user} lang={lang} />
diff --git a/components/MyPages/Avatar/avatar.module.css b/components/MyPages/Avatar/avatar.module.css new file mode 100644 index 000000000..0beb3c3d2 --- /dev/null +++ b/components/MyPages/Avatar/avatar.module.css @@ -0,0 +1,27 @@ +.avatar { + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + overflow: hidden; + cursor: pointer; + width: 35px; + height: 35px; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.05); +} + +.avatarInitialsText { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--Theme-Primary-Dark-Surface-Normal); + color: var(--Theme-Primary-Dark-On-Surface-Text); + font-size: var(--typography-Caption-Bold-fontSize); + font-family: var(--typography-Body-Regular-fontFamily); + font-weight: var(--typography-Caption-Bold-fontWeight); + line-height: 150%; + letter-spacing: 0.096px; +} diff --git a/components/MyPages/Avatar/index.tsx b/components/MyPages/Avatar/index.tsx new file mode 100644 index 000000000..d8f97bdd3 --- /dev/null +++ b/components/MyPages/Avatar/index.tsx @@ -0,0 +1,20 @@ +import { getInitials } from "@/utils/user" + +import styles from "./avatar.module.css" + +import { User } from "@/types/user" + +export default function Avatar({ + firstName, + lastName, +}: { + firstName: User["firstName"] + lastName: User["lastName"] +}) { + const initials = getInitials(firstName, lastName) + return ( + + {initials} + + ) +} diff --git a/components/TempDesignSystem/Divider/divider.module.css b/components/TempDesignSystem/Divider/divider.module.css index 29cd94f74..c4dfba6e4 100644 --- a/components/TempDesignSystem/Divider/divider.module.css +++ b/components/TempDesignSystem/Divider/divider.module.css @@ -17,6 +17,10 @@ border-bottom-color: var(--Theme-Primary-Light-On-Surface-Divider); } +.subtle { + border-bottom-color: var(--Base-Border-Subtle); +} + .opacity100 { opacity: 1; } diff --git a/components/TempDesignSystem/Divider/variants.ts b/components/TempDesignSystem/Divider/variants.ts index 42ef16c49..8a5564fcf 100644 --- a/components/TempDesignSystem/Divider/variants.ts +++ b/components/TempDesignSystem/Divider/variants.ts @@ -7,6 +7,7 @@ export const dividerVariants = cva(styles.divider, { color: { burgundy: styles.burgundy, peach: styles.peach, + subtle: styles.subtle, }, opacity: { 100: styles.opacity100, diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index 6aa0c5c60..92006940c 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -29,6 +29,33 @@ text-decoration: underline; } +.myPageMobileDropdown { + color: var(--Scandic-Brand-Burgundy); + font-family: var(--typography-Body-Regular-fontFamily); + font-size: var(--typography-Body-Regular-fontSize); + line-height: var(--typography-Body-Regular-lineHeight); + letter-spacing: 0.096px; + padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1) + var(--Spacing-x-one-and-half); + width: 100%; + border-radius: var(--Corner-radius-Medium); + gap: 4px; + display: flex; + align-items: center; + gap: 4px; + align-self: stretch; +} + +.myPageMobileDropdown.active { + background-color: var(--Scandic-Brand-Pale-Peach); + border-radius: var(--Corner-radius-Medium); + font-family: var(--typography-Body-Underlined-fontFamily); + font-size: var(--typography-Body-Underlined-fontSize); + font-weight: var(--typography-Body-Underlined-fontWeight); + letter-spacing: var(--typography-Body-Underlined-letterSpacing); + line-height: var(--typography-Body-Underlined-lineHeight); +} + .shortcut { align-items: center; border-bottom: 0.5px solid var(--Scandic-Beige-20); diff --git a/components/TempDesignSystem/Link/variants.ts b/components/TempDesignSystem/Link/variants.ts index a49023324..c943da924 100644 --- a/components/TempDesignSystem/Link/variants.ts +++ b/components/TempDesignSystem/Link/variants.ts @@ -23,6 +23,7 @@ export const linkVariants = cva(styles.link, { default: styles.default, icon: styles.icon, myPage: styles.myPage, + myPageMobileDropdown: styles.myPageMobileDropdown, shortcut: styles.shortcut, sidebar: styles.sidebar, }, diff --git a/components/TempDesignSystem/Text/Title/title.module.css b/components/TempDesignSystem/Text/Title/title.module.css index 6ce9aa080..9040a0cab 100644 --- a/components/TempDesignSystem/Text/Title/title.module.css +++ b/components/TempDesignSystem/Text/Title/title.module.css @@ -68,6 +68,10 @@ text-decoration: var(--typography-Title-5-textDecoration); } +.capitalize { + text-transform: capitalize; +} + .regular { text-transform: none; } diff --git a/components/TempDesignSystem/Text/Title/variants.ts b/components/TempDesignSystem/Text/Title/variants.ts index 499e6ba29..a0fbd2bad 100644 --- a/components/TempDesignSystem/Text/Title/variants.ts +++ b/components/TempDesignSystem/Text/Title/variants.ts @@ -16,6 +16,7 @@ const config = { left: styles.left, }, textTransform: { + capitalize: styles.capitalize, regular: styles.regular, uppercase: styles.uppercase, }, diff --git a/stores/main-menu.ts b/stores/main-menu.ts new file mode 100644 index 000000000..cbd6f1004 --- /dev/null +++ b/stores/main-menu.ts @@ -0,0 +1,31 @@ +import { create } from "zustand" + +interface DropdownState { + isHamburgerMenuOpen: boolean + isMyPagesMobileMenuOpen: boolean + toggleHamburgerMenu: () => void + toggleMyPagesMobileMenu: () => void +} + +const useDropdownStore = create((set) => ({ + isHamburgerMenuOpen: false, + isMyPagesMobileMenuOpen: false, + toggleHamburgerMenu: () => + set((state) => { + // Close the other dropdown if it's open + if (!state.isHamburgerMenuOpen && state.isMyPagesMobileMenuOpen) { + set({ isMyPagesMobileMenuOpen: false }) + } + return { isHamburgerMenuOpen: !state.isHamburgerMenuOpen } + }), + toggleMyPagesMobileMenu: () => + set((state) => { + // Close the other dropdown if it's open + if (!state.isMyPagesMobileMenuOpen && state.isHamburgerMenuOpen) { + set({ isHamburgerMenuOpen: false }) + } + return { isMyPagesMobileMenuOpen: !state.isMyPagesMobileMenuOpen } + }), +})) + +export default useDropdownStore diff --git a/types/components/current/header/mainMenu.ts b/types/components/current/header/mainMenu.ts index 7604c12b1..4251fd529 100644 --- a/types/components/current/header/mainMenu.ts +++ b/types/components/current/header/mainMenu.ts @@ -5,6 +5,7 @@ import type { CurrentHeaderLink, TopMenuHeaderLink, } from "@/types/requests/currentHeader" +import { User } from "@/types/user" export type MainMenuProps = { frontpageLinkText: string @@ -13,7 +14,8 @@ export type MainMenuProps = { logo: Image topMenuMobileLinks: TopMenuHeaderLink[] languageSwitcher: React.ReactNode | null + myPagesMobileDropdown: React.ReactNode | null bookingHref: string - isLoggedIn: boolean + user: User | null lang: Lang } diff --git a/utils/user.ts b/utils/user.ts index c2ff7b8e2..9260118b0 100644 --- a/utils/user.ts +++ b/utils/user.ts @@ -6,3 +6,13 @@ export function getMembership(memberships: User["memberships"]) { membership.membershipType.toLowerCase() === "guestpr" || "scandicfriend's" ) } + +export function getInitials( + firstName: User["firstName"], + lastName: User["lastName"] +) { + if (!firstName || !lastName) return null + const firstInitial = firstName.charAt(0).toUpperCase() + const lastInitial = lastName.charAt(0).toUpperCase() + return `${firstInitial}${lastInitial}` +}