From f23652b92926fd76898280aaa85d41b4a2aa5cc0 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Fri, 14 Nov 2025 09:51:44 +0000 Subject: [PATCH] Merged in feat/BOOK-434-users-should-redirect-to- (pull request #3154) * feat(BOOK-434): Moved redirect to middleware layer * feat(BOOK-434): Updated to handle no filters available scenario Approved-by: Erik Tiekstra --- .../DestinationCityPage/index.tsx | 7 +--- .../DestinationCountryPage/index.tsx | 7 +--- apps/scandic-web/middlewares/cmsContent.ts | 19 +++++++-- .../lib/graphql/Query/ResolveEntry.graphql.ts | 22 +++++++++++ .../cms/getUidAndContentTypeByPath.ts | 4 +- packages/trpc/lib/types/entry.ts | 39 +++++++++++++++---- packages/trpc/lib/utils/entry.ts | 11 ++++++ 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/index.tsx index f4074e7a3..a78a70d57 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCityPage/index.tsx @@ -1,4 +1,4 @@ -import { notFound, redirect } from "next/navigation" +import { notFound } from "next/navigation" import { Suspense } from "react" import { @@ -69,11 +69,6 @@ export default async function DestinationCityPage({ const activeSeoFilter = getActiveSeoFilter(seo_filters, filterFromUrl) - if (filterFromUrl && !activeSeoFilter) { - const updatedPathname = pathname.replace(`/${filterFromUrl}`, "") - return redirect(`${updatedPathname}${isMapView ? "?view=map" : ""}`) - } - const allHotels = await getHotelsByCityIdentifier(cityIdentifier) const hotelFilters = getFiltersFromHotels(allHotels, lang) diff --git a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx index ff0be5429..6a8864e0d 100644 --- a/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx +++ b/apps/scandic-web/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx @@ -1,4 +1,4 @@ -import { notFound, redirect } from "next/navigation" +import { notFound } from "next/navigation" import { Suspense } from "react" import { @@ -70,11 +70,6 @@ export default async function DestinationCountryPage({ const activeSeoFilter = getActiveSeoFilter(seo_filters, filterFromUrl) - if (filterFromUrl && !activeSeoFilter) { - const updatedPathname = pathname.replace(`/${filterFromUrl}`, "") - return redirect(`${updatedPathname}${isMapView ? "?view=map" : ""}`) - } - const [allHotels, allCities] = await Promise.all([ getHotelsByCountry(destination_settings.country), getDestinationCityPagesByCountry(destination_settings.country), diff --git a/apps/scandic-web/middlewares/cmsContent.ts b/apps/scandic-web/middlewares/cmsContent.ts index 2b9e9e384..723512674 100644 --- a/apps/scandic-web/middlewares/cmsContent.ts +++ b/apps/scandic-web/middlewares/cmsContent.ts @@ -40,8 +40,11 @@ export const middleware: NextMiddleware = async (request) => { if (incomingPathNameParts.length >= 2) { const subpage = incomingPathNameParts.pop() if (subpage) { - const { contentType: parentContentType, uid: parentUid } = - await getUidAndContentTypeByPath(incomingPathNameParts.join("/")) + const { + contentType: parentContentType, + seoFilters, + uid: parentUid, + } = await getUidAndContentTypeByPath(incomingPathNameParts.join("/")) if (parentUid) { switch (parentContentType) { @@ -53,8 +56,18 @@ export const middleware: NextMiddleware = async (request) => { break case PageContentTypeEnum.destinationCityPage: case PageContentTypeEnum.destinationCountryPage: - // E.g. Active filters inside destination pages to filter hotels. + // Validate Seo Filters from CMS and redirect if not found + if (!seoFilters?.includes(subpage)) { + return NextResponse.redirect( + new URL(incomingPathNameParts.join("/"), nextUrl), + { + status: 301, + } + ) + } + // E.g. Active Seo filters inside destination pages to filter hotels. searchParams.set("filterFromUrl", subpage) + contentType = parentContentType uid = parentUid break diff --git a/packages/trpc/lib/graphql/Query/ResolveEntry.graphql.ts b/packages/trpc/lib/graphql/Query/ResolveEntry.graphql.ts index e5bf1f377..b58337c9f 100644 --- a/packages/trpc/lib/graphql/Query/ResolveEntry.graphql.ts +++ b/packages/trpc/lib/graphql/Query/ResolveEntry.graphql.ts @@ -69,6 +69,17 @@ export const EntryByUrlBatch2 = gql` } all_destination_country_page(where: { url: $url }, locale: $locale) { items { + seo_filters { + filterConnection { + edges { + node { + ... on HotelFilter { + slug + } + } + } + } + } system { ...System } @@ -77,6 +88,17 @@ export const EntryByUrlBatch2 = gql` } all_destination_city_page(where: { url: $url }, locale: $locale) { items { + seo_filters { + filterConnection { + edges { + node { + ... on HotelFilter { + slug + } + } + } + } + } system { ...System } diff --git a/packages/trpc/lib/services/cms/getUidAndContentTypeByPath.ts b/packages/trpc/lib/services/cms/getUidAndContentTypeByPath.ts index 006dc8469..e88423746 100644 --- a/packages/trpc/lib/services/cms/getUidAndContentTypeByPath.ts +++ b/packages/trpc/lib/services/cms/getUidAndContentTypeByPath.ts @@ -11,10 +11,10 @@ export const getUidAndContentTypeByPath = async (pathname: string) => { const contentTypePathName = pathWithoutTrailingSlash.replace(`/${lang}`, "") - const { contentType, uid, error } = await resolveEntry( + const { contentType, seoFilters, uid, error } = await resolveEntry( contentTypePathName, lang ?? Lang.en ) - return { contentType, uid, error } + return { contentType, seoFilters, uid, error } } diff --git a/packages/trpc/lib/types/entry.ts b/packages/trpc/lib/types/entry.ts index 75bd50962..f81fb221d 100644 --- a/packages/trpc/lib/types/entry.ts +++ b/packages/trpc/lib/types/entry.ts @@ -1,12 +1,37 @@ import z from "zod" +const baseResolveSchema = z.object({ + system: z.object({ + content_type_uid: z.string(), + uid: z.string(), + }), +}) + const entryResolveSchema = z.object({ + items: z.array(baseResolveSchema), + total: z.number(), +}) + +const allDestinationPageResolveSchema = z.object({ items: z.array( - z.object({ - system: z.object({ - content_type_uid: z.string(), - uid: z.string(), - }), + baseResolveSchema.extend({ + seo_filters: z + .array( + z.object({ + filterConnection: z.object({ + edges: z + .array( + z.object({ + node: z.object({ + slug: z.string(), + }), + }) + ) + .nullish(), + }), + }) + ) + .nullish(), }) ), total: z.number(), @@ -22,8 +47,8 @@ export const validateEntryResolveSchema = z.object({ all_current_blocks_page: entryResolveSchema, all_hotel_page: entryResolveSchema, all_destination_overview_page: entryResolveSchema, - all_destination_country_page: entryResolveSchema, - all_destination_city_page: entryResolveSchema, + all_destination_country_page: allDestinationPageResolveSchema, + all_destination_city_page: allDestinationPageResolveSchema, all_start_page: entryResolveSchema, all_promo_campaign_page: entryResolveSchema, }) diff --git a/packages/trpc/lib/utils/entry.ts b/packages/trpc/lib/utils/entry.ts index 18caf86f5..4a32fce7c 100644 --- a/packages/trpc/lib/utils/entry.ts +++ b/packages/trpc/lib/utils/entry.ts @@ -54,6 +54,17 @@ export async function resolve(url: string, lang = Lang.en) { for (const value of Object.values(validatedData.data)) { if (value.total) { const { content_type_uid, uid } = value.items[0].system + const seoFilters = + "seo_filters" in value.items[0] ? value.items[0].seo_filters : null + if (seoFilters && seoFilters.length > 0) { + return { + contentType: content_type_uid, + uid, + seoFilters: seoFilters.flatMap((f) => + f.filterConnection.edges?.flatMap((e) => e.node.slug) + ), + } + } return { contentType: content_type_uid, uid,