Merged in chore/upgrade-next (pull request #3124)

Upgrade next@15.5.6

* chore: upgrade next@15.5.6

* chore: upgrade turborepo@2.6.1

* fix typings for scandic-web

* fix: set correct type for pages

* cleanup

* fix more route.ts typing issues

* Merge branch 'master' of bitbucket.org:scandic-swap/web into chore/upgrade-next

* explicitly import the types


Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2025-11-13 07:33:56 +00:00
parent ce469bc4b4
commit dc53ab9245
64 changed files with 746 additions and 404 deletions

View File

@@ -12,7 +12,7 @@ import { signOut } from "@/auth"
export async function GET(
request: NextRequest,
context: { params: Promise<{ lang: Lang }> }
context: RouteContext<"/[lang]/logout">
) {
const publicURL = getPublicURL(request)
@@ -71,6 +71,8 @@ export async function GET(
case Lang.sv:
redirectUrlValue = env.SEAMLESS_LOGOUT_SV
break
default:
throw new Error(`Unsupported language for logout: ${params.lang}`)
}
const redirectUrl = new URL(redirectUrlValue)
logger.debug(

View File

@@ -3,9 +3,7 @@ import { Suspense } from "react"
import Breadcrumbs from "@/components/Breadcrumbs"
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
import type { LangParams, PageArgs } from "@/types/params"
export default function AllBreadcrumbs({}: PageArgs<LangParams>) {
export default function AllBreadcrumbs() {
return (
<Suspense fallback={<BreadcrumbsSkeleton />}>
<Breadcrumbs size="contentWidth" />

View File

@@ -10,13 +10,9 @@ import { getIntl } from "@/i18n"
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function MyPages({}: PageArgs<
LangParams & { path: string[] }
>) {
export default async function MyPages() {
const caller = await serverClient()
const accountPageRes = await caller.contentstack.accountPage.get()
const intl = await getIntl()

View File

@@ -4,11 +4,9 @@ import { serverClient } from "@/lib/trpc/server"
import Profile from "@/components/MyPages/Profile"
import type { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function ProfilePage({}: PageArgs<LangParams>) {
export default async function ProfilePage() {
const caller = await serverClient()
const accountPage = await caller.contentstack.accountPage.get()

View File

@@ -2,20 +2,19 @@ import { setPreviewData } from "@scandic-hotels/trpc/previewContext"
import InitLivePreview from "@/components/LivePreview"
import type { PageArgs } from "@/types/params"
export default async function PreviewPage(
props: PageArgs<
object,
{ live_preview?: string; entry_uid?: string; isPreview?: string }
>
) {
export default async function PreviewPage(props: PageProps<"/[lang]">) {
const searchParams = await props.searchParams
const hash = searchParams.live_preview
const uid = searchParams.entry_uid
const shouldInitializePreview = searchParams.isPreview === "true"
if (shouldInitializePreview && hash && uid) {
if (
shouldInitializePreview &&
typeof hash === "string" &&
hash &&
typeof uid === "string" &&
uid
) {
setPreviewData({ hash, uid })
}

View File

@@ -5,20 +5,23 @@ import DestinationCityPageSkeleton from "@/components/ContentType/DestinationPag
import styles from "./page.module.css"
import type { PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function DestinationCityPagePage(
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }>
props: PageProps<"/[lang]/destination_city_page/[uid]">
) {
const searchParams = await props.searchParams
return (
<div className={styles.page}>
<Suspense fallback={<DestinationCityPageSkeleton />}>
<DestinationCityPage
isMapView={searchParams.view === "map"}
filterFromUrl={searchParams.filterFromUrl}
filterFromUrl={
typeof searchParams.filterFromUrl === "string"
? searchParams.filterFromUrl
: undefined
}
/>
</Suspense>
</div>

View File

@@ -5,20 +5,22 @@ import DestinationCountryPageSkeleton from "@/components/ContentType/Destination
import styles from "./page.module.css"
import type { PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function DestinationCountryPagePage(
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }>
props: PageProps<"/[lang]/destination_country_page/[uid]">
) {
const searchParams = await props.searchParams
return (
<div className={styles.page}>
<Suspense fallback={<DestinationCountryPageSkeleton />}>
<DestinationCountryPage
filterFromUrl={searchParams.filterFromUrl}
isMapView={searchParams.view === "map"}
filterFromUrl={
typeof searchParams.filterFromUrl === "string"
? searchParams.filterFromUrl
: undefined
}
/>
</Suspense>
</div>

View File

@@ -17,17 +17,21 @@ import HotelSubpage from "@/components/ContentType/HotelSubpage"
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function HotelPagePage(
props: PageArgs<LangParams, { subpage?: string; view?: "map" }>
props: PageProps<"/[lang]/hotel_page/[uid]">
) {
const searchParams = await props.searchParams
const params = await props.params
const hotelPageData = await getHotelPage()
const subpage =
typeof searchParams.subpage === "string" ? searchParams.subpage : null
const lang = params.lang as Lang
if (!hotelPageData) {
return notFound()
}
@@ -35,7 +39,7 @@ export default async function HotelPagePage(
const hotelData = await getHotel({
hotelId: hotelPageData.hotel_page_id,
isCardOnlyPayment: false,
language: params.lang,
language: lang,
})
if (!hotelData) {
@@ -48,10 +52,10 @@ export default async function HotelPagePage(
setTheme(hotelTheme)
if (searchParams.subpage) {
if (subpage) {
return (
<HotelPageWrapper hotelTheme={hotelTheme}>
<HotelSubpage hotelData={hotelData} subpage={searchParams.subpage} />
<HotelSubpage hotelData={hotelData} subpage={subpage} />
</HotelPageWrapper>
)
} else if (searchParams.view === "map") {

View File

@@ -1,22 +1,13 @@
import styles from "./layout.module.css"
import type { LangParams, LayoutArgs } from "@/types/params"
import type { LayoutProps } from "@/.next/types/app/[lang]/(live)/(public)/(contentTypes)/layout"
export default function ContentTypeLayout({
breadcrumbs,
preview,
children,
}: React.PropsWithChildren<
LayoutArgs<LangParams> & {
breadcrumbs: React.ReactNode
preview: React.ReactNode
}
>) {
export default function ContentTypeLayout(props: LayoutProps) {
return (
<section className={styles.layout}>
{preview}
{breadcrumbs}
{children}
{props.preview}
{props.breadcrumbs}
{props.children}
</section>
)
}

View File

@@ -4,12 +4,10 @@ import StartPage from "@/components/ContentType/StartPage"
import styles from "./page.module.css"
import type { NextSearchParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function StartPagePage(
props: PageArgs<object, NextSearchParams>
props: PageProps<"/[lang]/start_page/[uid]">
) {
const searchParams = await props.searchParams
const booking = parseBookingWidgetSearchParams(searchParams)

View File

@@ -5,10 +5,8 @@ import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import type { LangParams, PageArgs } from "@/types/params"
export default async function BookingConfirmationPage(
props: PageArgs<LangParams, { RefId?: string }>
props: PageProps<"/[lang]/hotelreservation/booking-confirmation">
) {
const searchParams = await props.searchParams
const lang = await getLang()

View File

@@ -5,28 +5,25 @@ import { logger } from "@scandic-hotels/common/logger"
import GuaranteeCallbackPage from "@/components/GuaranteeCallback"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import type { LangParams, PageArgs } from "@/types/params"
export default async function GuaranteePaymentCallbackPage(
props: PageArgs<
LangParams,
{
status?: PaymentCallbackStatusEnum
RefId?: string
confirmationNumber?: string
ancillary?: string
}
>
props: PageProps<"/[lang]/hotelreservation/gla-payment-callback">
) {
const searchParams = await props.searchParams
const params = await props.params
logger.debug(`[gla-payment-callback] callback started`)
const lang = params.lang
const status = searchParams.status
const confirmationNumber = searchParams.confirmationNumber
const refId = searchParams.RefId
const lang = params.lang as Lang
const status = searchParams.status as PaymentCallbackStatusEnum
const confirmationNumber =
typeof searchParams.confirmationNumber === "string"
? searchParams.confirmationNumber
: null
const refId =
typeof searchParams.RefId === "string" ? searchParams.RefId : null
if (!status || !confirmationNumber || !refId) {
notFound()
}

View File

@@ -1,9 +1,7 @@
import styles from "./layout.module.css"
import type { LangParams, LayoutArgs } from "@/types/params"
export default function PaymentCallbackLayout({
children,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
}: LayoutProps<"/[lang]/hotelreservation/payment-callback">) {
return <div className={styles.layout}>{children}</div>
}

View File

@@ -6,23 +6,16 @@ import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { auth } from "@/auth"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import type { LangParams, PageArgs } from "@/types/params"
export default async function PaymentCallbackPage(
props: PageArgs<
LangParams,
{
status?: PaymentCallbackStatusEnum
confirmationNumber?: string
}
>
props: PageProps<"/[lang]/hotelreservation/payment-callback">
) {
const searchParams = await props.searchParams
const params = await props.params
logger.debug(`[payment-callback] callback started`)
const lang = params.lang
const lang = params.lang as Lang
let userAccessToken = null
const session = await auth()

View File

@@ -4,22 +4,20 @@ import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getHotel } from "@/lib/trpc/memoizedRequests"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export async function generateMetadata({
searchParams,
params,
}: PageArgs<LangParams, { hotel: string }>): Promise<Metadata> {
}: PageProps<"/[lang]/hotelreservation/alternative-hotels/map">): Promise<Metadata> {
const intl = await getIntl()
const { hotel } = await searchParams
const { lang } = await params
const lang = (await params).lang as Lang
if (!hotel || Array.isArray(hotel)) {
return {}
@@ -50,10 +48,11 @@ export async function generateMetadata({
}
export default async function AlternativeHotelsMapPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/alternative-hotels/map">
) {
const searchParams = await props.searchParams
const lang = await getLang()
const params = await props.params
const lang = params.lang as Lang
return (
<div className={styles.main}>

View File

@@ -4,24 +4,19 @@ import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getHotel } from "@/lib/trpc/memoizedRequests"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import {
type LangParams,
type NextSearchParams,
type PageArgs,
} from "@/types/params"
export async function generateMetadata({
searchParams,
params,
}: PageArgs<LangParams, { hotel: string }>): Promise<Metadata> {
export async function generateMetadata(
props: PageProps<"/[lang]/hotelreservation/alternative-hotels">
): Promise<Metadata> {
const intl = await getIntl()
const searchParams = await props.searchParams
const params = await props.params
const { hotel } = await searchParams
const { lang } = await params
if (!hotel || Array.isArray(hotel)) {
return {}
@@ -29,7 +24,7 @@ export async function generateMetadata({
const hotelData = await getHotel({
hotelId: hotel,
language: lang,
language: params.lang as Lang,
isCardOnlyPayment: false,
})
const hotelName = hotelData?.additionalData?.name
@@ -52,10 +47,11 @@ export async function generateMetadata({
}
export default async function AlternativeHotelsPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/alternative-hotels">
) {
const searchParams = await props.searchParams
const lang = await getLang()
const lang = (await props.params).lang as Lang
return (
<AlternativeHotelsPagePrimitive
lang={lang}

View File

@@ -2,17 +2,17 @@ import { EnterDetailsPage as EnterDetailsPagePrimitive } from "@scandic-hotels/b
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function DetailsPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/details">
) {
const { lang } = await props.params
const params = await props.params
const searchParams = await props.searchParams
return (
<EnterDetailsPagePrimitive
lang={lang}
lang={params.lang as Lang}
searchParams={searchParams}
config={bookingFlowConfig}
/>

View File

@@ -1,9 +1,7 @@
import styles from "./layout.module.css"
import type { LangParams, LayoutArgs } from "@/types/params"
export default function StandardHotelReservationLayout({
children,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
}: LayoutProps<"/[lang]/hotelreservation">) {
return <div className={styles.layout}>{children}</div>
}

View File

@@ -6,16 +6,16 @@ import {
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function HotelReservationPage(
props: PageArgs<LangParams>
props: PageProps<"/[lang]/hotelreservation">
) {
const params = await props.params
const pageTrackingData: TrackingSDKPageData = {
pageId: "hotelreservation",
domainLanguage: params.lang,
domainLanguage: params.lang as Lang,
channel: TrackingChannelEnum["hotelreservation"],
pageName: "hotelreservation",
siteSections: "hotelreservation",

View File

@@ -3,34 +3,31 @@ import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase"
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export async function generateMetadata({
searchParams,
}: PageArgs<LangParams, { city: string }>): Promise<Metadata> {
const { city } = await searchParams
export async function generateMetadata(
props: PageProps<"/[lang]/hotelreservation/select-hotel/map">
): Promise<Metadata> {
const { city } = await props.searchParams
return {
title: `${toCapitalCase(city)}`,
title: `${toCapitalCase(typeof city === "string" ? city : "")}`,
}
}
export default async function SelectHotelMapPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/select-hotel/map">
) {
const searchParams = await props.searchParams
const lang = await getLang()
const params = await props.params
return (
<div className={styles.main}>
<SelectHotelMapPagePrimitive
lang={lang}
lang={params.lang as Lang}
searchParams={searchParams}
config={bookingFlowConfig}
/>

View File

@@ -3,27 +3,29 @@ import { toCapitalCase } from "@scandic-hotels/common/utils/toCapitalCase"
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getLang } from "@/i18n/serverContext"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export async function generateMetadata(
props: PageProps<"/[lang]/hotelreservation/select-hotel">
): Promise<Metadata> {
const searchParams = await props.searchParams
export async function generateMetadata({
searchParams,
}: PageArgs<LangParams, { city: string }>): Promise<Metadata> {
const { city } = await searchParams
if (typeof searchParams.city !== "string" || !searchParams.city) {
return {}
}
return {
title: `${toCapitalCase(city)}`,
title: `${toCapitalCase(searchParams.city)}`,
}
}
export default async function SelectHotelPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/select-hotel">
) {
const searchParams = await props.searchParams
const lang = await getLang()
const params = await props.params
const lang = params.lang as Lang
return (
<SelectHotelPagePrimitive

View File

@@ -3,26 +3,22 @@ import { SelectRatePage as SelectRatePagePrimitive } from "@scandic-hotels/booki
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getHotel } from "@/lib/trpc/memoizedRequests"
import { getLang } from "@/i18n/serverContext"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import {
type LangParams,
type NextSearchParams,
type PageArgs,
} from "@/types/params"
export async function generateMetadata(
props: PageProps<"/[lang]/hotelreservation/select-rate">
): Promise<Metadata> {
const searchParams = await props.searchParams
const params = await props.params
export async function generateMetadata({
searchParams,
params,
}: PageArgs<LangParams, { hotel: string }>): Promise<Metadata> {
const { hotel } = await searchParams
const { lang } = await params
if (typeof searchParams.hotel !== "string" || !searchParams.hotel) {
return {}
}
const hotelData = await getHotel({
hotelId: hotel,
language: lang,
hotelId: searchParams.hotel,
language: params.lang as Lang,
isCardOnlyPayment: false,
})
@@ -36,10 +32,12 @@ export async function generateMetadata({
}
export default async function SelectRatePage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation/select-rate">
) {
const searchParams = await props.searchParams
const lang = await getLang()
const params = await props.params
const lang = params.lang as Lang
return (
<SelectRatePagePrimitive

View File

@@ -3,19 +3,21 @@ import { setLang } from "@/i18n/serverContext"
import Tracking from "./tracking"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function MyStayPage(
props: PageArgs<LangParams, { RefId?: string }>
props: PageProps<"/[lang]/hotelreservation/my-stay">
) {
const searchParams = await props.searchParams
const params = await props.params
setLang(params.lang)
const refId = searchParams.RefId
const lang = params.lang as Lang
setLang(lang)
const refId = typeof searchParams.RefId === "string" ? searchParams.RefId : ""
return (
<>
<MyStay refId={refId} lang={params.lang} />
<MyStay refId={refId} lang={lang} />
<Tracking />
</>
)

View File

@@ -12,7 +12,7 @@ import { signIn } from "@/auth"
export async function GET(
request: NextRequest,
context: { params: Promise<{ lang: Lang }> }
context: RouteContext<"/[lang]/login">
) {
const contextParams = await context.params
const publicURL = getPublicURL(request)
@@ -89,6 +89,10 @@ export async function GET(
case Lang.sv:
redirectUrlValue = env.SEAMLESS_LOGIN_SV
break
default:
throw new Error(
`Unsupported language for login: ${contextParams.lang}`
)
}
const redirectUrl = new URL(redirectUrlValue)
logger.debug(

View File

@@ -8,11 +8,9 @@ import { getPublicURL } from "@/server/utils"
import { signIn } from "@/auth"
import type { Lang } from "@scandic-hotels/common/constants/language"
export async function GET(
request: NextRequest,
context: { params: Promise<{ lang: Lang }> }
context: RouteContext<"/[lang]/verifymagiclink">
) {
const publicURL = getPublicURL(request)

View File

@@ -6,10 +6,8 @@ import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
import { getLang } from "@/i18n/serverContext"
import type { NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetDestinationCityPage(
props: PageArgs<object, NextSearchParams>
props: PageProps<"/[lang]/destination_city_page/[uid]">
) {
const searchParams = await props.searchParams

View File

@@ -4,17 +4,16 @@ import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/uti
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import { getLang } from "@/i18n/serverContext"
import type { NextSearchParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function BookingWidgetHotelPage(
props: PageArgs<object, NextSearchParams & { subpage?: string }>
props: PageProps<"/[lang]/hotel_page/[uid]">
) {
const searchParams = await props.searchParams
const params = await props.params
const lang = params.lang as Lang
const hotelPageData = await getHotelPage()
const lang = await getLang()
const hotelData = await getHotel({
hotelId: hotelPageData?.hotel_page_id || "",
language: lang,

View File

@@ -3,18 +3,17 @@ import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/uti
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getLang } from "@/i18n/serverContext"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function BookingWidgetPage(
props: PageArgs<LangParams, NextSearchParams>
props: PageProps<"/[lang]/hotelreservation">
) {
const searchParams = await props.searchParams
const params = await props.params
const booking = parseBookingWidgetSearchParams(searchParams)
const lang = await getLang()
const lang = params.lang as Lang
return (
<BookingWidget booking={booking} lang={lang} config={bookingFlowConfig} />

View File

@@ -3,18 +3,15 @@ import { parseBookingWidgetSearchParams } from "@scandic-hotels/booking-flow/uti
import { bookingFlowConfig } from "@/constants/bookingFlowConfig"
import { getLang } from "@/i18n/serverContext"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage(
props: PageArgs<LangParams, NextSearchParams>
) {
export default async function BookingWidgetPage(props: PageProps<"/[lang]">) {
const searchParams = await props.searchParams
const params = await props.params
const booking = parseBookingWidgetSearchParams(searchParams)
const lang = await getLang()
const lang = params.lang as Lang
return (
<BookingWidget booking={booking} lang={lang} config={bookingFlowConfig} />

View File

@@ -35,21 +35,13 @@ import { getMessages } from "@/i18n"
import ClientIntlProvider from "@/i18n/Provider"
import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params"
export default async function RootLayout(
props: React.PropsWithChildren<
LayoutArgs<LangParams> & {
bookingwidget: React.ReactNode
}
>
) {
export default async function RootLayout(props: LayoutProps<"/[lang]">) {
const params = await props.params
const lang = params.lang as Lang
const { bookingwidget, children } = props
setLang(params.lang)
const messages = await getMessages(params.lang)
setLang(lang)
const messages = await getMessages(lang)
return (
<html lang={params.lang}>
@@ -69,7 +61,7 @@ export default async function RootLayout(
<SessionProvider basePath="/api/web/auth">
<ClientIntlProvider
defaultLocale={Lang.en}
locale={params.lang}
locale={lang}
messages={messages}
>
<NuqsAdapter>

View File

@@ -1,7 +1,7 @@
import NotFound from "@/components/NotFound"
import type { LangParams, PageArgs } from "@/types/params"
export default function NotFoundPage({}: PageArgs<LangParams>) {
export default function NotFoundPage(
_props: PageProps<"/[lang]/middleware-error/404">
) {
return <NotFound />
}

View File

@@ -1,9 +1,7 @@
import styles from "./page.module.css"
import type { LangParams, LayoutArgs, StatusParams } from "@/types/params"
export default async function MiddlewareError(
props: LayoutArgs<LangParams & StatusParams>
props: PageProps<"/[lang]/middleware-error/[status]">
) {
const params = await props.params
return (

View File

@@ -4,15 +4,14 @@ import { Suspense } from "react"
import { MyStaySkeleton } from "@/components/HotelReservation/MyStay/myStaySkeleton"
import { Receipt } from "@/components/HotelReservation/MyStay/Receipt"
import type { LangParams, PageArgs } from "@/types/params"
export default async function ReceiptPage(
props: PageArgs<LangParams, { RefId?: string }>
props: PageProps<"/[lang]/hotelreservation/my-stay/receipt">
) {
const searchParams = await props.searchParams
if (!searchParams.RefId) {
if (!searchParams.RefId || typeof searchParams.RefId !== "string") {
notFound()
}
return (
<Suspense fallback={<MyStaySkeleton />}>
<Receipt refId={searchParams.RefId} />

View File

@@ -25,20 +25,18 @@ import { getMessages } from "@/i18n"
import ClientIntlProvider from "@/i18n/Provider"
import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params"
import type { LayoutProps } from "@/.next/types/app/[lang]/(no-layout)/layout"
export default async function RootLayout(
props: React.PropsWithChildren<LayoutArgs<LangParams>>
) {
export default async function RootLayout(props: LayoutProps) {
const params = await props.params
const { children } = props
setLang(params.lang)
const messages = await getMessages(params.lang)
const lang = params.lang as Lang
setLang(lang)
const messages = await getMessages(lang)
return (
<html lang={params.lang}>
<html lang={lang}>
<head>
<AdobeSDKScript />
<GTMScript />
@@ -52,7 +50,7 @@ export default async function RootLayout(
<SessionProvider basePath="/api/web/auth">
<ClientIntlProvider
defaultLocale={Lang.en}
locale={params.lang}
locale={lang}
messages={messages}
>
<NuqsAdapter>

View File

@@ -25,7 +25,7 @@ const tokenResponseSchema = z.object({
export async function GET(
request: NextRequest,
props: { params: Promise<{ lang: string }> }
props: RouteContext<"/[lang]/sas-x-scandic/callback">
) {
const params = await props.params
const { lang } = params

View File

@@ -10,11 +10,8 @@ import { TooManyCodesError } from "../components/TooManyCodesError"
import { TooManyFailedAttemptsError } from "../components/TooManyFailedAttemptsError"
import { UnlinkError } from "../components/UnlinkError"
import type { LangParams, PageArgs, SearchParams } from "@/types/params"
export default async function Page(
props: PageArgs<LangParams> &
SearchParams<{ errorCode?: "dateOfBirthMismatch" }>
props: PageProps<"/[lang]/sas-x-scandic/error">
) {
const searchParams = await props.searchParams
const intl = await getIntl()

View File

@@ -9,14 +9,13 @@ import background from "@/public/_static/img/partner/sas/sas_x_scandic_airplane_
import styles from "./layout.module.css"
import type { PropsWithChildren } from "react"
import type { LangParams, LayoutArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function SasXScandicLayout(
props: PropsWithChildren<LayoutArgs<LangParams>>
props: LayoutProps<"/[lang]/sas-x-scandic">
) {
const params = await props.params
const lang = params.lang as Lang
const { children } = props
@@ -29,7 +28,7 @@ export default async function SasXScandicLayout(
>
<header className={styles.header}>
{/* TODO should this link to my-pages sas page? */}
<Link className={styles.backLink} href={profileOverview[params.lang]}>
<Link className={styles.backLink} href={profileOverview[lang]}>
<ArrowLeft height={20} width={20} />
<span className={styles.long}>
{intl.formatMessage({

View File

@@ -8,9 +8,9 @@ import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
import { SASModal } from "../components/SASModal"
import { LinkAccountForm } from "./LinkAccountForm"
import type { LangParams, PageArgs } from "@/types/params"
export default async function SASxScandicLinkPage(props: PageArgs<LangParams>) {
export default async function SASxScandicLinkPage(
props: PageProps<"/[lang]/sas-x-scandic/link">
) {
const params = await props.params
const profile = await getProfileSafely()

View File

@@ -9,15 +9,18 @@ import { getIntl } from "@/i18n"
import { SASModal } from "../../components/SASModal"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function SASxScandicLinkPage(props: PageArgs<LangParams>) {
export default async function SASxScandicLinkPage(
props: PageProps<"/[lang]/sas-x-scandic/link/success">
) {
const params = await props.params
const lang = params.lang as Lang
const intl = await getIntl()
return (
<SASModal>
<Redirect url={partnerSas[params.lang]} timeout={3000} />
<Redirect url={partnerSas[lang]} timeout={3000} />
<MaterialIcon
icon="check_circle"
size={64}

View File

@@ -15,7 +15,6 @@ import { SASModal } from "../components/SASModal"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { LangParams, PageArgs, SearchParams } from "@/types/params"
import type { State } from "../sasUtils"
const searchParamsSchema = z.object({
@@ -25,20 +24,23 @@ const searchParamsSchema = z.object({
type Intent = z.infer<typeof searchParamsSchema>["intent"]
export default async function SASxScandicLoginPage(
props: PageArgs<LangParams> & SearchParams
props: PageProps<"/[lang]/sas-x-scandic/login">
) {
const params = await props.params
const searchParams = await props.searchParams
const result = searchParamsSchema.safeParse(searchParams)
const lang = params.lang as Lang
if (!result.success) {
// TOOD where to redirect?
redirect(`/${params.lang}/sas-x-scandic/link`)
redirect(`/${lang}/sas-x-scandic/link`)
}
const parsedParams = result.data
const intl = await getIntl()
const redirectUri = new URL(
`/${params.lang}/sas-x-scandic/callback`,
`/${lang}/sas-x-scandic/callback`,
env.PUBLIC_URL
).toString()
@@ -49,7 +51,7 @@ export default async function SASxScandicLoginPage(
const audience = "eb-partner-api"
const scope = encodeURIComponent("openid profile email")
const loginLink = `${sasLoginHostname}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${urlState}&audience=${audience}&ui_locales=${SAS_LANGUAGE_MAP[params.lang]}`
const loginLink = `${sasLoginHostname}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${urlState}&audience=${audience}&ui_locales=${SAS_LANGUAGE_MAP[lang]}`
const intentDescriptions: Record<Intent, string> = {
link: intl.formatMessage({

View File

@@ -21,8 +21,6 @@ import OneTimePasswordForm, {
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { ReactNode } from "react"
import type { LangParams, PageArgs, SearchParams } from "@/types/params"
const otpError = z.enum(["invalidCode", "expiredCode"])
const intent = z.enum(["link", "unlink", "transfer"])
const searchParamsSchema = z.object({
@@ -35,10 +33,12 @@ export type OtpError = z.infer<typeof otpError>
type Intent = z.infer<typeof intent>
export default async function SASxScandicOneTimePasswordPage(
props: PageArgs<LangParams> & SearchParams
props: PageProps<"/[lang]/sas-x-scandic/otp">
) {
const params = await props.params
const searchParams = await props.searchParams
const lang = params.lang as Lang
const intl = await getIntl()
const cookieStore = await cookies()
const tokenCookie = cookieStore.get(SAS_TOKEN_STORAGE_KEY)
@@ -50,7 +50,7 @@ export default async function SASxScandicOneTimePasswordPage(
const { intent, to, error } = result.data
if (!verifyTokenValidity(tokenCookie?.value)) {
redirect(`/${params.lang}/sas-x-scandic/login?intent=${intent}`)
redirect(`/${lang}/sas-x-scandic/login?intent=${intent}`)
}
const handleOtpVerified: OnSubmitHandler = async ({ otp }) => {
@@ -65,7 +65,7 @@ export default async function SASxScandicOneTimePasswordPage(
switch (data.status) {
case "ABUSED":
return {
url: `/${params.lang}/sas-x-scandic/error?errorCode=tooManyFailedAttempts`,
url: `/${lang}/sas-x-scandic/error?errorCode=tooManyFailedAttempts`,
}
case "EXPIRED": {
const search = new URLSearchParams({
@@ -74,7 +74,7 @@ export default async function SASxScandicOneTimePasswordPage(
}).toString()
return {
url: `/${params.lang}/sas-x-scandic/otp?${search}`,
url: `/${lang}/sas-x-scandic/otp?${search}`,
}
}
case "RETRY": {
@@ -84,7 +84,7 @@ export default async function SASxScandicOneTimePasswordPage(
}).toString()
return {
url: `/${params.lang}/sas-x-scandic/otp?${search}`,
url: `/${lang}/sas-x-scandic/otp?${search}`,
}
}
case "NOTSENT":
@@ -97,11 +97,11 @@ export default async function SASxScandicOneTimePasswordPage(
switch (intent) {
case "link":
return handleLinkAccount({ lang: params.lang })
return handleLinkAccount({ lang: lang })
case "unlink":
return handleUnlinkAccount({ lang: params.lang })
return handleUnlinkAccount({ lang: lang })
case "transfer":
return handleTransferPoints({ lang: params.lang })
return handleTransferPoints({ lang: lang })
}
}

View File

@@ -19,15 +19,13 @@ import styles from "./transferSuccess.module.css"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { LangParams, PageArgs, SearchParams } from "@/types/params"
export default async function SASxScandicTransferSuccessPage(
props: PageArgs<LangParams> & SearchParams<{ p?: string }>
props: PageProps<"/[lang]/sas-x-scandic/transfer/success">
) {
const searchParams = await props.searchParams
const params = await props.params
const intl = await getIntl()
const { lang } = params
const lang = params.lang as Lang
// TODO get from api
const exchangeRate = 2
@@ -61,7 +59,7 @@ export default async function SASxScandicTransferSuccessPage(
fullWidth
className={styles.backButton}
>
<Link href={partnerSas[params.lang]} color="none">
<Link href={partnerSas[lang]} color="none">
{intl.formatMessage({
id: "transferEuroBonusPoints.backToMyPagesButton",
defaultMessage: "Go back to My Pages",

View File

@@ -9,17 +9,18 @@ import { getIntl } from "@/i18n"
import { SASModal } from "../../components/SASModal"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function SASxScandicUnlinkSuccessPage(
props: PageArgs<LangParams>
props: PageProps<"/[lang]/sas-x-scandic/unlink/success">
) {
const params = await props.params
const lang = params.lang as Lang
const intl = await getIntl()
return (
<SASModal>
<Redirect url={overview[params.lang]} timeout={3000} />
<Redirect url={overview[lang]} timeout={3000} />
<MaterialIcon
icon="check_circle"
size={64}

View File

@@ -25,21 +25,17 @@ import { getMessages } from "@/i18n"
import ClientIntlProvider from "@/i18n/Provider"
import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params"
export default async function RootLayout(
props: React.PropsWithChildren<LayoutArgs<LangParams>>
) {
export default async function RootLayout(props: LayoutProps<"/[lang]">) {
const params = await props.params
const lang = params.lang as Lang
const { children } = props
setLang(params.lang)
setLang(lang)
const messages = await getMessages(params.lang)
const messages = await getMessages(lang)
return (
<html lang={params.lang}>
<html lang={lang}>
<head>
<FontPreload />
<AdobeSDKScript />
@@ -52,7 +48,7 @@ export default async function RootLayout(
<body className="scandic">
<ClientIntlProvider
defaultLocale={Lang.en}
locale={params.lang}
locale={lang}
messages={messages}
>
<NuqsAdapter>
@@ -72,7 +68,7 @@ export default async function RootLayout(
strategy="beforeInteractive"
data-blockingmode="auto"
data-cbid="6d539de8-3e67-4f0f-a0df-8cef9070f712"
data-culture={params.lang}
data-culture={lang}
id="Cookiebot"
src="https://consent.cookiebot.com/uc.js"
async={true}

View File

@@ -5,25 +5,20 @@ import { logger } from "@scandic-hotels/common/logger"
import AccountPage from "@/components/Webviews/AccountPage"
import LoyaltyPage from "@/components/Webviews/LoyaltyPage"
import type {
ContentTypeWebviewParams,
LangParams,
PageArgs,
UIDParams,
} from "@/types/params"
type ContentType = "loyalty-page" | "account-page"
export default async function ContentTypePage(
props: PageArgs<LangParams & ContentTypeWebviewParams & UIDParams>
props: PageProps<"/[lang]/webview/[contentType]/[uid]">
) {
const params = await props.params
const contentType = params.contentType as ContentType
switch (params.contentType) {
switch (contentType) {
case "loyalty-page":
return <LoyaltyPage />
case "account-page":
return <AccountPage />
default:
const type: never = params.contentType
const type: never = contentType
logger.error(`Unsupported content type given: ${type}`)
notFound()
}

View File

@@ -6,33 +6,29 @@ import { myStay } from "@/constants/routes/webviews"
import GuaranteeCallbackPage from "@/components/GuaranteeCallback"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { PaymentCallbackStatusEnum } from "@scandic-hotels/common/constants/paymentCallbackStatusEnum"
import type { LangParams, PageArgs } from "@/types/params"
export default async function GuaranteePaymentWebViewCallbackPage(
props: PageArgs<
LangParams,
{
status?: PaymentCallbackStatusEnum
RefId?: string
confirmationNumber?: string
ancillary?: string
}
>
props: PageProps<"/[lang]/webview/hotelreservation/gla-payment-callback">
) {
const searchParams = await props.searchParams
const params = await props.params
logger.debug(`[gla-payment-callback] callback started`)
const lang = params.lang
const status = searchParams.status
const confirmationNumber = searchParams.confirmationNumber
const refId = searchParams.RefId
const lang = params.lang as Lang
const status = searchParams.status as PaymentCallbackStatusEnum
const confirmationNumber =
typeof searchParams.confirmationNumber === "string"
? searchParams.confirmationNumber
: undefined
const refId =
typeof searchParams.RefId === "string" ? searchParams.RefId : undefined
if (!status || !confirmationNumber || !refId) {
notFound()
}
const myStayUrl = `${myStay[lang]}?RefId=${encodeURIComponent(refId)}`
logger.debug(`[gla-payment-callback] callback started`)
return (
<GuaranteeCallbackPage
status={status}

View File

@@ -6,19 +6,21 @@ import { setLang } from "@/i18n/serverContext"
import Tracking from "./tracking"
import type { LangParams, PageArgs } from "@/types/params"
import type { Lang } from "@scandic-hotels/common/constants/language"
export default async function MyStayWebviewPage(
props: PageArgs<LangParams, { RefId?: string }>
props: PageProps<"/[lang]/webview/hotelreservation/my-stay">
) {
const searchParams = await props.searchParams
const params = await props.params
setLang(params.lang)
const refId = searchParams.RefId
const lang = params.lang as Lang
setLang(lang)
const refId =
typeof searchParams.RefId === "string" ? searchParams.RefId : undefined
return (
<Suspense fallback={<MyStaySkeleton />}>
<MyStay refId={refId} lang={params.lang} isWebview={true} />
<MyStay refId={refId} lang={lang} isWebview={true} />
<Tracking />
</Suspense>
)

View File

@@ -10,11 +10,7 @@ import { getProfile } from "@/lib/trpc/memoizedRequests"
import { getIntl } from "@/i18n"
import type { LangParams, LayoutArgs } from "@/types/params"
export default async function Layout(
props: React.PropsWithChildren<LayoutArgs<LangParams>>
) {
export default async function Layout(props: LayoutProps<"/[lang]/webview">) {
const params = await props.params
const { children } = props

View File

@@ -27,24 +27,22 @@ import styles from "./layout.module.css"
import type { Metadata } from "next"
import type { LangParams, LayoutArgs } from "@/types/params"
export const metadata: Metadata = {
title: "Webview",
}
export default async function RootLayout(
props: React.PropsWithChildren<LayoutArgs<LangParams>>
props: LayoutProps<"/[lang]/webview">
) {
const params = await props.params
const lang = params.lang as Lang
const { children } = props
setLang(params.lang)
const messages = await getMessages(params.lang)
setLang(lang)
const messages = await getMessages(lang)
return (
<html lang={params.lang}>
<html lang={lang}>
<head>
<FontPreload />
<AdobeSDKScript />
@@ -59,7 +57,7 @@ export default async function RootLayout(
<SessionProvider basePath="/api/web/auth">
<ClientIntlProvider
defaultLocale={Lang.en}
locale={params.lang}
locale={lang}
messages={messages}
>
<NuqsAdapter>

View File

@@ -2,9 +2,7 @@ import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export default function Refresh({}: PageArgs<LangParams>) {
export default function Refresh() {
return (
<div className={styles.container}>
<LoadingSpinner />

View File

@@ -9,7 +9,7 @@ import type { NextRequest } from "next/server"
export async function GET(
request: NextRequest,
props: { params: Promise<{ lang: string }> }
props: RouteContext<"/api/web/add-card-callback/[lang]">
) {
const addCardLogger = createLogger("add-card")

View File

@@ -26,7 +26,7 @@ export const revalidate = 28_800 // 8 hours
export async function GET(
_request: Request,
{ params }: { params: Promise<{ country: string; city: string }> }
{ params }: RouteContext<"/api/web/destinations/[country]/[city]">
) {
try {
const { country: countryParam, city: cityParam } = await params

View File

@@ -26,7 +26,7 @@ export const revalidate = 28_800 // 8 hours
export async function GET(
_request: Request,
{ params }: { params: Promise<{ country: string }> }
{ params }: RouteContext<"/api/web/destinations/[country]">
) {
try {
const { country: countryParam } = await params

View File

@@ -11,7 +11,7 @@ export const dynamic = "force-dynamic"
const sitemapLogger = createLogger("sitemap")
export async function GET(
_request: NextRequest,
context: { params: Promise<{ sitemapId: string }> }
context: RouteContext<"/sitemap/[sitemapId]">
) {
const params = await context.params
const sitemapId = params.sitemapId

View File

@@ -73,7 +73,7 @@
"md5": "^2.3.0",
"motion": "^12.10.0",
"nanoid": "^5.1.5",
"next": "15.3.4",
"next": "^15.5.6",
"next-auth": "5.0.0-beta.29",
"react": "19.1.0",
"react-aria-components": "^1.8.0",

View File

@@ -7,10 +7,6 @@ export type SearchParams<S = object> = {
searchParams: Promise<S & { [key: string]: string }>
}
type Params<P = object> = {
params: Promise<P>
}
export type LangParams = {
lang: Lang
}
@@ -33,17 +29,6 @@ export type ContentTypeParams = {
| PageContentTypeEnum.startPage
}
export type ContentTypeWebviewParams = {
contentType: "loyalty-page" | "account-page"
}
export type UIDParams = {
uid: string
}
export type LayoutArgs<P = undefined> = P extends undefined
? unknown
: Params<P>
export type PageArgs<P = undefined, S = undefined> = LayoutArgs<P> &
(S extends undefined ? unknown : SearchParams<S>)

View File

@@ -11,19 +11,11 @@ import { getTitle } from "./title"
import type { Metadata } from "next"
import type { AlternateURLs } from "next/dist/lib/metadata/types/alternative-urls-types"
import type {
ContentTypeParams,
LangParams,
PageArgs,
UIDParams,
} from "@/types/params"
export async function generateMetadata({
searchParams,
}: PageArgs<
LangParams & ContentTypeParams & UIDParams,
{ subpage?: string; filterFromUrl?: string }
>) {
}: {
searchParams: Promise<{ subpage?: string; filterFromUrl?: string }>
}) {
const { subpage, filterFromUrl, ...otherSearchParams } = await searchParams
// If there are other (real) search params, we don't want to index the page as this will
// cause duplicate content issues.
@@ -55,7 +47,7 @@ export async function generateMetadata({
return {
...metadata,
robots: {
...(metadata.robots ?? {}),
...metadata.robots,
index: isIndexable(metadata.robots?.index, alternates),
follow: isIndexable(metadata.robots?.follow, alternates),
},