fix(BOOK-559): Using same ContactInformation component on hotel pages and booking flow inside the hotel details sidepeek

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2025-11-13 14:32:17 +00:00
parent f52d210240
commit b0f2276b0b
33 changed files with 75 additions and 282 deletions

View File

@@ -1,61 +0,0 @@
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
gap: var(--Space-x2);
font-family: var(--typography-Body-Regular-fontFamily);
margin-bottom: var(--Space-x3);
}
.address,
.contactInfo {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
grid-column: 1 / 3;
grid-row: 1 / 4;
}
.contactInfo > li {
font-style: normal;
list-style-type: none;
display: flex;
flex-direction: column;
min-width: 0;
}
.soMeIcons {
display: flex;
gap: var(--Space-x15);
}
.ecoLabel {
width: 38px;
height: auto;
}
.ecoLabel img {
width: 100%;
height: auto;
flex-shrink: 0;
grid-column: 1 / 3;
grid-row: 4 / 4;
}
.ecoContainer {
display: flex;
align-items: center;
column-gap: var(--Space-x15);
grid-column: 1 / 3;
grid-row: 4 / 4;
font-size: var(--typography-Footnote-Regular-fontSize);
line-height: ();
margin-bottom: var(--Space-x1);
}
.ecoLabelText {
display: flex;
color: var(--UI-Text-Medium-contrast);
flex-direction: column;
justify-content: center;
}

View File

@@ -1,159 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import FacebookIcon from "@scandic-hotels/design-system/Icons/FacebookIcon"
import InstagramIcon from "@scandic-hotels/design-system/Icons/InstagramIcon"
import Image from "@scandic-hotels/design-system/Image"
import Link from "@scandic-hotels/design-system/OldDSLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import useLang from "../../hooks/useLang"
import styles from "./contact.module.css"
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
interface ContactProps {
hotel: Hotel
}
export default function Contact({ hotel }: ContactProps) {
const lang = useLang()
const intl = useIntl()
const addressStr = `${hotel.address.streetAddress}, `
const cityStr = hotel.address.city
return (
<section className={styles.wrapper}>
<address className={styles.address}>
<ul className={styles.contactInfo}>
<li>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.address",
defaultMessage: "Address",
})}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{addressStr}
<br />
{cityStr}
</p>
</Typography>
</li>
<li>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.drivingDirections",
defaultMessage: "Driving directions",
})}
</p>
</Typography>
<Link
href={`https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
`${hotel.name}, ${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`
)}`}
>
<Typography variant="Body/Underline/md">
<p>
{intl.formatMessage({
id: "common.googleMaps",
defaultMessage: "Google Maps",
})}
</p>
</Typography>
</Link>
</li>
<li>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.contactUs",
defaultMessage: "Contact us",
})}
</p>
</Typography>
<Link href={`tel:${hotel.contactInformation.phoneNumber}`}>
<Typography variant="Body/Underline/md">
<p>{hotel.contactInformation.phoneNumber}</p>
</Typography>
</Link>
</li>
<li>
{(hotel.socialMedia.facebook || hotel.socialMedia.instagram) && (
<>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.followUs",
defaultMessage: "Follow us",
})}
</p>
</Typography>
<div className={styles.soMeIcons}>
{hotel.socialMedia.instagram && (
<Link href={hotel.socialMedia.instagram} target="_blank">
<InstagramIcon color="Icon/Interactive/Default" />
</Link>
)}
{hotel.socialMedia.facebook && (
<Link href={hotel.socialMedia.facebook} target="_blank">
<FacebookIcon color="Icon/Interactive/Default" />
</Link>
)}
</div>
</>
)}
</li>
<li>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.email",
defaultMessage: "Email",
})}
</p>
</Typography>
<Link href={`mailto:${hotel.contactInformation.email}`}>
<Typography variant="Body/Underline/md">
<p>{hotel.contactInformation.email}</p>
</Typography>
</Link>
</li>
</ul>
</address>
{hotel.hotelFacts.ecoLabels?.nordicEcoLabel ? (
<div className={styles.ecoContainer}>
<div className={styles.ecoLabel}>
<Image
height={38}
width={38}
alt={intl.formatMessage({
id: "common.nordicSwanEcolabel",
defaultMessage: "Nordic Swan Ecolabel",
})}
src={`/_static/shared/icons/swan-eco/swan_eco_dark_${lang}.png`}
/>
</div>
<div className={styles.ecoLabelText}>
<span>
{intl.formatMessage({
id: "common.nordicSwanEcolabel",
defaultMessage: "Nordic Swan Ecolabel",
})}
</span>
<span>
{hotel.hotelFacts.ecoLabels.svanenEcoLabelCertificateNumber}
</span>
</div>
</div>
) : null}
</section>
)
}

