Merged in feat/SW-3520-update-footer-ui-and-footer-link (pull request #2910)

feat(SW-3520): Updated the footer

* feat(SW-3520): Updated links to route to scandic web

* feat(SW-3520): Updated the footer with language switcher

* feat(SW-3520): Updated the Contact-us link and removed double slash


Approved-by: Anton Gunnarsson
This commit is contained in:
Hrishikesh Vaipurkar
2025-10-09 06:44:58 +00:00
parent cc00322ffa
commit 566dd54087
13 changed files with 287 additions and 93 deletions

View File

@@ -28,12 +28,12 @@ import { FontPreload } from "@/fonts/font-preloading"
import { getMessages } from "@/i18n"
import ClientIntlProvider from "@/i18n/Provider"
import { setLang } from "@/i18n/serverContext"
import { routeToScandicWeb } from "@/util"
import { BookingFlowProviders } from "../../components/BookingFlowProviders"
import { Footer } from "../../components/Footer/Footer"
import { Header } from "../../components/Header/Header"
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
import type { Metadata } from "next"
export const metadata: Metadata = {
@@ -128,11 +128,3 @@ export default async function RootLayout(props: RootLayoutProps) {
</html>
)
}
function routeToScandicWeb(route: LangRoute) {
const url = `https://www.scandichotels.com`
return Object.entries(route).reduce((acc, [key, value]) => {
acc[key as Lang] = `${url}${value}`
return acc
}, {} as LangRoute)
}

View File

@@ -1,14 +1,16 @@
/* eslint-disable formatjs/no-literal-string-in-jsx */
import Link from "next/link"
import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getIntl } from "@/i18n"
import { FooterMenu } from "../Menu/FooterMenu"
import { PoweredByScandic } from "../PoweredByScandic/PoweredByScandic"
import styles from "./footer.module.css"
export function Footer() {
export async function Footer() {
const intl = await getIntl()
return (
<div className={styles.root}>
<div className={styles.top}>
@@ -23,42 +25,14 @@ export function Footer() {
/>
<PoweredByScandic />
</div>
<div className={styles.links}>
<Typography variant="Body/Paragraph/mdRegular">
<Link href="#" className={styles.link}>
Privacy policy
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href="#" className={styles.link}>
Terms of use
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href="#" className={styles.link}>
Your privacy choices
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href="#" className={styles.link}>
Cookie Policy
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href="#" className={styles.link}>
More links
</Link>
</Typography>
</div>
<FooterMenu />
</div>
<div className={styles.bottom}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>© 1999 Something something.</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>
[Place holder text], LP and SAS are not responsible for content on
external Web sites.
{intl.formatMessage({
defaultMessage: "© 2025 Scandic Hotels all rights reserved.",
})}
</p>
</Typography>
</div>

View File

@@ -27,22 +27,3 @@
padding: 32px 40px 40px;
}
}
.links {
display: flex;
flex-direction: column;
gap: 8px;
@media screen and (min-width: 768px) {
flex-direction: row;
}
}
.link {
text-decoration: none;
color: var(--TEMP-sas-40);
&:hover {
text-decoration: underline;
}
}

View File

@@ -20,11 +20,14 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { languageSwitcherVariants } from "./variants"
import styles from "./languageSwitcher.module.css"
type LanguageSwitcherProps = {
currentLanguage: Lang
isMobile?: boolean
type?: "footer" | "header"
}
export function replaceUrlPart(currentPath: string, newPart: string): string {
@@ -50,12 +53,16 @@ export function replaceUrlPart(currentPath: string, newPart: string): string {
export function LanguageSwitcher({
currentLanguage,
isMobile = false,
type = "header",
}: LanguageSwitcherProps) {
const classNames = languageSwitcherVariants({ position: type })
const isFooter = type === "footer"
return (
<div className={styles.languageSwitcher}>
<div className={classNames}>
<DialogTrigger>
<Button className={styles.triggerButton} variant={"Text"} wrapping>
{isMobile ? null : (
{isMobile && !isFooter ? null : (
<MaterialIcon
icon="globe"
size={16}
@@ -82,7 +89,10 @@ export function LanguageSwitcher({
</Typography>
</Button>
{isMobile ? (
<ModalOverlay isDismissable className={styles.languageModalOverlay}>
<ModalOverlay
isDismissable
className={`${styles.languageModalOverlay} ${isFooter ? styles.footer : null}`}
>
<Modal className={styles.languageModal}>
<Dialog>
{({ close }) => (
@@ -96,7 +106,11 @@ export function LanguageSwitcher({
</Modal>
</ModalOverlay>
) : (
<Popover offset={28} className={styles.languageSwitcherPopover}>
<Popover
offset={isFooter ? 0 : 21}
className={styles.languageSwitcherPopover}
placement={"bottom right"}
>
<Dialog>
{({ close }) => (
<LanguageSwitcherContent
@@ -142,11 +156,12 @@ function LanguageSwitcherContent({
<div className={styles.languageSwitcherContent}>
{isMobile ? (
<>
<div className={styles.closeModalWrapper}>
<Button
variant={"Text"}
size={"Medium"}
onPress={closeModal}
className={styles.arrowBack}
className={styles.closeModal}
>
<MaterialIcon
icon="chevron_left"
@@ -154,7 +169,14 @@ function LanguageSwitcherContent({
className={styles.arrowBackIcon}
color={"CurrentColor"}
/>
<MaterialIcon
icon="close"
size={32}
className={styles.closeIcon}
color={"CurrentColor"}
/>
</Button>
</div>
<Typography variant={"Title/Subtitle/md"}>
<h3 className={styles.title}>
{intl.formatMessage({
@@ -180,7 +202,7 @@ function LanguageSwitcherContent({
}
>
<Link
className={styles.link}
className={`${styles.link} ${isActive ? styles.active : ""}`}
href={replaceUrlPart(pathname, url)}
variant="languageSwitcher"
keepSearchParams

View File

@@ -1,18 +1,39 @@
.languageSwitcher {
.triggerButton {
gap: var(--Space-x1);
padding: var(--Space-x1);
justify-content: flex-start;
width: 100%;
border: 0 none;
}
.triggerText {
align-items: center;
display: flex;
justify-content: space-between;
width: 100%;
color: var(--Text-sas-20);
}
&.header {
.triggerButton {
padding: var(--Space-x1);
}
.triggerText {
justify-content: space-between;
}
}
&.footer {
.triggerText,
.chevron {
color: var(--TEMP-sas-40);
}
.chevron {
margin: 1px 0 0 2px;
}
.globeIcon {
color: var(--TEMP-sas-40);
}
}
}
.languageSwitcherContent {
@@ -42,6 +63,10 @@
justify-content: space-between;
align-items: center;
border-radius: var(--Space-x1);
&.active {
background-color: var(--Surface-Primary-Hover);
}
}
.languageModalOverlay {
@@ -61,22 +86,56 @@
z-index: 1001;
.closeModal {
position: fixed;
top: var(--Space-x2);
right: var(--Space-x2);
align-self: flex-end;
background-color: transparent;
color: transparent;
color: var(--TEMP-sas-40);
gap: 0;
}
.closeIcon {
display: none;
}
}
.footer .languageModal {
top: 0;
.languageSwitcherContent {
padding: 0;
.title,
.languageSwitcherListContainer {
padding: 0 var(--Space-x2);
}
}
.closeModalWrapper {
display: flex;
width: 100%;
padding: 0 var(--Space-x2);
border-bottom: 1px solid var(--SAS-90);
justify-content: flex-end;
}
.arrowBackIcon {
display: none;
}
.closeIcon {
display: block;
}
}
@media screen and (min-width: 768px) {
.languageSwitcher {
&.header {
.triggerText {
color: white;
}
.triggerButton {
color: white;
}
}
.triggerButton {
padding: 0;
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./languageSwitcher.module.css"
export const languageSwitcherVariants = cva(styles.languageSwitcher, {
variants: {
position: {
header: styles.header,
footer: styles.footer,
},
defaultVariants: {
position: "header",
},
},
})

View File

@@ -0,0 +1,30 @@
.linksWrapper {
display: flex;
flex-direction: column;
gap: var(--Space-x2);
@media screen and (min-width: 768px) {
flex-direction: row;
justify-content: space-between;
}
}
.links {
display: flex;
flex-direction: column;
gap: var(--Space-x1);
@media screen and (min-width: 768px) {
flex-direction: row;
}
}
.link {
text-decoration: none;
color: var(--TEMP-sas-40);
&:hover {
color: var(--TEMP-sas-40);
text-decoration: underline;
}
}

View File

@@ -0,0 +1,78 @@
"use client"
import { useEffect, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import {
customerService,
faq,
policies,
rates,
} from "@scandic-hotels/common/constants/routes/customerService"
import { partnerSas } from "@scandic-hotels/common/constants/routes/myPages"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { LanguageSwitcher } from "@/components/LanguageSwitcher"
import useLang from "@/hooks/useLang"
import { routeToScandicWeb } from "@/util"
import styles from "./footer-menu.module.css"
export function FooterMenu() {
const intl = useIntl()
const lang = useLang()
const checkIfMobile = useMediaQuery("(max-width: 767px)")
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
setIsMobile(checkIfMobile)
}, [checkIfMobile])
return (
<div className={styles.linksWrapper}>
<div className={styles.links}>
<Typography variant="Body/Paragraph/mdRegular">
<Link
href={routeToScandicWeb(customerService)[lang]}
className={styles.link}
>
{intl.formatMessage({ defaultMessage: "Contact us" })}
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href={routeToScandicWeb(faq)[lang]} className={styles.link}>
{intl.formatMessage({ defaultMessage: "FAQ" })}
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link href={routeToScandicWeb(rates)[lang]} className={styles.link}>
{intl.formatMessage({ defaultMessage: "Rates" })}
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link
href={routeToScandicWeb(policies)[lang]}
className={styles.link}
>
{intl.formatMessage({ defaultMessage: "Policies" })}
</Link>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<Link
href={routeToScandicWeb(partnerSas)[lang]}
className={styles.link}
>
{intl.formatMessage({ defaultMessage: "SAS EuroBonus" })}
</Link>
</Typography>
</div>
<LanguageSwitcher
currentLanguage={lang}
isMobile={isMobile}
type="footer"
/>
</div>
)
}

View File

@@ -30,7 +30,10 @@ export function MobileMenu({ children }: React.PropsWithChildren) {
type="button"
className={`${styles.hamburger} ${isOpen ? styles.isExpanded : ""}`}
aria-label={isOpen ? closeMsg : openMsg}
onPress={() => setIsOpen(!isOpen)}
onPress={() => {
window.scrollTo(0, 0)
setIsOpen(!isOpen)
}}
>
<span className={styles.bar} />
</Button>

View File

@@ -2,12 +2,14 @@
import { useIntl } from "react-intl"
import { customerService } from "@scandic-hotels/common/constants/routes/customerService"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/Link"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { LanguageSwitcher } from "@/components/LanguageSwitcher"
import useLang from "@/hooks/useLang"
import { routeToScandicWeb } from "@/util"
import { UserMenu } from "../UserMenu"
@@ -29,7 +31,7 @@ export function NavigationMenu({ isMobile = false }: { isMobile?: boolean }) {
}
>
<Link
href="#"
href={routeToScandicWeb(customerService)[lang]}
color={isMobile ? "none" : "white"}
className={`${styles.menuItem} ${styles.contactLink}`}
>

View File

@@ -42,6 +42,7 @@
--TEMP-sas-20: #00175c;
--TEMP-sas-40: #0030c2;
--Text-sas-20: #333;
--SAS-90: #e5e5e5;
@supports (interpolate-size: allow-keywords) {
interpolate-size: allow-keywords;

View File

@@ -0,0 +1,10 @@
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
export function routeToScandicWeb(route: LangRoute) {
const url = `https://www.scandichotels.com`
return Object.entries(route).reduce((acc, [key, value]) => {
acc[key as Lang] = `${url}${value}`
return acc
}, {} as LangRoute)
}

View File

@@ -10,3 +10,30 @@ export const customerService = {
no: `/${Lang.no}/kundeservice`,
sv: `/${Lang.sv}/kundservice`,
} as const satisfies LangRoute
export const faq = {
da: `${customerService[Lang.da]}/sporgsmal-og-svar`,
de: `${customerService[Lang.de]}/haufig-gestellte-fragen`,
en: `${customerService[Lang.en]}/faq`,
fi: `${customerService[Lang.fi]}/usein-kysytyt-kysymykset`,
no: `${customerService[Lang.no]}/sporsmal-svar`,
sv: `${customerService[Lang.sv]}/faq`,
} as const satisfies LangRoute
export const rates = {
da: `${customerService[Lang.da]}/priser`,
de: `${customerService[Lang.de]}/raten`,
en: `${customerService[Lang.en]}/rates`,
fi: `${customerService[Lang.fi]}/hinnat`,
no: `${customerService[Lang.no]}/priser`,
sv: `${customerService[Lang.sv]}/priser`,
} as const satisfies LangRoute
export const policies = {
da: `${customerService[Lang.da]}/politikker`,
de: `${customerService[Lang.de]}/richtlinien`,
en: `${customerService[Lang.en]}/policies`,
fi: `${customerService[Lang.fi]}/ehdot`,
no: `${customerService[Lang.no]}/betingelser`,
sv: `${customerService[Lang.sv]}/villkor`,
} as const satisfies LangRoute