fix(SW-2754): Fixed issue where server rendered html included faulty links
Approved-by: Matilda Landström
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { type ComponentProps, type PropsWithChildren } from "react"
|
||||||
import { type ComponentProps, type PropsWithChildren, useMemo } from "react"
|
|
||||||
|
|
||||||
import { trackClick } from "@/utils/tracking"
|
import { trackClick } from "@/utils/tracking"
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ export interface ButtonLinkProps
|
|||||||
VariantProps<typeof variants> {
|
VariantProps<typeof variants> {
|
||||||
trackingId?: string
|
trackingId?: string
|
||||||
trackingParams?: Record<string, string>
|
trackingParams?: Record<string, string>
|
||||||
appendToCurrentPath?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ButtonLink({
|
export default function ButtonLink({
|
||||||
@@ -31,10 +29,8 @@ export default function ButtonLink({
|
|||||||
onClick = () => {},
|
onClick = () => {},
|
||||||
trackingId,
|
trackingId,
|
||||||
trackingParams,
|
trackingParams,
|
||||||
appendToCurrentPath,
|
|
||||||
...props
|
...props
|
||||||
}: ButtonLinkProps) {
|
}: ButtonLinkProps) {
|
||||||
const currentPageSlug = usePathname()
|
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant,
|
variant,
|
||||||
color,
|
color,
|
||||||
@@ -44,19 +40,10 @@ export default function ButtonLink({
|
|||||||
className,
|
className,
|
||||||
})
|
})
|
||||||
|
|
||||||
const fullUrl = useMemo(() => {
|
|
||||||
let newPath = href
|
|
||||||
if (appendToCurrentPath) {
|
|
||||||
newPath = `${currentPageSlug}${newPath}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPath
|
|
||||||
}, [href, appendToCurrentPath, currentPageSlug])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={classNames}
|
className={classNames}
|
||||||
href={fullUrl}
|
href={href}
|
||||||
target={target}
|
target={target}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onClick(e)
|
onClick(e)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import AdditionalAmenities from "@/components/SidePeeks/AmenitiesSidepeekContent
|
|||||||
import Accordion from "@/components/TempDesignSystem/Accordion"
|
import Accordion from "@/components/TempDesignSystem/Accordion"
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
||||||
|
|
||||||
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
|
import { SidepeekSlugs } from "@/types/components/hotelPage/hotelPage"
|
||||||
import type { AmenitiesSidePeekProps } from "@/types/components/hotelPage/sidepeek/amenities"
|
import type { AmenitiesSidePeekProps } from "@/types/components/hotelPage/sidepeek/amenities"
|
||||||
@@ -20,6 +21,11 @@ export default async function AmenitiesSidePeek({
|
|||||||
}: AmenitiesSidePeekProps) {
|
}: AmenitiesSidePeekProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
const parkingPageHref = appendSlugToPathname(parking.parkingPageUrl)
|
||||||
|
const accessibilityPageHref = appendSlugToPathname(
|
||||||
|
accessibility.accessibilityPageUrl
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidePeek
|
<SidePeek
|
||||||
contentKey={SidepeekSlugs.amenities}
|
contentKey={SidepeekSlugs.amenities}
|
||||||
@@ -31,7 +37,7 @@ export default async function AmenitiesSidePeek({
|
|||||||
<ParkingAccordionItem
|
<ParkingAccordionItem
|
||||||
parking={parking.parking}
|
parking={parking.parking}
|
||||||
elevatorPitch={parking.parkingElevatorPitch}
|
elevatorPitch={parking.parkingElevatorPitch}
|
||||||
parkingPageUrl={parking.parkingPageUrl}
|
parkingPageHref={parkingPageHref}
|
||||||
/>
|
/>
|
||||||
<BreakfastAccordionItem
|
<BreakfastAccordionItem
|
||||||
restaurants={restaurants}
|
restaurants={restaurants}
|
||||||
@@ -39,7 +45,7 @@ export default async function AmenitiesSidePeek({
|
|||||||
/>
|
/>
|
||||||
<CheckInCheckOutAccordionItem checkInData={checkInInformation} />
|
<CheckInCheckOutAccordionItem checkInData={checkInInformation} />
|
||||||
<AccessibilityAccordionItem
|
<AccessibilityAccordionItem
|
||||||
accessibilityPageUrl={accessibility.accessibilityPageUrl}
|
accessibilityPageHref={accessibilityPageHref}
|
||||||
elevatorPitch={accessibility.elevatorPitch}
|
elevatorPitch={accessibility.elevatorPitch}
|
||||||
/>
|
/>
|
||||||
<AdditionalAmenities amenities={amenitiesList} />
|
<AdditionalAmenities amenities={amenitiesList} />
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Button from "@/components/TempDesignSystem/Button"
|
import ButtonLink from "@/components/ButtonLink"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
||||||
|
|
||||||
import SidePeekImages from "../Images"
|
import SidePeekImages from "../Images"
|
||||||
import { getConferenceRoomTexts } from "./util"
|
import { getConferenceRoomTexts } from "./util"
|
||||||
@@ -23,6 +23,7 @@ export default async function MeetingsAndConferencesSidePeek({
|
|||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
|
const { seatingText, roomText } = await getConferenceRoomTexts(meetingRooms)
|
||||||
const visibleImages = meetingFacilities?.heroImages.slice(0, 2)
|
const visibleImages = meetingFacilities?.heroImages.slice(0, 2)
|
||||||
|
const meetingPageHref = appendSlugToPathname(meetingPageUrl)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidePeek
|
<SidePeek
|
||||||
@@ -53,22 +54,21 @@ export default async function MeetingsAndConferencesSidePeek({
|
|||||||
</Body>
|
</Body>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{meetingPageUrl && (
|
{meetingPageHref ? (
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<Button fullWidth theme="base" intent="secondary" asChild>
|
<ButtonLink
|
||||||
<Link
|
variant="Secondary"
|
||||||
href={`/${meetingPageUrl}`}
|
color="Primary"
|
||||||
weight="bold"
|
size="Medium"
|
||||||
color="burgundy"
|
href={meetingPageHref}
|
||||||
appendToCurrentPath
|
typography="Body/Paragraph/mdBold"
|
||||||
>
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "Read more",
|
defaultMessage: "Read more",
|
||||||
})}
|
})}
|
||||||
</Link>
|
</ButtonLink>
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</SidePeek>
|
</SidePeek>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ButtonLink from "@/components/ButtonLink"
|
|||||||
import OpeningHours from "@/components/OpeningHours"
|
import OpeningHours from "@/components/OpeningHours"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
||||||
|
|
||||||
import SidePeekImages from "../../Images"
|
import SidePeekImages from "../../Images"
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ export default async function RestaurantBarItem({
|
|||||||
} = restaurant
|
} = restaurant
|
||||||
const visibleImages = restaurant.content.images.slice(0, 2)
|
const visibleImages = restaurant.content.images.slice(0, 2)
|
||||||
|
|
||||||
|
const restaurantPageHref = restaurantPage
|
||||||
|
? appendSlugToPathname(restaurant.nameInUrl)
|
||||||
|
: null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.restaurantBarItem}>
|
<div className={styles.restaurantBarItem}>
|
||||||
<div className={styles.stickyHeading}>
|
<div className={styles.stickyHeading}>
|
||||||
@@ -87,8 +92,8 @@ export default async function RestaurantBarItem({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{/* If (restaurantPage && bookTableUrl && mainBody==empty), link to external restaurant page. */}
|
{/* If (restaurantPageHref && bookTableUrl && mainBody==empty), link to external restaurant page. */}
|
||||||
{bookTableUrl || restaurantPage ? (
|
{bookTableUrl || restaurantPageHref ? (
|
||||||
<div className={styles.ctaWrapper}>
|
<div className={styles.ctaWrapper}>
|
||||||
{bookTableUrl ? (
|
{bookTableUrl ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
@@ -101,7 +106,7 @@ export default async function RestaurantBarItem({
|
|||||||
trackingId="book a table"
|
trackingId="book a table"
|
||||||
trackingParams={{ restaurantName: name }}
|
trackingParams={{ restaurantName: name }}
|
||||||
>
|
>
|
||||||
{restaurantPage && !mainBody?.length
|
{restaurantPageHref && !mainBody?.length
|
||||||
? intl.formatMessage({
|
? intl.formatMessage({
|
||||||
defaultMessage: "Read more",
|
defaultMessage: "Read more",
|
||||||
})
|
})
|
||||||
@@ -110,14 +115,13 @@ export default async function RestaurantBarItem({
|
|||||||
})}
|
})}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
) : null}
|
) : null}
|
||||||
{restaurantPage && mainBody?.length ? (
|
{restaurantPageHref && mainBody?.length ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
color="Primary"
|
color="Primary"
|
||||||
size="Medium"
|
size="Medium"
|
||||||
typography="Body/Paragraph/mdBold"
|
typography="Body/Paragraph/mdBold"
|
||||||
href={`/${restaurant.nameInUrl}`}
|
href={restaurantPageHref}
|
||||||
appendToCurrentPath
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import ButtonLink from "@/components/ButtonLink"
|
import ButtonLink from "@/components/ButtonLink"
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { appendSlugToPathname } from "@/utils/appendSlugToPathname"
|
||||||
|
|
||||||
import Facility from "./Facility"
|
import Facility from "./Facility"
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export default async function WellnessAndExerciseSidePeek({
|
|||||||
spaPage,
|
spaPage,
|
||||||
}: WellnessAndExerciseSidePeekProps) {
|
}: WellnessAndExerciseSidePeekProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
const wellnessExercisePageHref = appendSlugToPathname(wellnessExercisePageUrl)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidePeek
|
<SidePeek
|
||||||
@@ -40,19 +42,18 @@ export default async function WellnessAndExerciseSidePeek({
|
|||||||
{spaPage.buttonCTA}
|
{spaPage.buttonCTA}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
)}
|
)}
|
||||||
{wellnessExercisePageUrl && (
|
{wellnessExercisePageHref ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
href={`/${wellnessExercisePageUrl}`}
|
href={wellnessExercisePageHref}
|
||||||
color="Primary"
|
color="Primary"
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
typography="Body/Paragraph/mdBold"
|
typography="Body/Paragraph/mdBold"
|
||||||
appendToCurrentPath
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "Show Gym & Wellness",
|
defaultMessage: "Show Gym & Wellness",
|
||||||
})}
|
})}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SidePeek>
|
</SidePeek>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import type { AccessibilityAccordionItemProps } from "@/types/components/sidePee
|
|||||||
|
|
||||||
export default function AccessibilityAccordionItem({
|
export default function AccessibilityAccordionItem({
|
||||||
elevatorPitch,
|
elevatorPitch,
|
||||||
accessibilityPageUrl,
|
accessibilityPageHref,
|
||||||
}: AccessibilityAccordionItemProps) {
|
}: AccessibilityAccordionItemProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
if (!elevatorPitch && !accessibilityPageUrl) {
|
if (!elevatorPitch && !accessibilityPageHref) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,18 +36,17 @@ export default function AccessibilityAccordionItem({
|
|||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<p>{elevatorPitch}</p>
|
<p>{elevatorPitch}</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
{accessibilityPageUrl && (
|
{accessibilityPageHref ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
href={`/${accessibilityPageUrl}`}
|
href={accessibilityPageHref}
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
color="Primary"
|
color="Primary"
|
||||||
size="Medium"
|
size="Medium"
|
||||||
typography="Body/Paragraph/mdBold"
|
typography="Body/Paragraph/mdBold"
|
||||||
appendToCurrentPath
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ defaultMessage: "About accessibility" })}
|
{intl.formatMessage({ defaultMessage: "About accessibility" })}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { ParkingAccordionItemProps } from "@/types/components/sidePeeks/ame
|
|||||||
export default function ParkingAccordionItem({
|
export default function ParkingAccordionItem({
|
||||||
parking,
|
parking,
|
||||||
elevatorPitch,
|
elevatorPitch,
|
||||||
parkingPageUrl,
|
parkingPageHref,
|
||||||
}: ParkingAccordionItemProps) {
|
}: ParkingAccordionItemProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
@@ -39,20 +39,19 @@ export default function ParkingAccordionItem({
|
|||||||
{parking.map((data) => (
|
{parking.map((data) => (
|
||||||
<ParkingInformation key={data.type} parking={data} />
|
<ParkingInformation key={data.type} parking={data} />
|
||||||
))}
|
))}
|
||||||
{parkingPageUrl && (
|
{parkingPageHref ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
href={`/${parkingPageUrl}`}
|
href={parkingPageHref}
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
color="Primary"
|
color="Primary"
|
||||||
size="Medium"
|
size="Medium"
|
||||||
typography="Body/Paragraph/mdBold"
|
typography="Body/Paragraph/mdBold"
|
||||||
appendToCurrentPath
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "About parking",
|
defaultMessage: "About parking",
|
||||||
})}
|
})}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export default function Link({
|
|||||||
* in your component that passes the href here.
|
* in your component that passes the href here.
|
||||||
*/
|
*/
|
||||||
keepSearchParams,
|
keepSearchParams,
|
||||||
appendToCurrentPath,
|
|
||||||
...props
|
...props
|
||||||
}: LinkProps) {
|
}: LinkProps) {
|
||||||
const currentPageSlug = usePathname()
|
const currentPageSlug = usePathname()
|
||||||
@@ -55,9 +54,6 @@ export default function Link({
|
|||||||
|
|
||||||
const fullUrl = useMemo(() => {
|
const fullUrl = useMemo(() => {
|
||||||
let newPath = href
|
let newPath = href
|
||||||
if (appendToCurrentPath) {
|
|
||||||
newPath = `${currentPageSlug}${newPath}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keepSearchParams && searchParams.size) {
|
if (keepSearchParams && searchParams.size) {
|
||||||
if (newPath.includes("?")) {
|
if (newPath.includes("?")) {
|
||||||
@@ -74,13 +70,7 @@ export default function Link({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return newPath
|
return newPath
|
||||||
}, [
|
}, [href, searchParams, keepSearchParams])
|
||||||
href,
|
|
||||||
searchParams,
|
|
||||||
keepSearchParams,
|
|
||||||
appendToCurrentPath,
|
|
||||||
currentPageSlug,
|
|
||||||
])
|
|
||||||
|
|
||||||
// TODO: Remove this check (and hook) and only return <Link /> when current web is deleted
|
// TODO: Remove this check (and hook) and only return <Link /> when current web is deleted
|
||||||
const isExternal = useCheckIfExternalLink(href)
|
const isExternal = useCheckIfExternalLink(href)
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ export interface LinkProps
|
|||||||
trackingId?: string
|
trackingId?: string
|
||||||
trackingParams?: Record<string, string>
|
trackingParams?: Record<string, string>
|
||||||
keepSearchParams?: boolean
|
keepSearchParams?: boolean
|
||||||
appendToCurrentPath?: boolean
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { ButtonPropsSlot } from "@/components/TempDesignSystem/Button/button"
|
|
||||||
|
|
||||||
export type ButtonLinkProps = React.PropsWithChildren &
|
|
||||||
Omit<ButtonPropsSlot, "asChild"> &
|
|
||||||
Pick<React.AnchorHTMLAttributes<HTMLAnchorElement>, "onClick" | "target"> & {
|
|
||||||
href: string
|
|
||||||
trackingId?: string
|
|
||||||
trackingParams?: Record<string, string>
|
|
||||||
appendToCurrentPath?: boolean
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import type {
|
|||||||
} from "@/types/hotel"
|
} from "@/types/hotel"
|
||||||
|
|
||||||
export interface ParkingAccordionItemProps {
|
export interface ParkingAccordionItemProps {
|
||||||
parkingPageUrl?: string
|
parkingPageHref?: string | null
|
||||||
parking: Parking[]
|
parking: Parking[]
|
||||||
elevatorPitch?: string
|
elevatorPitch?: string
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ export interface CheckInCheckOutAccordionItemProps {
|
|||||||
|
|
||||||
export interface AccessibilityAccordionItemProps {
|
export interface AccessibilityAccordionItemProps {
|
||||||
elevatorPitch?: string
|
elevatorPitch?: string
|
||||||
accessibilityPageUrl?: string
|
accessibilityPageHref?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdditionalAmenitiesProps {
|
export interface AdditionalAmenitiesProps {
|
||||||
|
|||||||
14
apps/scandic-web/utils/appendSlugToPathname.ts
Normal file
14
apps/scandic-web/utils/appendSlugToPathname.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { headers } from "next/headers"
|
||||||
|
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
export function appendSlugToPathname(slug?: string) {
|
||||||
|
const pathname = headers().get("x-pathname")
|
||||||
|
const lang = getLang()
|
||||||
|
|
||||||
|
if (!pathname || !slug) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/${lang}${pathname}/${slug}`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user