Merged in feat/refactor-header-footer-sitewidealert (pull request #1374)
Refactor: removed parallel routes for header, footer and sidewidealert. Langswitcher and sidewidealert now client components * feat - removed parallel routes and made sidepeek and sitewidealerts as client components * Langswitcher as client component * Fixed lang switcher for current header * Passing lang when fetching siteconfig * Merge branch 'master' into feat/refactor-header-footer-sitewidealert * Refactor * Removed dead code * Show only languages that has translation * Refetch sitewidealert every 60 seconds * Merge branch 'master' into feat/refactor-header-footer-sitewidealert * Removed sidepeek parallel route from my-stay * Added missing env.var to env.test * Removed console.log Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -54,4 +54,5 @@ ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false"
|
||||
SHOW_SITE_WIDE_ALERT="false"
|
||||
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT="test"
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE="0"
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE="0"
|
||||
SITEMAP_SYNC_SECRET="test
|
||||
@@ -1,5 +0,0 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
|
||||
export default function LoadingHotelSidePeek() {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { getHotel } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function HotelSidePeek({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { hotel: string }>) {
|
||||
if (!searchParams.hotel) {
|
||||
return <SidePeek hotel={null} />
|
||||
}
|
||||
|
||||
const hotel = await getHotel({
|
||||
hotelId: searchParams.hotel,
|
||||
language: params.lang,
|
||||
isCardOnlyPayment: false,
|
||||
})
|
||||
|
||||
return <SidePeek hotel={hotel} />
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function HotelSidePeekSlot() {
|
||||
return null
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import styles from "./layout.module.css"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export default function HotelReservationLayout({
|
||||
children,
|
||||
sidePeek,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
||||
sidePeek: React.ReactNode
|
||||
}) {
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
{children}
|
||||
{sidePeek}
|
||||
<SidePeek />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||
import stringify from "json-stable-stringify-without-jsonify"
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { getHotel } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import HotelInfoCard, {
|
||||
HotelInfoCardSkeleton,
|
||||
} from "@/components/HotelReservation/SelectRate/HotelInfoCard"
|
||||
import {
|
||||
preload,
|
||||
RoomsContainer,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomsContainer"
|
||||
import SelectRate from "@/components/HotelReservation/SelectRate"
|
||||
import { HotelInfoCardSkeleton } from "@/components/HotelReservation/SelectRate/HotelInfoCard"
|
||||
import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
import { convertSearchParamsToObj } from "@/utils/url"
|
||||
|
||||
import { getHotelSearchDetails } from "../utils"
|
||||
import { getValidDates } from "./getValidDates"
|
||||
|
||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKHotelInfo,
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function SelectRatePage({
|
||||
@@ -34,93 +14,20 @@ export default async function SelectRatePage({
|
||||
searchParams,
|
||||
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
|
||||
setLang(params.lang)
|
||||
const searchDetails = await getHotelSearchDetails({ searchParams })
|
||||
if (!searchDetails?.hotel) {
|
||||
return notFound()
|
||||
}
|
||||
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } =
|
||||
searchDetails
|
||||
|
||||
const { fromDate, toDate } = getValidDates(
|
||||
selectHotelParams.fromDate,
|
||||
selectHotelParams.toDate
|
||||
)
|
||||
|
||||
preload(
|
||||
hotel.id,
|
||||
params.lang,
|
||||
fromDate.format("YYYY-MM-DD"),
|
||||
toDate.format("YYYY-MM-DD"),
|
||||
adultsInRoom,
|
||||
childrenInRoom
|
||||
)
|
||||
|
||||
const hotelData = await getHotel({
|
||||
hotelId: hotel.id,
|
||||
isCardOnlyPayment: false,
|
||||
language: params.lang,
|
||||
})
|
||||
|
||||
const arrivalDate = fromDate.toDate()
|
||||
const departureDate = toDate.toDate()
|
||||
|
||||
const pageTrackingData: TrackingSDKPageData = {
|
||||
pageId: "select-rate",
|
||||
domainLanguage: params.lang,
|
||||
channel: TrackingChannelEnum["hotelreservation"],
|
||||
pageName: "hotelreservation|select-rate",
|
||||
siteSections: "hotelreservation|select-rate",
|
||||
pageType: "bookingroomsandratespage",
|
||||
siteVersion: "new-web",
|
||||
}
|
||||
|
||||
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
||||
searchTerm: selectHotelParams.city ?? hotel?.name,
|
||||
arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
||||
departureDate: format(departureDate, "yyyy-MM-dd"),
|
||||
noOfAdults: adultsInRoom[0], // TODO: Handle multiple rooms
|
||||
noOfChildren: childrenInRoom?.length,
|
||||
ageOfChildren: childrenInRoom?.map((c) => c.age).join(","),
|
||||
childBedPreference: childrenInRoom
|
||||
?.map((c) => ChildBedMapEnum[c.bed])
|
||||
.join("|"),
|
||||
noOfRooms: 1, // // TODO: Handle multiple rooms
|
||||
duration: differenceInCalendarDays(departureDate, arrivalDate),
|
||||
leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
||||
searchType: "hotel",
|
||||
bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
||||
country: hotelData?.hotel.address.country,
|
||||
hotelID: hotel?.id,
|
||||
region: hotelData?.hotel.address.city,
|
||||
}
|
||||
|
||||
const hotelId = +hotel.id
|
||||
|
||||
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
|
||||
const suspenseKey = stringify(searchParams)
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<HotelInfoCardSkeleton />}>
|
||||
<HotelInfoCard hotelData={hotelData} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense key={suspenseKey} fallback={<RoomsContainerSkeleton />}>
|
||||
<RoomsContainer
|
||||
adultArray={adultsInRoom}
|
||||
booking={booking}
|
||||
childArray={childrenInRoom}
|
||||
fromDate={arrivalDate}
|
||||
hotelId={hotelId}
|
||||
lang={params.lang}
|
||||
toDate={departureDate}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense key={suspenseKey} fallback={null}>
|
||||
<TrackingSDK
|
||||
pageData={pageTrackingData}
|
||||
hotelInfo={hotelsTrackingData}
|
||||
/>
|
||||
</Suspense>
|
||||
</>
|
||||
return (
|
||||
<Suspense
|
||||
key={suspenseKey}
|
||||
fallback={
|
||||
<>
|
||||
<HotelInfoCardSkeleton />
|
||||
<RoomsContainerSkeleton />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<SelectRate params={params} searchParams={searchParams} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { getHotel } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function HotelSidePeek({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { hotel: string }>) {
|
||||
if (!searchParams.hotel) {
|
||||
return <SidePeek hotel={null} />
|
||||
}
|
||||
|
||||
const hotel = await getHotel({
|
||||
hotelId: searchParams.hotel,
|
||||
language: params.lang,
|
||||
isCardOnlyPayment: false,
|
||||
})
|
||||
|
||||
return <SidePeek hotel={hotel} />
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import SidePeek from "@/components/HotelReservation/SidePeek"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export default function HotelReservationLayout({
|
||||
children,
|
||||
sidePeek,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
||||
sidePeek: React.ReactNode
|
||||
}) {
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
{sidePeek}
|
||||
<SidePeek />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "../page"
|
||||
@@ -1,17 +0,0 @@
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import CurrentLoadingSpinner from "@/components/Current/LoadingSpinner"
|
||||
import { FooterDetailsSkeleton } from "@/components/Footer/Details"
|
||||
import { FooterNavigationSkeleton } from "@/components/Footer/Navigation"
|
||||
|
||||
export default function LoadingFooter() {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return <CurrentLoadingSpinner />
|
||||
}
|
||||
return (
|
||||
<footer>
|
||||
<FooterNavigationSkeleton />
|
||||
<FooterDetailsSkeleton />
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import CurrentFooter from "@/components/Current/Footer"
|
||||
import Footer, { preload } from "@/components/Footer"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default function FooterSlot({ params }: PageArgs<LangParams>) {
|
||||
setLang(params.lang)
|
||||
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return <CurrentFooter />
|
||||
}
|
||||
|
||||
preload()
|
||||
|
||||
return <Footer />
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "../page"
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import CurrentHeader from "@/components/Current/Header"
|
||||
import HeaderFallback from "@/components/Current/Header/HeaderFallback"
|
||||
import Header from "@/components/Header"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default function HeaderPage({ params }: PageArgs<LangParams>) {
|
||||
setLang(params.lang)
|
||||
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return (
|
||||
<Suspense fallback={<HeaderFallback />}>
|
||||
<CurrentHeader />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
return <Header />
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "../page"
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import SitewideAlert, { preload } from "@/components/SitewideAlert"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default function SitewideAlertPage({ params }: PageArgs<LangParams>) {
|
||||
if (!env.SHOW_SITE_WIDE_ALERT) {
|
||||
return null
|
||||
}
|
||||
|
||||
setLang(params.lang)
|
||||
void preload()
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<SitewideAlert />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
@@ -9,7 +9,10 @@ import TrpcProvider from "@/lib/trpc/Provider"
|
||||
|
||||
import { SessionRefresher } from "@/components/Auth/TokenRefresher"
|
||||
import CookieBotConsent from "@/components/CookieBot"
|
||||
import Footer from "@/components/Footer"
|
||||
import Header from "@/components/Header"
|
||||
import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner"
|
||||
import SitewideAlert from "@/components/SitewideAlert"
|
||||
import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
|
||||
import { preloadUserTracking } from "@/components/TrackingSDK"
|
||||
import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript"
|
||||
@@ -24,16 +27,10 @@ import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
export default async function RootLayout({
|
||||
bookingwidget,
|
||||
children,
|
||||
footer,
|
||||
header,
|
||||
sitewidealert,
|
||||
params,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams> & {
|
||||
bookingwidget: React.ReactNode
|
||||
footer: React.ReactNode
|
||||
header: React.ReactNode
|
||||
sitewidealert: React.ReactNode
|
||||
}
|
||||
>) {
|
||||
setLang(params.lang)
|
||||
@@ -62,11 +59,11 @@ export default async function RootLayout({
|
||||
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||
<TrpcProvider>
|
||||
<RouterTracking />
|
||||
{sitewidealert}
|
||||
{header}
|
||||
<SitewideAlert />
|
||||
<Header />
|
||||
{bookingwidget}
|
||||
{children}
|
||||
{footer}
|
||||
<Footer />
|
||||
<ToastHandler />
|
||||
<SessionRefresher />
|
||||
<StorageCleaner />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getLocations, getSiteConfig } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import BookingWidgetClient from "./Client"
|
||||
|
||||
import type { BookingWidgetProps } from "@/types/components/bookingWidget"
|
||||
@@ -12,8 +14,9 @@ export default async function BookingWidget({
|
||||
type,
|
||||
bookingWidgetSearchParams,
|
||||
}: BookingWidgetProps) {
|
||||
const lang = await getLang()
|
||||
const locations = await getLocations()
|
||||
const siteConfig = await getSiteConfig()
|
||||
const siteConfig = await getSiteConfig(lang)
|
||||
|
||||
if (!locations || "error" in locations || siteConfig?.bookingWidgetDisabled) {
|
||||
return null
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import Desktop from "./Desktop"
|
||||
import Mobile from "./Mobile"
|
||||
|
||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
export default function LanguageSwitcher() {
|
||||
const currentLanguage = useLang()
|
||||
const pathName = usePathname()
|
||||
|
||||
type LanguageSwitcherProps = { urls: LanguageSwitcherData }
|
||||
const { data: languagesResponse, isLoading } =
|
||||
trpc.contentstack.languageSwitcher.get.useQuery({
|
||||
pathName,
|
||||
lang: currentLanguage,
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonShimmer width="12ch" />
|
||||
}
|
||||
|
||||
if (!languagesResponse?.urls) {
|
||||
return null
|
||||
}
|
||||
|
||||
export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) {
|
||||
return (
|
||||
<>
|
||||
<Desktop urls={urls} />
|
||||
<Mobile urls={urls} />
|
||||
<Desktop urls={languagesResponse.urls} />
|
||||
<Mobile urls={languagesResponse.urls} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,8 +74,6 @@ export function MainMenu({
|
||||
toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
|
||||
}
|
||||
|
||||
console.log("isMyPagesMobileMenuOpen", isMyPagesMobileMenuOpen)
|
||||
|
||||
return (
|
||||
<div className={styles.mainMenu}>
|
||||
<div
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { homeHrefs } from "@/constants/homeHrefs"
|
||||
import { env } from "@/env/server"
|
||||
import {
|
||||
getCurrentHeader,
|
||||
getLanguageSwitcher,
|
||||
getName,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
import { getCurrentHeader, getName } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
@@ -18,13 +14,9 @@ import styles from "./header.module.css"
|
||||
|
||||
export default async function Header() {
|
||||
const lang = getLang()
|
||||
const [data, user, languages] = await Promise.all([
|
||||
getCurrentHeader(lang),
|
||||
getName(),
|
||||
getLanguageSwitcher(),
|
||||
])
|
||||
const [data, user] = await Promise.all([getCurrentHeader(lang), getName()])
|
||||
|
||||
if (!languages || !data?.header) {
|
||||
if (!data?.header) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -42,7 +34,7 @@ export default async function Header() {
|
||||
frontpageLinkText={frontpageLinkText}
|
||||
homeHref={homeHref}
|
||||
links={topMenu.links}
|
||||
languageSwitcher={<LanguageSwitcher urls={languages.urls} />}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
/>
|
||||
<MainMenu
|
||||
frontpageLinkText={frontpageLinkText}
|
||||
@@ -50,7 +42,7 @@ export default async function Header() {
|
||||
links={menu.links}
|
||||
logo={logo}
|
||||
topMenuMobileLinks={topMenuMobileLinks}
|
||||
languageSwitcher={<LanguageSwitcher urls={languages.urls} />}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
myPagesMobileDropdown={<MyPagesMobileDropdown />}
|
||||
bookingHref={homeHref}
|
||||
user={user}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getFooter, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import { getFooter } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
||||
@@ -17,7 +17,6 @@ export default async function FooterDetails() {
|
||||
const intl = await getIntl()
|
||||
// preloaded
|
||||
const footer = await getFooter()
|
||||
const languages = await getLanguageSwitcher()
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
return (
|
||||
@@ -69,9 +68,7 @@ export default async function FooterDetails() {
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
{languages?.urls ? (
|
||||
<LanguageSwitcher type="footer" urls={languages.urls} />
|
||||
) : null}
|
||||
<LanguageSwitcher type="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { getFooter, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import { env } from "@/env/server"
|
||||
import { getFooter } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import CurrentFooter from "@/components/Current/Footer"
|
||||
|
||||
import FooterDetails from "./Details"
|
||||
import FooterNavigation from "./Navigation"
|
||||
|
||||
export function preload() {
|
||||
void getFooter()
|
||||
void getLanguageSwitcher()
|
||||
}
|
||||
|
||||
export default function Footer() {
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return <CurrentFooter />
|
||||
}
|
||||
|
||||
preload()
|
||||
|
||||
return (
|
||||
<footer>
|
||||
<FooterNavigation />
|
||||
|
||||
@@ -21,7 +21,6 @@ import { IconName } from "@/types/components/icon"
|
||||
|
||||
export default function MobileMenu({
|
||||
children,
|
||||
languageUrls,
|
||||
topLink,
|
||||
isLoggedIn,
|
||||
}: React.PropsWithChildren<MobileMenuProps>) {
|
||||
@@ -91,7 +90,7 @@ export default function MobileMenu({
|
||||
<HeaderLink href="#" iconName={IconName.Service}>
|
||||
{intl.formatMessage({ id: "Customer service" })}
|
||||
</HeaderLink>
|
||||
<LanguageSwitcher type="mobileHeader" urls={languageUrls} />
|
||||
<LanguageSwitcher type="mobileHeader" />
|
||||
</footer>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import { getHeader } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import { isValidSession } from "@/utils/session"
|
||||
@@ -9,17 +9,15 @@ export default async function MobileMenuWrapper({
|
||||
children,
|
||||
}: React.PropsWithChildren) {
|
||||
// preloaded
|
||||
const languages = await getLanguageSwitcher()
|
||||
const header = await getHeader()
|
||||
const session = await auth()
|
||||
|
||||
if (!languages || !header) {
|
||||
if (!header) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileMenu
|
||||
languageUrls={languages.urls}
|
||||
topLink={header.data.topLink}
|
||||
isLoggedIn={isValidSession(session)}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getHeader, getLanguageSwitcher } from "@/lib/trpc/memoizedRequests"
|
||||
import { getHeader } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher"
|
||||
@@ -18,12 +18,11 @@ export default async function TopMenu() {
|
||||
// cached
|
||||
const intl = await getIntl()
|
||||
// both preloaded
|
||||
const languages = await getLanguageSwitcher()
|
||||
const header = await getHeader()
|
||||
const session = await auth()
|
||||
const isLoggedIn = isValidSession(session)
|
||||
|
||||
if (!languages || !header) {
|
||||
if (!header) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -32,7 +31,7 @@ export default async function TopMenu() {
|
||||
<div className={styles.content}>
|
||||
<TopLink isLoggedIn={isLoggedIn} topLink={header.data.topLink} />
|
||||
<div className={styles.options}>
|
||||
<LanguageSwitcher type="desktopHeader" urls={languages.urls} />
|
||||
<LanguageSwitcher type="desktopHeader" />
|
||||
|
||||
<Caption type="regular" color="textMediumContrast" asChild>
|
||||
<HeaderLink href="#" iconName={IconName.Search}>
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import {
|
||||
getHeader,
|
||||
getLanguageSwitcher,
|
||||
getName,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
import { env } from "@/env/server"
|
||||
import { getHeader, getName } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import CurrentHeader from "@/components/Current/Header"
|
||||
|
||||
import HeaderFallback from "../Current/Header/HeaderFallback"
|
||||
import MainMenu from "./MainMenu"
|
||||
import TopMenu, { TopMenuSkeleton } from "./TopMenu"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
export default function Header() {
|
||||
void getHeader()
|
||||
void getLanguageSwitcher()
|
||||
void getName()
|
||||
|
||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||
return (
|
||||
<Suspense fallback={<HeaderFallback />}>
|
||||
<CurrentHeader />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
void getHeader()
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Suspense fallback={<TopMenuSkeleton />}>
|
||||
|
||||
126
components/HotelReservation/SelectRate/index.tsx
Normal file
126
components/HotelReservation/SelectRate/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
||||
import stringify from "json-stable-stringify-without-jsonify"
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { getHotel } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getValidDates } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/getValidDates"
|
||||
import { getHotelSearchDetails } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/utils"
|
||||
import HotelInfoCard, {
|
||||
HotelInfoCardSkeleton,
|
||||
} from "@/components/HotelReservation/SelectRate/HotelInfoCard"
|
||||
import {
|
||||
preload,
|
||||
RoomsContainer,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomsContainer"
|
||||
import { RoomsContainerSkeleton } from "@/components/HotelReservation/SelectRate/RoomsContainer/RoomsContainerSkeleton"
|
||||
import TrackingSDK from "@/components/TrackingSDK"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
import { convertSearchParamsToObj } from "@/utils/url"
|
||||
|
||||
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
|
||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKHotelInfo,
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function SelectRatePage({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
|
||||
setLang(params.lang)
|
||||
const searchDetails = await getHotelSearchDetails({ searchParams })
|
||||
if (!searchDetails?.hotel) {
|
||||
return notFound()
|
||||
}
|
||||
const { hotel, adultsInRoom, childrenInRoom, selectHotelParams } =
|
||||
searchDetails
|
||||
|
||||
const { fromDate, toDate } = getValidDates(
|
||||
selectHotelParams.fromDate,
|
||||
selectHotelParams.toDate
|
||||
)
|
||||
|
||||
preload(
|
||||
hotel.id,
|
||||
params.lang,
|
||||
fromDate.format("YYYY-MM-DD"),
|
||||
toDate.format("YYYY-MM-DD"),
|
||||
adultsInRoom,
|
||||
childrenInRoom
|
||||
)
|
||||
|
||||
const hotelData = await getHotel({
|
||||
hotelId: hotel.id,
|
||||
isCardOnlyPayment: false,
|
||||
language: params.lang,
|
||||
})
|
||||
|
||||
const arrivalDate = fromDate.toDate()
|
||||
const departureDate = toDate.toDate()
|
||||
|
||||
const pageTrackingData: TrackingSDKPageData = {
|
||||
pageId: "select-rate",
|
||||
domainLanguage: params.lang,
|
||||
channel: TrackingChannelEnum["hotelreservation"],
|
||||
pageName: "hotelreservation|select-rate",
|
||||
siteSections: "hotelreservation|select-rate",
|
||||
pageType: "bookingroomsandratespage",
|
||||
siteVersion: "new-web",
|
||||
}
|
||||
|
||||
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
||||
searchTerm: selectHotelParams.city ?? hotel?.name,
|
||||
arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
|
||||
departureDate: format(departureDate, "yyyy-MM-dd"),
|
||||
noOfAdults: adultsInRoom[0], // TODO: Handle multiple rooms
|
||||
noOfChildren: childrenInRoom?.length,
|
||||
ageOfChildren: childrenInRoom?.map((c) => c.age).join(","),
|
||||
childBedPreference: childrenInRoom
|
||||
?.map((c) => ChildBedMapEnum[c.bed])
|
||||
.join("|"),
|
||||
noOfRooms: 1, // // TODO: Handle multiple rooms
|
||||
duration: differenceInCalendarDays(departureDate, arrivalDate),
|
||||
leadTime: differenceInCalendarDays(arrivalDate, new Date()),
|
||||
searchType: "hotel",
|
||||
bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday",
|
||||
country: hotelData?.hotel.address.country,
|
||||
hotelID: hotel?.id,
|
||||
region: hotelData?.hotel.address.city,
|
||||
}
|
||||
|
||||
const hotelId = +hotel.id
|
||||
|
||||
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
|
||||
const suspenseKey = stringify(searchParams)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<HotelInfoCardSkeleton />}>
|
||||
<HotelInfoCard hotelData={hotelData} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense key={suspenseKey} fallback={<RoomsContainerSkeleton />}>
|
||||
<RoomsContainer
|
||||
adultArray={adultsInRoom}
|
||||
booking={booking}
|
||||
childArray={childrenInRoom}
|
||||
fromDate={arrivalDate}
|
||||
hotelId={hotelId}
|
||||
lang={params.lang}
|
||||
toDate={departureDate}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense key={suspenseKey} fallback={null}>
|
||||
<TrackingSDK
|
||||
pageData={pageTrackingData}
|
||||
hotelInfo={hotelsTrackingData}
|
||||
/>
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -7,11 +7,7 @@ import HotelSidePeek from "@/components/SidePeeks/HotelSidePeek"
|
||||
import RoomSidePeek from "@/components/SidePeeks/RoomSidePeek"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import type { HotelReservationSidePeekProps } from "@/types/components/hotelReservation/sidePeek"
|
||||
|
||||
export default function HotelReservationSidePeek({
|
||||
hotel,
|
||||
}: HotelReservationSidePeekProps) {
|
||||
export default function HotelReservationSidePeek() {
|
||||
const activeSidePeek = useSidePeekStore((state) => state.activeSidePeek)
|
||||
const hotelId = useSidePeekStore((state) => state.hotelId)
|
||||
const roomTypeCode = useSidePeekStore((state) => state.roomTypeCode)
|
||||
@@ -27,7 +23,6 @@ export default function HotelReservationSidePeek({
|
||||
},
|
||||
{
|
||||
enabled: !!hotelId,
|
||||
initialData: hotel ?? undefined,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useRef } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { languages } from "@/constants/languages"
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import useDropdownStore from "@/stores/main-menu"
|
||||
|
||||
import { ChevronDownSmallIcon, GlobeIcon } from "@/components/Icons"
|
||||
@@ -11,6 +13,7 @@ import useClickOutside from "@/hooks/useClickOutside"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import SkeletonShimmer from "../SkeletonShimmer"
|
||||
import Caption from "../TempDesignSystem/Text/Caption"
|
||||
import LanguageSwitcherContainer from "./LanguageSwitcherContainer"
|
||||
import LanguageSwitcherContent from "./LanguageSwitcherContent"
|
||||
@@ -24,10 +27,7 @@ import {
|
||||
LanguageSwitcherTypesEnum,
|
||||
} from "@/types/components/languageSwitcher/languageSwitcher"
|
||||
|
||||
export default function LanguageSwitcher({
|
||||
urls,
|
||||
type,
|
||||
}: LanguageSwitcherProps) {
|
||||
export default function LanguageSwitcher({ type }: LanguageSwitcherProps) {
|
||||
const intl = useIntl()
|
||||
const currentLanguage = useLang()
|
||||
const {
|
||||
@@ -37,6 +37,14 @@ export default function LanguageSwitcher({
|
||||
isHeaderLanguageSwitcherOpen,
|
||||
} = useDropdownStore()
|
||||
|
||||
const pathName = usePathname()
|
||||
|
||||
const { data: languagesResponse, isLoading } =
|
||||
trpc.contentstack.languageSwitcher.get.useQuery({
|
||||
pathName,
|
||||
lang: currentLanguage,
|
||||
})
|
||||
|
||||
const languageSwitcherRef = useRef<HTMLDivElement>(null)
|
||||
const isFooter = type === LanguageSwitcherTypesEnum.Footer
|
||||
const isHeader = !isFooter
|
||||
@@ -80,6 +88,14 @@ export default function LanguageSwitcher({
|
||||
const closeMsg = intl.formatMessage({ id: "Close language menu" })
|
||||
const openMsg = intl.formatMessage({ id: "Open language menu" })
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonShimmer width="12ch" />
|
||||
}
|
||||
|
||||
if (!languagesResponse?.urls) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames} ref={languageSwitcherRef}>
|
||||
<button
|
||||
@@ -105,7 +121,7 @@ export default function LanguageSwitcher({
|
||||
{isLanguageSwitcherOpen ? (
|
||||
<LanguageSwitcherContainer type={type}>
|
||||
<LanguageSwitcherContent
|
||||
urls={urls}
|
||||
urls={languagesResponse.urls}
|
||||
onLanguageSwitch={() => toggleDropdown(dropdownType)}
|
||||
/>
|
||||
</LanguageSwitcherContainer>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useRef } from "react"
|
||||
|
||||
import { StickyElementNameEnum } from "@/stores/sticky-position"
|
||||
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import useStickyPosition from "@/hooks/useStickyPosition"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
|
||||
import styles from "./sitewideAlert.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { SitewideAlertProps } from "./sitewideAlert"
|
||||
|
||||
export default function SiteWideAlertClient({ alert }: SitewideAlertProps) {
|
||||
const alertRef = useRef<HTMLDivElement>(null)
|
||||
const isAlarm = alert.type === AlertTypeEnum.Alarm
|
||||
useStickyPosition({
|
||||
ref: isAlarm ? alertRef : undefined,
|
||||
name: StickyElementNameEnum.SITEWIDE_ALERT,
|
||||
})
|
||||
|
||||
const updateHeight = useCallback(() => {
|
||||
if (alertRef.current) {
|
||||
const height = alertRef.current.offsetHeight
|
||||
document.documentElement.style.setProperty(
|
||||
"--sitewide-alert-height",
|
||||
`${height}px`
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const alertElement = alertRef.current
|
||||
const debouncedResizeHandler = debounce(() => {
|
||||
updateHeight()
|
||||
}, 100)
|
||||
const observer = new ResizeObserver(debouncedResizeHandler)
|
||||
|
||||
if (alertElement) {
|
||||
observer.observe(alertElement)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (alertElement) {
|
||||
observer.unobserve(alertElement)
|
||||
}
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [updateHeight])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={alertRef}
|
||||
className={`${styles.sitewideAlert} ${isAlarm ? styles.alarm : ""}`}
|
||||
>
|
||||
<Alert
|
||||
variant="banner"
|
||||
type={alert.type}
|
||||
link={alert.link}
|
||||
phoneContact={alert.phoneContact}
|
||||
sidepeekCtaText={alert.sidepeekButton?.cta_text}
|
||||
sidepeekContent={alert.sidepeekContent}
|
||||
heading={alert.heading}
|
||||
text={alert.text}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,84 @@
|
||||
import { getSiteConfig } from "@/lib/trpc/memoizedRequests"
|
||||
"use client"
|
||||
|
||||
import SiteWideAlertClient from "./Client"
|
||||
import { useCallback, useEffect, useRef } from "react"
|
||||
|
||||
export function preload() {
|
||||
void getSiteConfig()
|
||||
}
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
import { StickyElementNameEnum } from "@/stores/sticky-position"
|
||||
|
||||
export default async function SitewideAlert() {
|
||||
const siteConfig = await getSiteConfig()
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import useStickyPosition from "@/hooks/useStickyPosition"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
|
||||
if (!siteConfig?.sitewideAlert) {
|
||||
import styles from "./sitewideAlert.module.css"
|
||||
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
export default function SiteWideAlert() {
|
||||
const alertRef = useRef<HTMLDivElement>(null)
|
||||
const lang = useLang()
|
||||
const { data: siteConfig, isLoading } =
|
||||
trpc.contentstack.base.siteConfig.useQuery(
|
||||
{ lang },
|
||||
{ refetchInterval: 60_000 }
|
||||
)
|
||||
|
||||
const alert = siteConfig?.sitewideAlert
|
||||
|
||||
const isAlarm = alert?.type === AlertTypeEnum.Alarm
|
||||
useStickyPosition({
|
||||
ref: isAlarm ? alertRef : undefined,
|
||||
name: StickyElementNameEnum.SITEWIDE_ALERT,
|
||||
})
|
||||
|
||||
const updateHeight = useCallback(() => {
|
||||
if (alertRef.current) {
|
||||
const height = alertRef.current.offsetHeight
|
||||
document.documentElement.style.setProperty(
|
||||
"--sitewide-alert-height",
|
||||
`${height}px`
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const alertElement = alertRef.current
|
||||
const debouncedResizeHandler = debounce(() => {
|
||||
updateHeight()
|
||||
}, 100)
|
||||
const observer = new ResizeObserver(debouncedResizeHandler)
|
||||
|
||||
if (alertElement) {
|
||||
observer.observe(alertElement)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (alertElement) {
|
||||
observer.unobserve(alertElement)
|
||||
}
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [updateHeight])
|
||||
|
||||
if (isLoading || !alert) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <SiteWideAlertClient alert={siteConfig.sitewideAlert} />
|
||||
return (
|
||||
<div
|
||||
ref={alertRef}
|
||||
className={`${styles.sitewideAlert} ${isAlarm ? styles.alarm : ""}`}
|
||||
>
|
||||
<Alert
|
||||
variant="banner"
|
||||
type={alert.type}
|
||||
link={alert.link}
|
||||
phoneContact={alert.phoneContact}
|
||||
sidepeekCtaText={alert.sidepeekButton?.cta_text}
|
||||
sidepeekContent={alert.sidepeekContent}
|
||||
heading={alert.heading}
|
||||
text={alert.text}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { Alert } from "@/types/trpc/routers/contentstack/siteConfig"
|
||||
|
||||
export interface SitewideAlertProps {
|
||||
alert: Alert
|
||||
}
|
||||
@@ -127,8 +127,10 @@ export const getLanguageSwitcher = cache(
|
||||
}
|
||||
)
|
||||
|
||||
export const getSiteConfig = cache(async function getMemoizedSiteConfig() {
|
||||
return serverClient().contentstack.base.siteConfig()
|
||||
export const getSiteConfig = cache(async function getMemoizedSiteConfig(
|
||||
lang: Lang
|
||||
) {
|
||||
return serverClient().contentstack.base.siteConfig({ lang })
|
||||
})
|
||||
|
||||
export const getBreakfastPackages = cache(
|
||||
|
||||
@@ -2,10 +2,12 @@ import { type NextMiddleware, NextResponse } from "next/server"
|
||||
|
||||
import { notFound } from "@/server/errors/next"
|
||||
|
||||
import { fetchAndCacheEntry } from "@/services/cms/fetchAndCacheEntry"
|
||||
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"
|
||||
import { findLang } from "@/utils/languages"
|
||||
import { removeTrailingSlash } from "@/utils/url"
|
||||
|
||||
import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils"
|
||||
import { getDefaultRequestHeaders } from "./utils"
|
||||
|
||||
import type { MiddlewareMatcher } from "@/types/middleware"
|
||||
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
||||
@@ -17,22 +19,21 @@ export const middleware: NextMiddleware = async (request) => {
|
||||
const pathWithoutTrailingSlash = removeTrailingSlash(nextUrl.pathname)
|
||||
|
||||
const contentTypePathName = pathWithoutTrailingSlash.replace(`/${lang}`, "")
|
||||
const isPreview = request.nextUrl.pathname.includes("/preview")
|
||||
const searchParams = new URLSearchParams(request.nextUrl.searchParams)
|
||||
|
||||
let { contentType, uid } = await fetchAndCacheEntry(
|
||||
isPreview
|
||||
? contentTypePathName.replace("/preview", "")
|
||||
: contentTypePathName,
|
||||
lang
|
||||
)
|
||||
const isPreview = request.nextUrl.pathname.includes("/preview")
|
||||
|
||||
const incomingPathName = isPreview
|
||||
? contentTypePathName.replace("/preview", "")
|
||||
: contentTypePathName
|
||||
|
||||
let { contentType, uid } = await getUidAndContentTypeByPath(incomingPathName)
|
||||
|
||||
const searchParams = new URLSearchParams(request.nextUrl.searchParams)
|
||||
|
||||
if (!contentType || !uid) {
|
||||
// Routes to subpages we need to check if the parent of the incomingPathName exists.
|
||||
// Then we considered the incomingPathName to be a subpage. These subpages do not live in the CMS.
|
||||
const incomingPathName = isPreview
|
||||
? contentTypePathName.replace("/preview", "")
|
||||
: contentTypePathName
|
||||
|
||||
const incomingPathNameParts = incomingPathName.split("/")
|
||||
|
||||
// If the incomingPathName has 2 or more parts, it could possibly be a subpage.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { type NextMiddleware,NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
myPages,
|
||||
@@ -6,15 +6,13 @@ import {
|
||||
profile,
|
||||
profileEdit,
|
||||
} from "@/constants/routes/myPages"
|
||||
import { env } from "@/env/server"
|
||||
import { internalServerError, notFound } from "@/server/errors/next"
|
||||
import { notFound } from "@/server/errors/next"
|
||||
import { getPublicNextURL } from "@/server/utils"
|
||||
|
||||
import { fetchAndCacheEntry } from "@/services/cms/fetchAndCacheEntry"
|
||||
import { findLang } from "@/utils/languages"
|
||||
|
||||
import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils"
|
||||
|
||||
import type { NextMiddleware } from "next/server"
|
||||
import { getDefaultRequestHeaders } from "./utils"
|
||||
|
||||
import type { MiddlewareMatcher } from "@/types/middleware"
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { getPublicNextURL } from "@/server/utils"
|
||||
|
||||
import { resolve as resolveEntry } from "@/utils/entry"
|
||||
import { findLang } from "@/utils/languages"
|
||||
import { removeTrailingSlash } from "@/utils/url"
|
||||
|
||||
import type { NextRequest } from "next/server"
|
||||
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export function getDefaultRequestHeaders(request: NextRequest) {
|
||||
const lang = findLang(request.nextUrl.pathname)!
|
||||
const nextUrlPublic = getPublicNextURL(request)
|
||||
@@ -23,49 +20,3 @@ export function getDefaultRequestHeaders(request: NextRequest) {
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
const entryResponseCache: Map<
|
||||
string,
|
||||
{
|
||||
contentType: string | null
|
||||
uid: string | null
|
||||
expiresAt: number
|
||||
}
|
||||
> = new Map()
|
||||
let size: number = 0
|
||||
|
||||
export const fetchAndCacheEntry = async (path: string, lang: Lang) => {
|
||||
const cacheKey = `${path + lang}`
|
||||
const cachedResponse = entryResponseCache.get(cacheKey)
|
||||
|
||||
if (cachedResponse && cachedResponse.expiresAt > Date.now() / 1000) {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE HIT")
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
if (cachedResponse && cachedResponse.expiresAt < Date.now() / 1000) {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE STALE")
|
||||
size -= JSON.stringify(cachedResponse).length
|
||||
entryResponseCache.delete(cacheKey)
|
||||
} else {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE MISS")
|
||||
}
|
||||
|
||||
const { contentType, uid } = await resolveEntry(path, lang)
|
||||
let expiresAt = Date.now() / 1000
|
||||
if (!contentType || !uid) {
|
||||
expiresAt += 600
|
||||
} else {
|
||||
expiresAt += 3600 * 12
|
||||
}
|
||||
const entryCache = { contentType, uid, expiresAt }
|
||||
size += JSON.stringify(entryCache).length
|
||||
console.log("[CMS MIDDLEWARE] Adding to cache", entryCache)
|
||||
console.log("[CMS MIDDLEWARE] Cache size (total)", size)
|
||||
entryResponseCache.set(cacheKey, entryCache)
|
||||
|
||||
return {
|
||||
contentType,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
import { env } from "@/env/server"
|
||||
import { badRequest, notFound } from "@/server/errors/next"
|
||||
|
||||
import { fetchAndCacheEntry } from "@/services/cms/fetchAndCacheEntry"
|
||||
import { decryptData } from "@/utils/aes"
|
||||
import { resolve as resolveEntry } from "@/utils/entry"
|
||||
import { findLang } from "@/utils/languages"
|
||||
|
||||
import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils"
|
||||
import { getDefaultRequestHeaders } from "./utils"
|
||||
|
||||
import type { MiddlewareMatcher } from "@/types/middleware"
|
||||
|
||||
|
||||
@@ -616,156 +616,161 @@ export const baseQueryRouter = router({
|
||||
|
||||
return validatedFooterConfig.data
|
||||
}),
|
||||
siteConfig: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
siteConfig: contentstackBaseProcedure
|
||||
.input(langInput)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const lang = input?.lang ?? ctx.lang
|
||||
|
||||
getSiteConfigRefCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.ref start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const responseRef = await request<GetSiteConfigRefData>(
|
||||
GetSiteConfigRef,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "site_config")],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!responseRef.data) {
|
||||
const notFoundError = notFound(responseRef)
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
getSiteConfigRefCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.ref start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
|
||||
responseRef.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfigRef.success) {
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfigRef.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: validatedSiteConfigRef.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const connections = getSiteConfigConnections(validatedSiteConfigRef.data)
|
||||
const siteConfigUid = responseRef.data.all_site_config.items[0].system.uid
|
||||
|
||||
const tags = [
|
||||
generateTagsFromSystem(lang, connections),
|
||||
generateTag(lang, siteConfigUid),
|
||||
].flat()
|
||||
|
||||
getSiteConfigRefSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.refs success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
getSiteConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const [siteConfigResponse, contactConfig] = await Promise.all([
|
||||
request<GetSiteConfigData>(
|
||||
GetSiteConfig,
|
||||
const responseRef = await request<GetSiteConfigRefData>(
|
||||
GetSiteConfigRef,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "site_config")],
|
||||
},
|
||||
}
|
||||
),
|
||||
getContactConfig(lang),
|
||||
])
|
||||
|
||||
if (!siteConfigResponse.data) {
|
||||
const notFoundError = notFound(siteConfigResponse)
|
||||
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.siteConfig not found error",
|
||||
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfig = siteConfigSchema.safeParse(
|
||||
siteConfigResponse.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfig.success) {
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedSiteConfig.error,
|
||||
if (!responseRef.data) {
|
||||
const notFoundError = notFound(responseRef)
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
|
||||
responseRef.data
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getSiteConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
if (!validatedSiteConfigRef.success) {
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfigRef.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: validatedSiteConfigRef.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const { sitewideAlert } = validatedSiteConfig.data
|
||||
const connections = getSiteConfigConnections(validatedSiteConfigRef.data)
|
||||
const siteConfigUid = responseRef.data.all_site_config.items[0].system.uid
|
||||
|
||||
return {
|
||||
...validatedSiteConfig.data,
|
||||
sitewideAlert: sitewideAlert
|
||||
? {
|
||||
...sitewideAlert,
|
||||
phoneContact: contactConfig
|
||||
? getAlertPhoneContactData(sitewideAlert, contactConfig)
|
||||
: null,
|
||||
const tags = [
|
||||
generateTagsFromSystem(lang, connections),
|
||||
generateTag(lang, siteConfigUid),
|
||||
].flat()
|
||||
|
||||
getSiteConfigRefSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.refs success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
getSiteConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const [siteConfigResponse, contactConfig] = await Promise.all([
|
||||
request<GetSiteConfigData>(
|
||||
GetSiteConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}),
|
||||
),
|
||||
getContactConfig(lang),
|
||||
])
|
||||
|
||||
if (!siteConfigResponse.data) {
|
||||
const notFoundError = notFound(siteConfigResponse)
|
||||
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.siteConfig not found error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfig = siteConfigSchema.safeParse(
|
||||
siteConfigResponse.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfig.success) {
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedSiteConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getSiteConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const { sitewideAlert } = validatedSiteConfig.data
|
||||
|
||||
return {
|
||||
...validatedSiteConfig.data,
|
||||
sitewideAlert: sitewideAlert
|
||||
? {
|
||||
...sitewideAlert,
|
||||
phoneContact: contactConfig
|
||||
? getAlertPhoneContactData(sitewideAlert, contactConfig)
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
10
server/routers/contentstack/languageSwitcher/input.ts
Normal file
10
server/routers/contentstack/languageSwitcher/input.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
export const getLanguageSwitcherInput = z
|
||||
.object({
|
||||
lang: z.nativeEnum(Lang),
|
||||
pathName: z.string(),
|
||||
})
|
||||
.optional()
|
||||
@@ -46,8 +46,10 @@ import {
|
||||
import { internalServerError } from "@/server/errors/trpc"
|
||||
import { publicProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"
|
||||
import { generateTag } from "@/utils/generateTag"
|
||||
|
||||
import { getLanguageSwitcherInput } from "./input"
|
||||
import { validateLanguageSwitcherData } from "./output"
|
||||
import { languageSwitcherAffix } from "./utils"
|
||||
|
||||
@@ -163,87 +165,103 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
|
||||
}
|
||||
|
||||
export const languageSwitcherQueryRouter = router({
|
||||
get: publicProcedure.query(async ({ ctx }) => {
|
||||
if (!ctx.uid || !ctx.lang) {
|
||||
return { lang: ctx.lang, urls: baseUrls }
|
||||
}
|
||||
getLanguageSwitcherCounter.add(1, {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
})
|
||||
console.info(
|
||||
"contentstack.languageSwitcher start",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
},
|
||||
get: publicProcedure
|
||||
.input(getLanguageSwitcherInput)
|
||||
.query(async ({ input, ctx }) => {
|
||||
let uid = ctx.uid
|
||||
let contentType = ctx.contentType
|
||||
let lang = ctx.lang ?? input?.lang
|
||||
|
||||
if (input) {
|
||||
const data = await getUidAndContentTypeByPath(input.pathName)
|
||||
uid = data.uid
|
||||
contentType = data.contentType ?? ctx.contentType
|
||||
}
|
||||
|
||||
if (!uid || !lang) {
|
||||
return { lang: lang, urls: baseUrls }
|
||||
}
|
||||
|
||||
getLanguageSwitcherCounter.add(1, {
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
})
|
||||
)
|
||||
const res = await getLanguageSwitcher({
|
||||
contentType: ctx.contentType!,
|
||||
uid: ctx.uid,
|
||||
})
|
||||
|
||||
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
|
||||
(acc, key) => {
|
||||
const item = res.data[key as Lang]
|
||||
const url = item
|
||||
? item.web?.original_url || `/${key}${item.url}`
|
||||
: undefined
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: { url, isExternal: !!item?.web?.original_url },
|
||||
}
|
||||
},
|
||||
{} as LanguageSwitcherData
|
||||
)
|
||||
|
||||
const validatedLanguageSwitcherData =
|
||||
validateLanguageSwitcherData.safeParse(urls)
|
||||
|
||||
if (!validatedLanguageSwitcherData.success) {
|
||||
getLanguageSwitcherFailCounter.add(1, {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedLanguageSwitcherData.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.languageSwitcher validation error",
|
||||
console.info(
|
||||
"contentstack.languageSwitcher start",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
},
|
||||
error: validatedLanguageSwitcherData.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
getLanguageSwitcherSuccessCounter.add(1, {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
})
|
||||
console.info(
|
||||
"contentstack.languageSwitcher success",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
uid: ctx.uid,
|
||||
lang: ctx.lang,
|
||||
contentType: ctx.contentType,
|
||||
},
|
||||
const res = await getLanguageSwitcher({
|
||||
contentType: contentType!,
|
||||
uid: uid,
|
||||
})
|
||||
)
|
||||
return {
|
||||
lang: ctx.lang,
|
||||
urls,
|
||||
}
|
||||
}),
|
||||
|
||||
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
|
||||
(acc, key) => {
|
||||
const item = res.data[key as Lang]
|
||||
|
||||
if (!item?.url) return acc // Skip languages without a URL
|
||||
|
||||
const url = item
|
||||
? item.web?.original_url || `/${key}${item.url}`
|
||||
: undefined
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: { url, isExternal: !!item?.web?.original_url },
|
||||
}
|
||||
},
|
||||
{} as LanguageSwitcherData
|
||||
)
|
||||
|
||||
const validatedLanguageSwitcherData =
|
||||
validateLanguageSwitcherData.safeParse(urls)
|
||||
|
||||
if (!validatedLanguageSwitcherData.success) {
|
||||
getLanguageSwitcherFailCounter.add(1, {
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedLanguageSwitcherData.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.languageSwitcher validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
},
|
||||
error: validatedLanguageSwitcherData.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
getLanguageSwitcherSuccessCounter.add(1, {
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
})
|
||||
console.info(
|
||||
"contentstack.languageSwitcher success",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
uid: uid,
|
||||
lang: lang,
|
||||
contentType: contentType,
|
||||
},
|
||||
})
|
||||
)
|
||||
return {
|
||||
lang: lang,
|
||||
urls,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
50
services/cms/fetchAndCacheEntry.ts
Normal file
50
services/cms/fetchAndCacheEntry.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { resolve as resolveEntry } from "@/utils/entry"
|
||||
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
const entryResponseCache: Map<
|
||||
string,
|
||||
{
|
||||
contentType: string | null
|
||||
uid: string | null
|
||||
expiresAt: number
|
||||
}
|
||||
> = new Map()
|
||||
|
||||
let size: number = 0
|
||||
|
||||
export const fetchAndCacheEntry = async (path: string, lang: Lang) => {
|
||||
const cacheKey = `${path + lang}`
|
||||
const cachedResponse = entryResponseCache.get(cacheKey)
|
||||
|
||||
if (cachedResponse && cachedResponse.expiresAt > Date.now() / 1000) {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE HIT")
|
||||
return cachedResponse
|
||||
}
|
||||
|
||||
if (cachedResponse && cachedResponse.expiresAt < Date.now() / 1000) {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE STALE")
|
||||
size -= JSON.stringify(cachedResponse).length
|
||||
entryResponseCache.delete(cacheKey)
|
||||
} else {
|
||||
console.log("[CMS MIDDLEWARE]: CACHE MISS")
|
||||
}
|
||||
|
||||
const { contentType, uid } = await resolveEntry(path, lang)
|
||||
let expiresAt = Date.now() / 1000
|
||||
if (!contentType || !uid) {
|
||||
expiresAt += 600
|
||||
} else {
|
||||
expiresAt += 3600 * 12
|
||||
}
|
||||
const entryCache = { contentType, uid, expiresAt }
|
||||
size += JSON.stringify(entryCache).length
|
||||
console.log("[CMS MIDDLEWARE] Adding to cache", entryCache)
|
||||
console.log("[CMS MIDDLEWARE] Cache size (total)", size)
|
||||
entryResponseCache.set(cacheKey, entryCache)
|
||||
|
||||
return {
|
||||
contentType,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
21
services/cms/getUidAndContentTypeByPath.ts
Normal file
21
services/cms/getUidAndContentTypeByPath.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import { findLang } from "@/utils/languages"
|
||||
import { removeTrailingSlash } from "@/utils/url"
|
||||
|
||||
import { fetchAndCacheEntry } from "./fetchAndCacheEntry"
|
||||
|
||||
export const getUidAndContentTypeByPath = async (pathname: string) => {
|
||||
const lang = findLang(pathname)
|
||||
|
||||
const pathWithoutTrailingSlash = removeTrailingSlash(pathname)
|
||||
|
||||
const contentTypePathName = pathWithoutTrailingSlash.replace(`/${lang}`, "")
|
||||
|
||||
const { contentType, uid } = await fetchAndCacheEntry(
|
||||
contentTypePathName,
|
||||
lang ?? Lang.en
|
||||
)
|
||||
|
||||
return { contentType, uid }
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
import type { Header } from "@/types/trpc/routers/contentstack/header"
|
||||
|
||||
export interface MobileMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
topLink: Header["header"]["topLink"]
|
||||
isLoggedIn: boolean
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import type { HotelData } from "@/types/hotel"
|
||||
|
||||
export enum SidePeekEnum {
|
||||
hotelDetails = "hotel-detail-side-peek",
|
||||
roomDetails = "room-detail-side-peek",
|
||||
}
|
||||
|
||||
export type HotelReservationSidePeekProps = {
|
||||
hotel: HotelData | null
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactElement } from "react"
|
||||
import type { ReactElement } from "react"
|
||||
|
||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
@@ -12,7 +12,6 @@ export type LanguageSwitcherTypes = `${LanguageSwitcherTypesEnum}`
|
||||
|
||||
export interface LanguageSwitcherProps {
|
||||
type: LanguageSwitcherTypes
|
||||
urls: LanguageSwitcherData
|
||||
}
|
||||
|
||||
export interface LanguageSwitcherContentProps {
|
||||
|
||||
Reference in New Issue
Block a user