From 7a76abbc067340ba85702a11d7030303e2253db8 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Fri, 6 Dec 2024 17:00:45 -0500 Subject: [PATCH 01/34] add rec provider https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/providers/providers.tsx | 8 ++++---- .../headless-commerce-ssr-remix/lib/commerce-engine.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/providers/providers.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/providers/providers.tsx index c7e7b248c68..506f5de703d 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/providers/providers.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/providers/providers.tsx @@ -1,6 +1,6 @@ import { listingEngineDefinition, - //recommendationEngineDefinition, + recommendationEngineDefinition, searchEngineDefinition, standaloneEngineDefinition, } from '@/lib/commerce-engine'; @@ -17,9 +17,9 @@ export const SearchProvider = buildProviderWithDefinition( ); // Wraps recommendations, whether in a standalone, search, or listing page -// export const RecommendationProvider = buildProviderWithDefinition( -// recommendationEngineDefinition -// ); +export const RecommendationProvider = buildProviderWithDefinition( + recommendationEngineDefinition +); // Used for components that don’t require triggering a search or product fetch (e.g., cart pages, standalone search box) export const StandaloneProvider = buildProviderWithDefinition( diff --git a/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine.ts b/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine.ts index f4ed310ec98..11c835d578b 100644 --- a/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine.ts +++ b/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine.ts @@ -10,6 +10,7 @@ export const engineDefinition = defineCommerceEngine(engineConfig); export const { listingEngineDefinition, searchEngineDefinition, + recommendationEngineDefinition, standaloneEngineDefinition, useEngine, } = engineDefinition; @@ -55,3 +56,11 @@ export type StandaloneStaticState = InferStaticState< export type StandaloneHydratedState = InferHydratedState< typeof standaloneEngineDefinition >; + +export type RecommendationStaticState = InferStaticState< + typeof recommendationEngineDefinition +>; + +export type RecommendationHydratedState = InferHydratedState< + typeof recommendationEngineDefinition +>; From 88051d99214fafc91783eb46640ef831c7d551c2 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Fri, 6 Dec 2024 17:03:47 -0500 Subject: [PATCH 02/34] add components from nextjs sample https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/breadcrumb-manager.tsx | 73 +++++ .../app/components/context-dropdown.tsx | 62 ++++ .../app/components/did-you-mean.tsx | 50 +++ .../app/components/facets/category-facet.tsx | 302 ++++++++++++++++++ .../app/components/facets/date-facet.tsx | 86 +++++ .../app/components/facets/facet-generator.tsx | 63 ++++ .../app/components/facets/numeric-facet.tsx | 211 ++++++++++++ .../app/components/facets/regular-facet.tsx | 220 +++++++++++++ .../app/components/hydration-metadata.tsx | 51 +++ .../app/components/instant-product.tsx | 39 +++ .../app/components/pagination.tsx | 48 +++ .../app/components/recent-queries.tsx | 24 ++ .../recommendations/popular-bought.tsx | 38 +++ .../recommendations/popular-viewed.tsx | 38 +++ .../app/components/search-box.tsx | 85 +++++ .../app/components/show-more.tsx | 35 ++ .../app/components/sort.tsx | 51 +++ .../app/components/standalone-search-box.tsx | 96 ++++++ .../app/components/summary.tsx | 40 +++ .../components/triggers/notify-trigger.tsx | 21 ++ .../app/components/triggers/query-trigger.tsx | 17 + .../triggers/redirection-trigger.tsx | 21 ++ .../app/components/triggers/triggers.tsx | 13 + .../components/standalone-search-box.tsx | 9 +- 24 files changed, 1689 insertions(+), 4 deletions(-) create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/instant-product.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/triggers/triggers.tsx diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx new file mode 100644 index 00000000000..f2f9f46a6c4 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx @@ -0,0 +1,73 @@ +'use client'; + +import {useBreadcrumbManager} from '@/lib/commerce-engine'; +import { + NumericFacetValue, + DateFacetValue, + CategoryFacetValue, + RegularFacetValue, + LocationFacetValue, +} from '@coveo/headless-react/ssr-commerce'; + +export default function BreadcrumbManager() { + const {state, methods} = useBreadcrumbManager(); + + const renderBreadcrumbValue = ( + value: + | CategoryFacetValue + | RegularFacetValue + | NumericFacetValue + | DateFacetValue + | LocationFacetValue, + type: string + ) => { + switch (type) { + case 'hierarchical': + return (value as CategoryFacetValue).path.join(' > '); + case 'regular': + return (value as RegularFacetValue).value; + case 'numericalRange': + return ( + (value as NumericFacetValue).start + + ' - ' + + (value as NumericFacetValue).end + ); + case 'dateRange': + return ( + (value as DateFacetValue).start + + ' - ' + + (value as DateFacetValue).end + ); + default: + // TODO COMHUB-291 support location breadcrumb + return null; + } + }; + + return ( +
+
+ +
+
    + {state.facetBreadcrumbs.map((facetBreadcrumb) => { + return ( +
  • + {facetBreadcrumb.values.map((value, index) => { + return ( + + ); + })} +
  • + ); + })} +
+
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx new file mode 100644 index 00000000000..121ee418026 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx @@ -0,0 +1,62 @@ +'use client'; + +import {useContext, useEngine} from '@/lib/commerce-engine'; +import { + CommerceEngine, + ContextOptions, + loadProductListingActions, + loadSearchActions, +} from '@coveo/headless-react/ssr-commerce'; + +// A hardcoded list of storefront associations for switching app context by language, country, and currency. +// Found in the admin console under "Storefront Associations," this list is static for demonstration purposes. +// In a real application, these values would likely come from sources like environment variables or an API. +const storefrontAssociations = [ + 'en-CA-CAD', + 'fr-CA-CAD', + 'en-GB-GBP', + 'en-US-USD', +]; + +export default function ContextDropdown({ + useCase, +}: { + useCase?: 'listing' | 'search'; +}) { + const {state, methods} = useContext(); + const engine = useEngine(); + + return ( +
+

+ Context dropdown : + +

+
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx new file mode 100644 index 00000000000..6ed7c43419f --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx @@ -0,0 +1,50 @@ +import { + DidYouMeanState, + DidYouMean as DidYouMeanController, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState} from 'react'; + +interface DidYouMeanProps { + staticState: DidYouMeanState; + controller?: DidYouMeanController; +} +export default function DidYouMean({staticState, controller}: DidYouMeanProps) { + const [state, setState] = useState(staticState); + + useEffect( + () => + controller?.subscribe(() => { + setState({...controller.state}); + }), + [controller] + ); + + if (!state.hasQueryCorrection) { + return null; + } + + if (state.wasAutomaticallyCorrected) { + return ( +
+

+ No results for {state.originalQuery} +

+

+ Query was automatically corrected to {state.wasCorrectedTo} +

+
+ ); + } + + return ( +
+

+ Search for + controller?.applyCorrection()}> + {state.queryCorrection.correctedQuery} + + instead? +

+
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx new file mode 100644 index 00000000000..418cbf91593 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx @@ -0,0 +1,302 @@ +'use client'; + +import { + CategoryFacetSearchResult, + CategoryFacetState, + CategoryFacetValue, + CategoryFacet as HeadlessCategoryFacet, +} from '@coveo/headless-react/ssr-commerce'; +import {useEffect, useRef, useState} from 'react'; + +interface ICategoryFacetProps { + controller?: HeadlessCategoryFacet; + staticState: CategoryFacetState; +} + +export default function CategoryFacet(props: ICategoryFacetProps) { + const {controller, staticState} = props; + + const [state, setState] = useState(staticState); + const [showFacetSearchResults, setShowFacetSearchResults] = useState(false); + + const facetSearchInputRef = useRef(null); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + const focusFacetSearchInput = (): void => { + facetSearchInputRef.current!.focus(); + }; + + const onChangeFacetSearchInput = ( + e: React.ChangeEvent + ): void => { + if (e.target.value === '') { + setShowFacetSearchResults(false); + controller?.facetSearch.clear(); + return; + } + + controller?.facetSearch.updateText(e.target.value); + controller?.facetSearch.search(); + setShowFacetSearchResults(true); + }; + + const onClickClearFacetSearch = (): void => { + setShowFacetSearchResults(false); + controller?.facetSearch.clear(); + focusFacetSearchInput(); + }; + + const highlightFacetSearchResult = (displayValue: string): string => { + const query = state.facetSearch.query; + const regex = new RegExp(query, 'gi'); + return displayValue.replace(regex, (match) => `${match}`); + }; + + const onClickFacetSearchResult = (value: CategoryFacetSearchResult): void => { + controller?.facetSearch.select(value); + controller?.facetSearch.clear(); + setShowFacetSearchResults(false); + focusFacetSearchInput(); + }; + + const onClickClearSelectedFacetValues = (): void => { + controller?.deselectAll(); + focusFacetSearchInput(); + }; + + const toggleSelectFacetValue = (value: CategoryFacetValue) => { + if (controller?.isValueSelected(value)) { + controller.deselectAll(); + } + controller?.toggleSelect(value); + }; + + const renderFacetSearchControls = () => { + return ( + + + + + {state.facetSearch.isLoading && ( + + {' '} + Facet search is loading... + + )} + + ); + }; + + const renderFacetSearchResults = () => { + return state.facetSearch.values.length === 0 ? ( + + No results for {state.facetSearch.query} + + ) : ( +
    + {state.facetSearch.values.map((value) => ( +
  • onClickFacetSearchResult(value)} + style={{width: 'fit-content'}} + > + + + + {' '} + ({value.count}) + +
  • + ))} +
+ ); + }; + + const renderActiveFacetValueTree = () => { + if (!state.hasActiveValues) { + return null; + } + + const ancestry = state.selectedValueAncestry!; + const activeValueChildren = ancestry[ancestry.length - 1]?.children ?? []; + + return ( +
    + {ancestry.map((ancestryValue) => { + const checkboxId = `ancestryFacetValueCheckbox-${ancestryValue.value}`; + return ( +
  • + toggleSelectFacetValue(ancestryValue)} + type="checkbox" + > + +
  • + ); + })} + {activeValueChildren.length > 0 && ( +
      + {activeValueChildren.map((child) => { + const checkboxId = `facetValueChildCheckbox-${child.value}`; + return ( +
    • + toggleSelectFacetValue(child)} + > + +
    • + ); + })} +
    + )} +
+ ); + }; + + const renderRootValues = () => { + if (state.hasActiveValues) { + return null; + } + + return ( +
    + {state.values.map((root) => { + return ( +
  • + toggleSelectFacetValue(root)} + > + + + {' '} + ({root.numberOfResults}) + +
  • + ); + })} +
+ ); + }; + + const renderFacetValues = () => { + return ( +
+ + {state.isLoading && ( + Facet is loading... + )} + {renderRootValues()} + {renderActiveFacetValueTree()} + + +
+ ); + }; + + return ( +
+ + {state.displayName ?? state.facetId} + + {renderFacetSearchControls()} + {showFacetSearchResults + ? renderFacetSearchResults() + : renderFacetValues()} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx new file mode 100644 index 00000000000..1541bbda894 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx @@ -0,0 +1,86 @@ +'use client'; + +import { + DateFacetState, + DateFacet as HeadlessDateFacet, +} from '@coveo/headless-react/ssr-commerce'; +import {useEffect, useState} from 'react'; + +interface IDateFacetProps { + controller?: HeadlessDateFacet; + staticState: DateFacetState; +} + +export default function DateFacet(props: IDateFacetProps) { + const {controller, staticState} = props; + + const [state, setState] = useState(staticState); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + const renderFacetValues = () => { + return ( +
    + {state.values.map((value) => { + const id = `${value.start}-${value.end}-${value.endInclusive}`; + return ( +
  • + controller?.toggleSelect(value)} + type="checkbox" + > + +
  • + ); + })} +
+ ); + }; + + return ( +
+ + {state.displayName ?? state.facetId} + + + {state.isLoading && ( + Facet is loading... + )} + {renderFacetValues()} + + +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx new file mode 100644 index 00000000000..8f9ad286870 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx @@ -0,0 +1,63 @@ +'use client'; + +import {useFacetGenerator} from '@/lib/commerce-engine'; +import CategoryFacet from './category-facet'; +import DateFacet from './date-facet'; +import NumericFacet from './numeric-facet'; +import RegularFacet from './regular-facet'; + +export default function FacetGenerator() { + const {state, methods} = useFacetGenerator(); + + return ( + + ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx new file mode 100644 index 00000000000..743edb8157d --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { + NumericFacet as HeadlessNumericFacet, + NumericFacetState, +} from '@coveo/headless-react/ssr-commerce'; +import {useEffect, useRef, useState} from 'react'; + +interface INumericFacetProps { + controller?: HeadlessNumericFacet; + staticState: NumericFacetState; +} + +export default function NumericFacet(props: INumericFacetProps) { + const {controller, staticState} = props; + + const [state, setState] = useState(staticState); + const [currentManualRange, setCurrentManualRange] = useState({ + start: + controller?.state.manualRange?.start ?? + controller?.state.domain?.min ?? + controller?.state.values[0]?.start ?? + 0, + end: + controller?.state.manualRange?.end ?? + controller?.state.domain?.max ?? + controller?.state.values[0]?.end ?? + 0, + }); + + const manualRangeStartInputRef = useRef(null); + + useEffect(() => { + controller?.subscribe(() => { + setState(controller.state), + setCurrentManualRange({ + start: + controller.state.manualRange?.start ?? + controller.state.domain?.min ?? + controller.state.values[0]?.start ?? + 0, + end: + controller.state.manualRange?.end ?? + controller.state.domain?.max ?? + controller.state.values[0]?.end ?? + 0, + }); + }); + }, [controller]); + + const focusManualRangeStartInput = (): void => { + manualRangeStartInputRef.current!.focus(); + }; + + const invalidRange = + currentManualRange.start >= currentManualRange.end || + isNaN(currentManualRange.start) || + isNaN(currentManualRange.end); + + const onChangeManualRangeStart = (e: React.ChangeEvent) => { + setCurrentManualRange({ + start: Number.parseInt(e.target.value), + end: currentManualRange.end, + }); + }; + + const onChangeManualRangeEnd = (e: React.ChangeEvent) => { + setCurrentManualRange({ + start: currentManualRange.start, + end: Number.parseInt(e.target.value), + }); + }; + + const onClickManualRangeSelect = () => { + const start = + state.domain && currentManualRange.start < state.domain.min + ? state.domain.min + : currentManualRange.start; + const end = + state.domain && currentManualRange.end > state.domain.max + ? state.domain.max + : currentManualRange.end; + controller?.setRanges([ + { + start, + end, + endInclusive: true, + state: 'selected', + }, + ]); + focusManualRangeStartInput(); + }; + + const onClickClearSelectedFacetValues = (): void => { + controller?.deselectAll(); + focusManualRangeStartInput(); + }; + + const renderManualRangeControls = () => { + return ( +
+ + + + + +
+ ); + }; + + const renderFacetValues = () => { + return ( +
+ + {state.isLoading && Facet is loading...} +
    + {state.values.map((value, index) => { + const checkboxId = `${value.start}-${value.end}-${value.endInclusive}`; + return ( +
  • + controller?.toggleSelect(value)} + type="checkbox" + > + +
  • + ); + })} +
+ + +
+ ); + }; + + return ( +
+ + {state.displayName ?? state.facetId} + + {renderManualRangeControls()} + {renderFacetValues()} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx new file mode 100644 index 00000000000..f7f19df39a5 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx @@ -0,0 +1,220 @@ +'use client'; + +import { + BaseFacetSearchResult, + RegularFacet as HeadlessRegularFacet, + RegularFacetState, + RegularFacetValue, +} from '@coveo/headless-react/ssr-commerce'; +import {useEffect, useRef, useState} from 'react'; + +interface IRegularFacetProps { + controller?: HeadlessRegularFacet; + staticState: RegularFacetState; +} + +export default function RegularFacet(props: IRegularFacetProps) { + const {controller, staticState} = props; + + const [state, setState] = useState(staticState); + const [showFacetSearchResults, setShowFacetSearchResults] = useState(false); + + const facetSearchInputRef = useRef(null); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + const focusFacetSearchInput = (): void => { + facetSearchInputRef.current!.focus(); + }; + + const onChangeFacetSearchInput = ( + e: React.ChangeEvent + ): void => { + if (e.target.value === '') { + setShowFacetSearchResults(false); + controller?.facetSearch.clear(); + return; + } + + controller?.facetSearch.updateText(e.target.value); + controller?.facetSearch.search(); + setShowFacetSearchResults(true); + }; + + const highlightFacetSearchResult = (displayValue: string): string => { + const query = state.facetSearch.query; + const regex = new RegExp(query, 'gi'); + return displayValue.replace(regex, (match) => `${match}`); + }; + + const onClickFacetSearchResult = (value: BaseFacetSearchResult): void => { + controller?.facetSearch.select(value); + controller?.facetSearch.clear(); + setShowFacetSearchResults(false); + focusFacetSearchInput(); + }; + + const onClickFacetSearchClear = (): void => { + setShowFacetSearchResults(false); + controller?.facetSearch.clear(); + focusFacetSearchInput(); + }; + + const onClickClearSelectedFacetValues = (): void => { + controller?.deselectAll(); + focusFacetSearchInput(); + }; + + const onChangeFacetValue = (facetValue: RegularFacetValue): void => { + controller?.toggleSelect(facetValue); + focusFacetSearchInput(); + }; + + const renderFacetSearchControls = () => { + return ( + + + + + {state.facetSearch.isLoading && ( + + {' '} + Facet search is loading... + + )} + + ); + }; + + const renderFacetSearchResults = () => { + return state.facetSearch.values.length === 0 ? ( + + No results for {state.facetSearch.query} + + ) : ( +
    + {state.facetSearch.values.map((value) => ( +
  • onClickFacetSearchResult(value)} + style={{width: 'fit-content'}} + > + + + + {' '} + ({value.count}) + +
  • + ))} +
+ ); + }; + + const renderFacetValues = () => { + return ( +
+ + {state.isLoading && ( + Facet is loading... + )} +
    + {state.values.map((value) => ( +
  • + onChangeFacetValue(value)} + type="checkbox" + > + +
  • + ))} +
+ + +
+ ); + }; + + return ( +
+ + {state.displayName ?? state.facetId} + + {renderFacetSearchControls()} + {showFacetSearchResults + ? renderFacetSearchResults() + : renderFacetValues()} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx new file mode 100644 index 00000000000..58efd70d0ae --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { + ListingHydratedState, + ListingStaticState, + SearchHydratedState, + SearchStaticState, +} from '@/lib/commerce-engine'; +import {FunctionComponent} from 'react'; + +export interface HydrationMetadataProps { + staticState: SearchStaticState | ListingStaticState; + hydratedState?: SearchHydratedState | ListingHydratedState; +} + +export const HydrationMetadata: FunctionComponent = ({ + staticState, + hydratedState, +}) => { + return ( + <> +
+ Hydrated:{' '} + +
+ + Rendered page with{' '} + { + (hydratedState ?? staticState).controllers.productList.state.products + .length + }{' '} + products + +
+ Items in cart:{' '} + {(hydratedState ?? staticState).controllers.cart.state.items.length} +
+
+ Rendered on{' '} + + {new Date().toISOString()} + +
+ + ); +}; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/instant-product.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/instant-product.tsx new file mode 100644 index 00000000000..d3af0f776a3 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/instant-product.tsx @@ -0,0 +1,39 @@ +import {useInstantProducts} from '@/lib/commerce-engine'; +import {Product} from '@coveo/headless-react/ssr-commerce'; +import {useNavigate} from '@remix-run/react'; +import AddToCartButton from './add-to-cart-button'; + +export default function InstantProducts() { + const navigate = useNavigate(); + + const {state, methods} = useInstantProducts(); + + const clickProduct = (product: Product) => { + methods?.interactiveProduct({options: {product}}).select(); + navigate( + `/products/${product.ec_product_id}?name=${product.ec_name}&price=${product.ec_price}` + ); + }; + + return ( +
    + Instant Products : + {state.products.map((product, index) => ( +
  • + + {product.ec_product_id && + product.ec_price !== null && + product.ec_name && ( + + )} +
  • + ))} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx new file mode 100644 index 00000000000..9732a990aad --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx @@ -0,0 +1,48 @@ +'use client'; + +import {usePagination} from '@/lib/commerce-engine'; + +export default function Pagination() { + const {state, methods} = usePagination(); + + const renderPageRadioButtons = () => { + return Array.from({length: state.totalPages}, (_, i) => { + const page = i + 1; + return ( + + ); + }); + }; + + return ( +
+
+ Page {state.page + 1} of {state.totalPages} +
+ + {renderPageRadioButtons()} + +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx new file mode 100644 index 00000000000..df287127d70 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx @@ -0,0 +1,24 @@ +import {useInstantProducts, useRecentQueriesList} from '@/lib/commerce-engine'; + +export default function RecentQueries() { + const {state, methods} = useRecentQueriesList(); + const {methods: instantProductsController} = useInstantProducts(); + + return ( +
+
    + Recent Queries : + {state.queries.map((query, index) => ( +
  • + {query} + +
  • + ))} +
+
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx new file mode 100644 index 00000000000..0abbb36f966 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx @@ -0,0 +1,38 @@ +'use client'; + +import {usePopularBoughtRecs} from '@/lib/commerce-engine'; +import {Product} from '@coveo/headless-react/ssr-commerce'; +import {useNavigate} from '@remix-run/react'; + +export default function PopularBought() { + const {state, methods} = usePopularBoughtRecs(); + const navigate = useNavigate(); + + const onProductClick = (product: Product) => { + methods?.interactiveProduct({options: {product}}).select(); + navigate( + `/products/${product.ec_product_id || ''}?name=${product.ec_name}&price=${product.ec_price}` + ); + }; + + return ( + <> +
    +

    {state.headline}

    + {state.products.map((product: Product) => ( +
  • + +
  • + ))} +
+ + ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx new file mode 100644 index 00000000000..4828b927957 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx @@ -0,0 +1,38 @@ +'use client'; + +import {usePopularViewedRecs} from '@/lib/commerce-engine'; +import {Product} from '@coveo/headless-react/ssr-commerce'; +import {useNavigate} from '@remix-run/react'; + +export default function PopularViewed() { + const {state, methods} = usePopularViewedRecs(); + const navigate = useNavigate(); + + const onProductClick = (product: Product) => { + methods?.interactiveProduct({options: {product}}).select(); + navigate( + `/products/${product.ec_product_id}?name=${product.ec_name}&price=${product.ec_price}` + ); + }; + + return ( + <> +
    +

    {state.headline}

    + {state.products.map((product: Product) => ( +
  • + +
  • + ))} +
+ + ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx new file mode 100644 index 00000000000..87dfd0fdbab --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { + useInstantProducts, + useRecentQueriesList, + useSearchBox, +} from '@/lib/commerce-engine'; +import {useState} from 'react'; +import InstantProducts from './instant-product'; +import RecentQueries from './recent-queries'; + +export default function SearchBox() { + const {state, methods} = useSearchBox(); + const {state: recentQueriesState} = useRecentQueriesList(); + const {state: instantProductsState, methods: instantProductsController} = + useInstantProducts(); + + const [isInputFocused, setIsInputFocused] = useState(false); + const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); + + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + setIsSelectingSuggestion(true); + methods?.updateText(e.target.value); + instantProductsController?.updateQuery(e.target.value); + }; + + const handleFocus = () => { + setIsInputFocused(true); + }; + + const handleBlur = () => { + if (!isSelectingSuggestion) { + setIsInputFocused(false); + } + }; + + return ( +
+ onSearchBoxInputChange(e)} + onFocus={handleFocus} + onBlur={handleBlur} + > + {state.value !== '' && ( + + + + )} + + + {isInputFocused && ( + <> + {recentQueriesState.queries.length > 0 && } + {state.suggestions.length > 0 && ( +
    + Suggestions : + {state.suggestions.map((suggestion, index) => ( +
  • + +
  • + ))} +
+ )} + {instantProductsState.products.length > 0 && } + + )} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx new file mode 100644 index 00000000000..d7fbf9cdebc --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx @@ -0,0 +1,35 @@ +'use client'; + +import {usePagination, useSummary} from '@/lib/commerce-engine'; + +export default function ShowMore() { + const {state, methods} = usePagination(); + const {state: summaryState} = useSummary(); + + const handleFetchMore = () => { + methods?.fetchMoreProducts(); + }; + + const isDisabled = () => { + return ( + !methods || + summaryState?.lastProduct === summaryState?.totalNumberOfProducts + ); + }; + + return ( + <> +
+ Displaying {summaryState?.lastProduct ?? state.pageSize} out of{' '} + {state.totalEntries} products +
+ + + ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx new file mode 100644 index 00000000000..2690c9c7891 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx @@ -0,0 +1,51 @@ +'use client'; + +// import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; +import {useSort} from '@/lib/commerce-engine'; +import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; + +export default function Sort() { + const {state, methods} = useSort(); + + if (state.availableSorts.length === 0) { + return null; + } + + const formatSortFieldLabel = (field: { + name: string; + direction?: string; + displayName?: string; + }) => field?.displayName ?? `${field.name} ${field.direction ?? ''}`.trim(); + + const getSortLabel = (criterion: SortCriterion) => { + switch (criterion.by) { + case SortBy.Relevance: + return 'Relevance'; + case SortBy.Fields: + return criterion.fields.map(formatSortFieldLabel); + } + }; + + return ( +
+ + +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx new file mode 100644 index 00000000000..84fd78f53dd --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx @@ -0,0 +1,96 @@ +'use client'; + +import { + useInstantProducts, + useRecentQueriesList, + useStandaloneSearchBox, +} from '@/lib/commerce-engine'; +import {useNavigate} from '@remix-run/react'; +import {useEffect, useState} from 'react'; +import InstantProducts from './instant-product'; +import RecentQueries from './recent-queries'; + +export default function StandaloneSearchBox() { + const {state, methods} = useStandaloneSearchBox(); + const {state: recentQueriesState} = useRecentQueriesList(); + const {state: instantProductsState, methods: instantProductsController} = + useInstantProducts(); + + const [isInputFocused, setIsInputFocused] = useState(false); + const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); + + const navigate = useNavigate(); + + useEffect(() => { + if (state.redirectTo === '/search') { + const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; + navigate(url, {preventScrollReset: true}); + methods?.afterRedirection(); + } + }, [state.redirectTo, state.value, navigate, methods]); + + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + setIsSelectingSuggestion(true); + methods?.updateText(e.target.value); + instantProductsController?.updateQuery(e.target.value); + }; + + const handleFocus = () => { + setIsInputFocused(true); + }; + + const handleBlur = () => { + if (!isSelectingSuggestion) { + setIsInputFocused(false); + } + }; + + return ( +
+ onSearchBoxInputChange(e)} + onFocus={handleFocus} + onBlur={handleBlur} + > + {state.value !== '' && ( + + + + )} + + + {isInputFocused && ( + <> + {recentQueriesState.queries.length > 0 && } + {state.suggestions.length > 0 && ( +
    + Suggestions : + {state.suggestions.map((suggestion, index) => ( +
  • + +
  • + ))} +
+ )} + {instantProductsState.products.length > 0 && } + + )} +
+ ); +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx new file mode 100644 index 00000000000..7818c505fdb --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx @@ -0,0 +1,40 @@ +'use client'; + +import {useSummary} from '@/lib/commerce-engine'; + +export default function Summary() { + const {state} = useSummary(); + + const renderBaseSummary = () => { + const {firstProduct, lastProduct, totalNumberOfProducts} = state; + return ( + + Showing results {firstProduct} - {lastProduct} of{' '} + {totalNumberOfProducts} + + ); + }; + + const renderQuerySummary = () => { + if (!('query' in state)) { + return null; + } + + return ( + + for {state.query} + + ); + }; + + const renderSummary = () => { + return ( +

+ {renderBaseSummary()} + {renderQuerySummary()} +

+ ); + }; + + return
{renderSummary()}
; +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx new file mode 100644 index 00000000000..dde3b5a609a --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx @@ -0,0 +1,21 @@ +'use client'; + +import {useNotifyTrigger} from '@/lib/commerce-engine'; +import {useCallback, useEffect} from 'react'; + +// The notify trigger query example in the searchuisamples org is 'notify me'. +export default function NotifyTrigger() { + const {state} = useNotifyTrigger(); + + const notify = useCallback(() => { + state.notifications.forEach((notification) => { + alert(`Notification: ${notification}`); + }); + }, [state.notifications]); + + useEffect(() => { + notify(); + }, [notify]); + + return null; +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx new file mode 100644 index 00000000000..f3027648a71 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx @@ -0,0 +1,17 @@ +'use client'; + +import {useQueryTrigger} from '@/lib/commerce-engine'; + +// The query trigger query example in the searchuisamples org is 'query me'. +export default function QueryTrigger() { + const {state} = useQueryTrigger(); + + if (state.wasQueryModified) { + return ( +
+ The query changed from {state.originalQuery} to {state.newQuery} +
+ ); + } + return null; +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx new file mode 100644 index 00000000000..84f060eebbe --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx @@ -0,0 +1,21 @@ +'use client'; + +import {useRedirectionTrigger} from '@/lib/commerce-engine'; +import {useCallback, useEffect} from 'react'; + +// The redirection trigger query example in the searchuisamples org is 'redirect me'. +export default function RedirectionTrigger() { + const {state} = useRedirectionTrigger(); + + const redirect = useCallback(() => { + if (state.redirectTo) { + window.location.replace(state.redirectTo); + } + }, [state.redirectTo]); + + useEffect(() => { + redirect(); + }, [redirect]); + + return null; +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/triggers.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/triggers.tsx new file mode 100644 index 00000000000..6ac0b54befa --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/triggers.tsx @@ -0,0 +1,13 @@ +import NotifyTrigger from './notify-trigger'; +import QueryTrigger from './query-trigger'; +import RedirectionTrigger from './redirection-trigger'; + +export default function Triggers() { + return ( + <> + + + + + ); +} diff --git a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx index 0c6fd973de2..029767c068e 100644 --- a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx +++ b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx @@ -5,7 +5,7 @@ import { useRecentQueriesList, useStandaloneSearchBox, } from '@/lib/commerce-engine'; -import {useRouter} from 'next/navigation'; +import {useNavigate} from '@remix-run/react'; import {useEffect, useState} from 'react'; import InstantProducts from './instant-product'; import RecentQueries from './recent-queries'; @@ -19,15 +19,16 @@ export default function StandaloneSearchBox() { const [isInputFocused, setIsInputFocused] = useState(false); const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); - const router = useRouter(); + const navigate = useNavigate(); + // Update the navigation in useEffect useEffect(() => { if (state.redirectTo === '/search') { const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; - router.push(url, {scroll: false}); + navigate(url, {preventScrollReset: true}); // equivalent to Next.js's scroll: false methods?.afterRedirection(); } - }, [state.redirectTo, state.value, router, methods]); + }, [state.redirectTo, state.value, navigate, methods]); const onSearchBoxInputChange = (e: React.ChangeEvent) => { setIsSelectingSuggestion(true); From ea99cdf158b58cc804a1e8cc2c05e1afd3e7587e Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Fri, 6 Dec 2024 17:05:51 -0500 Subject: [PATCH 03/34] update routes to use new components https://coveord.atlassian.net/browse/KIT-3774 --- packages/atomic/src/components.d.ts | 217 +++++++++--------- .../app/routes/cart.tsx | 65 ++++-- .../app/routes/listings.$listingId.tsx | 63 ++++- .../app/routes/products.$productId.tsx | 12 +- .../app/routes/search.tsx | 31 ++- 5 files changed, 249 insertions(+), 139 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index ea07f2d0929..bc4c71cc4c6 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -5,8 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { AutomaticFacet, CategoryFacetSortCriterion, DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, FoldedResult, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, InteractiveResult, LogLevel as LogLevel1, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition, SearchEngine, SearchStatus } from "@coveo/headless"; -import { CategoryFacet, CommerceEngine, DateFacet, InteractiveProduct, LogLevel, NumericFacet, Product, ProductListing, ProductListingSummaryState, ProductTemplate, ProductTemplateCondition, RegularFacet, Search, SearchSummaryState, Summary } from "@coveo/headless/commerce"; +import { AutomaticFacet, CategoryFacetSortCriterion, DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, FoldedResult, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, InteractiveResult, LogLevel, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition, SearchEngine, SearchStatus } from "@coveo/headless"; import { CommerceBindings as Bindings } from "./components/commerce/atomic-commerce-interface/atomic-commerce-interface"; import { Range } from "./components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input"; import { i18n } from "i18next"; @@ -21,7 +20,7 @@ import { unknown as AnyBindings, ItemDisplayBasicLayout as ItemDisplayBasicLayou import { AnyBindings as AnyBindings1 } from "./components/common/interface/bindings"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; import { NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types"; -import { InsightEngine, FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, LogLevel as InsightLogLevel, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "./components/insight"; +import { unknown as InsightEngine, unknown as InsightFacetSortCriterion, unknown as InsightFoldedResult, unknown as InsightInteractiveResult, unknown as InsightLogLevel, unknown as InsightRangeFacetRangeAlgorithm, unknown as InsightRangeFacetSortCriterion, unknown as InsightResult, unknown as InsightResultTemplate, unknown as InsightResultTemplateCondition, unknown as IUserAction } from "./components/insight"; import { InsightInitializationOptions } from "./components/insight/atomic-insight-interface/atomic-insight-interface"; import { AtomicInsightStore } from "./components/insight/atomic-insight-interface/store"; import { InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; @@ -30,8 +29,7 @@ import { Section } from "./components/common/atomic-layout-section/sections"; import { AtomicCommonStore, AtomicCommonStoreData } from "./components/common/interface/store"; import { SelectChildProductEventArgs } from "./components/commerce/product-template-components/atomic-product-children/atomic-product-children"; import { TruncateAfter } from "./components/common/expandable-text/expandable-text"; -import { RecommendationEngine } from "@coveo/headless/recommendation"; -import { InteractiveResult as RecsInteractiveResult, LogLevel as RecsLogLevel, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "./components/recommendations"; +import { unknown as RecsInteractiveResult, unknown as RecsLogLevel, unknown as RecsResult, unknown as RecsResultTemplate, unknown as RecsResultTemplateCondition } from "./components/recommendations"; import { RecsInitializationOptions } from "./components/recommendations/atomic-recs-interface/atomic-recs-interface"; import { AtomicRecsStore } from "./components/recommendations/atomic-recs-interface/store"; import { Bindings as Bindings1 } from "./components/search/atomic-search-interface/atomic-search-interface"; @@ -53,7 +51,7 @@ export { unknown as AnyBindings, ItemDisplayBasicLayout as ItemDisplayBasicLayou export { AnyBindings as AnyBindings1 } from "./components/common/interface/bindings"; export { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; export { NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types"; -export { InsightEngine, FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, LogLevel as InsightLogLevel, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "./components/insight"; +export { unknown as InsightEngine, unknown as InsightFacetSortCriterion, unknown as InsightFoldedResult, unknown as InsightInteractiveResult, unknown as InsightLogLevel, unknown as InsightRangeFacetRangeAlgorithm, unknown as InsightRangeFacetSortCriterion, unknown as InsightResult, unknown as InsightResultTemplate, unknown as InsightResultTemplateCondition, unknown as IUserAction } from "./components/insight"; export { InsightInitializationOptions } from "./components/insight/atomic-insight-interface/atomic-insight-interface"; export { AtomicInsightStore } from "./components/insight/atomic-insight-interface/store"; export { InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; @@ -62,8 +60,7 @@ export { Section } from "./components/common/atomic-layout-section/sections"; export { AtomicCommonStore, AtomicCommonStoreData } from "./components/common/interface/store"; export { SelectChildProductEventArgs } from "./components/commerce/product-template-components/atomic-product-children/atomic-product-children"; export { TruncateAfter } from "./components/common/expandable-text/expandable-text"; -export { RecommendationEngine } from "@coveo/headless/recommendation"; -export { InteractiveResult as RecsInteractiveResult, LogLevel as RecsLogLevel, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "./components/recommendations"; +export { unknown as RecsInteractiveResult, unknown as RecsLogLevel, unknown as RecsResult, unknown as RecsResultTemplate, unknown as RecsResultTemplateCondition } from "./components/recommendations"; export { RecsInitializationOptions } from "./components/recommendations/atomic-recs-interface/atomic-recs-interface"; export { AtomicRecsStore } from "./components/recommendations/atomic-recs-interface/store"; export { Bindings as Bindings1 } from "./components/search/atomic-search-interface/atomic-search-interface"; @@ -287,7 +284,7 @@ export namespace Components { } /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface AtomicCommerceBreadbox { /** @@ -299,7 +296,7 @@ export namespace Components { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface AtomicCommerceCategoryFacet { /** @@ -326,7 +323,7 @@ export namespace Components { } /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface AtomicCommerceFacet { /** @@ -348,7 +345,7 @@ export namespace Components { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface AtomicCommerceFacetNumberInput { "bindings": Bindings; @@ -359,7 +356,7 @@ export namespace Components { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface AtomicCommerceFacets { /** @@ -444,7 +441,7 @@ export namespace Components { } /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface AtomicCommerceLoadMoreProducts { } @@ -455,7 +452,7 @@ export namespace Components { } /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface AtomicCommerceNumericFacet { /** @@ -477,7 +474,7 @@ export namespace Components { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface AtomicCommercePager { /** @@ -521,7 +518,7 @@ export namespace Components { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface AtomicCommerceProductsPerPage { /** @@ -536,13 +533,13 @@ export namespace Components { } /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface AtomicCommerceQueryError { } /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface AtomicCommerceQuerySummary { } @@ -590,7 +587,7 @@ export namespace Components { } /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface AtomicCommerceRecommendationList { /** @@ -638,7 +635,7 @@ export namespace Components { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface AtomicCommerceRefineModal { /** @@ -651,13 +648,13 @@ export namespace Components { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface AtomicCommerceRefineToggle { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBox { /** @@ -693,7 +690,7 @@ export namespace Components { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxInstantProducts { /** @@ -716,7 +713,7 @@ export namespace Components { } /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxQuerySuggestions { /** @@ -734,7 +731,7 @@ export namespace Components { } /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxRecentQueries { /** @@ -752,7 +749,7 @@ export namespace Components { } /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface AtomicCommerceSortDropdown { } @@ -773,7 +770,7 @@ export namespace Components { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface AtomicCommerceTimeframeFacet { /** @@ -1673,7 +1670,7 @@ export namespace Components { "userActions": Array; } /** - * @component + * @component * @example */ interface AtomicInsightUserActionsTimeline { @@ -2006,7 +2003,7 @@ export namespace Components { } /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface AtomicProduct { /** @@ -2113,7 +2110,7 @@ export namespace Components { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface AtomicProductFieldCondition { /** @@ -2137,7 +2134,7 @@ export namespace Components { } /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface AtomicProductImage { /** @@ -2210,7 +2207,7 @@ export namespace Components { } /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface AtomicProductRating { /** @@ -3444,7 +3441,7 @@ export namespace Components { /** * The severity level of the messages to log in the console. */ - "logLevel"?: LogLevel1; + "logLevel"?: LogLevel; /** * The search interface [query pipeline](https://docs.coveo.com/en/180/). If the search interface is initialized using [`initializeWithSearchEngine`](https://docs.coveo.com/en/atomic/latest/reference/components/atomic-search-interface/#initializewithsearchengine), the query pipeline should instead be configured in the target engine. */ @@ -3997,7 +3994,7 @@ declare global { }; /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface HTMLAtomicCommerceBreadboxElement extends Components.AtomicCommerceBreadbox, HTMLStencilElement { } @@ -4008,7 +4005,7 @@ declare global { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface HTMLAtomicCommerceCategoryFacetElement extends Components.AtomicCommerceCategoryFacet, HTMLStencilElement { } @@ -4027,7 +4024,7 @@ declare global { }; /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetElement extends Components.AtomicCommerceFacet, HTMLStencilElement { } @@ -4040,7 +4037,7 @@ declare global { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetNumberInputElement extends Components.AtomicCommerceFacetNumberInput, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceFacetNumberInputElement, ev: AtomicCommerceFacetNumberInputCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4059,7 +4056,7 @@ declare global { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetsElement extends Components.AtomicCommerceFacets, HTMLStencilElement { } @@ -4090,7 +4087,7 @@ declare global { }; /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface HTMLAtomicCommerceLoadMoreProductsElement extends Components.AtomicCommerceLoadMoreProducts, HTMLStencilElement { } @@ -4109,7 +4106,7 @@ declare global { }; /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface HTMLAtomicCommerceNumericFacetElement extends Components.AtomicCommerceNumericFacet, HTMLStencilElement { } @@ -4122,7 +4119,7 @@ declare global { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface HTMLAtomicCommercePagerElement extends Components.AtomicCommercePager, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommercePagerElement, ev: AtomicCommercePagerCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4152,7 +4149,7 @@ declare global { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface HTMLAtomicCommerceProductsPerPageElement extends Components.AtomicCommerceProductsPerPage, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceProductsPerPageElement, ev: AtomicCommerceProductsPerPageCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4170,7 +4167,7 @@ declare global { }; /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface HTMLAtomicCommerceQueryErrorElement extends Components.AtomicCommerceQueryError, HTMLStencilElement { } @@ -4180,7 +4177,7 @@ declare global { }; /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface HTMLAtomicCommerceQuerySummaryElement extends Components.AtomicCommerceQuerySummary, HTMLStencilElement { } @@ -4199,7 +4196,7 @@ declare global { }; /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRecommendationListElement extends Components.AtomicCommerceRecommendationList, HTMLStencilElement { } @@ -4210,7 +4207,7 @@ declare global { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRefineModalElement extends Components.AtomicCommerceRefineModal, HTMLStencilElement { } @@ -4221,7 +4218,7 @@ declare global { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRefineToggleElement extends Components.AtomicCommerceRefineToggle, HTMLStencilElement { } @@ -4234,7 +4231,7 @@ declare global { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxElement extends Components.AtomicCommerceSearchBox, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceSearchBoxElement, ev: AtomicCommerceSearchBoxCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4254,7 +4251,7 @@ declare global { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxInstantProductsElement extends Components.AtomicCommerceSearchBoxInstantProducts, HTMLStencilElement { } @@ -4264,7 +4261,7 @@ declare global { }; /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxQuerySuggestionsElement extends Components.AtomicCommerceSearchBoxQuerySuggestions, HTMLStencilElement { } @@ -4274,7 +4271,7 @@ declare global { }; /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxRecentQueriesElement extends Components.AtomicCommerceSearchBoxRecentQueries, HTMLStencilElement { } @@ -4284,7 +4281,7 @@ declare global { }; /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSortDropdownElement extends Components.AtomicCommerceSortDropdown, HTMLStencilElement { } @@ -4304,7 +4301,7 @@ declare global { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface HTMLAtomicCommerceTimeframeFacetElement extends Components.AtomicCommerceTimeframeFacet, HTMLStencilElement { } @@ -4757,7 +4754,7 @@ declare global { new (): HTMLAtomicInsightUserActionsSessionElement; }; /** - * @component + * @component * @example */ interface HTMLAtomicInsightUserActionsTimelineElement extends Components.AtomicInsightUserActionsTimeline, HTMLStencilElement { @@ -4977,7 +4974,7 @@ declare global { }; /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface HTMLAtomicProductElement extends Components.AtomicProduct, HTMLStencilElement { } @@ -5027,7 +5024,7 @@ declare global { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface HTMLAtomicProductFieldConditionElement extends Components.AtomicProductFieldCondition, HTMLStencilElement { } @@ -5037,7 +5034,7 @@ declare global { }; /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface HTMLAtomicProductImageElement extends Components.AtomicProductImage, HTMLStencilElement { } @@ -5084,7 +5081,7 @@ declare global { }; /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface HTMLAtomicProductRatingElement extends Components.AtomicProductRating, HTMLStencilElement { } @@ -6513,7 +6510,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface AtomicCommerceBreadbox { /** @@ -6525,7 +6522,7 @@ declare namespace LocalJSX { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface AtomicCommerceCategoryFacet { /** @@ -6552,7 +6549,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface AtomicCommerceFacet { /** @@ -6574,7 +6571,7 @@ declare namespace LocalJSX { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface AtomicCommerceFacetNumberInput { "bindings": Bindings; @@ -6586,7 +6583,7 @@ declare namespace LocalJSX { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface AtomicCommerceFacets { /** @@ -6659,7 +6656,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface AtomicCommerceLoadMoreProducts { } @@ -6670,7 +6667,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface AtomicCommerceNumericFacet { /** @@ -6692,7 +6689,7 @@ declare namespace LocalJSX { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface AtomicCommercePager { /** @@ -6732,7 +6729,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface AtomicCommerceProductsPerPage { /** @@ -6748,13 +6745,13 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface AtomicCommerceQueryError { } /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface AtomicCommerceQuerySummary { } @@ -6798,7 +6795,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface AtomicCommerceRecommendationList { /** @@ -6833,7 +6830,7 @@ declare namespace LocalJSX { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface AtomicCommerceRefineModal { /** @@ -6846,13 +6843,13 @@ declare namespace LocalJSX { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface AtomicCommerceRefineToggle { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBox { /** @@ -6892,7 +6889,7 @@ declare namespace LocalJSX { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxInstantProducts { /** @@ -6910,7 +6907,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxQuerySuggestions { /** @@ -6928,7 +6925,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxRecentQueries { /** @@ -6946,7 +6943,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface AtomicCommerceSortDropdown { } @@ -6967,7 +6964,7 @@ declare namespace LocalJSX { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface AtomicCommerceTimeframeFacet { /** @@ -7838,7 +7835,7 @@ declare namespace LocalJSX { "userActions": Array; } /** - * @component + * @component * @example */ interface AtomicInsightUserActionsTimeline { @@ -8159,7 +8156,7 @@ declare namespace LocalJSX { } /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface AtomicProduct { /** @@ -8267,7 +8264,7 @@ declare namespace LocalJSX { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface AtomicProductFieldCondition { /** @@ -8291,7 +8288,7 @@ declare namespace LocalJSX { } /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface AtomicProductImage { /** @@ -8351,7 +8348,7 @@ declare namespace LocalJSX { } /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface AtomicProductRating { /** @@ -9529,7 +9526,7 @@ declare namespace LocalJSX { /** * The severity level of the messages to log in the console. */ - "logLevel"?: LogLevel1; + "logLevel"?: LogLevel; /** * The search interface [query pipeline](https://docs.coveo.com/en/180/). If the search interface is initialized using [`initializeWithSearchEngine`](https://docs.coveo.com/en/atomic/latest/reference/components/atomic-search-interface/#initializewithsearchengine), the query pipeline should instead be configured in the target engine. */ @@ -10162,13 +10159,13 @@ declare module "@stencil/core" { "atomic-color-facet": LocalJSX.AtomicColorFacet & JSXBase.HTMLAttributes; /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ "atomic-commerce-breadbox": LocalJSX.AtomicCommerceBreadbox & JSXBase.HTMLAttributes; /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ "atomic-commerce-category-facet": LocalJSX.AtomicCommerceCategoryFacet & JSXBase.HTMLAttributes; /** @@ -10177,18 +10174,18 @@ declare module "@stencil/core" { "atomic-commerce-did-you-mean": LocalJSX.AtomicCommerceDidYouMean & JSXBase.HTMLAttributes; /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ "atomic-commerce-facet": LocalJSX.AtomicCommerceFacet & JSXBase.HTMLAttributes; /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ "atomic-commerce-facet-number-input": LocalJSX.AtomicCommerceFacetNumberInput & JSXBase.HTMLAttributes; /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ "atomic-commerce-facets": LocalJSX.AtomicCommerceFacets & JSXBase.HTMLAttributes; /** @@ -10204,7 +10201,7 @@ declare module "@stencil/core" { "atomic-commerce-layout": LocalJSX.AtomicCommerceLayout & JSXBase.HTMLAttributes; /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ "atomic-commerce-load-more-products": LocalJSX.AtomicCommerceLoadMoreProducts & JSXBase.HTMLAttributes; /** @@ -10213,12 +10210,12 @@ declare module "@stencil/core" { "atomic-commerce-no-products": LocalJSX.AtomicCommerceNoProducts & JSXBase.HTMLAttributes; /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ "atomic-commerce-numeric-facet": LocalJSX.AtomicCommerceNumericFacet & JSXBase.HTMLAttributes; /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ "atomic-commerce-pager": LocalJSX.AtomicCommercePager & JSXBase.HTMLAttributes; /** @@ -10227,17 +10224,17 @@ declare module "@stencil/core" { "atomic-commerce-product-list": LocalJSX.AtomicCommerceProductList & JSXBase.HTMLAttributes; /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ "atomic-commerce-products-per-page": LocalJSX.AtomicCommerceProductsPerPage & JSXBase.HTMLAttributes; /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ "atomic-commerce-query-error": LocalJSX.AtomicCommerceQueryError & JSXBase.HTMLAttributes; /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ "atomic-commerce-query-summary": LocalJSX.AtomicCommerceQuerySummary & JSXBase.HTMLAttributes; /** @@ -10246,46 +10243,46 @@ declare module "@stencil/core" { "atomic-commerce-recommendation-interface": LocalJSX.AtomicCommerceRecommendationInterface & JSXBase.HTMLAttributes; /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ "atomic-commerce-recommendation-list": LocalJSX.AtomicCommerceRecommendationList & JSXBase.HTMLAttributes; /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ "atomic-commerce-refine-modal": LocalJSX.AtomicCommerceRefineModal & JSXBase.HTMLAttributes; /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ "atomic-commerce-refine-toggle": LocalJSX.AtomicCommerceRefineToggle & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ "atomic-commerce-search-box": LocalJSX.AtomicCommerceSearchBox & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ "atomic-commerce-search-box-instant-products": LocalJSX.AtomicCommerceSearchBoxInstantProducts & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ "atomic-commerce-search-box-query-suggestions": LocalJSX.AtomicCommerceSearchBoxQuerySuggestions & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ "atomic-commerce-search-box-recent-queries": LocalJSX.AtomicCommerceSearchBoxRecentQueries & JSXBase.HTMLAttributes; /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ "atomic-commerce-sort-dropdown": LocalJSX.AtomicCommerceSortDropdown & JSXBase.HTMLAttributes; /** @@ -10295,7 +10292,7 @@ declare module "@stencil/core" { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ "atomic-commerce-timeframe-facet": LocalJSX.AtomicCommerceTimeframeFacet & JSXBase.HTMLAttributes; /** @@ -10410,7 +10407,7 @@ declare module "@stencil/core" { */ "atomic-insight-user-actions-session": LocalJSX.AtomicInsightUserActionsSession & JSXBase.HTMLAttributes; /** - * @component + * @component * @example */ "atomic-insight-user-actions-timeline": LocalJSX.AtomicInsightUserActionsTimeline & JSXBase.HTMLAttributes; @@ -10470,7 +10467,7 @@ declare module "@stencil/core" { "atomic-popover": LocalJSX.AtomicPopover & JSXBase.HTMLAttributes; /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ "atomic-product": LocalJSX.AtomicProduct & JSXBase.HTMLAttributes; /** @@ -10489,12 +10486,12 @@ declare module "@stencil/core" { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ "atomic-product-field-condition": LocalJSX.AtomicProductFieldCondition & JSXBase.HTMLAttributes; /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ "atomic-product-image": LocalJSX.AtomicProductImage & JSXBase.HTMLAttributes; /** @@ -10516,7 +10513,7 @@ declare module "@stencil/core" { "atomic-product-price": LocalJSX.AtomicProductPrice & JSXBase.HTMLAttributes; /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ "atomic-product-rating": LocalJSX.AtomicProductRating & JSXBase.HTMLAttributes; /** diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx index 2259ef2f228..28af0631f6f 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx @@ -1,8 +1,17 @@ +import Cart from '@/app/components/cart'; +import ContextDropdown from '@/app/components/context-dropdown'; +import { + RecommendationProvider, + StandaloneProvider, +} from '@/app/components/providers/providers'; +import PopularBought from '@/app/components/recommendations/popular-bought'; import externalCartService, { ExternalCartItem, } from '@/external-services/external-cart-service'; import externalContextService from '@/external-services/external-context-service'; import { + recommendationEngineDefinition, + RecommendationStaticState, standaloneEngineDefinition, StandaloneStaticState, } from '@/lib/commerce-engine'; @@ -14,8 +23,6 @@ import { import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; -import Cart from '../components/cart'; -import {StandaloneProvider} from '../components/providers/providers'; export const loader = async ({request}: LoaderFunctionArgs) => { const navigatorContext = await getNavigatorContext(request); @@ -47,31 +54,53 @@ export const loader = async ({request}: LoaderFunctionArgs) => { }, }); - return {staticState, items, totalPrice, language, currency}; + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + ['popularBoughtRecs', 'popularViewedRecs'] + ); + + return {staticState, items, totalPrice, language, currency, recsStaticState}; }; export default function CartRoute() { - const {staticState, navigatorContext, items, totalPrice, language, currency} = - useLoaderData<{ - staticState: StandaloneStaticState; - navigatorContext: NavigatorContext; - items: ExternalCartItem[]; - totalPrice: number; - language: string; - currency: string; - }>(); + const { + staticState, + navigatorContext, + items, + totalPrice, + language, + currency, + recsStaticState, + } = useLoaderData<{ + staticState: StandaloneStaticState; + navigatorContext: NavigatorContext; + items: ExternalCartItem[]; + totalPrice: number; + language: string; + currency: string; + recsStaticState: RecommendationStaticState; + }>(); return (

Cart

- +
+ + {/* */} + + + + +
); } diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index 2a0b65b14aa..d74897d9e8c 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -1,8 +1,20 @@ +import BreadcrumbManager from '@/app/components/breadcrumb-manager'; +import ContextDropdown from '@/app/components/context-dropdown'; +import FacetGenerator from '@/app/components/facets/facet-generator'; +import Pagination from '@/app/components/pagination'; +import ProductList from '@/app/components/product-list'; +import PopularBought from '@/app/components/recommendations/popular-bought'; +import PopularViewed from '@/app/components/recommendations/popular-viewed'; +import Sort from '@/app/components/sort'; +import StandaloneSearchBox from '@/app/components/standalone-search-box'; +import Summary from '@/app/components/summary'; import externalCartService from '@/external-services/external-cart-service'; import externalContextService from '@/external-services/external-context-service'; import { listingEngineDefinition, ListingStaticState, + recommendationEngineDefinition, + RecommendationStaticState, } from '@/lib/commerce-engine'; import {getNavigatorContext} from '@/lib/navigator-context'; import { @@ -13,8 +25,11 @@ import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData, useParams} from '@remix-run/react'; import invariant from 'tiny-invariant'; -import ProductList from '../components/product-list'; -import {ListingProvider} from '../components/providers/providers'; +import { + ListingProvider, + RecommendationProvider, +} from '../components/providers/providers'; +//import StandaloneSearchBox from '../components/standalone-search-box'; import {coveo_visitorId} from '../cookies.server'; export const loader = async ({params, request}: LoaderFunctionArgs) => { @@ -24,6 +39,10 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { listingEngineDefinition.setNavigatorContextProvider(() => navigatorContext); + recommendationEngineDefinition.setNavigatorContextProvider( + () => navigatorContext + ); + const {country, currency, language} = await externalContextService.getContextInformation(); @@ -45,9 +64,14 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { }, }); + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + ['popularBoughtRecs', 'popularViewedRecs'] + ); + return { staticState, navigatorContext, + recsStaticState, headers: { 'Set-Cookie': await coveo_visitorId.serialize(navigatorContext.clientId), }, @@ -56,9 +80,10 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { export default function ListingRoute() { const params = useParams(); - const {staticState, navigatorContext} = useLoaderData<{ + const {staticState, navigatorContext, recsStaticState} = useLoaderData<{ staticState: ListingStaticState; navigatorContext: NavigatorContext; + recsStaticState: RecommendationStaticState; }>(); const getTitle = () => { @@ -76,7 +101,37 @@ export default function ListingRoute() { navigatorContext={navigatorContext} >

{getTitle()}

- + +
+
+ +
+ +
+ + + + + + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} + + {/* */} +
+ +
+ + + + +
+
); } diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/products.$productId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/products.$productId.tsx index b0c20fcd631..e630ebb5cdc 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/products.$productId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/products.$productId.tsx @@ -1,7 +1,11 @@ +import ContextDropdown from '@/app/components/context-dropdown'; +import ProductView from '@/app/components/product-view'; +import {StandaloneProvider} from '@/app/components/providers/providers'; +import StandaloneSearchBox from '@/app/components/standalone-search-box'; import externalCartService, { ExternalCartItem, } from '@/external-services/external-cart-service'; -import externalCatalogService, { +import externalCatalogAPI, { ExternalCatalogItem, } from '@/external-services/external-catalog-service'; import externalContextService from '@/external-services/external-context-service'; @@ -18,15 +22,13 @@ import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; import invariant from 'tiny-invariant'; -import ProductView from '../components/product-view'; -import {StandaloneProvider} from '../components/providers/providers'; export const loader = async ({params, request}: LoaderFunctionArgs) => { const productId = params.productId; invariant(productId, 'Missing productId parameter'); - const catalogItem = await externalCatalogService.getItem(request.url); + const catalogItem = await externalCatalogAPI.getItem(request.url); const {country, currency, language} = await externalContextService.getContextInformation(); @@ -90,6 +92,8 @@ export default function ProductRoute() { staticState={staticState} navigatorContext={navigatorContext} > + + { const navigatorContext = await getNavigatorContext(request); @@ -52,7 +59,25 @@ export default function SearchRoute() { navigatorContext={navigatorContext} >

Search

- + +
+
+ +
+
+ + + + + + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} + {/* */} + +
+
); } From 9bb627c5b29e102d9c67cc50d1d42471bbf25661 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Fri, 6 Dec 2024 17:11:38 -0500 Subject: [PATCH 04/34] Update standalone-search-box.tsx --- .../components/standalone-search-box.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx index 029767c068e..e1c5c889326 100644 --- a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx +++ b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx @@ -20,12 +20,11 @@ export default function StandaloneSearchBox() { const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); const navigate = useNavigate(); - - // Update the navigation in useEffect + useEffect(() => { if (state.redirectTo === '/search') { const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; - navigate(url, {preventScrollReset: true}); // equivalent to Next.js's scroll: false + navigate(url, {preventScrollReset: true}); methods?.afterRedirection(); } }, [state.redirectTo, state.value, navigate, methods]); From 8a29b93ab95840cf187321cca8d437a91c3ba6f2 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Fri, 6 Dec 2024 17:15:16 -0500 Subject: [PATCH 05/34] add searchbar to cart https://coveord.atlassian.net/browse/KIT-3774 --- .../samples/headless-commerce-ssr-remix/app/routes/cart.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx index 28af0631f6f..13e00e98f01 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx @@ -5,6 +5,7 @@ import { StandaloneProvider, } from '@/app/components/providers/providers'; import PopularBought from '@/app/components/recommendations/popular-bought'; +import StandaloneSearchBox from '@/app/components/standalone-search-box'; import externalCartService, { ExternalCartItem, } from '@/external-services/external-cart-service'; @@ -87,7 +88,7 @@ export default function CartRoute() {

Cart

- {/* */} + Date: Fri, 6 Dec 2024 22:25:07 +0000 Subject: [PATCH 06/34] Add generated files https://coveord.atlassian.net/browse/KIT-3774 --- packages/atomic/src/components.d.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index bc4c71cc4c6..a338ab114a1 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -5,7 +5,8 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { AutomaticFacet, CategoryFacetSortCriterion, DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, FoldedResult, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, InteractiveResult, LogLevel, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition, SearchEngine, SearchStatus } from "@coveo/headless"; +import { AutomaticFacet, CategoryFacetSortCriterion, DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, FoldedResult, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, InteractiveResult, LogLevel as LogLevel1, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition, SearchEngine, SearchStatus } from "@coveo/headless"; +import { CategoryFacet, CommerceEngine, DateFacet, InteractiveProduct, LogLevel, NumericFacet, Product, ProductListing, ProductListingSummaryState, ProductTemplate, ProductTemplateCondition, RegularFacet, Search, SearchSummaryState, Summary } from "@coveo/headless/commerce"; import { CommerceBindings as Bindings } from "./components/commerce/atomic-commerce-interface/atomic-commerce-interface"; import { Range } from "./components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input"; import { i18n } from "i18next"; @@ -20,7 +21,7 @@ import { unknown as AnyBindings, ItemDisplayBasicLayout as ItemDisplayBasicLayou import { AnyBindings as AnyBindings1 } from "./components/common/interface/bindings"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; import { NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types"; -import { unknown as InsightEngine, unknown as InsightFacetSortCriterion, unknown as InsightFoldedResult, unknown as InsightInteractiveResult, unknown as InsightLogLevel, unknown as InsightRangeFacetRangeAlgorithm, unknown as InsightRangeFacetSortCriterion, unknown as InsightResult, unknown as InsightResultTemplate, unknown as InsightResultTemplateCondition, unknown as IUserAction } from "./components/insight"; +import { InsightEngine, FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, LogLevel as InsightLogLevel, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "./components/insight"; import { InsightInitializationOptions } from "./components/insight/atomic-insight-interface/atomic-insight-interface"; import { AtomicInsightStore } from "./components/insight/atomic-insight-interface/store"; import { InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; @@ -29,7 +30,8 @@ import { Section } from "./components/common/atomic-layout-section/sections"; import { AtomicCommonStore, AtomicCommonStoreData } from "./components/common/interface/store"; import { SelectChildProductEventArgs } from "./components/commerce/product-template-components/atomic-product-children/atomic-product-children"; import { TruncateAfter } from "./components/common/expandable-text/expandable-text"; -import { unknown as RecsInteractiveResult, unknown as RecsLogLevel, unknown as RecsResult, unknown as RecsResultTemplate, unknown as RecsResultTemplateCondition } from "./components/recommendations"; +import { RecommendationEngine } from "@coveo/headless/recommendation"; +import { InteractiveResult as RecsInteractiveResult, LogLevel as RecsLogLevel, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "./components/recommendations"; import { RecsInitializationOptions } from "./components/recommendations/atomic-recs-interface/atomic-recs-interface"; import { AtomicRecsStore } from "./components/recommendations/atomic-recs-interface/store"; import { Bindings as Bindings1 } from "./components/search/atomic-search-interface/atomic-search-interface"; @@ -51,7 +53,7 @@ export { unknown as AnyBindings, ItemDisplayBasicLayout as ItemDisplayBasicLayou export { AnyBindings as AnyBindings1 } from "./components/common/interface/bindings"; export { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; export { NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types"; -export { unknown as InsightEngine, unknown as InsightFacetSortCriterion, unknown as InsightFoldedResult, unknown as InsightInteractiveResult, unknown as InsightLogLevel, unknown as InsightRangeFacetRangeAlgorithm, unknown as InsightRangeFacetSortCriterion, unknown as InsightResult, unknown as InsightResultTemplate, unknown as InsightResultTemplateCondition, unknown as IUserAction } from "./components/insight"; +export { InsightEngine, FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, LogLevel as InsightLogLevel, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "./components/insight"; export { InsightInitializationOptions } from "./components/insight/atomic-insight-interface/atomic-insight-interface"; export { AtomicInsightStore } from "./components/insight/atomic-insight-interface/store"; export { InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; @@ -60,7 +62,8 @@ export { Section } from "./components/common/atomic-layout-section/sections"; export { AtomicCommonStore, AtomicCommonStoreData } from "./components/common/interface/store"; export { SelectChildProductEventArgs } from "./components/commerce/product-template-components/atomic-product-children/atomic-product-children"; export { TruncateAfter } from "./components/common/expandable-text/expandable-text"; -export { unknown as RecsInteractiveResult, unknown as RecsLogLevel, unknown as RecsResult, unknown as RecsResultTemplate, unknown as RecsResultTemplateCondition } from "./components/recommendations"; +export { RecommendationEngine } from "@coveo/headless/recommendation"; +export { InteractiveResult as RecsInteractiveResult, LogLevel as RecsLogLevel, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "./components/recommendations"; export { RecsInitializationOptions } from "./components/recommendations/atomic-recs-interface/atomic-recs-interface"; export { AtomicRecsStore } from "./components/recommendations/atomic-recs-interface/store"; export { Bindings as Bindings1 } from "./components/search/atomic-search-interface/atomic-search-interface"; @@ -3441,7 +3444,7 @@ export namespace Components { /** * The severity level of the messages to log in the console. */ - "logLevel"?: LogLevel; + "logLevel"?: LogLevel1; /** * The search interface [query pipeline](https://docs.coveo.com/en/180/). If the search interface is initialized using [`initializeWithSearchEngine`](https://docs.coveo.com/en/atomic/latest/reference/components/atomic-search-interface/#initializewithsearchengine), the query pipeline should instead be configured in the target engine. */ @@ -9526,7 +9529,7 @@ declare namespace LocalJSX { /** * The severity level of the messages to log in the console. */ - "logLevel"?: LogLevel; + "logLevel"?: LogLevel1; /** * The search interface [query pipeline](https://docs.coveo.com/en/180/). If the search interface is initialized using [`initializeWithSearchEngine`](https://docs.coveo.com/en/atomic/latest/reference/components/atomic-search-interface/#initializewithsearchengine), the query pipeline should instead be configured in the target engine. */ From 32781996281f58d75db90f9e5f0c65ed9a068b9c Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 11:14:30 -0500 Subject: [PATCH 07/34] disable recs https://coveord.atlassian.net/browse/KIT-3774 --- .../app/routes/cart.tsx | 44 +++++++------------ .../app/routes/listings.$listingId.tsx | 21 +++------ 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx index 13e00e98f01..2c53eba9d81 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx @@ -1,18 +1,12 @@ import Cart from '@/app/components/cart'; import ContextDropdown from '@/app/components/context-dropdown'; -import { - RecommendationProvider, - StandaloneProvider, -} from '@/app/components/providers/providers'; -import PopularBought from '@/app/components/recommendations/popular-bought'; +import {StandaloneProvider} from '@/app/components/providers/providers'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import externalCartService, { ExternalCartItem, } from '@/external-services/external-cart-service'; import externalContextService from '@/external-services/external-context-service'; import { - recommendationEngineDefinition, - RecommendationStaticState, standaloneEngineDefinition, StandaloneStaticState, } from '@/lib/commerce-engine'; @@ -55,31 +49,23 @@ export const loader = async ({request}: LoaderFunctionArgs) => { }, }); - const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + /* const recsStaticState = await recommendationEngineDefinition.fetchStaticState( ['popularBoughtRecs', 'popularViewedRecs'] ); - - return {staticState, items, totalPrice, language, currency, recsStaticState}; + */ + return {staticState, items, totalPrice, language, currency}; }; export default function CartRoute() { - const { - staticState, - navigatorContext, - items, - totalPrice, - language, - currency, - recsStaticState, - } = useLoaderData<{ - staticState: StandaloneStaticState; - navigatorContext: NavigatorContext; - items: ExternalCartItem[]; - totalPrice: number; - language: string; - currency: string; - recsStaticState: RecommendationStaticState; - }>(); + const {staticState, navigatorContext, items, totalPrice, language, currency} = + useLoaderData<{ + staticState: StandaloneStaticState; + navigatorContext: NavigatorContext; + items: ExternalCartItem[]; + totalPrice: number; + language: string; + currency: string; + }>(); return ( - - + */}
); diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index d74897d9e8c..adb4ef64265 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -3,8 +3,6 @@ import ContextDropdown from '@/app/components/context-dropdown'; import FacetGenerator from '@/app/components/facets/facet-generator'; import Pagination from '@/app/components/pagination'; import ProductList from '@/app/components/product-list'; -import PopularBought from '@/app/components/recommendations/popular-bought'; -import PopularViewed from '@/app/components/recommendations/popular-viewed'; import Sort from '@/app/components/sort'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import Summary from '@/app/components/summary'; @@ -14,7 +12,6 @@ import { listingEngineDefinition, ListingStaticState, recommendationEngineDefinition, - RecommendationStaticState, } from '@/lib/commerce-engine'; import {getNavigatorContext} from '@/lib/navigator-context'; import { @@ -25,10 +22,7 @@ import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData, useParams} from '@remix-run/react'; import invariant from 'tiny-invariant'; -import { - ListingProvider, - RecommendationProvider, -} from '../components/providers/providers'; +import {ListingProvider} from '../components/providers/providers'; //import StandaloneSearchBox from '../components/standalone-search-box'; import {coveo_visitorId} from '../cookies.server'; @@ -64,14 +58,14 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { }, }); - const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + /* const recsStaticState = await recommendationEngineDefinition.fetchStaticState( ['popularBoughtRecs', 'popularViewedRecs'] - ); + ); */ return { staticState, navigatorContext, - recsStaticState, + headers: { 'Set-Cookie': await coveo_visitorId.serialize(navigatorContext.clientId), }, @@ -80,10 +74,9 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { export default function ListingRoute() { const params = useParams(); - const {staticState, navigatorContext, recsStaticState} = useLoaderData<{ + const {staticState, navigatorContext} = useLoaderData<{ staticState: ListingStaticState; navigatorContext: NavigatorContext; - recsStaticState: RecommendationStaticState; }>(); const getTitle = () => { @@ -122,7 +115,7 @@ export default function ListingRoute() { /> */} -
+ {/*
-
+
*/} ); From c49203b021dd74373fd48d69c1b8a3d23a86cf48 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 11:16:27 -0500 Subject: [PATCH 08/34] remove use client https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/breadcrumb-manager.tsx | 2 -- .../app/components/context-dropdown.tsx | 2 -- .../app/components/facets/category-facet.tsx | 2 -- .../app/components/facets/date-facet.tsx | 2 -- .../app/components/facets/facet-generator.tsx | 2 -- .../app/components/facets/numeric-facet.tsx | 2 -- .../app/components/facets/regular-facet.tsx | 2 -- .../app/components/hydration-metadata.tsx | 2 -- .../headless-commerce-ssr-remix/app/components/pagination.tsx | 2 -- .../app/components/recommendations/popular-bought.tsx | 2 -- .../app/components/recommendations/popular-viewed.tsx | 2 -- .../headless-commerce-ssr-remix/app/components/search-box.tsx | 2 -- .../headless-commerce-ssr-remix/app/components/show-more.tsx | 2 -- .../samples/headless-commerce-ssr-remix/app/components/sort.tsx | 2 -- .../app/components/standalone-search-box.tsx | 2 -- .../headless-commerce-ssr-remix/app/components/summary.tsx | 2 -- .../app/components/triggers/notify-trigger.tsx | 2 -- .../app/components/triggers/query-trigger.tsx | 2 -- .../app/components/triggers/redirection-trigger.tsx | 2 -- 19 files changed, 38 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx index f2f9f46a6c4..5cdc64bd831 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/breadcrumb-manager.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useBreadcrumbManager} from '@/lib/commerce-engine'; import { NumericFacetValue, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx index 121ee418026..9aec273e229 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useContext, useEngine} from '@/lib/commerce-engine'; import { CommerceEngine, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx index 418cbf91593..78a9f63cd4a 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx @@ -1,5 +1,3 @@ -'use client'; - import { CategoryFacetSearchResult, CategoryFacetState, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx index 1541bbda894..cf443356a40 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx @@ -1,5 +1,3 @@ -'use client'; - import { DateFacetState, DateFacet as HeadlessDateFacet, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx index 8f9ad286870..1beb57f64ab 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useFacetGenerator} from '@/lib/commerce-engine'; import CategoryFacet from './category-facet'; import DateFacet from './date-facet'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx index 743edb8157d..9184a2ab3e2 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx @@ -1,5 +1,3 @@ -'use client'; - import { NumericFacet as HeadlessNumericFacet, NumericFacetState, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx index f7f19df39a5..caff19345b2 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx @@ -1,5 +1,3 @@ -'use client'; - import { BaseFacetSearchResult, RegularFacet as HeadlessRegularFacet, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx index 58efd70d0ae..749ce8dfa90 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx @@ -1,5 +1,3 @@ -'use client'; - import { ListingHydratedState, ListingStaticState, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx index 9732a990aad..14bf0413fe4 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/pagination.tsx @@ -1,5 +1,3 @@ -'use client'; - import {usePagination} from '@/lib/commerce-engine'; export default function Pagination() { diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx index 0abbb36f966..e696c79dff1 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx @@ -1,5 +1,3 @@ -'use client'; - import {usePopularBoughtRecs} from '@/lib/commerce-engine'; import {Product} from '@coveo/headless-react/ssr-commerce'; import {useNavigate} from '@remix-run/react'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx index 4828b927957..ed46d0c70e2 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx @@ -1,5 +1,3 @@ -'use client'; - import {usePopularViewedRecs} from '@/lib/commerce-engine'; import {Product} from '@coveo/headless-react/ssr-commerce'; import {useNavigate} from '@remix-run/react'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx index 87dfd0fdbab..de25ade3e7a 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx @@ -1,5 +1,3 @@ -'use client'; - import { useInstantProducts, useRecentQueriesList, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx index d7fbf9cdebc..8cdc9e93035 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/show-more.tsx @@ -1,5 +1,3 @@ -'use client'; - import {usePagination, useSummary} from '@/lib/commerce-engine'; export default function ShowMore() { diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx index 2690c9c7891..f124f80044a 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx @@ -1,5 +1,3 @@ -'use client'; - // import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; import {useSort} from '@/lib/commerce-engine'; import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx index 84fd78f53dd..0f6ac205c4c 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx @@ -1,5 +1,3 @@ -'use client'; - import { useInstantProducts, useRecentQueriesList, diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx index 7818c505fdb..ced98f88793 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useSummary} from '@/lib/commerce-engine'; export default function Summary() { diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx index dde3b5a609a..24b8a2e66d2 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/notify-trigger.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useNotifyTrigger} from '@/lib/commerce-engine'; import {useCallback, useEffect} from 'react'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx index f3027648a71..fde509ef3b4 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useQueryTrigger} from '@/lib/commerce-engine'; // The query trigger query example in the searchuisamples org is 'query me'. diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx index 84f060eebbe..69f9b45e3a1 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/redirection-trigger.tsx @@ -1,5 +1,3 @@ -'use client'; - import {useRedirectionTrigger} from '@/lib/commerce-engine'; import {useCallback, useEffect} from 'react'; From e78005076bc433d802bd6a7d519d9e9ad7064aed Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 11:32:22 -0500 Subject: [PATCH 09/34] use context api https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/context-dropdown.tsx | 92 +++++++++++-------- .../app/routes/context.update.tsx | 15 +++ .../external-context-service.ts | 29 ++++++ 3 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 packages/samples/headless-commerce-ssr-remix/app/routes/context.update.tsx diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx index 9aec273e229..b81375cf06b 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/context-dropdown.tsx @@ -1,3 +1,4 @@ +import externalContextService from '@/external-services/external-context-service'; import {useContext, useEngine} from '@/lib/commerce-engine'; import { CommerceEngine, @@ -5,16 +6,14 @@ import { loadProductListingActions, loadSearchActions, } from '@coveo/headless-react/ssr-commerce'; +import {LoaderFunctionArgs} from '@remix-run/node'; +import {Form, useFetcher, useLoaderData} from '@remix-run/react'; +import {useState} from 'react'; -// A hardcoded list of storefront associations for switching app context by language, country, and currency. -// Found in the admin console under "Storefront Associations," this list is static for demonstration purposes. -// In a real application, these values would likely come from sources like environment variables or an API. -const storefrontAssociations = [ - 'en-CA-CAD', - 'fr-CA-CAD', - 'en-GB-GBP', - 'en-US-USD', -]; +export const loader = async ({}: LoaderFunctionArgs) => { + const contextInfo = await externalContextService.getContextInformation(); + return contextInfo; +}; export default function ContextDropdown({ useCase, @@ -23,38 +22,55 @@ export default function ContextDropdown({ }) { const {state, methods} = useContext(); const engine = useEngine(); + const fetcher = useFetcher(); + const serverContext = useLoaderData(); + const [, setContext] = useState<{ + language: string; + country: string; + currency: string; + }>(serverContext); + + const handleChange = async (e: React.ChangeEvent) => { + const [language, country, currency] = e.target.value.split('-'); + const newContext = {language, country, currency}; + setContext(newContext); + methods?.setLanguage(language); + methods?.setCountry(country); + methods?.setCurrency(currency as ContextOptions['currency']); + + fetcher.submit( + {language, country, currency}, + {method: 'post', action: '/context/update'} + ); + + if (useCase === 'search') { + engine?.dispatch( + loadSearchActions(engine as CommerceEngine).executeSearch() + ); + } else if (useCase === 'listing') { + engine?.dispatch( + loadProductListingActions( + engine as CommerceEngine + ).fetchProductListing() + ); + } + }; return (
-

- Context dropdown : - -

+ Context dropdown: +
+ +
); } diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/context.update.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/context.update.tsx new file mode 100644 index 00000000000..2888b80b723 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/context.update.tsx @@ -0,0 +1,15 @@ +import externalContextService from '@/external-services/external-context-service'; +import {ActionFunctionArgs} from '@remix-run/node'; + +export const action = async ({request}: ActionFunctionArgs) => { + const formData = await request.formData(); + const newContext = { + language: formData.get('language')!.toString(), + country: formData.get('country')!.toString(), + currency: formData.get('currency')!.toString(), + }; + + await externalContextService.setContextInformation(newContext); + + return newContext; +}; diff --git a/packages/samples/headless-commerce-ssr-remix/external-services/external-context-service.ts b/packages/samples/headless-commerce-ssr-remix/external-services/external-context-service.ts index 5212a750e99..5012461e487 100644 --- a/packages/samples/headless-commerce-ssr-remix/external-services/external-context-service.ts +++ b/packages/samples/headless-commerce-ssr-remix/external-services/external-context-service.ts @@ -11,6 +11,29 @@ export type ExternalContextInformation = { language: string; }; +const contextOptions = [ + { + country: 'US', + currency: 'USD', + language: 'en', + }, + { + country: 'CA', + currency: 'CAD', + language: 'en', + }, + { + country: 'CA', + currency: 'CAD', + language: 'fr', + }, + { + country: 'GB', + currency: 'GBP', + language: 'en', + }, +]; + class ExternalContextService { private contextDB: {country: string; currency: string; language: string} = { country: 'US', @@ -37,6 +60,12 @@ class ExternalContextService { ): Promise { this.contextDB = localeInformation; } + + public getContextOptions(): string[] { + return contextOptions.map( + (option) => `${option.language}-${option.country}-${option.currency}` + ); + } } declare global { From 8fe54c41f4e967fd770e5c87e1306d6e3fe31b70 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 11:34:11 -0500 Subject: [PATCH 10/34] remove unused component https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/hydration-metadata.tsx | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx deleted file mode 100644 index 749ce8dfa90..00000000000 --- a/packages/samples/headless-commerce-ssr-remix/app/components/hydration-metadata.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { - ListingHydratedState, - ListingStaticState, - SearchHydratedState, - SearchStaticState, -} from '@/lib/commerce-engine'; -import {FunctionComponent} from 'react'; - -export interface HydrationMetadataProps { - staticState: SearchStaticState | ListingStaticState; - hydratedState?: SearchHydratedState | ListingHydratedState; -} - -export const HydrationMetadata: FunctionComponent = ({ - staticState, - hydratedState, -}) => { - return ( - <> -
- Hydrated:{' '} - -
- - Rendered page with{' '} - { - (hydratedState ?? staticState).controllers.productList.state.products - .length - }{' '} - products - -
- Items in cart:{' '} - {(hydratedState ?? staticState).controllers.cart.state.items.length} -
-
- Rendered on{' '} - - {new Date().toISOString()} - -
- - ); -}; From 0177ab178417b55fa71b87f30bfb725964eb6f34 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 11:40:14 -0500 Subject: [PATCH 11/34] Apply suggestions from code review Co-authored-by: Frederic Beaudoin --- .../app/components/facets/category-facet.tsx | 2 +- .../app/components/facets/numeric-facet.tsx | 2 +- .../app/components/facets/regular-facet.tsx | 2 +- .../app/components/recent-queries.tsx | 6 ++---- .../headless-commerce-ssr-remix/app/components/sort.tsx | 1 - .../headless-commerce-ssr-remix/app/components/summary.tsx | 4 ++-- .../app/components/triggers/query-trigger.tsx | 2 +- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx index 78a9f63cd4a..4129f5c4505 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx @@ -24,7 +24,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { }, [controller]); const focusFacetSearchInput = (): void => { - facetSearchInputRef.current!.focus(); + facetSearchInputRef.current?.focus(); }; const onChangeFacetSearchInput = ( diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx index 9184a2ab3e2..d49dec60562 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx @@ -47,7 +47,7 @@ export default function NumericFacet(props: INumericFacetProps) { }, [controller]); const focusManualRangeStartInput = (): void => { - manualRangeStartInputRef.current!.focus(); + manualRangeStartInputRef.current?.focus(); }; const invalidRange = diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx index caff19345b2..3f9197f1cd5 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx @@ -24,7 +24,7 @@ export default function RegularFacet(props: IRegularFacetProps) { }, [controller]); const focusFacetSearchInput = (): void => { - facetSearchInputRef.current!.focus(); + facetSearchInputRef.current?.focus(); }; const onChangeFacetSearchInput = ( diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx index df287127d70..311333dae23 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx @@ -7,15 +7,13 @@ export default function RecentQueries() { return (
    - Recent Queries : + Recent queries: {state.queries.map((query, index) => (
  • - {query} + >{query}
  • ))}
diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx index f124f80044a..f0f7b7581c9 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/sort.tsx @@ -1,4 +1,3 @@ -// import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; import {useSort} from '@/lib/commerce-engine'; import {SortBy, SortCriterion} from '@coveo/headless-react/ssr-commerce'; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx index ced98f88793..c40a858e6a7 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx @@ -14,13 +14,13 @@ export default function Summary() { }; const renderQuerySummary = () => { - if (!('query' in state)) { + if (!('query' in state || query.trim() === '')) { return null; } return ( - for {state.query} + {' '}for {state.query} ); }; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx index fde509ef3b4..101f363a826 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx @@ -7,7 +7,7 @@ export default function QueryTrigger() { if (state.wasQueryModified) { return (
- The query changed from {state.originalQuery} to {state.newQuery} + The query changed from {state.originalQuery} to {state.newQuery}
); } From e667e89a6289f9110e8e74c4a09963bde281cfb6 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 14:23:56 -0500 Subject: [PATCH 12/34] fix summary https://coveord.atlassian.net/browse/KIT-3774 --- .../headless-commerce-ssr-remix/app/components/summary.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx index c40a858e6a7..fe5fd1c2401 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/summary.tsx @@ -14,13 +14,14 @@ export default function Summary() { }; const renderQuerySummary = () => { - if (!('query' in state || query.trim() === '')) { + if (!('query' in state) || state.query.trim() === '') { return null; } return ( - {' '}for {state.query} + {' '} + for {state.query} ); }; From e3fcb22cec31f4590ec9526524462b460efae13f Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 14:24:04 -0500 Subject: [PATCH 13/34] add didyoumean https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/did-you-mean.tsx | 24 ++++--------------- .../app/routes/search.tsx | 2 ++ 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx index 6ed7c43419f..5c8eae17cb3 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/did-you-mean.tsx @@ -1,23 +1,7 @@ -import { - DidYouMeanState, - DidYouMean as DidYouMeanController, -} from '@coveo/headless/ssr-commerce'; -import {useEffect, useState} from 'react'; +import {useDidYouMean} from '@/lib/commerce-engine'; -interface DidYouMeanProps { - staticState: DidYouMeanState; - controller?: DidYouMeanController; -} -export default function DidYouMean({staticState, controller}: DidYouMeanProps) { - const [state, setState] = useState(staticState); - - useEffect( - () => - controller?.subscribe(() => { - setState({...controller.state}); - }), - [controller] - ); +export default function DidYouMean() { + const {state, methods} = useDidYouMean(); if (!state.hasQueryCorrection) { return null; @@ -40,7 +24,7 @@ export default function DidYouMean({staticState, controller}: DidYouMeanProps) {

Search for - controller?.applyCorrection()}> + methods?.applyCorrection()}> {state.queryCorrection.correctedQuery} instead? diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx index 3f0556eab87..7162167e6c2 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx @@ -1,5 +1,6 @@ import BreadcrumbManager from '@/app/components/breadcrumb-manager'; import ContextDropdown from '@/app/components/context-dropdown'; +import DidYouMean from '@/app/components/did-you-mean'; import FacetGenerator from '@/app/components/facets/facet-generator'; import ProductList from '@/app/components/product-list'; import {SearchProvider} from '@/app/components/providers/providers'; @@ -67,6 +68,7 @@ export default function SearchRoute() {

+ From 3499fe88d8d834943fd84b8b46cb2aa6a83f474e Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 16 Dec 2024 14:25:12 -0500 Subject: [PATCH 14/34] add sort on search https://coveord.atlassian.net/browse/KIT-3774 --- .../samples/headless-commerce-ssr-remix/app/routes/search.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx index 7162167e6c2..b498a269e89 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx @@ -6,6 +6,7 @@ import ProductList from '@/app/components/product-list'; import {SearchProvider} from '@/app/components/providers/providers'; import SearchBox from '@/app/components/search-box'; import ShowMore from '@/app/components/show-more'; +import Sort from '@/app/components/sort'; import Summary from '@/app/components/summary'; import Triggers from '@/app/components/triggers/triggers'; import externalCartService from '@/external-services/external-cart-service'; @@ -71,6 +72,7 @@ export default function SearchRoute() { + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} {/* Date: Mon, 16 Dec 2024 18:06:02 -0500 Subject: [PATCH 15/34] add parameter manager https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/parameter-manager.tsx | 91 +++++++++++++++++++ .../app/components/standalone-search-box.tsx | 2 +- .../app/routes/listings.$listingId.tsx | 18 +++- .../app/routes/search.tsx | 18 +++- .../lib/commerce-engine-config.ts | 5 +- .../lib/commerce-engine.ts | 2 +- 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx new file mode 100644 index 00000000000..3c287b7d172 --- /dev/null +++ b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx @@ -0,0 +1,91 @@ +import {useParameterManager} from '@/lib/commerce-engine'; +import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; +import {useSearchParams, useLocation} from '@remix-run/react'; +import {useEffect, useMemo, useRef} from 'react'; + +export default function ParameterManager({url}: {url: string | null}) { + const {state, methods} = useParameterManager(); + + const {serialize, deserialize} = buildParameterSerializer(); + + const location = useLocation(); + + const initialUrl = useMemo(() => new URL(url ?? location.pathname), [url]); + const previousUrl = useRef(initialUrl.href); + const [searchParams] = useSearchParams(); + + /** + * This flag serves to ensure that history navigation between pages does not clear commerce parameters and result in + * history state loss. + * + * When navigating to a new page, the ParameterManager controller is rebuilt with its initial state. Consequently, if + * we serialize the state parameters and push them to the browser history when navigating back to a page, any commerce + * parameters in the URL that were not part of the controller's initial state will be lost. + * + * By having a "guard" that prevents effect execution when the flag is set to true and sets the flag back to false, + * we are able to prevent this. + * + * For instance, suppose that a user initially navigates to /search?q=test. They then select the next page of results + * so that the URL becomes /search?q=test&page=1. Then, they navigate to a product page (e.g., /product/123). At this + * point, if they use their browser history to go back to the search page, the URL will be /search?q=test&page=1, but + * the ParameterManager controller's state will have been reset to only include the q=test parameter. Thanks to the + * flag, however, the navigation event will not cause the URL to be updated, but the useSearchParams hook will cause + * the controller to synchronize its state with the URL, thus preserving the page=1 parameter. + */ + const flag = useRef(true); + + /** + * When the URL search parameters change, this effect deserializes them and synchronizes them into the + * ParameterManager controller's state. + */ + useEffect(() => { + if (methods === undefined) { + return; + } + + if (flag.current) { + flag.current = false; + return; + } + + const newCommerceParams = deserialize(searchParams); + + const newUrl = serialize(newCommerceParams, new URL(previousUrl.current)); + + if (newUrl === previousUrl.current) { + return; + } + + flag.current = true; + previousUrl.current = newUrl; + methods.synchronize(newCommerceParams); + }, [deserialize, methods, searchParams, serialize]); + + /** + * When the ParameterManager controller's state changes, this effect serializes it into the URL and pushes the new URL + * to the browser history. + * */ + useEffect(() => { + // Ensures that the effect only executes if the controller is hydrated, so that it plays well with the other effect. + if (methods === undefined) { + return; + } + + if (flag.current) { + flag.current = false; + return; + } + + const newUrl = serialize(state.parameters, new URL(previousUrl.current)); + + if (previousUrl.current === newUrl) { + return; + } + + flag.current = true; + previousUrl.current = newUrl; + history.pushState(null, document.title, newUrl); + }, [methods, serialize, state.parameters]); + + return null; +} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx index 0f6ac205c4c..9ea559c240b 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx @@ -21,7 +21,7 @@ export default function StandaloneSearchBox() { useEffect(() => { if (state.redirectTo === '/search') { - const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; + const url = `${state.redirectTo}?q=${encodeURIComponent(state.value)}`; navigate(url, {preventScrollReset: true}); methods?.afterRedirection(); } diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index adb4ef64265..c3037a02d3e 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -3,6 +3,7 @@ import ContextDropdown from '@/app/components/context-dropdown'; import FacetGenerator from '@/app/components/facets/facet-generator'; import Pagination from '@/app/components/pagination'; import ProductList from '@/app/components/product-list'; +import {ListingProvider} from '@/app/components/providers/providers'; import Sort from '@/app/components/sort'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import Summary from '@/app/components/summary'; @@ -18,12 +19,13 @@ import { toCoveoCartItems, toCoveoCurrency, } from '@/utils/external-api-conversions'; -import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; +import { + buildParameterSerializer, + NavigatorContext, +} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData, useParams} from '@remix-run/react'; import invariant from 'tiny-invariant'; -import {ListingProvider} from '../components/providers/providers'; -//import StandaloneSearchBox from '../components/standalone-search-box'; import {coveo_visitorId} from '../cookies.server'; export const loader = async ({params, request}: LoaderFunctionArgs) => { @@ -31,6 +33,11 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { const navigatorContext = await getNavigatorContext(request); + const url = new URL(request.url); + + const {deserialize} = buildParameterSerializer(); + const parameters = deserialize(url.searchParams); + listingEngineDefinition.setNavigatorContextProvider(() => navigatorContext); recommendationEngineDefinition.setNavigatorContextProvider( @@ -47,6 +54,11 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { items: toCoveoCartItems(await externalCartService.getItems()), }, }, + parameterManager: { + initialState: { + parameters: parameters, + }, + }, context: { language, country, diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx index b498a269e89..d69b06fa539 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx @@ -2,6 +2,7 @@ import BreadcrumbManager from '@/app/components/breadcrumb-manager'; import ContextDropdown from '@/app/components/context-dropdown'; import DidYouMean from '@/app/components/did-you-mean'; import FacetGenerator from '@/app/components/facets/facet-generator'; +import ParameterManager from '@/app/components/parameter-manager'; import ProductList from '@/app/components/product-list'; import {SearchProvider} from '@/app/components/providers/providers'; import SearchBox from '@/app/components/search-box'; @@ -17,13 +18,21 @@ import { toCoveoCartItems, toCoveoCurrency, } from '@/utils/external-api-conversions'; -import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; +import { + buildParameterSerializer, + NavigatorContext, +} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; export const loader = async ({request}: LoaderFunctionArgs) => { const navigatorContext = await getNavigatorContext(request); + const url = new URL(request.url); + + const {deserialize} = buildParameterSerializer(); + const parameters = deserialize(await url.searchParams); + searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); const {country, currency, language} = @@ -36,6 +45,11 @@ export const loader = async ({request}: LoaderFunctionArgs) => { items: toCoveoCartItems(await externalCartService.getItems()), }, }, + parameterManager: { + initialState: { + parameters: parameters, + }, + }, context: { language, country, @@ -55,6 +69,7 @@ export default function SearchRoute() { staticState: SearchStaticState; navigatorContext: NavigatorContext; }>(); + return ( + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} {/* Date: Tue, 17 Dec 2024 10:48:26 -0500 Subject: [PATCH 16/34] remove unneeded interface declarations https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/facets/category-facet.tsx | 11 +++++------ .../app/components/facets/date-facet.tsx | 11 +++++------ .../app/components/facets/numeric-facet.tsx | 11 +++++------ .../app/components/facets/regular-facet.tsx | 11 +++++------ 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx index 4129f5c4505..01e6e32aede 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/category-facet.tsx @@ -6,14 +6,13 @@ import { } from '@coveo/headless-react/ssr-commerce'; import {useEffect, useRef, useState} from 'react'; -interface ICategoryFacetProps { +export default function CategoryFacet({ + controller, + staticState, +}: { controller?: HeadlessCategoryFacet; staticState: CategoryFacetState; -} - -export default function CategoryFacet(props: ICategoryFacetProps) { - const {controller, staticState} = props; - +}) { const [state, setState] = useState(staticState); const [showFacetSearchResults, setShowFacetSearchResults] = useState(false); diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx index cf443356a40..f430bbe04b4 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/date-facet.tsx @@ -4,14 +4,13 @@ import { } from '@coveo/headless-react/ssr-commerce'; import {useEffect, useState} from 'react'; -interface IDateFacetProps { +export default function DateFacet({ + controller, + staticState, +}: { controller?: HeadlessDateFacet; staticState: DateFacetState; -} - -export default function DateFacet(props: IDateFacetProps) { - const {controller, staticState} = props; - +}) { const [state, setState] = useState(staticState); useEffect(() => { diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx index d49dec60562..d95f251eadd 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/numeric-facet.tsx @@ -4,14 +4,13 @@ import { } from '@coveo/headless-react/ssr-commerce'; import {useEffect, useRef, useState} from 'react'; -interface INumericFacetProps { +export default function NumericFacet({ + controller, + staticState, +}: { controller?: HeadlessNumericFacet; staticState: NumericFacetState; -} - -export default function NumericFacet(props: INumericFacetProps) { - const {controller, staticState} = props; - +}) { const [state, setState] = useState(staticState); const [currentManualRange, setCurrentManualRange] = useState({ start: diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx index 3f9197f1cd5..1f5c8fde57c 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/regular-facet.tsx @@ -6,14 +6,13 @@ import { } from '@coveo/headless-react/ssr-commerce'; import {useEffect, useRef, useState} from 'react'; -interface IRegularFacetProps { +export default function RegularFacet({ + controller, + staticState, +}: { controller?: HeadlessRegularFacet; staticState: RegularFacetState; -} - -export default function RegularFacet(props: IRegularFacetProps) { - const {controller, staticState} = props; - +}) { const [state, setState] = useState(staticState); const [showFacetSearchResults, setShowFacetSearchResults] = useState(false); From 4fb94375a550c3b75a73d6e7101fae4e42a4acf7 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 11:25:38 -0500 Subject: [PATCH 17/34] add todo for location facet https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/facets/facet-generator.tsx | 1 + .../headless-ssr-commerce/components/facets/facet-generator.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx index 1beb57f64ab..963583669d1 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/facets/facet-generator.tsx @@ -52,6 +52,7 @@ export default function FacetGenerator() { staticState={facetState} /> ); + //TODO: add location facet support https://coveord.atlassian.net/browse/KIT-3808 default: return null; } diff --git a/packages/samples/headless-ssr-commerce/components/facets/facet-generator.tsx b/packages/samples/headless-ssr-commerce/components/facets/facet-generator.tsx index 8f9ad286870..7dc334749d0 100644 --- a/packages/samples/headless-ssr-commerce/components/facets/facet-generator.tsx +++ b/packages/samples/headless-ssr-commerce/components/facets/facet-generator.tsx @@ -54,6 +54,7 @@ export default function FacetGenerator() { staticState={facetState} /> ); + //TODO: add location facet support https://coveord.atlassian.net/browse/KIT-3808 default: return null; } From 18a419fc456bd64ee323a6f8189f7ea8f0691e23 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 11:59:28 -0500 Subject: [PATCH 18/34] enable recs https://coveord.atlassian.net/browse/KIT-3774 --- .../app/routes/cart.tsx | 64 ++++++++++++++----- .../app/routes/listings.$listingId.tsx | 45 ++++++++++--- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx index 2c53eba9d81..db6b212ebd6 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx @@ -1,12 +1,17 @@ import Cart from '@/app/components/cart'; import ContextDropdown from '@/app/components/context-dropdown'; -import {StandaloneProvider} from '@/app/components/providers/providers'; +import { + RecommendationProvider, + StandaloneProvider, +} from '@/app/components/providers/providers'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import externalCartService, { ExternalCartItem, } from '@/external-services/external-cart-service'; import externalContextService from '@/external-services/external-context-service'; import { + recommendationEngineDefinition, + RecommendationStaticState, standaloneEngineDefinition, StandaloneStaticState, } from '@/lib/commerce-engine'; @@ -18,6 +23,7 @@ import { import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; +import PopularBought from '../components/recommendations/popular-bought'; export const loader = async ({request}: LoaderFunctionArgs) => { const navigatorContext = await getNavigatorContext(request); @@ -49,23 +55,49 @@ export const loader = async ({request}: LoaderFunctionArgs) => { }, }); - /* const recsStaticState = await recommendationEngineDefinition.fetchStaticState( - ['popularBoughtRecs', 'popularViewedRecs'] + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + { + controllers: { + popularViewedRecs: {enabled: true}, + popularBoughtRecs: {enabled: true}, + cart: { + initialState: { + items: toCoveoCartItems(items), + }, + }, + context: { + language, + country, + currency: toCoveoCurrency(currency), + view: { + url: 'https://sports.barca.group/cart', + }, + }, + }, + } ); - */ - return {staticState, items, totalPrice, language, currency}; + + return {staticState, items, totalPrice, language, currency, recsStaticState}; }; export default function CartRoute() { - const {staticState, navigatorContext, items, totalPrice, language, currency} = - useLoaderData<{ - staticState: StandaloneStaticState; - navigatorContext: NavigatorContext; - items: ExternalCartItem[]; - totalPrice: number; - language: string; - currency: string; - }>(); + const { + staticState, + navigatorContext, + items, + totalPrice, + language, + currency, + recsStaticState, + } = useLoaderData<{ + staticState: StandaloneStaticState; + navigatorContext: NavigatorContext; + items: ExternalCartItem[]; + totalPrice: number; + language: string; + currency: string; + recsStaticState: RecommendationStaticState; + }>(); return ( - {/* - */} +
); diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index c3037a02d3e..bffac0473b3 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -3,7 +3,12 @@ import ContextDropdown from '@/app/components/context-dropdown'; import FacetGenerator from '@/app/components/facets/facet-generator'; import Pagination from '@/app/components/pagination'; import ProductList from '@/app/components/product-list'; -import {ListingProvider} from '@/app/components/providers/providers'; +import { + ListingProvider, + RecommendationProvider, +} from '@/app/components/providers/providers'; +import PopularBought from '@/app/components/recommendations/popular-bought'; +import PopularViewed from '@/app/components/recommendations/popular-viewed'; import Sort from '@/app/components/sort'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import Summary from '@/app/components/summary'; @@ -13,6 +18,7 @@ import { listingEngineDefinition, ListingStaticState, recommendationEngineDefinition, + RecommendationStaticState, } from '@/lib/commerce-engine'; import {getNavigatorContext} from '@/lib/navigator-context'; import { @@ -47,11 +53,13 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { const {country, currency, language} = await externalContextService.getContextInformation(); + const items = await externalCartService.getItems(); + const staticState = await listingEngineDefinition.fetchStaticState({ controllers: { cart: { initialState: { - items: toCoveoCartItems(await externalCartService.getItems()), + items: toCoveoCartItems(items), }, }, parameterManager: { @@ -70,14 +78,32 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { }, }); - /* const recsStaticState = await recommendationEngineDefinition.fetchStaticState( - ['popularBoughtRecs', 'popularViewedRecs'] - ); */ + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + { + controllers: { + popularViewedRecs: {enabled: true}, + popularBoughtRecs: {enabled: true}, + cart: { + initialState: { + items: toCoveoCartItems(items), + }, + }, + context: { + language, + country, + currency: toCoveoCurrency(currency), + view: { + url: 'https://sports.barca.group/cart', + }, + }, + }, + } + ); return { staticState, navigatorContext, - + recsStaticState, headers: { 'Set-Cookie': await coveo_visitorId.serialize(navigatorContext.clientId), }, @@ -86,9 +112,10 @@ export const loader = async ({params, request}: LoaderFunctionArgs) => { export default function ListingRoute() { const params = useParams(); - const {staticState, navigatorContext} = useLoaderData<{ + const {staticState, navigatorContext, recsStaticState} = useLoaderData<{ staticState: ListingStaticState; navigatorContext: NavigatorContext; + recsStaticState: RecommendationStaticState; }>(); const getTitle = () => { @@ -127,7 +154,7 @@ export default function ListingRoute() { /> */}
- {/*
+
-
*/} +
); From 25b352620790a2dcc66e23bc9c7d7797130d65dd Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 14:03:41 -0500 Subject: [PATCH 19/34] combined rec components https://coveord.atlassian.net/browse/KIT-3774 --- .../recommendations/popular-bought.tsx | 36 ------------------- ...viewed.tsx => popular-recommendations.tsx} | 18 ++++++++-- .../app/routes/cart.tsx | 4 +-- .../app/routes/listings.$listingId.tsx | 7 ++-- 4 files changed, 20 insertions(+), 45 deletions(-) delete mode 100644 packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx rename packages/samples/headless-commerce-ssr-remix/app/components/recommendations/{popular-viewed.tsx => popular-recommendations.tsx} (69%) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx deleted file mode 100644 index e696c79dff1..00000000000 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-bought.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import {usePopularBoughtRecs} from '@/lib/commerce-engine'; -import {Product} from '@coveo/headless-react/ssr-commerce'; -import {useNavigate} from '@remix-run/react'; - -export default function PopularBought() { - const {state, methods} = usePopularBoughtRecs(); - const navigate = useNavigate(); - - const onProductClick = (product: Product) => { - methods?.interactiveProduct({options: {product}}).select(); - navigate( - `/products/${product.ec_product_id || ''}?name=${product.ec_name}&price=${product.ec_price}` - ); - }; - - return ( - <> -
    -

    {state.headline}

    - {state.products.map((product: Product) => ( -
  • - -
  • - ))} -
- - ); -} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-recommendations.tsx similarity index 69% rename from packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx rename to packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-recommendations.tsx index ed46d0c70e2..66ef06f1446 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-viewed.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recommendations/popular-recommendations.tsx @@ -1,9 +1,21 @@ -import {usePopularViewedRecs} from '@/lib/commerce-engine'; +import { + usePopularBoughtRecs, + usePopularViewedRecs, +} from '@/lib/commerce-engine'; import {Product} from '@coveo/headless-react/ssr-commerce'; import {useNavigate} from '@remix-run/react'; -export default function PopularViewed() { - const {state, methods} = usePopularViewedRecs(); +type RecommendationType = 'bought' | 'viewed'; + +interface PopularRecommendationsProps { + type: RecommendationType; +} + +export default function PopularRecommendations({ + type, +}: PopularRecommendationsProps) { + const {state, methods} = + type === 'bought' ? usePopularBoughtRecs() : usePopularViewedRecs(); const navigate = useNavigate(); const onProductClick = (product: Product) => { diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx index db6b212ebd6..8c92a5534ff 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/cart.tsx @@ -4,6 +4,7 @@ import { RecommendationProvider, StandaloneProvider, } from '@/app/components/providers/providers'; +import PopularRecommendations from '@/app/components/recommendations/popular-recommendations'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import externalCartService, { ExternalCartItem, @@ -23,7 +24,6 @@ import { import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; -import PopularBought from '../components/recommendations/popular-bought'; export const loader = async ({request}: LoaderFunctionArgs) => { const navigatorContext = await getNavigatorContext(request); @@ -117,7 +117,7 @@ export default function CartRoute() { staticState={recsStaticState} navigatorContext={navigatorContext} > - + diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index bffac0473b3..caabfa7b86c 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -7,8 +7,7 @@ import { ListingProvider, RecommendationProvider, } from '@/app/components/providers/providers'; -import PopularBought from '@/app/components/recommendations/popular-bought'; -import PopularViewed from '@/app/components/recommendations/popular-viewed'; +import PopularRecommendations from '@/app/components/recommendations/popular-recommendations'; import Sort from '@/app/components/sort'; import StandaloneSearchBox from '@/app/components/standalone-search-box'; import Summary from '@/app/components/summary'; @@ -159,8 +158,8 @@ export default function ListingRoute() { staticState={recsStaticState} navigatorContext={navigatorContext} > - - + + From 67eedba70b6d379602ad90832053ef3029557e2d Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 14:16:36 -0500 Subject: [PATCH 20/34] use fragment instead of query params https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/parameter-manager.tsx | 15 ++++++++------- .../app/components/standalone-search-box.tsx | 2 +- .../app/routes/search.tsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx index 3c287b7d172..a9bb37bc6e3 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx @@ -1,6 +1,6 @@ import {useParameterManager} from '@/lib/commerce-engine'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; -import {useSearchParams, useLocation} from '@remix-run/react'; +import {useLocation} from '@remix-run/react'; import {useEffect, useMemo, useRef} from 'react'; export default function ParameterManager({url}: {url: string | null}) { @@ -12,7 +12,6 @@ export default function ParameterManager({url}: {url: string | null}) { const initialUrl = useMemo(() => new URL(url ?? location.pathname), [url]); const previousUrl = useRef(initialUrl.href); - const [searchParams] = useSearchParams(); /** * This flag serves to ensure that history navigation between pages does not clear commerce parameters and result in @@ -35,7 +34,7 @@ export default function ParameterManager({url}: {url: string | null}) { const flag = useRef(true); /** - * When the URL search parameters change, this effect deserializes them and synchronizes them into the + * When the URL fragment changes, this effect deserializes it and synchronizes it into the * ParameterManager controller's state. */ useEffect(() => { @@ -48,7 +47,8 @@ export default function ParameterManager({url}: {url: string | null}) { return; } - const newCommerceParams = deserialize(searchParams); + const fragment = location.hash.substring(1); // Remove the leading '#' + const newCommerceParams = deserialize(new URLSearchParams(fragment)); const newUrl = serialize(newCommerceParams, new URL(previousUrl.current)); @@ -59,10 +59,10 @@ export default function ParameterManager({url}: {url: string | null}) { flag.current = true; previousUrl.current = newUrl; methods.synchronize(newCommerceParams); - }, [deserialize, methods, searchParams, serialize]); + }, [deserialize, location.hash, methods, serialize]); /** - * When the ParameterManager controller's state changes, this effect serializes it into the URL and pushes the new URL + * When the ParameterManager controller's state changes, this effect serializes it into the URL fragment and pushes the new URL * to the browser history. * */ useEffect(() => { @@ -77,6 +77,7 @@ export default function ParameterManager({url}: {url: string | null}) { } const newUrl = serialize(state.parameters, new URL(previousUrl.current)); + const newFragment = `#${new URL(newUrl).searchParams.toString()}`; if (previousUrl.current === newUrl) { return; @@ -84,7 +85,7 @@ export default function ParameterManager({url}: {url: string | null}) { flag.current = true; previousUrl.current = newUrl; - history.pushState(null, document.title, newUrl); + history.pushState(null, document.title, newFragment); }, [methods, serialize, state.parameters]); return null; diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx index 9ea559c240b..0f6ac205c4c 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx @@ -21,7 +21,7 @@ export default function StandaloneSearchBox() { useEffect(() => { if (state.redirectTo === '/search') { - const url = `${state.redirectTo}?q=${encodeURIComponent(state.value)}`; + const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; navigate(url, {preventScrollReset: true}); methods?.afterRedirection(); } diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx index d69b06fa539..2df465e7ecc 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx @@ -31,7 +31,7 @@ export const loader = async ({request}: LoaderFunctionArgs) => { const url = new URL(request.url); const {deserialize} = buildParameterSerializer(); - const parameters = deserialize(await url.searchParams); + const parameters = deserialize(url.searchParams); searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); From 1a3e588748a5920987c5fb04703095e804c40262 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 14:47:33 -0500 Subject: [PATCH 21/34] improve searchbox https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/search-box.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx index de25ade3e7a..826b229d90a 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/search-box.tsx @@ -39,13 +39,25 @@ export default function SearchBox() { aria-label="searchbox" placeholder="search" value={state.value} + onKeyDown={(e) => { + if (e.key === 'Enter') { + methods?.submit(); + } + }} onChange={(e) => onSearchBoxInputChange(e)} onFocus={handleFocus} onBlur={handleBlur} > {state.value !== '' && ( - + )} From ff6d82643e3308ac0e555c184ecd4533492a581d Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 17 Dec 2024 16:49:55 -0500 Subject: [PATCH 22/34] fix build https://coveord.atlassian.net/browse/KIT-3774 --- package-lock.json | 121 +----------------- .../headless-commerce-ssr-remix/package.json | 2 +- 2 files changed, 2 insertions(+), 121 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f8db6f7c4d..d271afc243c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59767,7 +59767,7 @@ "packages/samples/headless-commerce-ssr-remix": { "name": "@coveo/headless-commerce-ssr-remix-samples", "dependencies": { - "@coveo/headless-react": "2.2.0", + "@coveo/headless-react": "2.4.0", "@remix-run/node": "2.15.2", "@remix-run/react": "2.15.2", "@remix-run/serve": "2.15.2", @@ -59784,125 +59784,6 @@ "vite-tsconfig-paths": "5.1.4" } }, - "packages/samples/headless-commerce-ssr-remix/node_modules/@coveo/headless": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@coveo/headless/-/headless-3.11.0.tgz", - "integrity": "sha512-w+81ZmbWPUh3cf4oifR7aMLd1DtuJgGQNGWfkG08nZF4HPO90PNVfRbYJpjcpJnG+K0s2Hy2llha6m/YMdzL6g==", - "license": "Apache-2.0", - "dependencies": { - "@coveo/bueno": "1.0.7", - "@coveo/relay": "0.7.10", - "@coveo/relay-event-types": "12.0.1", - "@microsoft/fetch-event-source": "2.0.1", - "@reduxjs/toolkit": "2.3.0", - "abortcontroller-polyfill": "1.7.6", - "coveo.analytics": "2.30.42", - "dayjs": "1.11.13", - "exponential-backoff": "3.1.0", - "fast-equals": "5.0.1", - "navigator.sendbeacon": "0.0.20", - "node-abort-controller": "^3.0.0", - "pino": "9.5.0", - "redux-thunk": "3.1.0", - "ts-debounce": "4.0.0" - }, - "engines": { - "node": "^20.9.0 || ^22.11.0" - }, - "peerDependencies": { - "encoding": "^0.1.13", - "pino-pretty": "^6.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0" - } - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/@coveo/headless-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@coveo/headless-react/-/headless-react-2.2.0.tgz", - "integrity": "sha512-d3I3zKoIQKtsjSU35LZzxeCI9Z8vZpNoshHuXM1MsNkgglSYcz/eN4b42AAX9OW+jL6Y7ty6/TWbn+Sjz61DfA==", - "license": "Apache-2.0", - "dependencies": { - "@coveo/headless": "3.11.0" - }, - "engines": { - "node": "^20.9.0 || ^22.11.0" - }, - "optionalDependencies": { - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0" - }, - "peerDependencies": { - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "react": "^18", - "react-dom": "^18" - } - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/@coveo/headless/node_modules/@coveo/relay-event-types": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@coveo/relay-event-types/-/relay-event-types-12.0.1.tgz", - "integrity": "sha512-Or5JfE9UyCyMkSbQ+teJoas+GpN+j2FAXILVq+V5DLZlVW2fmwuD9EGvazlaC+DEea7O01EckBRCp/JQcYcFPg==", - "license": "Apache-2.0" - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/@coveo/headless/node_modules/@reduxjs/toolkit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", - "integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==", - "license": "MIT", - "dependencies": { - "immer": "^10.0.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/abortcontroller-polyfill": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.6.tgz", - "integrity": "sha512-Zypm+LjYdWAzvuypZvDN0smUJrhOurcuBWhhMRBExqVLRvdjp3Z9mASxKyq19K+meZMshwjjy5S0lkm388zE4Q==", - "license": "MIT" - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/fast-equals": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", - "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "packages/samples/headless-commerce-ssr-remix/node_modules/pino": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", - "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, "packages/samples/headless-react": { "name": "@coveo/headless-react-samples", "version": "0.0.0", diff --git a/packages/samples/headless-commerce-ssr-remix/package.json b/packages/samples/headless-commerce-ssr-remix/package.json index 9b2cb275485..d9ef9379e16 100644 --- a/packages/samples/headless-commerce-ssr-remix/package.json +++ b/packages/samples/headless-commerce-ssr-remix/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc" }, "dependencies": { - "@coveo/headless-react": "2.2.0", + "@coveo/headless-react": "2.4.0", "@remix-run/node": "2.15.2", "@remix-run/react": "2.15.2", "@remix-run/serve": "2.15.2", From f7871347ed0059d39884e03c623a85eade8e110e Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Wed, 18 Dec 2024 12:02:42 -0500 Subject: [PATCH 23/34] linting https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/recent-queries.tsx | 4 +++- .../app/components/triggers/query-trigger.tsx | 3 ++- .../components/standalone-search-box.tsx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx index 311333dae23..8a5e9585cdc 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/recent-queries.tsx @@ -13,7 +13,9 @@ export default function RecentQueries() { + > + {query} + ))} diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx index 101f363a826..f4926b66768 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/triggers/query-trigger.tsx @@ -7,7 +7,8 @@ export default function QueryTrigger() { if (state.wasQueryModified) { return (
- The query changed from {state.originalQuery} to {state.newQuery} + The query changed from {state.originalQuery} to{' '} + {state.newQuery}
); } diff --git a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx index e1c5c889326..84fd78f53dd 100644 --- a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx +++ b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx @@ -20,7 +20,7 @@ export default function StandaloneSearchBox() { const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); const navigate = useNavigate(); - + useEffect(() => { if (state.redirectTo === '/search') { const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; From daeb5635c50957118adccdcde9de9a02d9689d70 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Fri, 20 Dec 2024 09:02:05 -0500 Subject: [PATCH 24/34] Adjust parameter manager for remix https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/parameter-manager.tsx | 66 +++++++------------ 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx index a9bb37bc6e3..b40b8d1d224 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx @@ -1,6 +1,6 @@ import {useParameterManager} from '@/lib/commerce-engine'; import {buildParameterSerializer} from '@coveo/headless-react/ssr-commerce'; -import {useLocation} from '@remix-run/react'; +import {useSearchParams} from '@remix-run/react'; import {useEffect, useMemo, useRef} from 'react'; export default function ParameterManager({url}: {url: string | null}) { @@ -8,30 +8,9 @@ export default function ParameterManager({url}: {url: string | null}) { const {serialize, deserialize} = buildParameterSerializer(); - const location = useLocation(); - - const initialUrl = useMemo(() => new URL(url ?? location.pathname), [url]); + const initialUrl = useMemo(() => new URL(url ?? ''), [url]); const previousUrl = useRef(initialUrl.href); - - /** - * This flag serves to ensure that history navigation between pages does not clear commerce parameters and result in - * history state loss. - * - * When navigating to a new page, the ParameterManager controller is rebuilt with its initial state. Consequently, if - * we serialize the state parameters and push them to the browser history when navigating back to a page, any commerce - * parameters in the URL that were not part of the controller's initial state will be lost. - * - * By having a "guard" that prevents effect execution when the flag is set to true and sets the flag back to false, - * we are able to prevent this. - * - * For instance, suppose that a user initially navigates to /search?q=test. They then select the next page of results - * so that the URL becomes /search?q=test&page=1. Then, they navigate to a product page (e.g., /product/123). At this - * point, if they use their browser history to go back to the search page, the URL will be /search?q=test&page=1, but - * the ParameterManager controller's state will have been reset to only include the q=test parameter. Thanks to the - * flag, however, the navigation event will not cause the URL to be updated, but the useSearchParams hook will cause - * the controller to synchronize its state with the URL, thus preserving the page=1 parameter. - */ - const flag = useRef(true); + const [searchParams, setSearchParams] = useSearchParams(); /** * When the URL fragment changes, this effect deserializes it and synchronizes it into the @@ -42,13 +21,7 @@ export default function ParameterManager({url}: {url: string | null}) { return; } - if (flag.current) { - flag.current = false; - return; - } - - const fragment = location.hash.substring(1); // Remove the leading '#' - const newCommerceParams = deserialize(new URLSearchParams(fragment)); + const newCommerceParams = deserialize(searchParams); const newUrl = serialize(newCommerceParams, new URL(previousUrl.current)); @@ -56,37 +29,44 @@ export default function ParameterManager({url}: {url: string | null}) { return; } - flag.current = true; previousUrl.current = newUrl; methods.synchronize(newCommerceParams); - }, [deserialize, location.hash, methods, serialize]); + }, [searchParams]); /** * When the ParameterManager controller's state changes, this effect serializes it into the URL fragment and pushes the new URL * to the browser history. * */ useEffect(() => { - // Ensures that the effect only executes if the controller is hydrated, so that it plays well with the other effect. if (methods === undefined) { return; } - if (flag.current) { - flag.current = false; - return; - } - const newUrl = serialize(state.parameters, new URL(previousUrl.current)); - const newFragment = `#${new URL(newUrl).searchParams.toString()}`; if (previousUrl.current === newUrl) { return; } - flag.current = true; previousUrl.current = newUrl; - history.pushState(null, document.title, newFragment); - }, [methods, serialize, state.parameters]); + + setSearchParams((params) => { + const updatedParams = new URL(newUrl).searchParams; + params.keys().forEach((key) => { + if (!updatedParams.has(key)) { + params.delete(key); + } + }); + updatedParams.keys().forEach((key) => { + const values = updatedParams.getAll(key); + params.set(key, values[0]); + for (let i = 1; i < values.length; i++) { + params.append(key, values[i]); + } + }); + return params; + }); + }, [state.parameters]); return null; } From 3097b651a3a61c7bfd47ca36ce83d46ea23f0e93 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Fri, 20 Dec 2024 09:02:19 -0500 Subject: [PATCH 25/34] Add parameter manager to listing pages https://coveord.atlassian.net/browse/KIT-3774 --- .../app/routes/listings.$listingId.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx index caabfa7b86c..3926548a706 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/listings.$listingId.tsx @@ -31,6 +31,7 @@ import { import {LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData, useParams} from '@remix-run/react'; import invariant from 'tiny-invariant'; +import ParameterManager from '../components/parameter-manager'; import {coveo_visitorId} from '../cookies.server'; export const loader = async ({params, request}: LoaderFunctionArgs) => { @@ -131,6 +132,7 @@ export default function ListingRoute() { staticState={staticState} navigatorContext={navigatorContext} > +

{getTitle()}

From 3a668e865d08bdcd580521f9679451f78fbfd0f4 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Fri, 20 Dec 2024 09:02:34 -0500 Subject: [PATCH 26/34] Store q in query string https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/standalone-search-box.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx index 0f6ac205c4c..9ea559c240b 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/standalone-search-box.tsx @@ -21,7 +21,7 @@ export default function StandaloneSearchBox() { useEffect(() => { if (state.redirectTo === '/search') { - const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; + const url = `${state.redirectTo}?q=${encodeURIComponent(state.value)}`; navigate(url, {preventScrollReset: true}); methods?.afterRedirection(); } From f1b88d8b4b8f17f46ad69d8759ca116756bd5336 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Fri, 20 Dec 2024 09:17:21 -0500 Subject: [PATCH 27/34] Revert faulty changes and use query string instead of hash to store q https://coveord.atlassian.net/browse/KIT-3774 --- .../components/standalone-search-box.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx index 84fd78f53dd..b7446793b0e 100644 --- a/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx +++ b/packages/samples/headless-ssr-commerce/components/standalone-search-box.tsx @@ -5,7 +5,7 @@ import { useRecentQueriesList, useStandaloneSearchBox, } from '@/lib/commerce-engine'; -import {useNavigate} from '@remix-run/react'; +import {useRouter} from 'next/navigation'; import {useEffect, useState} from 'react'; import InstantProducts from './instant-product'; import RecentQueries from './recent-queries'; @@ -19,15 +19,15 @@ export default function StandaloneSearchBox() { const [isInputFocused, setIsInputFocused] = useState(false); const [isSelectingSuggestion, setIsSelectingSuggestion] = useState(false); - const navigate = useNavigate(); + const router = useRouter(); useEffect(() => { if (state.redirectTo === '/search') { - const url = `${state.redirectTo}#q=${encodeURIComponent(state.value)}`; - navigate(url, {preventScrollReset: true}); + const url = `${state.redirectTo}?q=${encodeURIComponent(state.value)}`; + router.push(url, {scroll: false}); methods?.afterRedirection(); } - }, [state.redirectTo, state.value, navigate, methods]); + }, [state.redirectTo, state.value, router, methods]); const onSearchBoxInputChange = (e: React.ChangeEvent) => { setIsSelectingSuggestion(true); From d033d4a2eed8a11d6eb29193e22cad4d1bc1587d Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:32:54 -0500 Subject: [PATCH 28/34] fix tests https://coveord.atlassian.net/browse/KIT-3774 --- package-lock.json | 1 + .../headless-core-parameter-manager.ssr.ts | 23 ++++++++++++------- .../e2e/listing/listing.spec.ts | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index d271afc243c..e6a4fb0cddf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29475,6 +29475,7 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, "license": "MIT" }, "node_modules/debounce": { diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index 4d757539fe1..4acd3150fcc 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -4,6 +4,7 @@ import { SolutionType, SubControllerDefinitionWithProps, } from '../../../../app/commerce-ssr-engine/types/common.js'; +import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js'; import {CoreEngineNext} from '../../../../app/engine.js'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice.js'; import {manualNumericFacetReducer as manualNumericFacetSet} from '../../../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice.js'; @@ -55,18 +56,24 @@ export function defineParameterManager< if (!loadCommerceProductListingParameterReducers(engine)) { throw loadReducerError; } - return buildProductListing(engine).parameterManager({ - ...props, - excludeDefaultParameters: true, - }); + return { + ...buildProductListing(engine).parameterManager({ + ...props, + excludeDefaultParameters: true, + }), + _kind: Kind.ParameterManager, + }; } else { if (!loadCommerceSearchParameterReducers(engine)) { throw loadReducerError; } - return buildSearch(engine).parameterManager({ - ...props, - excludeDefaultParameters: true, - }); + return { + ...buildSearch(engine).parameterManager({ + ...props, + excludeDefaultParameters: true, + }), + _kind: Kind.ParameterManager, + }; } }, } as SubControllerDefinitionWithProps< diff --git a/packages/samples/headless-ssr-commerce/e2e/listing/listing.spec.ts b/packages/samples/headless-ssr-commerce/e2e/listing/listing.spec.ts index 64bed901937..9a5607ccf72 100644 --- a/packages/samples/headless-ssr-commerce/e2e/listing/listing.spec.ts +++ b/packages/samples/headless-ssr-commerce/e2e/listing/listing.spec.ts @@ -33,7 +33,7 @@ test.describe('default', () => { }); test('should go to search page', async ({page}) => { - await page.waitForURL('**/search#q=*'); + await page.waitForURL('**/search?q=*'); const currentUrl = page.url(); @@ -47,7 +47,7 @@ test.describe('default', () => { }); test('should go to search page', async ({page}) => { - await page.waitForURL('**/search#q=*'); + await page.waitForURL('**/search?q=*'); const currentUrl = page.url(); expect(currentUrl).toContain('shoes'); }); From a0bfae9859debf07a22772daeeda330d2b986c17 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:34:25 -0500 Subject: [PATCH 29/34] fix param manager problems https://coveord.atlassian.net/browse/KIT-3774 --- .../src/app/commerce-ssr-engine/types/kind.ts | 16 +++++++++ .../context/cart/headless-cart.ssr.ts | 11 ++++--- .../commerce/context/headless-context.ssr.ts | 11 ++++--- .../headless-core-parameter-manager.ssr.ts | 33 ++++++++++--------- .../headless-recommendations.ssr.ts | 16 +++------ .../lib/commerce-engine-config.ts | 8 +---- 6 files changed, 52 insertions(+), 43 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/types/kind.ts b/packages/headless/src/app/commerce-ssr-engine/types/kind.ts index 74c4ba9eb2e..6b70dd684e4 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/kind.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/kind.ts @@ -4,3 +4,19 @@ export enum Kind { ParameterManager = 'PARAMETER_MANAGER', Recommendations = 'RECOMMENDATIONS', } + +export function createControllerWithKind( + controller: TController, + kind: TKind +): TController & {_kind: TKind} { + const copy = Object.defineProperties( + {}, + Object.getOwnPropertyDescriptors(controller) + ); + + Object.defineProperty(copy, '_kind', { + value: kind, + }); + + return copy as TController & {_kind: TKind}; +} diff --git a/packages/headless/src/controllers/commerce/context/cart/headless-cart.ssr.ts b/packages/headless/src/controllers/commerce/context/cart/headless-cart.ssr.ts index 8e17fb57e13..e65140c7739 100644 --- a/packages/headless/src/controllers/commerce/context/cart/headless-cart.ssr.ts +++ b/packages/headless/src/controllers/commerce/context/cart/headless-cart.ssr.ts @@ -1,5 +1,8 @@ import {UniversalControllerDefinitionWithProps} from '../../../../app/commerce-ssr-engine/types/common.js'; -import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js'; +import { + createControllerWithKind, + Kind, +} from '../../../../app/commerce-ssr-engine/types/kind.js'; import {Cart, buildCart, CartInitialState} from './headless-cart.js'; export type {CartState, CartItem, CartProps} from './headless-cart.js'; @@ -27,10 +30,8 @@ export function defineCart(): CartDefinition { standalone: true, recommendation: true, buildWithProps: (engine, props) => { - return { - ...buildCart(engine, {initialState: props.initialState}), - _kind: Kind.Cart, - }; + const controller = buildCart(engine, {initialState: props.initialState}); + return createControllerWithKind(controller, Kind.Cart); }, }; } diff --git a/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts b/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts index 269167b28e3..bbc48e3a46a 100644 --- a/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts +++ b/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts @@ -1,5 +1,8 @@ import {UniversalControllerDefinitionWithProps} from '../../../app/commerce-ssr-engine/types/common.js'; -import {Kind} from '../../../app/commerce-ssr-engine/types/kind.js'; +import { + createControllerWithKind, + Kind, +} from '../../../app/commerce-ssr-engine/types/kind.js'; import { Context, buildContext, @@ -29,10 +32,8 @@ export function defineContext(): ContextDefinition { standalone: true, recommendation: true, buildWithProps: (engine, props) => { - return { - ...buildContext(engine, {options: props}), - _kind: Kind.Context, - }; + const controller = buildContext(engine, {options: props}); + return createControllerWithKind(controller, Kind.Cart); }, }; } diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index 4acd3150fcc..f4923db4d0f 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -4,7 +4,11 @@ import { SolutionType, SubControllerDefinitionWithProps, } from '../../../../app/commerce-ssr-engine/types/common.js'; -import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js'; +import { + createControllerWithKind, + Kind, +} from '../../../../app/commerce-ssr-engine/types/kind.js'; +// import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js'; import {CoreEngineNext} from '../../../../app/engine.js'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice.js'; import {manualNumericFacetReducer as manualNumericFacetSet} from '../../../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice.js'; @@ -56,24 +60,23 @@ export function defineParameterManager< if (!loadCommerceProductListingParameterReducers(engine)) { throw loadReducerError; } - return { - ...buildProductListing(engine).parameterManager({ - ...props, - excludeDefaultParameters: true, - }), - _kind: Kind.ParameterManager, - }; + const controller = buildProductListing(engine).parameterManager({ + ...props, + excludeDefaultParameters: true, + }); + + return createControllerWithKind(controller, Kind.ParameterManager); } else { if (!loadCommerceSearchParameterReducers(engine)) { throw loadReducerError; } - return { - ...buildSearch(engine).parameterManager({ - ...props, - excludeDefaultParameters: true, - }), - _kind: Kind.ParameterManager, - }; + + const controller = buildSearch(engine).parameterManager({ + ...props, + excludeDefaultParameters: true, + }); + + return createControllerWithKind(controller, Kind.ParameterManager); } }, } as SubControllerDefinitionWithProps< diff --git a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts index 7f7dcb9472e..b19f83d16f5 100644 --- a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts +++ b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts @@ -2,7 +2,10 @@ import { recommendationInternalOptionKey, RecommendationOnlyControllerDefinitionWithProps, } from '../../../app/commerce-ssr-engine/types/common.js'; -import {Kind} from '../../../app/commerce-ssr-engine/types/kind.js'; +import { + createControllerWithKind, + Kind, +} from '../../../app/commerce-ssr-engine/types/kind.js'; import { RecommendationsOptions, RecommendationsState, @@ -52,16 +55,7 @@ export function defineRecommendations( const controller = buildRecommendations(engine, { options: {...staticOptions, ...options}, }); - const copy = Object.defineProperties( - {}, - Object.getOwnPropertyDescriptors(controller) - ); - - Object.defineProperty(copy, '_kind', { - value: Kind.Recommendations, - }); - - return copy as typeof controller & {_kind: Kind.Recommendations}; + return createControllerWithKind(controller, Kind.Recommendations); }, }; } diff --git a/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine-config.ts b/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine-config.ts index 3effc5e433f..458334ce77f 100644 --- a/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine-config.ts +++ b/packages/samples/headless-commerce-ssr-remix/lib/commerce-engine-config.ts @@ -1,6 +1,4 @@ import { - Controller, - ControllerDefinitionsMap, CommerceEngineDefinitionOptions, defineProductList, defineCart, @@ -24,10 +22,6 @@ import { defineParameterManager, } from '@coveo/headless-react/ssr-commerce'; -type CommerceEngineConfig = CommerceEngineDefinitionOptions< - ControllerDefinitionsMap ->; - export default { /** * By default, the logger level is set to 'warn'. This level may not provide enough information for some server-side @@ -69,4 +63,4 @@ export default { facetGenerator: defineFacetGenerator(), breadcrumbManager: defineBreadcrumbManager(), }, -} satisfies CommerceEngineConfig; +} satisfies CommerceEngineDefinitionOptions; From 7ca5dd5377c5b3125e1bae7dc8334c1866ee7409 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:41:24 -0500 Subject: [PATCH 30/34] wrong _kind https://coveord.atlassian.net/browse/KIT-3774 --- .../src/controllers/commerce/context/headless-context.ssr.ts | 2 +- .../parameter-manager/headless-core-parameter-manager.ssr.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts b/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts index bbc48e3a46a..589b9b61750 100644 --- a/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts +++ b/packages/headless/src/controllers/commerce/context/headless-context.ssr.ts @@ -33,7 +33,7 @@ export function defineContext(): ContextDefinition { recommendation: true, buildWithProps: (engine, props) => { const controller = buildContext(engine, {options: props}); - return createControllerWithKind(controller, Kind.Cart); + return createControllerWithKind(controller, Kind.Context); }, }; } diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index f4923db4d0f..42efaceb758 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -8,7 +8,6 @@ import { createControllerWithKind, Kind, } from '../../../../app/commerce-ssr-engine/types/kind.js'; -// import {Kind} from '../../../../app/commerce-ssr-engine/types/kind.js'; import {CoreEngineNext} from '../../../../app/engine.js'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice.js'; import {manualNumericFacetReducer as manualNumericFacetSet} from '../../../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice.js'; From af02a180d5ad1ed62389c385ab36612d9e5ad7b6 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Tue, 7 Jan 2025 15:19:44 -0500 Subject: [PATCH 31/34] Move param manager up https://coveord.atlassian.net/browse/KIT-3774 --- .../samples/headless-commerce-ssr-remix/app/routes/search.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx index 2df465e7ecc..c5285f69cfc 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/routes/search.tsx @@ -75,6 +75,7 @@ export default function SearchRoute() { staticState={staticState} navigatorContext={navigatorContext} > +

Search

@@ -89,7 +90,7 @@ export default function SearchRoute() { - + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} {/* Date: Tue, 7 Jan 2025 15:21:21 -0500 Subject: [PATCH 32/34] Implement param manager in remix sample https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/parameter-manager.tsx | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx index b40b8d1d224..97dc04533c4 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/parameter-manager.tsx @@ -10,7 +10,7 @@ export default function ParameterManager({url}: {url: string | null}) { const initialUrl = useMemo(() => new URL(url ?? ''), [url]); const previousUrl = useRef(initialUrl.href); - const [searchParams, setSearchParams] = useSearchParams(); + const [searchParams] = useSearchParams(); /** * When the URL fragment changes, this effect deserializes it and synchronizes it into the @@ -25,7 +25,7 @@ export default function ParameterManager({url}: {url: string | null}) { const newUrl = serialize(newCommerceParams, new URL(previousUrl.current)); - if (newUrl === previousUrl.current) { + if (newUrl === previousUrl.current || newUrl === initialUrl.href) { return; } @@ -44,28 +44,12 @@ export default function ParameterManager({url}: {url: string | null}) { const newUrl = serialize(state.parameters, new URL(previousUrl.current)); - if (previousUrl.current === newUrl) { + if (previousUrl.current === newUrl || newUrl === initialUrl.href) { return; } previousUrl.current = newUrl; - - setSearchParams((params) => { - const updatedParams = new URL(newUrl).searchParams; - params.keys().forEach((key) => { - if (!updatedParams.has(key)) { - params.delete(key); - } - }); - updatedParams.keys().forEach((key) => { - const values = updatedParams.getAll(key); - params.set(key, values[0]); - for (let i = 1; i < values.length; i++) { - params.append(key, values[i]); - } - }); - return params; - }); + history.pushState(null, '', newUrl); }, [state.parameters]); return null; From 47498e689d51deac153638e0ed7fc8ef940aaecf Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Tue, 7 Jan 2025 15:49:30 -0500 Subject: [PATCH 33/34] Fix product view logic https://coveord.atlassian.net/browse/KIT-3774 --- .../app/components/product-view.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/samples/headless-commerce-ssr-remix/app/components/product-view.tsx b/packages/samples/headless-commerce-ssr-remix/app/components/product-view.tsx index b01284f39a4..3d6057841e9 100644 --- a/packages/samples/headless-commerce-ssr-remix/app/components/product-view.tsx +++ b/packages/samples/headless-commerce-ssr-remix/app/components/product-view.tsx @@ -2,7 +2,7 @@ import {ExternalCartItem} from '@/external-services/external-cart-service'; import {ExternalCatalogItem} from '@/external-services/external-catalog-service'; import {useProductView} from '@/lib/commerce-engine'; import {formatCurrency} from '@/utils/format-currency'; -import {useEffect, useRef} from 'react'; +import {useEffect} from 'react'; import AddToCartButton from './add-to-cart-button'; import RemoveFromCartButton from './remove-from-cart-button'; @@ -25,12 +25,12 @@ export default function ProductView({ uniqueId: productId, } = catalogItem; - const viewed = useRef(false); + let viewed = false; useEffect(() => { - if (methods && !viewed.current) { - viewed.current = true; + if (methods && !viewed) { methods.view({name, price, productId}); + viewed = true; } }, []); From 0158fa66e226a019201878e47e5a9bcb624c0e24 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Mon, 13 Jan 2025 19:36:13 +0000 Subject: [PATCH 34/34] Add generated files https://coveord.atlassian.net/browse/KIT-3774 --- packages/atomic/src/components.d.ts | 200 ++++++++++++++-------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index a338ab114a1..ea07f2d0929 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -287,7 +287,7 @@ export namespace Components { } /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface AtomicCommerceBreadbox { /** @@ -299,7 +299,7 @@ export namespace Components { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface AtomicCommerceCategoryFacet { /** @@ -326,7 +326,7 @@ export namespace Components { } /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface AtomicCommerceFacet { /** @@ -348,7 +348,7 @@ export namespace Components { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface AtomicCommerceFacetNumberInput { "bindings": Bindings; @@ -359,7 +359,7 @@ export namespace Components { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface AtomicCommerceFacets { /** @@ -444,7 +444,7 @@ export namespace Components { } /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface AtomicCommerceLoadMoreProducts { } @@ -455,7 +455,7 @@ export namespace Components { } /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface AtomicCommerceNumericFacet { /** @@ -477,7 +477,7 @@ export namespace Components { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface AtomicCommercePager { /** @@ -521,7 +521,7 @@ export namespace Components { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface AtomicCommerceProductsPerPage { /** @@ -536,13 +536,13 @@ export namespace Components { } /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface AtomicCommerceQueryError { } /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface AtomicCommerceQuerySummary { } @@ -590,7 +590,7 @@ export namespace Components { } /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface AtomicCommerceRecommendationList { /** @@ -638,7 +638,7 @@ export namespace Components { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface AtomicCommerceRefineModal { /** @@ -651,13 +651,13 @@ export namespace Components { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface AtomicCommerceRefineToggle { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBox { /** @@ -693,7 +693,7 @@ export namespace Components { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxInstantProducts { /** @@ -716,7 +716,7 @@ export namespace Components { } /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxQuerySuggestions { /** @@ -734,7 +734,7 @@ export namespace Components { } /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxRecentQueries { /** @@ -752,7 +752,7 @@ export namespace Components { } /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface AtomicCommerceSortDropdown { } @@ -773,7 +773,7 @@ export namespace Components { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface AtomicCommerceTimeframeFacet { /** @@ -1673,7 +1673,7 @@ export namespace Components { "userActions": Array; } /** - * @component + * @component * @example */ interface AtomicInsightUserActionsTimeline { @@ -2006,7 +2006,7 @@ export namespace Components { } /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface AtomicProduct { /** @@ -2113,7 +2113,7 @@ export namespace Components { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface AtomicProductFieldCondition { /** @@ -2137,7 +2137,7 @@ export namespace Components { } /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface AtomicProductImage { /** @@ -2210,7 +2210,7 @@ export namespace Components { } /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface AtomicProductRating { /** @@ -3997,7 +3997,7 @@ declare global { }; /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface HTMLAtomicCommerceBreadboxElement extends Components.AtomicCommerceBreadbox, HTMLStencilElement { } @@ -4008,7 +4008,7 @@ declare global { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface HTMLAtomicCommerceCategoryFacetElement extends Components.AtomicCommerceCategoryFacet, HTMLStencilElement { } @@ -4027,7 +4027,7 @@ declare global { }; /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetElement extends Components.AtomicCommerceFacet, HTMLStencilElement { } @@ -4040,7 +4040,7 @@ declare global { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetNumberInputElement extends Components.AtomicCommerceFacetNumberInput, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceFacetNumberInputElement, ev: AtomicCommerceFacetNumberInputCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4059,7 +4059,7 @@ declare global { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface HTMLAtomicCommerceFacetsElement extends Components.AtomicCommerceFacets, HTMLStencilElement { } @@ -4090,7 +4090,7 @@ declare global { }; /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface HTMLAtomicCommerceLoadMoreProductsElement extends Components.AtomicCommerceLoadMoreProducts, HTMLStencilElement { } @@ -4109,7 +4109,7 @@ declare global { }; /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface HTMLAtomicCommerceNumericFacetElement extends Components.AtomicCommerceNumericFacet, HTMLStencilElement { } @@ -4122,7 +4122,7 @@ declare global { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface HTMLAtomicCommercePagerElement extends Components.AtomicCommercePager, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommercePagerElement, ev: AtomicCommercePagerCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4152,7 +4152,7 @@ declare global { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface HTMLAtomicCommerceProductsPerPageElement extends Components.AtomicCommerceProductsPerPage, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceProductsPerPageElement, ev: AtomicCommerceProductsPerPageCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4170,7 +4170,7 @@ declare global { }; /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface HTMLAtomicCommerceQueryErrorElement extends Components.AtomicCommerceQueryError, HTMLStencilElement { } @@ -4180,7 +4180,7 @@ declare global { }; /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface HTMLAtomicCommerceQuerySummaryElement extends Components.AtomicCommerceQuerySummary, HTMLStencilElement { } @@ -4199,7 +4199,7 @@ declare global { }; /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRecommendationListElement extends Components.AtomicCommerceRecommendationList, HTMLStencilElement { } @@ -4210,7 +4210,7 @@ declare global { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRefineModalElement extends Components.AtomicCommerceRefineModal, HTMLStencilElement { } @@ -4221,7 +4221,7 @@ declare global { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface HTMLAtomicCommerceRefineToggleElement extends Components.AtomicCommerceRefineToggle, HTMLStencilElement { } @@ -4234,7 +4234,7 @@ declare global { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxElement extends Components.AtomicCommerceSearchBox, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLAtomicCommerceSearchBoxElement, ev: AtomicCommerceSearchBoxCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -4254,7 +4254,7 @@ declare global { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxInstantProductsElement extends Components.AtomicCommerceSearchBoxInstantProducts, HTMLStencilElement { } @@ -4264,7 +4264,7 @@ declare global { }; /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxQuerySuggestionsElement extends Components.AtomicCommerceSearchBoxQuerySuggestions, HTMLStencilElement { } @@ -4274,7 +4274,7 @@ declare global { }; /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSearchBoxRecentQueriesElement extends Components.AtomicCommerceSearchBoxRecentQueries, HTMLStencilElement { } @@ -4284,7 +4284,7 @@ declare global { }; /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface HTMLAtomicCommerceSortDropdownElement extends Components.AtomicCommerceSortDropdown, HTMLStencilElement { } @@ -4304,7 +4304,7 @@ declare global { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface HTMLAtomicCommerceTimeframeFacetElement extends Components.AtomicCommerceTimeframeFacet, HTMLStencilElement { } @@ -4757,7 +4757,7 @@ declare global { new (): HTMLAtomicInsightUserActionsSessionElement; }; /** - * @component + * @component * @example */ interface HTMLAtomicInsightUserActionsTimelineElement extends Components.AtomicInsightUserActionsTimeline, HTMLStencilElement { @@ -4977,7 +4977,7 @@ declare global { }; /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface HTMLAtomicProductElement extends Components.AtomicProduct, HTMLStencilElement { } @@ -5027,7 +5027,7 @@ declare global { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface HTMLAtomicProductFieldConditionElement extends Components.AtomicProductFieldCondition, HTMLStencilElement { } @@ -5037,7 +5037,7 @@ declare global { }; /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface HTMLAtomicProductImageElement extends Components.AtomicProductImage, HTMLStencilElement { } @@ -5084,7 +5084,7 @@ declare global { }; /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface HTMLAtomicProductRatingElement extends Components.AtomicProductRating, HTMLStencilElement { } @@ -6513,7 +6513,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ interface AtomicCommerceBreadbox { /** @@ -6525,7 +6525,7 @@ declare namespace LocalJSX { /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ interface AtomicCommerceCategoryFacet { /** @@ -6552,7 +6552,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ interface AtomicCommerceFacet { /** @@ -6574,7 +6574,7 @@ declare namespace LocalJSX { } /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ interface AtomicCommerceFacetNumberInput { "bindings": Bindings; @@ -6586,7 +6586,7 @@ declare namespace LocalJSX { /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ interface AtomicCommerceFacets { /** @@ -6659,7 +6659,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ interface AtomicCommerceLoadMoreProducts { } @@ -6670,7 +6670,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ interface AtomicCommerceNumericFacet { /** @@ -6692,7 +6692,7 @@ declare namespace LocalJSX { } /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ interface AtomicCommercePager { /** @@ -6732,7 +6732,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ interface AtomicCommerceProductsPerPage { /** @@ -6748,13 +6748,13 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ interface AtomicCommerceQueryError { } /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ interface AtomicCommerceQuerySummary { } @@ -6798,7 +6798,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ interface AtomicCommerceRecommendationList { /** @@ -6833,7 +6833,7 @@ declare namespace LocalJSX { /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ interface AtomicCommerceRefineModal { /** @@ -6846,13 +6846,13 @@ declare namespace LocalJSX { /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ interface AtomicCommerceRefineToggle { } /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBox { /** @@ -6892,7 +6892,7 @@ declare namespace LocalJSX { * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxInstantProducts { /** @@ -6910,7 +6910,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxQuerySuggestions { /** @@ -6928,7 +6928,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ interface AtomicCommerceSearchBoxRecentQueries { /** @@ -6946,7 +6946,7 @@ declare namespace LocalJSX { } /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ interface AtomicCommerceSortDropdown { } @@ -6967,7 +6967,7 @@ declare namespace LocalJSX { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ interface AtomicCommerceTimeframeFacet { /** @@ -7838,7 +7838,7 @@ declare namespace LocalJSX { "userActions": Array; } /** - * @component + * @component * @example */ interface AtomicInsightUserActionsTimeline { @@ -8159,7 +8159,7 @@ declare namespace LocalJSX { } /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ interface AtomicProduct { /** @@ -8267,7 +8267,7 @@ declare namespace LocalJSX { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ interface AtomicProductFieldCondition { /** @@ -8291,7 +8291,7 @@ declare namespace LocalJSX { } /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ interface AtomicProductImage { /** @@ -8351,7 +8351,7 @@ declare namespace LocalJSX { } /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ interface AtomicProductRating { /** @@ -10162,13 +10162,13 @@ declare module "@stencil/core" { "atomic-color-facet": LocalJSX.AtomicColorFacet & JSXBase.HTMLAttributes; /** * The `atomic-commerce-breadbox` component creates breadcrumbs that display a summary of the currently active facet values. - * @alpha + * @alpha */ "atomic-commerce-breadbox": LocalJSX.AtomicCommerceBreadbox & JSXBase.HTMLAttributes; /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). * An `atomic-commerce-category-facet` displays a facet of values in a browsable, hierarchical fashion. - * @alpha + * @alpha */ "atomic-commerce-category-facet": LocalJSX.AtomicCommerceCategoryFacet & JSXBase.HTMLAttributes; /** @@ -10177,18 +10177,18 @@ declare module "@stencil/core" { "atomic-commerce-did-you-mean": LocalJSX.AtomicCommerceDidYouMean & JSXBase.HTMLAttributes; /** * The `atomic-commerce-facet` component renders a commerce facet that the end user can interact with to filter products. - * @alpha + * @alpha */ "atomic-commerce-facet": LocalJSX.AtomicCommerceFacet & JSXBase.HTMLAttributes; /** * Internal component made to be integrated in a NumericFacet. - * @alpha + * @alpha */ "atomic-commerce-facet-number-input": LocalJSX.AtomicCommerceFacetNumberInput & JSXBase.HTMLAttributes; /** * The `atomic-commerce-facets` component automatically renders commerce facets based on the Commerce API response. * Unlike regular facets, which require explicit definition and request in the query, the `atomic-commerce-facets` component dynamically generates facets. - * @alpha + * @alpha */ "atomic-commerce-facets": LocalJSX.AtomicCommerceFacets & JSXBase.HTMLAttributes; /** @@ -10204,7 +10204,7 @@ declare module "@stencil/core" { "atomic-commerce-layout": LocalJSX.AtomicCommerceLayout & JSXBase.HTMLAttributes; /** * The `atomic-commerce-load-more-products` component allows the user to load additional products if more are available. - * @alpha + * @alpha */ "atomic-commerce-load-more-products": LocalJSX.AtomicCommerceLoadMoreProducts & JSXBase.HTMLAttributes; /** @@ -10213,12 +10213,12 @@ declare module "@stencil/core" { "atomic-commerce-no-products": LocalJSX.AtomicCommerceNoProducts & JSXBase.HTMLAttributes; /** * The `atomic-commerce-numeric-facet` component is responsible for rendering a commerce facet that allows the user to filter products using numeric ranges. - * @alpha + * @alpha */ "atomic-commerce-numeric-facet": LocalJSX.AtomicCommerceNumericFacet & JSXBase.HTMLAttributes; /** * The `atomic-pager` provides buttons that allow the end user to navigate through the different product pages. - * @alpha + * @alpha */ "atomic-commerce-pager": LocalJSX.AtomicCommercePager & JSXBase.HTMLAttributes; /** @@ -10227,17 +10227,17 @@ declare module "@stencil/core" { "atomic-commerce-product-list": LocalJSX.AtomicCommerceProductList & JSXBase.HTMLAttributes; /** * The `atomic-commerce-products-per-page` component determines how many products to display per page. - * @alpha + * @alpha */ "atomic-commerce-products-per-page": LocalJSX.AtomicCommerceProductsPerPage & JSXBase.HTMLAttributes; /** * The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. - * @alpha + * @alpha */ "atomic-commerce-query-error": LocalJSX.AtomicCommerceQueryError & JSXBase.HTMLAttributes; /** * The `atomic-commerce-query-summary` component displays information about the current range of results and the request duration (e.g., "Results 1-10 of 123 in 0.47 seconds"). - * @alpha + * @alpha */ "atomic-commerce-query-summary": LocalJSX.AtomicCommerceQuerySummary & JSXBase.HTMLAttributes; /** @@ -10246,46 +10246,46 @@ declare module "@stencil/core" { "atomic-commerce-recommendation-interface": LocalJSX.AtomicCommerceRecommendationInterface & JSXBase.HTMLAttributes; /** * The `atomic-commerce-recommendation-list` component displays a list of product recommendations by applying one or more product templates. - * @alpha + * @alpha */ "atomic-commerce-recommendation-list": LocalJSX.AtomicCommerceRecommendationList & JSXBase.HTMLAttributes; /** * The `atomic-commerce-refine-modal` is automatically created as a child of the `atomic-commerce-search-interface` when the `atomic-commerce-refine-toggle` is initialized. * When the modal is opened, the class `atomic-modal-opened` is added to the interface element and the body, allowing further customization. - * @alpha + * @alpha */ "atomic-commerce-refine-modal": LocalJSX.AtomicCommerceRefineModal & JSXBase.HTMLAttributes; /** * The `atomic-commerce-refine-toggle` component displays a button that opens a modal containing the facets and the sort components. * When this component is added to the `atomic-commerce-search-interface`, an `atomic-commerce-refine-modal` component is automatically created. - * @alpha + * @alpha */ "atomic-commerce-refine-toggle": LocalJSX.AtomicCommerceRefineToggle & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box` component creates a search box with built-in support for suggestions. - * @alpha + * @alpha */ "atomic-commerce-search-box": LocalJSX.AtomicCommerceSearchBox & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-instant-products` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of instant results behavior. * This component does not support accessibility out-of-the-box. To do so, see [Instant Results Accessibility](https://docs.coveo.com/en/atomic/latest/usage/accessibility/#instant-results-accessibility). * This component is not supported on mobile. - * @alpha + * @alpha */ "atomic-commerce-search-box-instant-products": LocalJSX.AtomicCommerceSearchBoxInstantProducts & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-query-suggestions` component can be added as a child of an `atomic-search-box` component, allowing for the configuration of query suggestion behavior. - * @alpha + * @alpha */ "atomic-commerce-search-box-query-suggestions": LocalJSX.AtomicCommerceSearchBoxQuerySuggestions & JSXBase.HTMLAttributes; /** * The `atomic-commerce-search-box-recent-queries` component can be added as a child of an `atomic-commerce-search-box` component, allowing for the configuration of recent query suggestions. - * @alpha + * @alpha */ "atomic-commerce-search-box-recent-queries": LocalJSX.AtomicCommerceSearchBoxRecentQueries & JSXBase.HTMLAttributes; /** * The `atomic-commerce-sort-dropdown` component renders a dropdown that the end user can interact with to select the criteria to use when sorting products. - * @alpha + * @alpha */ "atomic-commerce-sort-dropdown": LocalJSX.AtomicCommerceSortDropdown & JSXBase.HTMLAttributes; /** @@ -10295,7 +10295,7 @@ declare module "@stencil/core" { /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-commerce-timeframe-facet` displays a facet of the results for the current query as date intervals. - * @alpha + * @alpha */ "atomic-commerce-timeframe-facet": LocalJSX.AtomicCommerceTimeframeFacet & JSXBase.HTMLAttributes; /** @@ -10410,7 +10410,7 @@ declare module "@stencil/core" { */ "atomic-insight-user-actions-session": LocalJSX.AtomicInsightUserActionsSession & JSXBase.HTMLAttributes; /** - * @component + * @component * @example */ "atomic-insight-user-actions-timeline": LocalJSX.AtomicInsightUserActionsTimeline & JSXBase.HTMLAttributes; @@ -10470,7 +10470,7 @@ declare module "@stencil/core" { "atomic-popover": LocalJSX.AtomicPopover & JSXBase.HTMLAttributes; /** * The `atomic-product` component is used internally by the `atomic-commerce-product-list` component. - * @alpha + * @alpha */ "atomic-product": LocalJSX.AtomicProduct & JSXBase.HTMLAttributes; /** @@ -10489,12 +10489,12 @@ declare module "@stencil/core" { /** * The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined. * The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`). - * @alpha + * @alpha */ "atomic-product-field-condition": LocalJSX.AtomicProductFieldCondition & JSXBase.HTMLAttributes; /** * The `atomic-product-image` component renders an image from a product field. - * @alpha + * @alpha */ "atomic-product-image": LocalJSX.AtomicProductImage & JSXBase.HTMLAttributes; /** @@ -10516,7 +10516,7 @@ declare module "@stencil/core" { "atomic-product-price": LocalJSX.AtomicProductPrice & JSXBase.HTMLAttributes; /** * The `atomic-product-rating` element renders a star rating. - * @alpha + * @alpha */ "atomic-product-rating": LocalJSX.AtomicProductRating & JSXBase.HTMLAttributes; /**