Merged in feat/SW-2999-cleanup (pull request #2810)
feat(SW-2999): cleanup current web * feat(SW-2999): cleanup current web * Merge master * Removed unused fonts Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
import Script from "next/script"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
export default function AdobeScript() {
|
||||
return env.ADOBE_SCRIPT_SRC ? (
|
||||
<Script data-cookieconsent="statistics" src={env.ADOBE_SCRIPT_SRC} />
|
||||
) : null
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import styles from "./contact.module.css"
|
||||
|
||||
import { type ContactNode, Section } from "@/types/requests/asides/contact"
|
||||
|
||||
export default function Contact({ sections, system: { locale } }: ContactNode) {
|
||||
if (!sections.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const visitingAddressMessage = getVisitingAddressMessage(locale)
|
||||
return (
|
||||
<section>
|
||||
{sections.map((section, idx) => {
|
||||
switch (section.__typename) {
|
||||
case Section.ContactBlockSectionsExtraInfo:
|
||||
return <p>{section.extra_info.text}</p>
|
||||
case Section.ContactBlockSectionsMailingAddress:
|
||||
return (
|
||||
<p key={`section-mail-${idx}`}>
|
||||
{section.mailing_address.name}
|
||||
<br />
|
||||
{section.mailing_address.street}
|
||||
<br />
|
||||
{section.mailing_address.zip} {section.mailing_address.city}
|
||||
<br />
|
||||
{section.mailing_address.country}
|
||||
</p>
|
||||
)
|
||||
case Section.ContactBlockSectionsPhone:
|
||||
return (
|
||||
<div
|
||||
className={styles.highlightBlock}
|
||||
key={`section-phone-${idx}`}
|
||||
>
|
||||
<h3>{section.phone.title}</h3>
|
||||
<div className={styles.phoneContainer}>
|
||||
<svg
|
||||
focusable="false"
|
||||
className={styles.phoneIcon}
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<use
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
xlinkHref="/_static/img/icons/sprites.svg#icon-phone"
|
||||
></use>
|
||||
</svg>
|
||||
|
||||
<a
|
||||
href={`tel:+${section.phone.number}`}
|
||||
itemProp="telephone"
|
||||
className={styles.phoneNumberLink}
|
||||
>
|
||||
+{section.phone.number}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case Section.ContactBlockSectionsTitle:
|
||||
return (
|
||||
<h2 className={styles.heading} key={`section-heading-${idx}`}>
|
||||
{section.title.text}
|
||||
</h2>
|
||||
)
|
||||
case Section.ContactBlockSectionsVisitingAddress:
|
||||
return (
|
||||
<p key={`section-visiting-address-${idx}`}>
|
||||
{visitingAddressMessage}: {section.visiting_address.street}{" "}
|
||||
</p>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function getVisitingAddressMessage(lang: Lang) {
|
||||
switch (lang) {
|
||||
case Lang.sv:
|
||||
return "Besöksadress"
|
||||
case Lang.en:
|
||||
return "Visiting address"
|
||||
case Lang.da:
|
||||
return "Besøgsadresse"
|
||||
case Lang.de:
|
||||
return "Besuchsadresse"
|
||||
case Lang.fi:
|
||||
return "Vierailuosoite"
|
||||
case Lang.no:
|
||||
return "Besøksadresse"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
.highlightBlock {
|
||||
padding: 10px 10px 15px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-family: BrandonText-Bold, Arial, Helvetica, sans-serif;
|
||||
font-size: 1.375rem;
|
||||
line-height: 1.1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
color: #483729;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.phoneContainer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.phoneNumberLink {
|
||||
line-height: 1.1em;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
font-size: 1.125rem;
|
||||
color: #00838e;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.phoneIcon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
fill: #00838e;
|
||||
}
|
||||
|
||||
.p {
|
||||
color: #333;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 1em;
|
||||
padding-top: 7px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.phoneNumberLink:active,
|
||||
.phoneNumberLink:hover {
|
||||
outline: 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.heading {
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
|
||||
.phoneNumberLink {
|
||||
line-height: 1.1em;
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Contact from "./Contact"
|
||||
|
||||
import type { ContactsProps } from "@/types/components/current/asides/contact"
|
||||
|
||||
export default function Contacts({ contacts }: ContactsProps) {
|
||||
return contacts.map((contact) => (
|
||||
<Contact key={contact.node.system.uid} {...contact.node} />
|
||||
))
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
"use client"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||
|
||||
import { renderOptions as currentRenderOptions } from "@/components/Current/currentRenderOptions"
|
||||
import DeprecatedJsonToHtml from "@/components/DeprecatedJsonToHtml"
|
||||
|
||||
import { renderOptions } from "./renderOptions"
|
||||
|
||||
import styles from "./puff.module.css"
|
||||
|
||||
import type { PuffProps } from "@/types/components/current/asides/puff"
|
||||
import { PuffStyleEnum } from "@/types/requests/puff"
|
||||
|
||||
export default function Puff({
|
||||
imageConnection,
|
||||
link,
|
||||
text,
|
||||
puff_style,
|
||||
title,
|
||||
}: PuffProps) {
|
||||
const router = useRouter()
|
||||
|
||||
switch (puff_style) {
|
||||
case PuffStyleEnum.button:
|
||||
function onClick() {
|
||||
router.push(link.href)
|
||||
}
|
||||
|
||||
return (
|
||||
<article>
|
||||
{imageConnection.edges.map((image) => (
|
||||
<Image
|
||||
alt={image.node.title}
|
||||
className={styles.image}
|
||||
height={image.node.dimension.height}
|
||||
key={image.node.system.uid}
|
||||
src={image.node.url}
|
||||
width={image.node.dimension.width}
|
||||
/>
|
||||
))}
|
||||
<section className={styles.content}>
|
||||
<DeprecatedJsonToHtml
|
||||
embeds={[]}
|
||||
nodes={text.json.children}
|
||||
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
|
||||
/>
|
||||
<div>
|
||||
<Button onPress={onClick}>{link.title || title}</Button>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
)
|
||||
case PuffStyleEnum.default:
|
||||
return (
|
||||
<a className={styles.link} href={link.href}>
|
||||
<article>
|
||||
{imageConnection.edges.map((image) => (
|
||||
<Image
|
||||
alt={image.node.title}
|
||||
className={styles.image}
|
||||
height={image.node.dimension.height}
|
||||
key={image.node.system.uid}
|
||||
src={image.node.url}
|
||||
width={image.node.dimension.width}
|
||||
/>
|
||||
))}
|
||||
<section className={styles.content}>
|
||||
<header>
|
||||
<h3 className={styles.heading}>{title}</h3>
|
||||
</header>
|
||||
<DeprecatedJsonToHtml
|
||||
embeds={[]}
|
||||
nodes={text.json.children}
|
||||
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
.link {
|
||||
display: inline-block;
|
||||
transition: 200ms ease;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
text-decoration: none;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.image {
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
gap: 27px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: #00838e;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
margin-bottom: 0;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.link:hover .heading {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.p {
|
||||
color: #333;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.content {
|
||||
padding: 20px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.heading {
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { RTETypeEnum } from "@scandic-hotels/trpc/types/RTEenums"
|
||||
|
||||
import styles from "./puff.module.css"
|
||||
|
||||
import type { EmbedByUid } from "@/types/components/deprecatedjsontohtml"
|
||||
import type { RTEDefaultNode, RTENext } from "@/types/rte/node"
|
||||
import type { RenderOptions } from "@/types/rte/option"
|
||||
|
||||
export const renderOptions: RenderOptions = {
|
||||
[RTETypeEnum.p]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
return (
|
||||
<p key={node.uid} className={styles.p}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</p>
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.wrapper {
|
||||
padding: 20px 10px 5px;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import Contacts from "./Contacts"
|
||||
import Puff from "./Puff"
|
||||
|
||||
import styles from "./aside.module.css"
|
||||
|
||||
import type { AsideProps } from "@/types/components/current/aside"
|
||||
import { AsideTypenameEnum } from "@/types/requests/utils/typename"
|
||||
|
||||
export default function Aside({ blocks }: AsideProps) {
|
||||
if (!blocks?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={styles.wrapper}>
|
||||
{blocks.map((block, idx) => {
|
||||
const type = block.__typename
|
||||
switch (type) {
|
||||
case AsideTypenameEnum.CurrentBlocksPageAsideContact:
|
||||
return (
|
||||
<Contacts
|
||||
contacts={block.contact.contactConnection.edges}
|
||||
key={`block-${idx}`}
|
||||
/>
|
||||
)
|
||||
case AsideTypenameEnum.CurrentBlocksPageAsidePuff:
|
||||
return <Puff key={`block-${idx}`} {...block.puff} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./list.module.css"
|
||||
|
||||
import { BlockListItemsEnum, type ListItem } from "@/types/requests/blocks/list"
|
||||
|
||||
const config = {
|
||||
variants: {
|
||||
type: {
|
||||
default: styles.disc,
|
||||
checkmark: styles.checkmark,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
type: "default",
|
||||
},
|
||||
} as const
|
||||
|
||||
const listItemStyle = cva(styles.listItem, config)
|
||||
|
||||
export default function ListItem({ listItem }: { listItem: ListItem }) {
|
||||
const typeName = listItem.__typename
|
||||
|
||||
switch (typeName) {
|
||||
case BlockListItemsEnum.CurrentBlocksPageBlocksListBlockListItemsListItem:
|
||||
return (
|
||||
<li
|
||||
className={listItemStyle({
|
||||
type: listItem.list_item.list_item_style,
|
||||
})}
|
||||
>
|
||||
{listItem.list_item.subtitle ? (
|
||||
<>
|
||||
<strong>{listItem.list_item.title}</strong>
|
||||
<br />
|
||||
<span>{listItem.list_item.subtitle}</span>
|
||||
</>
|
||||
) : (
|
||||
listItem.list_item.title
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
case BlockListItemsEnum.CurrentBlocksPageBlocksListBlockListItemsListItemExternalLink:
|
||||
return (
|
||||
<li
|
||||
className={listItemStyle({
|
||||
type: listItem.list_item_external_link.list_item_style,
|
||||
})}
|
||||
>
|
||||
<a
|
||||
className={styles.link}
|
||||
href={listItem.list_item_external_link.link.href}
|
||||
>
|
||||
<strong>{listItem.list_item_external_link.link.title}</strong>
|
||||
</a>
|
||||
{listItem.list_item_external_link.subtitle && (
|
||||
<span>
|
||||
<br />
|
||||
{listItem.list_item_external_link.subtitle}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import ListItem from "./ListItem"
|
||||
|
||||
import styles from "./list.module.css"
|
||||
|
||||
import type { ListProps } from "@/types/requests/blocks/list"
|
||||
|
||||
export default function List({ list }: ListProps) {
|
||||
return (
|
||||
<>
|
||||
{list.title ? <h2 className={styles.title}>{list.title}</h2> : null}
|
||||
<ul className={styles.ul}>
|
||||
{list.list_items.map((item, i) => (
|
||||
<ListItem listItem={item} key={`list-item-${i}`} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
.title {
|
||||
color: #483729;
|
||||
font-family: BrandonText-Bold, Arial, Helvetica, sans-serif;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.1em;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ul {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 1.3em;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
padding-left: 1.6em;
|
||||
}
|
||||
|
||||
.checkmark::before,
|
||||
.disc::before {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
display: inline-block;
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.checkmark::before {
|
||||
content: url("/_static/img/bullet-list-tick-birch-v2.svg");
|
||||
transform: scale(0.9);
|
||||
left: -1.2em;
|
||||
}
|
||||
|
||||
.disc::before {
|
||||
content: "•";
|
||||
color: rgb(157, 160, 161);
|
||||
font-size: 26px;
|
||||
left: -0.7em;
|
||||
}
|
||||
|
||||
.link {
|
||||
border-bottom: 1px dotted #00838e;
|
||||
color: #00838e;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.link:active,
|
||||
.link:hover {
|
||||
text-decoration: underline;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.title {
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function Puffs() {
|
||||
return <></>
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import DeprecatedJsonToHtml from "@/components/DeprecatedJsonToHtml"
|
||||
|
||||
import { renderOptions } from "./../currentRenderOptions"
|
||||
|
||||
import type { TextProps } from "@/types/components/current/blocks/text"
|
||||
|
||||
export default function Text({ text }: TextProps) {
|
||||
return (
|
||||
<DeprecatedJsonToHtml
|
||||
embeds={text.content.embedded_itemsConnection.edges}
|
||||
nodes={text.content.json.children}
|
||||
renderOptions={renderOptions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.wrapper {
|
||||
background-color: #fff;
|
||||
padding: 20px 10px 5px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.wrapper {
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
|
||||
import List from "./List"
|
||||
import Puffs from "./Puffs"
|
||||
import Text from "./Text"
|
||||
|
||||
import styles from "./blocks.module.css"
|
||||
|
||||
import type { BlocksProps } from "@/types/components/current/blocks"
|
||||
import { BlocksTypenameEnum } from "@/types/requests/utils/typename"
|
||||
|
||||
export default function Blocks({ blocks }: BlocksProps) {
|
||||
if (!blocks?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.wrapper}>
|
||||
{blocks.map((block, idx) => {
|
||||
const type = block.__typename
|
||||
switch (type) {
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksList:
|
||||
return <List key={`${block.__typename}-${idx}`} {...block} />
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksPuffs:
|
||||
return <Puffs key={`${block.__typename}-${idx}`} {...block} />
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksText:
|
||||
return <Text key={`${block.__typename}-${idx}`} {...block} />
|
||||
default:
|
||||
logger.error(`Unknown type: (${type})`)
|
||||
return null
|
||||
}
|
||||
})}
|
||||
<div id="mainarea_personalized"></div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
background: #f3f2f1;
|
||||
display: block;
|
||||
padding-bottom: 50px;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import Aside from "@/components/Current/Aside"
|
||||
import Blocks from "@/components/Current/Blocks"
|
||||
import Hero from "@/components/Current/Hero"
|
||||
import Preamble from "@/components/Current/Preamble"
|
||||
import Section from "@/components/Current/Section"
|
||||
import SubnavMobile from "@/components/Current/SubnavMobile"
|
||||
|
||||
import styles from "./contentPage.module.css"
|
||||
|
||||
import type { ContentPageProps } from "@/types/components/current/contentPage"
|
||||
|
||||
export default function ContentPage({ data }: ContentPageProps) {
|
||||
const page = data.all_current_blocks_page.items[0]
|
||||
const images = page.hero?.imagesConnection
|
||||
const breadcrumbs = page.breadcrumbs.parents
|
||||
const parent = breadcrumbs.at(-1)
|
||||
|
||||
return (
|
||||
<>
|
||||
{images?.totalCount ? <Hero images={images.edges} /> : null}
|
||||
<main className={styles.wrapper} id="maincontent" role="main">
|
||||
<input
|
||||
id="lbl-personalized-areas"
|
||||
name="lbl-personalized-areas"
|
||||
type="hidden"
|
||||
value=""
|
||||
/>
|
||||
<SubnavMobile
|
||||
breadcrumbs={breadcrumbs}
|
||||
parent={parent}
|
||||
title={page.breadcrumbs.title}
|
||||
/>
|
||||
<Preamble
|
||||
breadcrumbs={breadcrumbs}
|
||||
breadcrumbParent={parent}
|
||||
breadcrumbTitle={page.breadcrumbs.title}
|
||||
preamble={page.preamble}
|
||||
title={page.title}
|
||||
/>
|
||||
<Section>
|
||||
<Blocks blocks={page.blocks} />
|
||||
<Aside blocks={page.aside} />
|
||||
</Section>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import styles from "./navigation.module.css"
|
||||
|
||||
import type { FooterNavigationProps } from "@/types/components/current/footer"
|
||||
|
||||
export default function Navigation({ linkGroups }: FooterNavigationProps) {
|
||||
return (
|
||||
<ul className={styles.container}>
|
||||
{linkGroups.map((group) => (
|
||||
<li className={styles.section} key={group.title}>
|
||||
<div className={styles.linkList}>
|
||||
<h3 className={styles.linkListTitle}>{group.title}</h3>
|
||||
<ul className={styles.listPages}>
|
||||
{group.links.map((link) => (
|
||||
<li className={styles.li} key={link.href}>
|
||||
<a className={styles.listLink} href={link.href}>
|
||||
{link.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
.container {
|
||||
position: relative;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
z-index: 9;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid #868686;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.container::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
z-index: 3;
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.85) 0 0 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
box-sizing: content-box;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 10px 0 10px;
|
||||
}
|
||||
|
||||
.contentHeading {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: BrandonText-Bold, Arial, Helvetica, sans-serif;
|
||||
font-size: 22px;
|
||||
line-height: 17.6px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
color: #483729;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000px;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footerSections {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.footerLink {
|
||||
clear: both;
|
||||
padding: 0 0 3px;
|
||||
}
|
||||
|
||||
.footerLinkHeader {
|
||||
display: none;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #e3e0db;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.contentBottom {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contentBottomTitle {
|
||||
margin: 16px 0;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
line-height: 22.4px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footerAboutText {
|
||||
margin-bottom: 16px;
|
||||
line-height: 22.4px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.appDownloadTable {
|
||||
height: 62px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
height: 62px;
|
||||
}
|
||||
|
||||
.tableData {
|
||||
width: 50px;
|
||||
height: 62px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.sectionContainer {
|
||||
text-align: center;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin: 16px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.socialMediaIconsContainer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-right: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.socialMediaIconLink {
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.socialMediaIcon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
text-indent: -9999px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.container {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.contentHeading {
|
||||
margin: 0;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.socialMediaIconsContainer {
|
||||
gap: 9px;
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
|
||||
import { getCurrentFooter } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import Navigation from "./Navigation"
|
||||
|
||||
import styles from "./footer.module.css"
|
||||
|
||||
export default async function Footer() {
|
||||
const lang = await getLang()
|
||||
const footerData = await getCurrentFooter(lang)
|
||||
if (!footerData) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<footer className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<h2 className={styles.contentHeading}>
|
||||
<Image
|
||||
alt={footerData.logoConnection.edges[0].node.title}
|
||||
data-nosvgsrc="/_static/img/scandic-logotype-white.png" // what here?
|
||||
height={23}
|
||||
src={footerData.logoConnection.edges[0].node.url}
|
||||
width={102.17}
|
||||
/>
|
||||
<span className={styles.hiddenAccessible}>Scandic</span>
|
||||
</h2>
|
||||
<ul className={styles.footerSections}>
|
||||
{footerData.navigation.map((group) => (
|
||||
<li className={styles.footerLink} key={group.title}>
|
||||
<div className={styles.footerLinkHeader}>{group.title}</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<Navigation linkGroups={footerData.navigation} />
|
||||
|
||||
<div className={styles.contentBottom}>
|
||||
<p className={styles.contentBottomTitle}>
|
||||
{footerData.about.title}
|
||||
</p>
|
||||
<div>
|
||||
<p className={styles.footerAboutText}>{footerData.about.text}</p>
|
||||
<p className={styles.contentBottomTitle}>
|
||||
{footerData.app_downloads.title}
|
||||
</p>
|
||||
<table className={styles.appDownloadTable}>
|
||||
<tbody>
|
||||
<tr className={styles.tableRow}>
|
||||
<td
|
||||
className={styles.tableData}
|
||||
style={{ textAlign: "right" }}
|
||||
>
|
||||
<a
|
||||
title="Appstore"
|
||||
href={footerData.app_downloads.app_store.href}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Image
|
||||
alt={
|
||||
footerData.app_downloads.app_store.imageConnection
|
||||
.edges[0].node.title
|
||||
}
|
||||
height={45}
|
||||
src={
|
||||
footerData.app_downloads.app_store.imageConnection
|
||||
.edges[0].node.url
|
||||
}
|
||||
width={135}
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
<td
|
||||
className={styles.tableData}
|
||||
style={{ textAlign: "left" }}
|
||||
>
|
||||
<a
|
||||
title="Google Play"
|
||||
href={footerData.app_downloads.google_play.href}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Image
|
||||
alt={
|
||||
footerData.app_downloads.google_play.imageConnection
|
||||
.edges[0].node.title
|
||||
}
|
||||
height={45}
|
||||
src={
|
||||
footerData.app_downloads.google_play.imageConnection
|
||||
.edges[0].node.url
|
||||
}
|
||||
width={135}
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.sectionContainer}>
|
||||
<h3 className={styles.sectionTitle}>
|
||||
{footerData.social_media.title}
|
||||
</h3>
|
||||
<section className={styles.socialMediaIconsContainer}>
|
||||
<a
|
||||
href={footerData.social_media.facebook.href}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
className={styles.socialMediaIconLink}
|
||||
title="Scandic on Facebook"
|
||||
>
|
||||
<svg
|
||||
focusable="false"
|
||||
className={styles.socialMediaIcon}
|
||||
viewBox="0 0 150 150"
|
||||
role="img"
|
||||
aria-labelledby="social-icons facebook-icon"
|
||||
>
|
||||
<title id="facebook-icon">
|
||||
{footerData.social_media.facebook.title}
|
||||
</title>
|
||||
<use xlinkHref="/_static/img/icons/sprites.svg#icon-facebook"></use>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href={footerData.social_media.instagram.href}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
className={styles.socialMediaIconLink}
|
||||
title="Scandic on Instagram"
|
||||
>
|
||||
<svg
|
||||
focusable="false"
|
||||
className={styles.socialMediaIcon}
|
||||
viewBox="0 0 150 150"
|
||||
role="img"
|
||||
aria-labelledby="social-icons instagram-icon"
|
||||
>
|
||||
<title id="instagram-icon">
|
||||
{footerData.social_media.instagram.title}
|
||||
</title>
|
||||
<use xlinkHref="/_static/img/icons/sprites.svg#icon-instagram"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
<div className={styles.sectionContainer}>
|
||||
<h3 className={styles.sectionTitle}>
|
||||
{footerData.trip_advisor.title}
|
||||
</h3>
|
||||
<Image
|
||||
alt="Trip Advisor logotype"
|
||||
height={24}
|
||||
src={footerData.trip_advisor.logoConnection.edges[0].node.url}
|
||||
width={160}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
.container {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 0 0 3px;
|
||||
display: block;
|
||||
height: auto;
|
||||
float: left;
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.linkList {
|
||||
padding: 10px 20px 20px;
|
||||
}
|
||||
|
||||
.linkListTitle {
|
||||
color: #fff;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
border-bottom: 1px solid #e3e0db;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
padding-bottom: 3px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.listPages {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.li {
|
||||
padding-left: 38px;
|
||||
margin-bottom: 8px;
|
||||
background-image: url(/_static/img/bullet-list-arrow-circle-white.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
background-size: 19px;
|
||||
}
|
||||
|
||||
.listLink {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.listLink:hover,
|
||||
.listLink:active,
|
||||
.listLink:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 4px;
|
||||
padding: 0 0 20px;
|
||||
}
|
||||
|
||||
.linkList {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.linkListTitle {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.listPages {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.linkListTitle {
|
||||
padding-bottom: 2px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import "@/public/_static/css/design-system-current-deprecated.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({
|
||||
defaultMessage: "Book",
|
||||
})}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
.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: 1367px) {
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
"use client"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
|
||||
import { languages } from "@/constants/languages"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./desktop.module.css"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
|
||||
import { languages } from "@/constants/languages"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import styles from "./mobile.module.css"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
.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: 1367px) {
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
import { trpc } from "@scandic-hotels/trpc/client"
|
||||
|
||||
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} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
"use client"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { findMyBookingCurrentWebPath } from "@scandic-hotels/common/constants/routes/findMyBookingRoutes"
|
||||
import { logout } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||
import { myPages } from "@scandic-hotels/common/constants/routes/myPages"
|
||||
import { useLazyPathname } from "@scandic-hotels/common/hooks/useLazyPathname"
|
||||
import { getCurrentWebUrl } from "@scandic-hotels/common/utils/url"
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
|
||||
import { env } from "@/env/client"
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import Avatar from "@/components/MyPages/Avatar"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { trackClick, trackLoginClick } 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 baseUrl = env.NEXT_PUBLIC_PUBLIC_URL || "https://www.scandichotels.com"
|
||||
const loginPathname = useLazyPathname({ includeSearchParams: true })
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
const trackHamburgerMenuClick = (title: string) => {
|
||||
if (isHamburgerMenuOpen) {
|
||||
trackClick(`hamburger - ${title}`)
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
priority
|
||||
/>
|
||||
</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({
|
||||
defaultMessage: "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
|
||||
lang={lang}
|
||||
redirectTo={loginPathname}
|
||||
trackingId="loginStartHamburgerMenu"
|
||||
className={styles.mobileLinkButton}
|
||||
onClick={() => {
|
||||
trackLoginClick("hamburger menu")
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Log in",
|
||||
})}
|
||||
</LoginButton>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<span className={styles.mobileSeparator} />
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<Link
|
||||
className={styles.mobileLinkButton}
|
||||
href={getCurrentWebUrl({
|
||||
path: findMyBookingCurrentWebPath[lang],
|
||||
lang,
|
||||
baseUrl,
|
||||
})}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Find booking",
|
||||
})}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className={styles.mainLinks}>
|
||||
{links.map((link, i) => (
|
||||
<li className={styles.li} key={link.href + i}>
|
||||
<Link
|
||||
id={`hamburger - ${link.title}`}
|
||||
className={styles.link}
|
||||
href={link.href}
|
||||
onClick={() => trackHamburgerMenuClick(link.title)}
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<ul className={styles.mobileList}>
|
||||
{topMenuMobileLinks.map(({ link }, i) => (
|
||||
<li className={styles.mobileLi} key={link.href + i}>
|
||||
<Link
|
||||
id={`hamburger - ${link.title}`}
|
||||
className={styles.mobileLink}
|
||||
href={link.href}
|
||||
onClick={() => trackHamburgerMenuClick(link.title)}
|
||||
>
|
||||
{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({
|
||||
defaultMessage: "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({
|
||||
defaultMessage: "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>
|
||||
)
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { logout } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
import Title from "@scandic-hotels/design-system/Title"
|
||||
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import { useMyPagesNavigation } from "@/components/Header/MainMenu/MyPagesMenuContent"
|
||||
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({
|
||||
defaultMessage: "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 />
|
||||
</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="large"
|
||||
variant="myPageMobileDropdown"
|
||||
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"
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<Link
|
||||
href={logout[lang]}
|
||||
prefetch={false}
|
||||
size="small"
|
||||
variant="myPageMobileDropdown"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "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>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
.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);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import { logout } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
||||
import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
||||
|
||||
import { getName } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getPathname } from "@/utils/getPathname"
|
||||
import { trackLoginClick } from "@/utils/tracking"
|
||||
|
||||
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()
|
||||
const lang = await getLang()
|
||||
const pathname = await getPathname()
|
||||
|
||||
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[lang]}
|
||||
className={styles.sessionLink}
|
||||
prefetch={false}
|
||||
>
|
||||
<span data-hj-suppress>{capitalize(user.firstName)}</span>
|
||||
</Link>
|
||||
) : null}
|
||||
<div className={styles.loginSeparator} />
|
||||
<Link
|
||||
href={logout[lang]}
|
||||
className={styles.sessionLink}
|
||||
prefetch={false}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Log out",
|
||||
})}
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<LoginButton
|
||||
lang={lang}
|
||||
redirectTo={pathname}
|
||||
trackingId="loginStartTopMenu"
|
||||
className={`${styles.sessionLink} ${styles.loginLink}`}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
trackLoginClick("hamburger menu")
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Log in",
|
||||
})}
|
||||
</LoginButton>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export async function TopMenuSkeleton() {
|
||||
const intl = await getIntl()
|
||||
const links = new Array(5).fill("")
|
||||
const lang = await getLang()
|
||||
const pathname = await getPathname()
|
||||
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
|
||||
lang={lang}
|
||||
redirectTo={pathname}
|
||||
trackingId="loginStartTopMenu"
|
||||
className={`${styles.sessionLink} ${styles.loginLink}`}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
trackLoginClick("hamburger menu")
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Log in",
|
||||
})}
|
||||
</LoginButton>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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 = await getLang()
|
||||
const [data, user] = await Promise.all([getCurrentHeader(lang), getName()])
|
||||
|
||||
if (!data?.header) {
|
||||
return null
|
||||
}
|
||||
|
||||
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
.wrapper {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.picture {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity 400ms ease-in-out 0s;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
}
|
||||
|
||||
.heroImage {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 600px;
|
||||
min-height: 100px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.heroImage {
|
||||
aspect-ratio: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
|
||||
import styles from "./hero.module.css"
|
||||
|
||||
import type { HeroProps } from "@/types/components/current/hero"
|
||||
|
||||
export default function Hero({ images }: HeroProps) {
|
||||
return (
|
||||
<div className={styles.wrapper} aria-label="Hero" tabIndex={0}>
|
||||
{images.map(({ node: image }) => (
|
||||
<picture className={styles.picture} key={image.title}>
|
||||
<Image
|
||||
alt={image.title}
|
||||
className={styles.heroImage}
|
||||
height={image.dimension.height}
|
||||
src={image.url}
|
||||
width={image.dimension.width}
|
||||
/>
|
||||
</picture>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||
|
||||
import { headers } from "next/headers"
|
||||
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import { localeToLang } from "@/constants/languages"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
export default async function LangPopup() {
|
||||
const headersList = await headers()
|
||||
const preferedLang = headersList.get("Accept-Language") ?? ""
|
||||
|
||||
const possibleLangs = Object.keys(localeToLang)
|
||||
|
||||
if (!possibleLangs.includes(preferedLang)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const langOfChoice: Lang = localeToLang[preferedLang as Lang]
|
||||
|
||||
const lang = await getLang()
|
||||
if (langOfChoice === lang) {
|
||||
return null
|
||||
}
|
||||
|
||||
let language = ""
|
||||
let viewIn = ""
|
||||
|
||||
switch (langOfChoice) {
|
||||
case Lang.de:
|
||||
language = "Deutsch"
|
||||
viewIn = "Ansicht in"
|
||||
break
|
||||
case Lang.da:
|
||||
language = "Dansk"
|
||||
viewIn = "Se in"
|
||||
break
|
||||
case Lang.fi:
|
||||
language = "Suomi"
|
||||
viewIn = "Katso in"
|
||||
break
|
||||
case Lang.no:
|
||||
language = "Norsk"
|
||||
viewIn = "Se in"
|
||||
break
|
||||
case Lang.sv:
|
||||
language = "Svenska"
|
||||
viewIn = "Visa in"
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lang-popup hidden" id="lang-popup">
|
||||
<a
|
||||
href="#"
|
||||
className="lang-popup__close"
|
||||
title="Close language change popup"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
style={{
|
||||
stroke: "#757575",
|
||||
width: "14px",
|
||||
height: "14px",
|
||||
display: "block",
|
||||
strokeWidth: "2px",
|
||||
}}
|
||||
>
|
||||
<title>Close</title>
|
||||
<g id="web-close" fillRule="evenodd">
|
||||
<line x1="0" y1="0" x2="100%" y2="100%" />
|
||||
<line x1="0" y1="100%" x2="100%" y2="0" />
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<div className="lang-popup__body">
|
||||
<p className="lang-popup__msg">
|
||||
You are viewing our website in English, would you like to change to{" "}
|
||||
{language}?
|
||||
</p>
|
||||
</div>
|
||||
<div className="lang-popup__footer">
|
||||
<a href="" className="lang-popup__cta btn btn--primary">
|
||||
{viewIn} {language}
|
||||
</a>
|
||||
<a href="#" className="lang-popup__cancel btn btn--link">
|
||||
No thanks
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import styles from "./loading.module.css"
|
||||
|
||||
export default function LoadingSpinner() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.spinner}>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.spinner div {
|
||||
transform-origin: 40px 40px;
|
||||
animation: spinnerAnimation 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.spinner div::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 37px;
|
||||
width: 6px;
|
||||
height: 18px;
|
||||
border-radius: 20%;
|
||||
background: red;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(1) {
|
||||
transform: rotate(0deg);
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(2) {
|
||||
transform: rotate(30deg);
|
||||
animation-delay: -1s;
|
||||
}
|
||||
.spinner div:nth-child(3) {
|
||||
transform: rotate(60deg);
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
.spinner div:nth-child(4) {
|
||||
transform: rotate(90deg);
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
.spinner div:nth-child(5) {
|
||||
transform: rotate(120deg);
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
.spinner div:nth-child(6) {
|
||||
transform: rotate(150deg);
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
.spinner div:nth-child(7) {
|
||||
transform: rotate(180deg);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
.spinner div:nth-child(8) {
|
||||
transform: rotate(210deg);
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
.spinner div:nth-child(9) {
|
||||
transform: rotate(240deg);
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.spinner div:nth-child(10) {
|
||||
transform: rotate(270deg);
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
.spinner div:nth-child(11) {
|
||||
transform: rotate(300deg);
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
.spinner div:nth-child(12) {
|
||||
transform: rotate(330deg);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
@keyframes spinnerAnimation {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
.nav {
|
||||
display: none;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.parent {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
grid-auto-flow: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.currentPage {
|
||||
color: #7f7369;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.currentPage,
|
||||
.li {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.li::before,
|
||||
.currentPage::before {
|
||||
content: "›";
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@media (min-width: 740px) {
|
||||
.nav {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import styles from "./breadcrumbs.module.css"
|
||||
|
||||
import type { BreadcrumbsProps } from "@/types/components/current/breadcrumbs"
|
||||
|
||||
export default function Breadcrumbs({
|
||||
breadcrumbs,
|
||||
parent,
|
||||
title,
|
||||
}: BreadcrumbsProps) {
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.list}>
|
||||
{parent ? (
|
||||
<li className={styles.parent}>
|
||||
<a href={parent.href}>{parent.title}</a>
|
||||
</li>
|
||||
) : null}
|
||||
{breadcrumbs.map((breadcrumb) => (
|
||||
<li className={styles.li} itemProp="breadcrumb" key={breadcrumb.href}>
|
||||
<a className={styles.link} href={breadcrumb.href}>
|
||||
{breadcrumb.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.currentPage}>
|
||||
<span>{title}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import DeprecatedJsonToHtml from "@/components/DeprecatedJsonToHtml"
|
||||
|
||||
import { renderOptions as currentRenderOptions } from "./../currentRenderOptions"
|
||||
import Breadcrumbs from "./Breadcrumbs"
|
||||
import { renderOptions } from "./renderOptions"
|
||||
|
||||
import styles from "./preamble.module.css"
|
||||
|
||||
import type { PreambleProps } from "@/types/components/current/preamble"
|
||||
|
||||
export default function Preamble({
|
||||
breadcrumbs,
|
||||
breadcrumbParent,
|
||||
breadcrumbTitle,
|
||||
preamble,
|
||||
title,
|
||||
}: PreambleProps) {
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<section>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={breadcrumbs}
|
||||
parent={breadcrumbParent}
|
||||
title={breadcrumbTitle}
|
||||
/>
|
||||
<h1>{title}</h1>
|
||||
{preamble?.text ? (
|
||||
<DeprecatedJsonToHtml
|
||||
embeds={preamble.text.embedded_itemsConnection.edges}
|
||||
nodes={preamble.text.json.children}
|
||||
renderOptions={{ ...currentRenderOptions, ...renderOptions }}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: 60px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 10px 25px;
|
||||
background: #fff;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.preamble {
|
||||
color: #333;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 300;
|
||||
line-height: normal;
|
||||
text-transform: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.container {
|
||||
background: transparent;
|
||||
padding: 30px 30px 15px;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.container {
|
||||
padding: 50px 30px 35px;
|
||||
}
|
||||
|
||||
.preamble {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { RTETypeEnum } from "@scandic-hotels/trpc/types/RTEenums"
|
||||
|
||||
import styles from "./preamble.module.css"
|
||||
|
||||
import type { EmbedByUid } from "@/types/components/deprecatedjsontohtml"
|
||||
import type { RTEDefaultNode, RTENext } from "@/types/rte/node"
|
||||
import type { RenderOptions } from "@/types/rte/option"
|
||||
|
||||
export const renderOptions: RenderOptions = {
|
||||
[RTETypeEnum.p]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
return (
|
||||
<p key={node.uid} className={styles.preamble}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</p>
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import styles from "./section.module.css"
|
||||
|
||||
export default function Section({ children }: React.PropsWithChildren) {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<section className={styles.section}>{children}</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
.section {
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.section {
|
||||
padding: 30px 30px 15px;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
display: grid;
|
||||
gap: 70px;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.section {
|
||||
padding: 50px 30px 35px;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { SubnavMobileProps } from "@/types/components/current/subnavMobile"
|
||||
|
||||
export default async function SubnavMobile({
|
||||
breadcrumbs,
|
||||
parent,
|
||||
title,
|
||||
}: SubnavMobileProps) {
|
||||
return (
|
||||
<div className="subnav-mobile hidden-small hidden-medium hidden-large">
|
||||
<nav className="u-flex">
|
||||
<ul className="breadcrumb-list hidden-small hidden-medium hidden-large">
|
||||
{parent ? (
|
||||
<li className="breadcrumb-list__parent hidden-medium hidden-large">
|
||||
<a href={parent.href}>{parent.title}</a>
|
||||
</li>
|
||||
) : null}
|
||||
{breadcrumbs.map((breadcrumb) => (
|
||||
<li className="breadcrumb-list__body" key={breadcrumb.href}>
|
||||
<a href={breadcrumb.href}>{breadcrumb.title}</a>
|
||||
</li>
|
||||
))}
|
||||
<li className="breadcrumb-list__body">
|
||||
<span>{title}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
|
||||
import type {
|
||||
SiteSectionObject,
|
||||
TrackingData,
|
||||
TrackingProps,
|
||||
} from "@scandic-hotels/tracking/types"
|
||||
|
||||
function createPageObject(trackingData: TrackingData) {
|
||||
const englishSegments = trackingData.englishUrl
|
||||
? trackingData.englishUrl.split("/").filter((seg?: string) => seg)
|
||||
: null
|
||||
|
||||
const [lang, ...segments] = trackingData.pathName
|
||||
.split("/")
|
||||
.filter((seg: string) => seg)
|
||||
|
||||
function getSiteSections(segments: string[]): SiteSectionObject {
|
||||
/*
|
||||
Adobe expects the properties sitesection1 - sitessection6, hence the for-loop below
|
||||
The segments ['explore-scandic', 'wifi'] should result in:
|
||||
{
|
||||
sitesection1: "explore-scandic",
|
||||
sitesection2: "explore-scandic|wifi",
|
||||
sitesection3: "explore-scandic|wifi|",
|
||||
sitesection4: "explore-scandic|wifi||",
|
||||
sitesection5: "explore-scandic|wifi|||",
|
||||
sitesection6: "explore-scandic|wifi||||",
|
||||
}
|
||||
*/
|
||||
const sitesections = {} as SiteSectionObject
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const key = ("sitesection" + (i + 1)) as keyof SiteSectionObject
|
||||
|
||||
sitesections[key] = segments.slice(0, i + 1).join("|")
|
||||
if (i > 0 && !segments[i]) {
|
||||
sitesections[key] = sitesections[key].concat(
|
||||
"|".repeat(i + 1 - segments.length)
|
||||
)
|
||||
}
|
||||
}
|
||||
return sitesections
|
||||
}
|
||||
const sitesections = englishSegments
|
||||
? getSiteSections(englishSegments)
|
||||
: getSiteSections(segments)
|
||||
const { host: domain, href: fullurl, origin } = window.location
|
||||
const page_obj = {
|
||||
pagename: englishSegments ? englishSegments.join("|") : segments.join("|"),
|
||||
pagetype: "contentpage",
|
||||
pageurl: origin + trackingData.pathName,
|
||||
fullurl,
|
||||
createDate: trackingData.createdDate,
|
||||
publishDate: trackingData.publishedDate,
|
||||
domain,
|
||||
errorcode: null, // handle
|
||||
querystring: trackingData.queryString || "",
|
||||
pageid: trackingData.pageId,
|
||||
// sessionid: "<unique identifier of session>", // base on what?
|
||||
domainlanguage: trackingData.lang ? trackingData.lang : lang,
|
||||
hotelbrand: "scandic",
|
||||
siteversion: "new-web",
|
||||
...sitesections,
|
||||
}
|
||||
return page_obj
|
||||
}
|
||||
|
||||
export default function Tracking({ pageData }: TrackingProps) {
|
||||
const pathName = usePathname()
|
||||
const queryString = useSearchParams().toString()
|
||||
|
||||
function CookiebotCallbackOnAccept() {
|
||||
const cookie = window._satellite.cookie.get("CookieConsent")
|
||||
|
||||
if (window.Cookiebot?.changed && window.adobe) {
|
||||
if (cookie?.includes("statistics:true")) {
|
||||
window.adobe.optIn.approve(window.adobe.OptInCategories.ANALYTICS, true)
|
||||
} else {
|
||||
window.adobe.optIn.deny(window.adobe.OptInCategories.ANALYTICS, true)
|
||||
}
|
||||
window.adobe.optIn.complete()
|
||||
logger.warn("window.load event explicitly dispatched.")
|
||||
window.dispatchEvent(new Event("load"))
|
||||
}
|
||||
}
|
||||
|
||||
function CookebotCallbackOnDecline() {
|
||||
if (window.Cookiebot?.changed && window.adobe) {
|
||||
window.adobe.optIn.deny(window.adobe.OptInCategories.ANALYTICS, true)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (window.dataLayer) {
|
||||
const trackingData = { ...pageData, pathName, queryString }
|
||||
const pageObject = createPageObject(trackingData)
|
||||
|
||||
window.dataLayer.page = pageObject
|
||||
}
|
||||
}, [pathName, queryString, pageData])
|
||||
|
||||
useEffect(() => {
|
||||
// handle consent
|
||||
window.addEventListener("CookiebotOnAccept", CookiebotCallbackOnAccept)
|
||||
window.addEventListener("CookiebotOnDecline", CookebotCallbackOnDecline)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("CookiebotOnAccept", CookiebotCallbackOnAccept)
|
||||
window.removeEventListener(
|
||||
"CookiebotOnDecline",
|
||||
CookebotCallbackOnDecline
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
.image {
|
||||
height: auto;
|
||||
margin-bottom: var(--Spacing-x2);
|
||||
max-width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
@@ -1,477 +0,0 @@
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import {
|
||||
RTEItemTypeEnum,
|
||||
RTETypeEnum,
|
||||
} from "@scandic-hotels/trpc/types/RTEenums"
|
||||
|
||||
import styles from "./currentRenderOptions.module.css"
|
||||
|
||||
import type { EmbedByUid } from "@/types/components/deprecatedjsontohtml"
|
||||
import { EmbedEnum } from "@/types/requests/utils/embeds"
|
||||
import type { Attributes } from "@/types/rte/attrs"
|
||||
import {
|
||||
type RTEDefaultNode,
|
||||
RTEMarkType,
|
||||
type RTENext,
|
||||
type RTENode,
|
||||
type RTERegularNode,
|
||||
} from "@/types/rte/node"
|
||||
import type { RenderOptions } from "@/types/rte/option"
|
||||
|
||||
function extractPossibleAttributes(attrs: Attributes | undefined) {
|
||||
if (!attrs) return {}
|
||||
const props: Record<string, any> = {}
|
||||
if (attrs.id) {
|
||||
props.id = attrs.id
|
||||
}
|
||||
|
||||
if (attrs.class) {
|
||||
props.className = attrs.class
|
||||
} else if (attrs["class-name"]) {
|
||||
props.className = attrs["class-name"]
|
||||
} else if (attrs.classname) {
|
||||
props.className = attrs.classname
|
||||
} else if (attrs?.style?.["text-align"]) {
|
||||
props.style = {
|
||||
textAlign: attrs?.style?.["text-align"],
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
export const renderOptions: RenderOptions = {
|
||||
[RTETypeEnum.a]: (
|
||||
node: RTERegularNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
if (node.attrs.url) {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
href={node.attrs.url}
|
||||
target={node.attrs.target ?? "_blank"}
|
||||
key={node.uid}
|
||||
>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
[RTETypeEnum.blockquote]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<blockquote key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</blockquote>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.code]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<code key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.embed]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
if (node.attrs.src) {
|
||||
props.src = node.attrs.src
|
||||
}
|
||||
if (node.attrs.url) {
|
||||
props.src = node.attrs.url
|
||||
}
|
||||
if (!props.src) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<iframe key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</iframe>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h1]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h1 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h1>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h2]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h2 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h2>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h3]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h3 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h3>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h4]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h4 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h4>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h5]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h5 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h5>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.h6]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<h6 key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</h6>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.hr]: () => {
|
||||
return <hr />
|
||||
},
|
||||
|
||||
[RTETypeEnum.li]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<li key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</li>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.ol]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<ol key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</ol>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.p]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<p {...props} key={node.uid}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</p>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.reference]: (
|
||||
node: RTENode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
if ("attrs" in node) {
|
||||
const type = node.attrs.type
|
||||
if (type === RTEItemTypeEnum.asset) {
|
||||
const image = embeds?.[node?.attrs?.["asset-uid"]]
|
||||
if (image?.node.__typename === EmbedEnum.SysAsset) {
|
||||
const alt = image?.node?.title ?? node.attrs.alt
|
||||
const alignment = node.attrs?.style?.["text-align"]
|
||||
? {
|
||||
alignSelf: node.attrs?.style?.["text-align"],
|
||||
}
|
||||
: {}
|
||||
return (
|
||||
<Image
|
||||
key={node.uid}
|
||||
alt={alt}
|
||||
className={styles.image}
|
||||
height={image.node.dimension.height}
|
||||
src={image?.node?.url}
|
||||
width={image.node.dimension.width}
|
||||
style={alignment}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
const href = node.attrs?.locale
|
||||
? `/${node.attrs.locale}${node.attrs.href}`
|
||||
: node.attrs.href
|
||||
return (
|
||||
<Link {...props} href={href} key={node.uid}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
[RTETypeEnum.table]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<table key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</table>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.thead]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<thead key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</thead>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.tbody]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<tbody key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</tbody>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.tfoot]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<tfoot key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</tfoot>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.tr]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<tr key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</tr>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.th]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<th key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</th>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.td]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<td key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</td>
|
||||
)
|
||||
},
|
||||
|
||||
[RTETypeEnum.ul]: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
const props = extractPossibleAttributes(node.attrs)
|
||||
return (
|
||||
<ul key={node.uid} {...props}>
|
||||
{next(node.children, embeds, fullRenderOptions)}
|
||||
</ul>
|
||||
)
|
||||
},
|
||||
|
||||
/** TextNode wrappers */
|
||||
[RTEMarkType.bold]: (children: React.ReactNode) => {
|
||||
return <strong>{children}</strong>
|
||||
},
|
||||
|
||||
[RTEMarkType.italic]: (children: React.ReactNode) => {
|
||||
return <em>{children}</em>
|
||||
},
|
||||
|
||||
[RTEMarkType.underline]: (children: React.ReactNode) => {
|
||||
return <u>{children}</u>
|
||||
},
|
||||
|
||||
[RTEMarkType.strikethrough]: (children: React.ReactNode) => {
|
||||
return <s>{children}</s>
|
||||
},
|
||||
|
||||
[RTEMarkType.inlineCode]: (children: React.ReactNode) => {
|
||||
return <span>{children}</span>
|
||||
},
|
||||
|
||||
[RTEMarkType.subscript]: (children: React.ReactNode) => {
|
||||
return <sub>{children}</sub>
|
||||
},
|
||||
|
||||
[RTEMarkType.superscript]: (children: React.ReactNode) => {
|
||||
return <sup>{children}</sup>
|
||||
},
|
||||
|
||||
[RTEMarkType.break]: (children: React.ReactNode) => {
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
},
|
||||
|
||||
[RTEMarkType.classnameOrId]: (
|
||||
children: React.ReactNode,
|
||||
className?: string,
|
||||
id?: string
|
||||
) => {
|
||||
let props = {
|
||||
className,
|
||||
id,
|
||||
}
|
||||
if (!className) {
|
||||
delete props.className
|
||||
}
|
||||
if (!id) {
|
||||
delete props.id
|
||||
}
|
||||
return (
|
||||
<span key={id} {...props}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Contentstack can return something called `default` as seen here in their
|
||||
* own SDK (https://github.com/contentstack/contentstack-utils-javascript/blob/master/src/options/default-node-options.ts#L89)
|
||||
*/
|
||||
default: (
|
||||
node: RTEDefaultNode,
|
||||
embeds: EmbedByUid,
|
||||
next: RTENext,
|
||||
fullRenderOptions: RenderOptions
|
||||
) => {
|
||||
return next(node.children, embeds, fullRenderOptions)
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { getHeader, getName } from "@/lib/trpc/memoizedRequests"
|
||||
import { getHeader } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import MainMenu from "./MainMenu"
|
||||
import TopMenu, { TopMenuSkeleton } from "./TopMenu"
|
||||
@@ -8,7 +8,6 @@ import TopMenu, { TopMenuSkeleton } from "./TopMenu"
|
||||
import styles from "./header.module.css"
|
||||
|
||||
export default async function Header() {
|
||||
void getName()
|
||||
void getHeader()
|
||||
|
||||
return (
|
||||
|
||||
-1
@@ -37,7 +37,6 @@
|
||||
.text {
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
export default async function SkipToMainContent() {
|
||||
const intl = await getIntl()
|
||||
return (
|
||||
<div className="navigation--skip-to-content">
|
||||
<a data-js="skip-to-content" href="#maincontent">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Skip to main content",
|
||||
})}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -37,7 +37,6 @@
|
||||
.text {
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
|
||||
Reference in New Issue
Block a user