diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 2462bc462b..13911666d7 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -97,10 +97,19 @@ export function buildControllerDefinitions< ? definition['standalone'] === false : false; + const unavailabeInRecs = + // TODO: use this disjunction pattern for all other conditions + (solutionType === SolutionType['recommendation'] && + !('recommendation' in definition)) || + ('recommendation' in definition && + definition['recommendation'] === false && + solutionType === SolutionType['recommendation']); + if ( unavailableInSearchSolutionType || unavailableInListingSolutionType || - unavailableInStandaloneSolutionType + unavailableInStandaloneSolutionType || + unavailabeInRecs ) { return null; } diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx index 740a6869fa..fe2cd83548 100644 --- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx @@ -6,11 +6,15 @@ import FacetGenerator from '@/components/facets/facet-generator'; import Pagination from '@/components/pagination'; import ProductList from '@/components/product-list'; import ListingProvider from '@/components/providers/listing-provider'; +import RecommendationProvider from '@/components/providers/recommendation-provider'; import Recommendations from '@/components/recommendation-list'; import Sort from '@/components/sort'; import StandaloneSearchBox from '@/components/standalone-search-box'; import Summary from '@/components/summary'; -import {listingEngineDefinition} from '@/lib/commerce-engine'; +import { + listingEngineDefinition, + recommendationEngineDefinition, +} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {headers} from 'next/headers'; @@ -54,6 +58,11 @@ export default async function Listing({params}: {params: {category: string}}) { }, }); + const recStaticState = await recommendationEngineDefinition.fetchStaticState([ + 'popularBoughtRecs', + 'popularViewedRecs', + ]); + return (
- + + +
diff --git a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx index d0112efb94..7264137062 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx @@ -28,7 +28,7 @@ export default function ListingProvider({ useEffect(() => { listingEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, controllers: { cart: { initialState: {items: staticState.controllers.cart.state.items}, diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx new file mode 100644 index 0000000000..deea79c460 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { + recommendationEngineDefinition, + RecommendationHydratedState, + RecommendationStaticState, +} from '@/lib/commerce-engine'; +import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; +import {PropsWithChildren, useEffect, useState} from 'react'; + +interface RecommendationPageProps { + staticState: RecommendationStaticState; + navigatorContext: NavigatorContext; +} + +export default function RecommendationProvider({ + staticState, + navigatorContext, + children, +}: PropsWithChildren) { + const [hydratedState, setHydratedState] = useState< + RecommendationHydratedState | undefined + >(undefined); + + // Setting the navigator context provider also in client-side before hydrating the application + recommendationEngineDefinition.setNavigatorContextProvider( + () => navigatorContext + ); + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recommendationEngineDefinition.hydrateStaticState as any)({ + searchActions: staticState.searchActions, + // controllers: { }, + }).then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + // Refreshing recommendations in the browser after hydrating the state in the client-side + // Recommendation refresh in the server is not supported yet. + // controllers.popularBoughtRecs.refresh(); // FIXME: does not work + }); + }, [staticState]); + + if (hydratedState) { + return ( + + <>{children} + + ); + } else { + return ( + + {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. + Type 'bigint' is not assignable to type 'ReactNode'.*/} + <>{children} + + ); + } +} diff --git a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx index e9e099e286..7467928bb6 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx @@ -28,7 +28,7 @@ export default function SearchProvider({ useEffect(() => { searchEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, controllers: { cart: { initialState: {items: staticState.controllers.cart.state.items},