import type { ReactNode } from "react"; import type { PageProps, ParallelModalSearchParams } from "@/core/types/common"; import getStores from "@/app/_actions/getStores"; import getProductList from "@/app/(visualization)/project/@modals/(.)customization/_actions/getProductList"; import getProductFilters from "@/app/(visualization)/project/@modals/(.)customization/_actions/getProductFilters"; import { getServerSession } from "next-auth"; import { getFavourites } from "@/app/(visualization)/project/@modals/(.)customization/_actions/favourites"; import { authOptions } from "@/core/lib/auth/options"; import Logger from "@/core/utils/helpers/logger"; import ParallelModalWrapper from "@/core/components/ParallelModal"; import CustomizationComponent from "@/app/(visualization)/project/@modals/(.)customization/_components/Customization"; type Params = { segments: string[] }; /** * This is separated _**server page**_ component which placed in the parallel slot `@modals`. * The {@link CustomizationComponent} has a large infrastructure, so this way we separate it as completed _logical_ part.
* Such as this the server **page**, we're able to fetch required initial data on the server side and then put it to the children client components. * The fetched data could be **revalidated** by some conditions as well if needed, with revalidation by tags – {@link revalidateTag}.
* @param props {@link PageParams} props of the server page. In the current case, it is a **_parallel route_** slot `modals`. * @returns {ReactNode} – client component node which got from _server-side_ fetched data as its `props`. * @constructor */ export default async function CustomizationModal( props: PageProps>): Promise { Logger.dev("CustomizationModal.props.searchParams: ", props.searchParams); const session = await getServerSession(authOptions); Logger.dev("[CustomizationModal.session]: ", session); /** Route segments which defines the selected categories & subcategories of product lists */ const category: string = props.params.segments[0].replace("textures", "texture"); // TODO const subcategory: string | undefined = props.params.segments[1]; const subcategoryType: string | undefined = props.searchParams?.subcategoryType; const savedProducts: boolean = (props.searchParams?.["saved"] !== undefined); const shopIds = props.searchParams["shopIds"] as string | string[]; const shops: string[] = shopIds ? typeof shopIds === "string" ? props.searchParams["shopIds"]?.split(",") : shopIds as string[] : []; Logger.log("SELECTED SHOPS: ", shops); /** Markers for conditional fetching data */ const isPatternTiles: boolean = props.params.segments.includes("tile_patterns"); const isGrout: boolean = props.params.segments.includes("grout"); /** Request filter search params */ const requestFilterParams = new URLSearchParams(); /** Object to combine same params keys */ const combinedParams: Record = {}; /** Combine params .forEach callback */ const combineParams = (key: string) => { const filterName = key.replace(/^(bar|common)/, '').toLowerCase(); if (combinedParams.hasOwnProperty(filterName)) { combinedParams[filterName] += `,${props.searchParams[key]}`; } else { combinedParams[filterName] = props.searchParams[key]; } } /** Collecting common filter options below the product lists */ Object .keys(props.searchParams) .filter(o => o !== "modal" && o !== "origin" && o.startsWith("common")) .forEach(combineParams); /** Collecting barFilter options if barFilter has been applied */ if (props.searchParams?.["barFilter"] === "applied") { Object .keys(props.searchParams) .filter(o => o !== "modal" && o !== "origin" && o !== "barFilter" && o.startsWith("bar")) .forEach(combineParams); } /** Collecting unique param values in alphabetical order to correct caching the results */ const paramsEntries = Object.entries(combinedParams) .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) .map(([key, value]) => [key, value.split(',').sort().join(',')]); if (paramsEntries.length) { for (const [key, value] of paramsEntries) { const uniqueValues = Array.from(new Set(value.split(','))).join(','); requestFilterParams.append(key.toLowerCase(), uniqueValues); } } /** Development logging actual request params */ Logger.dev("Products request search params: ", requestFilterParams) /** * Create cached requests with keyParts signatures. Request caching will depend on changes of these key signatures * TODO: Also, the dynamic data such as gotten from cookies() OR headers() will not supported by Next's cache. * TODO: So if needed, this data should be gotten outside the cached function and passed to them as arguments. * */ //const getCachedStores = cache // .unstable_cache(getStores, [], { revalidate: 360, tags: ["cached_stores"] }); //const getCachedFilters = cache // .unstable_cache(getProductFilters, [props.searchParams?.shopIds?.toString()], { tags: ["cached_filters"] }); ///** Cached Products */ //const getCachedProducts = cache.unstable_cache( // getProductList, // [requestFilterParams.toString(), isPatternTiles.toString(), isGrout.toString(), props.searchParams?.shopIds?.toString()], // { tags: ["cached_products"] }); //const getCachedTiles = cache.unstable_cache( // getProductList, // [requestFilterParams.toString(), isPatternTiles.toString(), props.searchParams?.shopIds?.toString()], // { tags: ["cached_tiles"] }); //const getCachedGrout = cache.unstable_cache( // getProductList, // [requestFilterParams.toString(), props.searchParams?.shopIds?.toString()], // { tags: ["cached_grout"] }); /** Fetch stores data first */ const stores = await getStores(shops); /** The concurrent conditional fetching data */ const [filters, products, grout, tiles, favourites] = await Promise.all([ shops ? getProductFilters(shops) : [], (!isPatternTiles && !isGrout) ? getProductList( category, subcategory, subcategoryType, requestFilterParams, shops, undefined, savedProducts) : [], getProductList("texture", "grout", undefined, undefined, shops), isPatternTiles ? getProductList("texture", "floor_tile", undefined, undefined, shops) : undefined, getFavourites(session?.user.id || "") ]); /** Development logging */ Logger.dev("PROMISE.ALL - STORES: ", stores); Logger.dev("PROMISE.ALL - FILTERS: ", filters); Logger.dev("PROMISE.ALL - PRODUCTS.length: ", products?.length); Logger.dev("PROMISE.ALL - FAVOURITES: ", favourites); Logger.dev("PROMISE.ALL - GROUT.length: ", grout?.length); Logger.dev("PROMISE.ALL - TILES.length: ", tiles?.length); return ( ); }