View File

@@ -0,0 +1,50 @@
.wrapper {
display: flex;
flex-direction: column;
gap: var(--Space-x15);
}
.information {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--Space-x2);
grid-template-areas:
"address drivingDirections"
"contact socials"
"email email"
"ecoLabel ecoLabel";
font-style: normal;
}
.address {
grid-area: address;
}
.drivingDirections {
grid-area: drivingDirections;
}
.contact {
grid-area: contact;
}
.socials {
grid-area: socials;
}
.socialIcons {
display: flex;
gap: var(--Space-x1);
align-items: center;
}
.email {
grid-area: email;
}
.ecoLabel {
grid-area: ecoLabel;
display: flex;
gap: var(--Space-x15);
color: var(--Text-Secondary);
}

View File

@@ -0,0 +1,152 @@
"use client"
import NextLink from "next/link"
import { useIntl } from "react-intl"
import FacebookIcon from "@scandic-hotels/design-system/Icons/FacebookIcon"
import InstagramIcon from "@scandic-hotels/design-system/Icons/InstagramIcon"
import Image from "@scandic-hotels/design-system/Image"
import { LocalCallCharges } from "@scandic-hotels/design-system/LocalCallCharges"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import useLang from "../../../hooks/useLang"
import styles from "./contactInformation.module.css"
import type { Hotel, HotelAddress } from "@scandic-hotels/trpc/types/hotel"
interface ContactInformationProps {
hotelAddress: HotelAddress
contact: Hotel["contactInformation"]
socials: Hotel["socialMedia"]
ecoLabels: Hotel["hotelFacts"]["ecoLabels"]
hotelName: Hotel["name"]
}
export function ContactInformation({
hotelAddress,
contact,
socials,
ecoLabels,
hotelName,
}: ContactInformationProps) {
const intl = useIntl()
const lang = useLang()
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
`${hotelName}, ${hotelAddress.streetAddress}, ${hotelAddress.zipCode} ${hotelAddress.city}`
)}`
return (
<div className={styles.wrapper}>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({
id: "common.practicalInformation",
defaultMessage: "Practical information",
})}
</h3>
</Typography>
<Typography variant="Body/Paragraph/mdBold">
<address className={styles.information}>
<div className={styles.address}>
<p>
{intl.formatMessage({
id: "common.address",
defaultMessage: "Address",
})}
</p>
<Typography variant="Body/Paragraph/mdRegular">
<div>
<p>{hotelAddress.streetAddress}</p>
<p>{hotelAddress.city}</p>
</div>
</Typography>
</div>
<div className={styles.drivingDirections}>
<p>
{intl.formatMessage({
id: "common.drivingDirections",
defaultMessage: "Driving directions",
})}
</p>
<TextLink href={directionsUrl} target="_blank">
{intl.formatMessage({
id: "common.googleMaps",
defaultMessage: "Google Maps",
})}
</TextLink>
</div>
<div className={styles.contact}>
<p>
{intl.formatMessage({
id: "common.contactUs",
defaultMessage: "Contact us",
})}
</p>
<TextLink href={`tel:+${contact.phoneNumber}`}>
{contact.phoneNumber}
</TextLink>
<LocalCallCharges country={hotelAddress.country} />
</div>
{socials.instagram || socials.facebook ? (
<div className={styles.socials}>
<p>
{intl.formatMessage({
id: "common.followUs",
defaultMessage: "Follow us",
})}
</p>
<div className={styles.socialIcons}>
{socials.instagram && (
<NextLink href={socials.instagram}>
<InstagramIcon color="Icon/Interactive/Default" />
</NextLink>
)}
{socials.facebook && (
<NextLink href={socials.facebook}>
<FacebookIcon color="Icon/Interactive/Default" />
</NextLink>
)}
</div>
</div>
) : null}
<div className={styles.email}>
<p>
{intl.formatMessage({
id: "common.email",
defaultMessage: "Email",
})}
</p>
<TextLink href={`mailto:${contact.email}`}>
{contact.email}
</TextLink>
</div>
{ecoLabels.nordicEcoLabel && (
<div className={styles.ecoLabel}>
<Image
height={38}
width={38}
alt={intl.formatMessage({
id: "common.nordicSwanEcolabel",
defaultMessage: "Nordic Swan Ecolabel",
})}
src={`/_static/shared/icons/swan-eco/swan_eco_dark_${lang}.png`}
/>
<Typography variant="Body/Supporting text (caption)/smRegular">
<div>
<p>
{intl.formatMessage({
id: "common.nordicSwanEcolabel",
defaultMessage: "Nordic Swan Ecolabel",
})}
</p>
<p>{ecoLabels.svanenEcoLabelCertificateNumber}</p>
</div>
</Typography>
</div>
)}
</address>
</Typography>
</div>
)
}

View File

@@ -1,5 +1,5 @@
.content {
display: grid;
gap: var(--Space-x2);
gap: var(--Space-x4);
color: var(--Text-Default);
}

View File

@@ -10,10 +10,10 @@ import { trackAccordionClick } from "@scandic-hotels/tracking/componentEvents"
import { useBookingFlowConfig } from "../../../bookingFlowConfig/bookingFlowConfigContext"
import { routeToScandicWebUrl } from "../../../utils/routeToScandicWebUrl"
import AdditionalAmenities from "../../AdditionalAmenities"
import Contact from "../../Contact"
import BreakfastAccordionItem from "../../SidePeekAccordions/BreakfastAccordionItem"
import CheckInCheckOutAccordionItem from "../../SidePeekAccordions/CheckInCheckOutAccordionItem"
import ParkingAccordionItem from "../../SidePeekAccordions/ParkingAccordionItem"
import { ContactInformation } from "../ContactInformation"
import styles from "./hotelSidePeek.module.css"
@@ -41,15 +41,13 @@ export function HotelSidePeekContent({
return (
<div className={styles.content}>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({
id: "common.practicalInformation",
defaultMessage: "Practical information",
})}
</h3>
</Typography>
<Contact hotel={hotel} />
<ContactInformation
hotelAddress={hotel.address}
contact={hotel.contactInformation}
socials={hotel.socialMedia}
ecoLabels={hotel.hotelFacts.ecoLabels}
hotelName={hotel.name}
/>
<Accordion type="sidepeek">
<ParkingAccordionItem

View File

@@ -18,6 +18,7 @@
"./BookingWidget/BookingWidgetForm/FormContent/Search": "./lib/components/BookingWidget/BookingWidgetForm/FormContent/Search/index.tsx",
"./BookingWidget/FloatingBookingWidget": "./lib/components/BookingWidget/FloatingBookingWidget/index.tsx",
"./components/AdditionalAmenities": "./lib/components/AdditionalAmenities/index.tsx",
"./components/HotelDetails/ContactInformation": "./lib/components/HotelDetailsSidePeek/ContactInformation/index.tsx",
"./components/AddToCalendar": "./lib/components/AddToCalendar/index.tsx",
"./components/BookingConfirmation/Header/Actions/helpers": "./lib/components/BookingConfirmation/Header/Actions/helpers.ts",
"./components/EnterDetails/enterDetailsErrors": "./lib/components/EnterDetails/enterDetailsErrors.ts",

View File

@@ -0,0 +1,8 @@
export enum Country {
Denmark = "Denmark",
Finland = "Finland",
Germany = "Germany",
Norway = "Norway",
Poland = "Poland",
Sweden = "Sweden",
}

View File

@@ -16,6 +16,7 @@
"./constants/alert": "./constants/alert.ts",
"./constants/booking": "./constants/booking.ts",
"./constants/bookingWidget": "./constants/bookingWidget.ts",
"./constants/country": "./constants/country.ts",
"./constants/currency": "./constants/currency.ts",
"./constants/dateFormats": "./constants/dateFormats.ts",
"./constants/facilities": "./constants/facilities.ts",

View File

@@ -0,0 +1,28 @@
'use client'
import { Country } from '@scandic-hotels/common/constants/country'
import { useIntl } from 'react-intl'
import { Typography } from '../Typography'
interface LocalCallChargesProps {
country: string
className?: string
}
export function LocalCallCharges({
country,
className,
}: LocalCallChargesProps) {
const intl = useIntl()
return country === Country.Finland ? (
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={className}>
{intl.formatMessage({
id: 'common.localCallCharges',
defaultMessage: 'Price: €0.16/min + local call charges',
})}
</p>
</Typography>
) : null
}

View File

@@ -141,6 +141,7 @@
"./Lightbox": "./lib/components/Lightbox/index.tsx",
"./Link": "./lib/components/Link/index.tsx",
"./LoadingSpinner": "./lib/components/LoadingSpinner/index.tsx",
"./LocalCallCharges": "./lib/components/LocalCallCharges/index.tsx",
"./LoginButton": "./lib/components/LoginButton/index.tsx",
"./Map/InfoWindow": "./lib/components/Map/InfoWindow/index.tsx",
"./Map/InteractiveMap": "./lib/components/Map/InteractiveMap/index.tsx",

View File

@@ -10,13 +10,15 @@ import { safeProtectedServiceProcedure } from "../../procedures"
import { getCityPageUrls } from "../../routers/contentstack/destinationCityPage/utils"
import { getCountryPageUrls } from "../../routers/contentstack/destinationCountryPage/utils"
import { getHotelPageUrls } from "../../routers/contentstack/hotelPage/utils"
import { ApiCountry, type Country } from "../../types/country"
import { ApiCountry } from "../../types/country"
import { getCitiesByCountry } from "../hotels/services/getCitiesByCountry"
import { getCountries } from "../hotels/services/getCountries"
import { getLocationsByCountries } from "../hotels/services/getLocationsByCountries"
import { filterAndCategorizeAutoComplete } from "./util/filterAndCategorizeAutoComplete"
import { mapLocationToAutoCompleteLocation } from "./util/mapLocationToAutoCompleteLocation"
import type { Country } from "@scandic-hotels/common/constants/country"
import type { AutoCompleteLocation } from "./schema"
const destinationsAutoCompleteInputSchema = z.object({

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import { Country } from "../../../types/country"
import { Country } from "@scandic-hotels/common/constants/country"
export const getCityPagesInput = z.object({
country: z.nativeEnum(Country),

View File

@@ -1,9 +1,9 @@
import { z } from "zod"
import { Country } from "@scandic-hotels/common/constants/country"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { Country } from "../../../types/country"
import { DestinationCountryPageEnum } from "../../../types/destinationCountryPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {

View File

@@ -3,13 +3,14 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { GetDestinationCityListData } from "../../../graphql/Query/DestinationCityPage/DestinationCityListData.graphql"
import { GetCountryPageUrls } from "../../../graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
import { request } from "../../../graphql/request"
import { ApiCountry, type Country } from "../../../types/country"
import { ApiCountry } from "../../../types/country"
import { DestinationCountryPageEnum } from "../../../types/destinationCountryPage"
import { generateTag, generateTagsFromSystem } from "../../../utils/generateTag"
import { getCitiesByCountry } from "../../hotels/services/getCitiesByCountry"
import { destinationCityListDataSchema } from "../destinationCityPage/output"
import { countryPageUrlsSchema } from "./output"
import type { Country } from "@scandic-hotels/common/constants/country"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { GetDestinationCityListDataResponse } from "../../../types/destinationCityPage"

View File

@@ -12,7 +12,7 @@ import {
contentstackExtendedProcedureUID,
serviceProcedure,
} from "../../../procedures"
import { ApiCountry, type Country } from "../../../types/country"
import { ApiCountry } from "../../../types/country"
import {
generateRefsResponseTag,
generateTag,
@@ -28,6 +28,8 @@ import {
} from "./output"
import { getSortedDestinationsByLanguage } from "./utils"
import type { Country } from "@scandic-hotels/common/constants/country"
import type {
GetDestinationOverviewPageData,
GetDestinationOverviewPageRefsSchema,

View File

@@ -1,6 +1,7 @@
import { Country } from "@scandic-hotels/common/constants/country"
import { Lang } from "@scandic-hotels/common/constants/language"
import { ApiCountry, Country } from "../../../types/country"
import { ApiCountry } from "../../../types/country"
import type { DestinationsData } from "../../../types/destinationsData"

View File

@@ -1,5 +1,6 @@
import { z } from "zod"
import { Country } from "@scandic-hotels/common/constants/country"
import {
type FindMyBookingRoute,
findMyBookingRoutes,
@@ -7,7 +8,6 @@ import {
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { Country } from "../../../types/country"
import { RTETypeEnum } from "../../../types/RTEenums"
import { destinationFilterSchema } from "../schemas/destinationFilters"
import { systemSchema } from "../schemas/system"

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { Country } from "@scandic-hotels/common/constants/country"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { Country } from "../../../../types/country"
export const locationFilterSchema = z
.object({

View File

@@ -1,11 +1,11 @@
import { z } from "zod"
import { Country } from "@scandic-hotels/common/constants/country"
import { Lang } from "@scandic-hotels/common/constants/language"
import { BreakfastPackageEnum } from "../../enums/breakfast"
import { ChildBedMapEnum } from "../../enums/childBedMapEnum"
import { RoomPackageCodeEnum } from "../../enums/roomFilter"
import { Country } from "../../types/country"
const childrenInRoomSchema = z
.array(

View File

@@ -12,10 +12,9 @@ import { locationCitySchema } from "../schemas/location/city"
import { locationHotelSchema } from "../schemas/location/hotel"
import { getCity } from "./getCity"
import type { Country } from "@scandic-hotels/common/constants/country"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Country } from "../../../types/country"
const hotelUtilsLogger = createLogger("getLocationsByCountries")
type CitiesNamesByCountry = Record<

View File

@@ -1,14 +1,6 @@
import { Country } from "@scandic-hotels/common/constants/country"
import { Lang } from "@scandic-hotels/common/constants/language"
export enum Country {
Denmark = "Denmark",
Finland = "Finland",
Germany = "Germany",
Norway = "Norway",
Poland = "Poland",
Sweden = "Sweden",
}
export const ApiCountry: Record<Lang, Record<Country, string>> = {
[Lang.da]: {
[Country.Denmark]: "Danmark",

View File

@@ -1,3 +1,4 @@
import type { Country } from "@scandic-hotels/common/constants/country"
import type { z } from "zod"
import type {
@@ -5,7 +6,6 @@ import type {
countriesSchema,
locationsSchema,
} from "../routers/hotels/output"
import type { Country } from "./country"
export interface LocationSchema extends z.output<typeof locationsSchema> {}