diff --git a/package-lock.json b/package-lock.json index 34c77b27161..b65e805f9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55808,7 +55808,7 @@ }, "packages/atomic": { "name": "@coveo/atomic", - "version": "2.77.0", + "version": "2.77.2", "license": "Apache-2.0", "dependencies": { "@coveo/bueno": "0.46.1", @@ -55830,7 +55830,7 @@ "@axe-core/playwright": "4.9.1", "@babel/core": "7.24.9", "@coveo/atomic": "file:.", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@coveo/release": "1.0.0", "@custom-elements-manifest/analyzer": "0.10.3", "@fullhuman/postcss-purgecss": "6.0.0", @@ -55907,7 +55907,7 @@ "node": ">=12.9.0" }, "peerDependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" } }, "packages/atomic-angular": { @@ -55922,14 +55922,14 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic": "2.77.0", + "@coveo/atomic": "2.77.2", "rxjs": "7.8.1" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.8", "@angular/cli": "17.3.8", "@angular/compiler-cli": "17.3.12", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@types/jasmine": "5.1.4", "@types/node": "20.14.12", "jasmine-core": "5.2.0", @@ -55943,7 +55943,7 @@ "typescript": "5.4.5" }, "peerDependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" } }, "packages/atomic-angular/node_modules/jasmine-core": { @@ -55972,25 +55972,25 @@ }, "packages/atomic-angular/projects/atomic-angular": { "name": "@coveo/atomic-angular", - "version": "2.27.0", + "version": "2.27.2", "license": "Apache-2.0", "dependencies": { - "@coveo/atomic": "2.77.0", + "@coveo/atomic": "2.77.2", "tslib": "2.6.3" }, "peerDependencies": { "@angular/common": "14 - 17", "@angular/core": "14 - 17", - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" } }, "packages/atomic-hosted-page": { "name": "@coveo/atomic-hosted-page", - "version": "0.6.4", + "version": "0.6.6", "license": "Apache-2.0", "dependencies": { "@coveo/bueno": "0.46.1", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@stencil/core": "4.20.0" }, "devDependencies": { @@ -56069,12 +56069,12 @@ }, "packages/atomic-react": { "name": "@coveo/atomic-react", - "version": "2.13.3", + "version": "2.13.5", "dependencies": { - "@coveo/atomic": "2.77.0" + "@coveo/atomic": "2.77.2" }, "devDependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@coveo/release": "1.0.0", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.0.0", @@ -56091,7 +56091,7 @@ "rollup-plugin-polyfill-node": "^0.13.0" }, "peerDependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } @@ -59225,7 +59225,7 @@ }, "packages/headless": { "name": "@coveo/headless", - "version": "2.77.0", + "version": "2.79.0", "license": "Apache-2.0", "dependencies": { "@coveo/bueno": "0.46.1", @@ -59268,10 +59268,10 @@ }, "packages/headless-react": { "name": "@coveo/headless-react", - "version": "1.1.1", + "version": "1.1.3", "license": "Apache-2.0", "dependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" }, "devDependencies": { "@coveo/release": "1.0.0", @@ -59826,12 +59826,12 @@ }, "packages/quantic": { "name": "@coveo/quantic", - "version": "2.55.0", + "version": "2.57.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@coveo/bueno": "0.46.1", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "dompurify": "3.1.6", "marked": "12.0.2" }, @@ -61528,7 +61528,7 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic-angular": "2.27.0", + "@coveo/atomic-angular": "2.27.2", "rxjs": "7.8.1", "tslib": "2.6.3", "zone.js": "0.14.8" @@ -61829,9 +61829,9 @@ "name": "@coveo/atomic-next-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" @@ -61894,9 +61894,9 @@ "name": "@coveo/atomic-react-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "react": "18.3.1", "react-dom": "18.3.1" }, @@ -62384,7 +62384,7 @@ "name": "@coveo/headless-commerce-react-samples", "version": "0.1.0", "dependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/user-event": "14.5.2", @@ -64852,7 +64852,7 @@ "version": "0.0.0", "dependencies": { "@coveo/auth": "1.11.22", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", @@ -68518,8 +68518,8 @@ "name": "@coveo/headless-ssr-samples-common", "version": "0.0.0", "dependencies": { - "@coveo/headless": "2.77.0", - "@coveo/headless-react": "1.1.1", + "@coveo/headless": "2.79.0", + "@coveo/headless-react": "1.1.3", "next": "14.2.5", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -68543,7 +68543,7 @@ "name": "@coveo/headless-ssr-commerce-samples", "version": "0.0.0", "dependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "next": "14.2.5", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -68662,10 +68662,10 @@ "version": "0.1.0", "dependencies": { "@babel/standalone": "7.25.0", - "@coveo/atomic": "2.77.0", - "@coveo/atomic-hosted-page": "0.6.4", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-hosted-page": "0.6.6", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "react": "18.3.1", "react-dom": "18.3.1" }, @@ -68734,8 +68734,8 @@ "name": "@coveo/atomic-stencil-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/headless": "2.79.0", "@stencil/core": "4.20.0", "stencil-router-v2": "0.6.0" }, @@ -69018,7 +69018,7 @@ "name": "@coveo/atomic-vuejs-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.77.0", + "@coveo/atomic": "2.77.2", "vue": "^3.4.15" }, "devDependencies": { diff --git a/packages/atomic-angular/package.json b/packages/atomic-angular/package.json index 2e3662a979c..0c22d6b8c29 100644 --- a/packages/atomic-angular/package.json +++ b/packages/atomic-angular/package.json @@ -20,17 +20,17 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic": "2.77.0", + "@coveo/atomic": "2.77.2", "rxjs": "7.8.1" }, "peerDependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.8", "@angular/cli": "17.3.8", "@angular/compiler-cli": "17.3.12", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@types/jasmine": "5.1.4", "@types/node": "20.14.12", "jasmine-core": "5.2.0", diff --git a/packages/atomic-angular/projects/atomic-angular/CHANGELOG.md b/packages/atomic-angular/projects/atomic-angular/CHANGELOG.md index 2374f4f5704..f8027e3c64b 100644 --- a/packages/atomic-angular/projects/atomic-angular/CHANGELOG.md +++ b/packages/atomic-angular/projects/atomic-angular/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.27.1 (2024-08-20) + +### Bug Fixes + +- **refine-modal:** include facets from atomic-external ([#4219](https://github.com/coveo/ui-kit/issues/4219)) ([107c56b](https://github.com/coveo/ui-kit/commits/107c56b2d4a1467847210db05b1726b36ad4b50c)) + # 2.27.0 (2024-08-14) ### Features diff --git a/packages/atomic-angular/projects/atomic-angular/package.json b/packages/atomic-angular/projects/atomic-angular/package.json index 1e77ce8f223..f4472094f35 100644 --- a/packages/atomic-angular/projects/atomic-angular/package.json +++ b/packages/atomic-angular/projects/atomic-angular/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/atomic-angular", - "version": "2.27.0", + "version": "2.27.2", "license": "Apache-2.0", "repository": { "url": "https://github.com/coveo/ui-kit" @@ -8,10 +8,10 @@ "peerDependencies": { "@angular/common": "14 - 17", "@angular/core": "14 - 17", - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" }, "dependencies": { - "@coveo/atomic": "2.77.0", + "@coveo/atomic": "2.77.2", "tslib": "2.6.3" } } diff --git a/packages/atomic-hosted-page/package.json b/packages/atomic-hosted-page/package.json index f731b99dafd..3d64bed6768 100644 --- a/packages/atomic-hosted-page/package.json +++ b/packages/atomic-hosted-page/package.json @@ -1,7 +1,7 @@ { "name": "@coveo/atomic-hosted-page", "description": "Web Component used to inject a Coveo Hosted Search Page in the DOM.", - "version": "0.6.4", + "version": "0.6.6", "repository": { "type": "git", "url": "git+https://github.com/coveo/ui-kit.git", @@ -31,7 +31,7 @@ }, "dependencies": { "@coveo/bueno": "0.46.1", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@stencil/core": "4.20.0" }, "devDependencies": { diff --git a/packages/atomic-react/package.json b/packages/atomic-react/package.json index dfe510b59d0..37c7868bcb8 100644 --- a/packages/atomic-react/package.json +++ b/packages/atomic-react/package.json @@ -1,7 +1,7 @@ { "name": "@coveo/atomic-react", "sideEffects": false, - "version": "2.13.3", + "version": "2.13.5", "description": "React specific wrapper for the Atomic component library", "repository": { "type": "git", @@ -29,11 +29,11 @@ "commerce/" ], "dependencies": { - "@coveo/atomic": "2.77.0" + "@coveo/atomic": "2.77.2" }, "devDependencies": { "@coveo/release": "1.0.0", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-replace": "^5.0.0", @@ -49,7 +49,7 @@ "@rollup/plugin-terser": "0.4.4" }, "peerDependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } diff --git a/packages/atomic/CHANGELOG.md b/packages/atomic/CHANGELOG.md index 18e92103719..257308a2725 100644 --- a/packages/atomic/CHANGELOG.md +++ b/packages/atomic/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.77.1 (2024-08-20) + +### Bug Fixes + +- **refine-modal:** include facets from atomic-external ([#4219](https://github.com/coveo/ui-kit/issues/4219)) ([107c56b](https://github.com/coveo/ui-kit/commits/107c56b2d4a1467847210db05b1726b36ad4b50c)) + # 2.77.0 (2024-08-14) ### Bug Fixes diff --git a/packages/atomic/package.json b/packages/atomic/package.json index b50e9fc3c6a..935250ba696 100644 --- a/packages/atomic/package.json +++ b/packages/atomic/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/atomic", - "version": "2.77.0", + "version": "2.77.2", "description": "A web-component library for building modern UIs interfacing with the Coveo platform", "homepage": "https://docs.coveo.com/en/atomic/latest/", "repository": { @@ -66,7 +66,7 @@ "@axe-core/playwright": "4.9.1", "@babel/core": "7.24.9", "@coveo/atomic": "file:.", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@coveo/release": "1.0.0", "@custom-elements-manifest/analyzer": "0.10.3", "@fullhuman/postcss-purgecss": "6.0.0", @@ -140,7 +140,7 @@ "wait-on": "7.2.0" }, "peerDependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" }, "license": "Apache-2.0", "engines": { diff --git a/packages/headless-react/package.json b/packages/headless-react/package.json index 7360d3229c9..f404f3bd434 100644 --- a/packages/headless-react/package.json +++ b/packages/headless-react/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/headless-react", - "version": "1.1.1", + "version": "1.1.3", "description": "React utilities for SSR (Server Side Rendering) with headless", "homepage": "https://docs.coveo.com/en/headless/latest/", "repository": { @@ -33,7 +33,7 @@ "promote:npm:latest": "node ../../scripts/deploy/update-npm-tag.mjs latest" }, "dependencies": { - "@coveo/headless": "2.77.0" + "@coveo/headless": "2.79.0" }, "devDependencies": { "@coveo/release": "1.0.0", diff --git a/packages/headless/CHANGELOG.md b/packages/headless/CHANGELOG.md index 63cea604235..a60fad6b209 100644 --- a/packages/headless/CHANGELOG.md +++ b/packages/headless/CHANGELOG.md @@ -1,3 +1,20 @@ +# 2.79.0 (2024-08-21) + +### Features + +- **headless:** Add locale to insight search ([#4302](https://github.com/coveo/ui-kit/issues/4302)) ([3bea5b6](https://github.com/coveo/ui-kit/commits/3bea5b6df981f514f158fc530ee2e4428421b3e6)) + +# 2.78.0 (2024-08-20) + +### Bug Fixes + +- **answerApi:** search context trigger new request ([#4293](https://github.com/coveo/ui-kit/issues/4293)) ([49b33a6](https://github.com/coveo/ui-kit/commits/49b33a66d55f1823029e51c58ce72e696fb56f01)) +- **rga:** state reset keep configid ([#4286](https://github.com/coveo/ui-kit/issues/4286)) ([a822cea](https://github.com/coveo/ui-kit/commits/a822ceab6f6fd9866a261c73248f0d580eb8b05d)) + +### Features + +- **headless SSR:** support both search and listing solution types ([#4249](https://github.com/coveo/ui-kit/issues/4249)) ([dcd35d8](https://github.com/coveo/ui-kit/commits/dcd35d87e674a7d95e72800ae5b5dfc56d8ddcb8)) + # 2.77.0 (2024-08-14) ### Bug Fixes diff --git a/packages/headless/package.json b/packages/headless/package.json index 1c8f57e4e48..9c62f13c8ff 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -14,7 +14,7 @@ }, "types": "./dist/definitions/index.d.ts", "license": "Apache-2.0", - "version": "2.77.0", + "version": "2.79.0", "files": [ "dist/", "recommendation/", diff --git a/packages/headless/src/api/service/insight/query/query-request.ts b/packages/headless/src/api/service/insight/query/query-request.ts index 3e365a326d8..4cc00a566b6 100644 --- a/packages/headless/src/api/service/insight/query/query-request.ts +++ b/packages/headless/src/api/service/insight/query/query-request.ts @@ -1,6 +1,7 @@ import { ContextParam, FoldingParam, + LocaleParam, NumberOfResultsParam, } from '../../../platform-service-params'; import { @@ -36,6 +37,7 @@ export type InsightQueryRequest = InsightParam & TabParam & FoldingParam & ContextParam & + LocaleParam & PipelineRuleParams; interface CaseContextParam { diff --git a/packages/headless/src/app/insight-engine/insight-engine-configuration.ts b/packages/headless/src/app/insight-engine/insight-engine-configuration.ts index 93a2204b62e..9e3353965dd 100644 --- a/packages/headless/src/app/insight-engine/insight-engine-configuration.ts +++ b/packages/headless/src/app/insight-engine/insight-engine-configuration.ts @@ -1,5 +1,8 @@ -import {Schema} from '@coveo/bueno'; -import {requiredNonEmptyString} from '../../utils/validate-payload'; +import {RecordValue, Schema} from '@coveo/bueno'; +import { + nonEmptyString, + requiredNonEmptyString, +} from '../../utils/validate-payload'; import { EngineConfiguration, engineConfigurationDefinitions, @@ -13,10 +16,36 @@ export interface InsightEngineConfiguration extends EngineConfiguration { * Specifies the unique identifier of the target insight configuration. */ insightId: string; + /** + * Specifies the configuration for the insight search. + */ + search?: InsightEngineSearchConfigurationOptions; +} + +/** + * The insight engine search configuration options. + */ +export interface InsightEngineSearchConfigurationOptions { + /** + * The locale of the current user. Must comply with IETF’s BCP 47 definition: https://www.rfc-editor.org/rfc/bcp/bcp47.txt. + * + * Notes: + * Coveo Machine Learning models use this information to provide contextually relevant output. + * Moreover, this information can be referred to in query expressions and QPL statements by using the $locale object. + */ + locale?: string; } export const insightEngineConfigurationSchema = new Schema({ ...engineConfigurationDefinitions, insightId: requiredNonEmptyString, + search: new RecordValue({ + options: { + required: false, + }, + values: { + locale: nonEmptyString, + }, + }), }); diff --git a/packages/headless/src/app/insight-engine/insight-engine.test.ts b/packages/headless/src/app/insight-engine/insight-engine.test.ts index 111415266c5..f703bad376b 100644 --- a/packages/headless/src/app/insight-engine/insight-engine.test.ts +++ b/packages/headless/src/app/insight-engine/insight-engine.test.ts @@ -23,7 +23,12 @@ describe('buildInsightEngine', () => { beforeEach(() => { options = { - configuration: getSampleInsightEngineConfiguration(), + configuration: { + ...getSampleInsightEngineConfiguration(), + search: { + locale: 'en-US', + }, + }, loggerOptions: {level: 'silent'}, }; @@ -41,6 +46,10 @@ describe('buildInsightEngine', () => { ); }); + it('sets the locale correctly', () => { + expect(engine.state.configuration?.search?.locale).toEqual('en-US'); + }); + it('exposes an #executeFirstSearch method', () => { expect(engine.executeFirstSearch).toBeTruthy(); }); diff --git a/packages/headless/src/app/insight-engine/insight-engine.ts b/packages/headless/src/app/insight-engine/insight-engine.ts index 12a191f632b..d18c542377b 100644 --- a/packages/headless/src/app/insight-engine/insight-engine.ts +++ b/packages/headless/src/app/insight-engine/insight-engine.ts @@ -5,6 +5,7 @@ import {NoopPreprocessRequest} from '../../api/preprocess-request'; import {InsightAPIClient} from '../../api/service/insight/insight-api-client'; import {interfaceLoad} from '../../features/analytics/analytics-actions'; import {LegacySearchAction} from '../../features/analytics/analytics-utils'; +import {updateSearchConfiguration} from '../../features/configuration/configuration-actions'; import {setInsightConfiguration} from '../../features/insight-configuration/insight-configuration-actions'; import {insightConfigurationReducer as insightConfiguration} from '../../features/insight-configuration/insight-configuration-slice'; import {insightInterfaceReducer as insightInterface} from '../../features/insight-interface/insight-interface-slice'; @@ -28,9 +29,13 @@ import {buildThunkExtraArguments} from '../thunk-extra-arguments'; import { InsightEngineConfiguration, insightEngineConfigurationSchema, + InsightEngineSearchConfigurationOptions, } from './insight-engine-configuration'; -export type {InsightEngineConfiguration}; +export type { + InsightEngineConfiguration, + InsightEngineSearchConfigurationOptions, +}; const insightEngineReducers = { insightConfiguration, @@ -99,7 +104,7 @@ export function buildInsightEngine( const engine = buildEngine(augmentedOptions, thunkArguments); - const {insightId} = options.configuration; + const {insightId, search} = options.configuration; engine.dispatch( setInsightConfiguration({ @@ -107,6 +112,10 @@ export function buildInsightEngine( }) ); + if (search) { + engine.dispatch(updateSearchConfiguration(search)); + } + return { ...engine, diff --git a/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts b/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts new file mode 100644 index 00000000000..1822d547246 --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts @@ -0,0 +1,36 @@ +import {ensureAtLeastOneSolutionType} from '../../../../app/commerce-ssr-engine/common'; +import { + ControllerDefinitionOption, + SolutionType, + SubControllerDefinitionWithoutProps, +} from '../../../../app/commerce-ssr-engine/types/common'; +import {buildProductListing} from '../../product-listing/headless-product-listing'; +import {buildSearch} from '../../search/headless-search'; +import { + Pagination, + PaginationProps, + PaginationState, +} from './headless-core-commerce-pagination'; + +export type {Pagination, PaginationProps, PaginationState}; + +/** + * Defines a `Pagination` controller instance. + * + * @param props - The configurable `Pagination` properties. + * @returns The `Pagination` controller definition. + * + * @internal + */ +export function definePagination< + TOptions extends ControllerDefinitionOption | undefined, +>(props?: PaginationProps, options?: TOptions) { + ensureAtLeastOneSolutionType(options); + return { + ...options, + build: (engine, solutionType) => + solutionType === SolutionType.listing + ? buildProductListing(engine).pagination(props) + : buildSearch(engine).pagination(props), + } as SubControllerDefinitionWithoutProps; +} 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 new file mode 100644 index 00000000000..17e2dc5b1c7 --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -0,0 +1,105 @@ +import {ensureAtLeastOneSolutionType} from '../../../../app/commerce-ssr-engine/common'; +import { + ControllerDefinitionOption, + SolutionType, + SubControllerDefinitionWithProps, +} from '../../../../app/commerce-ssr-engine/types/common'; +import {CoreEngineNext} from '../../../../app/engine'; +import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice'; +import {manualNumericFacetReducer as manualNumericFacetSet} from '../../../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice'; +import {paginationReducer as commercePagination} from '../../../../features/commerce/pagination/pagination-slice'; +import {Parameters} from '../../../../features/commerce/parameters/parameters-actions'; +import {ProductListingParameters} from '../../../../features/commerce/product-listing-parameters/product-listing-parameters-actions'; +import {queryReducer as query} from '../../../../features/commerce/query/query-slice'; +import {CommerceSearchParameters} from '../../../../features/commerce/search-parameters/search-parameters-actions'; +import {sortReducer as commerceSort} from '../../../../features/commerce/sort/sort-slice'; +import {facetOrderReducer as facetOrder} from '../../../../features/facets/facet-order/facet-order-slice'; +import {querySetReducer as querySet} from '../../../../features/query-set/query-set-slice'; +import {loadReducerError} from '../../../../utils/errors'; +import {buildProductListing} from '../../product-listing/headless-product-listing'; +import {buildSearch} from '../../search/headless-search'; +import { + ParameterManager, + ParameterManagerProps, + ParameterManagerState, +} from './headless-core-parameter-manager'; + +export type { + ParameterManager, + ParameterManagerProps, + ParameterManagerState, + Parameters, + ProductListingParameters, + CommerceSearchParameters, +}; + +/** + * Defines a `ParameterManager` controller instance. + * + * @returns The `ParameterManager` controller definition. + * + * @internal + */ +export function defineParameterManager< + TOptions extends ControllerDefinitionOption | undefined, +>(options?: TOptions) { + ensureAtLeastOneSolutionType(options); + return { + ...options, + buildWithProps: (engine, props, solutionType) => { + if (solutionType === SolutionType.listing) { + if (!loadCommerceProductListingParameterReducers(engine)) { + throw loadReducerError; + } + return buildProductListing(engine).parameterManager(props); + } else { + if (!loadCommerceSearchParameterReducers(engine)) { + throw loadReducerError; + } + return buildSearch(engine).parameterManager(props); + } + }, + } as SubControllerDefinitionWithProps< + ParameterManager>, + TOptions, + ParameterManagerProps> + >; +} + +type MappedParameterTypes< + TOptions extends ControllerDefinitionOption | undefined, +> = TOptions extends {listing: true; search: true} | undefined + ? ProductListingParameters | CommerceSearchParameters + : TOptions extends {listing: true; search: false} + ? ProductListingParameters + : TOptions extends {listing: false; search: true} + ? CommerceSearchParameters + : never; + +function loadCommerceCommonParameterReducers( + engine: CoreEngineNext +): engine is CoreEngineNext> { + engine.addReducers({ + commerceFacetSet, + commerceSort, + commercePagination, + facetOrder, + manualNumericFacetSet, + }); + return true; +} + +function loadCommerceSearchParameterReducers( + engine: CoreEngineNext +): engine is CoreEngineNext> { + loadCommerceCommonParameterReducers(engine); + engine.addReducers({query, querySet}); + return true; +} + +function loadCommerceProductListingParameterReducers( + engine: CoreEngineNext +): engine is CoreEngineNext> { + loadCommerceCommonParameterReducers(engine); + return true; +} diff --git a/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts b/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts new file mode 100644 index 00000000000..501c93c9efe --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts @@ -0,0 +1,32 @@ +import {ensureAtLeastOneSolutionType} from '../../../../app/commerce-ssr-engine/common'; +import { + ControllerDefinitionOption, + SolutionType, + SubControllerDefinitionWithoutProps, +} from '../../../../app/commerce-ssr-engine/types/common'; +import {buildProductListing} from '../../product-listing/headless-product-listing'; +import {buildSearch} from '../../search/headless-search'; +import {Sort, SortProps, SortState} from './headless-core-commerce-sort'; + +export type {Sort, SortProps, SortState}; + +/** + * Defines a `Sort` controller instance. + * + * @param props - The configurable `Sort` properties. + * @returns The `Sort` controller definition. + * + * @internal + */ +export function defineSort< + TOptions extends ControllerDefinitionOption | undefined, +>(props?: SortProps, options?: TOptions) { + ensureAtLeastOneSolutionType(options); + return { + ...options, + build: (engine, solutionType) => + solutionType === SolutionType.listing + ? buildProductListing(engine).sort(props) + : buildSearch(engine).sort(props), + } as SubControllerDefinitionWithoutProps; +} diff --git a/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts b/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts new file mode 100644 index 00000000000..ace73ffa7f0 --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts @@ -0,0 +1,40 @@ +import {ensureAtLeastOneSolutionType} from '../../../../app/commerce-ssr-engine/common'; +import { + ControllerDefinitionOption, + SolutionType, + SubControllerDefinitionWithoutProps, +} from '../../../../app/commerce-ssr-engine/types/common'; +import {buildProductListing} from '../../product-listing/headless-product-listing'; +import {ProductListingSummaryState} from '../../product-listing/summary/headless-product-listing-summary'; +import {RecommendationsSummaryState} from '../../recommendations/summary/headless-recommendations-summary'; +import {buildSearch} from '../../search/headless-search'; +import {SearchSummaryState} from '../../search/summary/headless-search-summary'; +import {Summary, SummaryState} from './headless-core-summary'; + +export type { + Summary, + ProductListingSummaryState, + RecommendationsSummaryState, + SearchSummaryState, + SummaryState, +}; + +/** + * Defines a `Summary` controller instance. + * + * @returns The `Summary` controller definition. + * + * @internal + */ +export function defineSummary< + TOptions extends ControllerDefinitionOption | undefined, +>(options?: TOptions) { + ensureAtLeastOneSolutionType(options); + return { + ...options, + build: (engine, solutionType) => + solutionType === SolutionType.listing + ? buildProductListing(engine).summary() + : buildSearch(engine).summary(), + } as SubControllerDefinitionWithoutProps; +} diff --git a/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts b/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts index 69163c28983..444b7bc7e7a 100644 --- a/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts +++ b/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts @@ -8,7 +8,10 @@ import {buildSearch, Search} from '../search/headless-search'; import {ProductListing, buildProductListing} from './headless-product-listing'; export type {ProductListingState as ProductListState} from './headless-product-listing'; -export type ProductList = Pick; +export type ProductList = Pick< + ProductListing | Search, + 'state' | 'subscribe' | 'interactiveProduct' +>; /** * Defines a `ProductListing` controller instance. diff --git a/packages/headless/src/controllers/commerce/product-view/headless-product-view.ssr.ts b/packages/headless/src/controllers/commerce/product-view/headless-product-view.ssr.ts new file mode 100644 index 00000000000..4d4862a1fdb --- /dev/null +++ b/packages/headless/src/controllers/commerce/product-view/headless-product-view.ssr.ts @@ -0,0 +1,43 @@ +import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine'; +import {SharedControllerDefinitionWithoutProps} from '../../../app/commerce-ssr-engine/types/common'; +import { + buildController, + Controller, +} from '../../controller/headless-controller'; +import { + buildProductView, + ProductView as BaseProductView, +} from './headless-product-view'; + +export interface ProductViewDefinition + extends SharedControllerDefinitionWithoutProps {} + +/** + * Defines a `ProductView` controller instance. + * + * This controller is stateless and does not implement a `subscribe` method, + * making it simpler but different from other controllers in the system. + * Its sole purpose is to log an `ec.productView` event. + * + * @returns The `ProductView` controller definition. + * + * @internal + */ +export function defineProductView(): ProductViewDefinition { + return { + listing: true, + search: true, + build: (engine) => buildSSRProductView(engine), + }; +} + +export interface ProductView extends BaseProductView, Controller {} + +function buildSSRProductView(engine: CommerceEngine): ProductView { + const controller = buildController(engine); + const productView = buildProductView(engine); + return { + ...controller, + ...productView, + }; +} diff --git a/packages/headless/src/controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr.ts b/packages/headless/src/controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr.ts new file mode 100644 index 00000000000..73844ef12e2 --- /dev/null +++ b/packages/headless/src/controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr.ts @@ -0,0 +1,19 @@ +import {SearchOnlyControllerDefinitionWithoutProps} from '../../../../app/commerce-ssr-engine/types/common'; +import {buildSearch} from '../headless-search'; +import {DidYouMean, DidYouMeanState} from './headless-did-you-mean'; + +export type {DidYouMean, DidYouMeanState}; + +/** + * Defines a `DidYouMean` controller instance. + * + * @returns The `DidYouMean` controller definition. + * + * @internal + * */ +export function defineDidYouMean(): SearchOnlyControllerDefinitionWithoutProps { + return { + search: true, + build: (engine) => buildSearch(engine).didYouMean(), + }; +} diff --git a/packages/headless/src/features/commerce/parameters/parameters-serializer.ts b/packages/headless/src/features/commerce/parameters/parameters-serializer.ts index 059fa63cb82..e4f71fe1e71 100644 --- a/packages/headless/src/features/commerce/parameters/parameters-serializer.ts +++ b/packages/headless/src/features/commerce/parameters/parameters-serializer.ts @@ -38,6 +38,8 @@ export const searchSerializer: Serializer = { deserialize, }; +// TODO KIT-3462: add/export commerce SSR parameter serializer + export const productListingSerializer = { serialize, deserialize, diff --git a/packages/headless/src/features/insight-search/insight-search-request.test.ts b/packages/headless/src/features/insight-search/insight-search-request.test.ts index f26b5cde06f..822db02d2a2 100644 --- a/packages/headless/src/features/insight-search/insight-search-request.test.ts +++ b/packages/headless/src/features/insight-search/insight-search-request.test.ts @@ -39,6 +39,7 @@ describe('insight search request', () => { expect(params.accessToken).toBe(state.configuration.accessToken); expect(params.organizationId).toBe(state.configuration.organizationId); expect(params.url).toBe(state.configuration.platformUrl); + expect(params.locale).toBe(state.configuration.search.locale); }); it('#buildInsightSearchRequest returns the state #insightId', async () => { diff --git a/packages/headless/src/features/insight-search/insight-search-request.ts b/packages/headless/src/features/insight-search/insight-search-request.ts index 527f9a2aa4e..eab2b7ddd7a 100644 --- a/packages/headless/src/features/insight-search/insight-search-request.ts +++ b/packages/headless/src/features/insight-search/insight-search-request.ts @@ -21,11 +21,11 @@ export const buildInsightBaseRequest = async ( ): Promise> => { const cq = buildConstantQuery(state); const facets = getAllFacets(state); - return mapSearchRequest({ accessToken: state.configuration.accessToken, organizationId: state.configuration.organizationId, url: state.configuration.platformUrl, + locale: state.configuration.search.locale, insightId: state.insightConfiguration.insightId, ...(state.configuration.analytics.enabled && (await fromAnalyticsStateToAnalyticsParams( diff --git a/packages/headless/src/insight.index.ts b/packages/headless/src/insight.index.ts index ed5effa583a..3c4d75589ad 100644 --- a/packages/headless/src/insight.index.ts +++ b/packages/headless/src/insight.index.ts @@ -11,6 +11,7 @@ export type { InsightEngine, InsightEngineOptions, InsightEngineConfiguration, + InsightEngineSearchConfigurationOptions, } from './app/insight-engine/insight-engine'; export {buildInsightEngine} from './app/insight-engine/insight-engine'; diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index d6ab85296a9..e1002f58379 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -53,17 +53,28 @@ export type { Subscribable, } from './controllers/controller/headless-controller'; -export type {CategoryFacet} from './controllers/commerce/core/facets/category/headless-commerce-category-facet'; export type { - DateFacet, - DateFacetState, -} from './controllers/commerce/core/facets/date/headless-commerce-date-facet'; -export type {RegularFacetValue} from './controllers/commerce/core/facets/headless-core-commerce-facet'; + DidYouMean, + DidYouMeanState, +} from './controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr'; +export {defineDidYouMean} from './controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr'; + +export type { + Pagination, + PaginationProps, + PaginationState, +} from './controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr'; +export {definePagination} from './controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr'; + export type { - NumericFacet, - NumericFacetState, -} from './controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet'; -export type {RegularFacet} from './controllers/commerce/core/facets/regular/headless-commerce-regular-facet'; + ParameterManager, + ParameterManagerProps, + ParameterManagerState, + Parameters, + ProductListingParameters, + CommerceSearchParameters, +} from './controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr'; +export {defineParameterManager} from './controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr'; export type { ProductList, @@ -71,11 +82,24 @@ export type { } from './controllers/commerce/product-listing/headless-product-listing.ssr'; export {defineProductList} from './controllers/commerce/product-listing/headless-product-listing.ssr'; +export type {ProductView} from './controllers/commerce/product-view/headless-product-view.ssr'; +export {defineProductView} from './controllers/commerce/product-view/headless-product-view.ssr'; + +export type { + Sort, + SortProps, + SortState, +} from './controllers/commerce/core/sort/headless-core-commerce-sort.ssr'; +export {defineSort} from './controllers/commerce/core/sort/headless-core-commerce-sort.ssr'; + export type { - ProductListingSummaryState, Summary, -} from './controllers/commerce/core/sub-controller/headless-sub-controller.ssr'; -export {defineQuerySummary} from './controllers/commerce/core/sub-controller/headless-sub-controller.ssr'; + ProductListingSummaryState, + RecommendationsSummaryState, + SearchSummaryState, + SummaryState, +} from './controllers/commerce/core/summary/headless-core-summary.ssr'; +export {defineSummary} from './controllers/commerce/core/summary/headless-core-summary.ssr'; // TODO: KIT-3391 - export other SSR commerce controllers @@ -109,7 +133,7 @@ export {buildResultTemplatesManager} from './features/result-templates/result-te //#endregion // Types & Helpers -export {buildSSRSearchParameterSerializer} from './features/search-parameters/search-parameter-serializer.ssr'; +// TODO KIT-3462: add export commerce SSR parameter serializer export type { BaseProduct, Product, @@ -134,7 +158,7 @@ export { buildRelevanceSortCriterion, } from './features/sort-criteria/criteria'; export {parseCriterionExpression} from './features/sort-criteria/criteria-parser'; -export type {Template} from './features/templates/templates-manager.ts'; +export type {Template} from './features/templates/templates-manager'; export type { ProductTemplate, ProductTemplateCondition, diff --git a/packages/quantic/CHANGELOG.md b/packages/quantic/CHANGELOG.md index ad2d352155a..81f292e663c 100644 --- a/packages/quantic/CHANGELOG.md +++ b/packages/quantic/CHANGELOG.md @@ -1,3 +1,16 @@ +# 2.57.0 (2024-08-21) + +### Features + +- **headless:** Add locale to insight search ([#4302](https://github.com/coveo/ui-kit/issues/4302)) ([3bea5b6](https://github.com/coveo/ui-kit/commits/3bea5b6df981f514f158fc530ee2e4428421b3e6)) + +# 2.56.0 (2024-08-20) + +### Features + +- **quantic:** Add option for rephrase buttons, remove citations numbers ([#4277](https://github.com/coveo/ui-kit/issues/4277)) ([e65b99f](https://github.com/coveo/ui-kit/commits/e65b99f2bf1b8f2fead1595942a439731dd3f623)) +- **quantic:** added support for custom options in the quanticSort component ([#4101](https://github.com/coveo/ui-kit/issues/4101)) ([ecb5e24](https://github.com/coveo/ui-kit/commits/ecb5e2484b80822356dba24130e8058deb1413a6)) + # 2.55.0 (2024-08-14) ### Bug Fixes diff --git a/packages/quantic/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js b/packages/quantic/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js index 3cceb57b57c..b7835389f08 100644 --- a/packages/quantic/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js +++ b/packages/quantic/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js @@ -1,5 +1,6 @@ // @ts-ignore import getHeadlessConfiguration from '@salesforce/apex/InsightController.getHeadlessConfiguration'; +import LOCALE from '@salesforce/i18n/locale'; import { getHeadlessBindings, loadDependencies, @@ -64,6 +65,9 @@ export default class QuanticInsightInterface extends LightningElement { configuration: { ...JSON.parse(data), insightId: this.insightId, + search: { + locale: LOCALE, + }, }, }; setEngineOptions( diff --git a/packages/quantic/package.json b/packages/quantic/package.json index 2c060f6619f..8c55a0f1fb0 100644 --- a/packages/quantic/package.json +++ b/packages/quantic/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/quantic", - "version": "2.55.0", + "version": "2.57.0", "description": "A Salesforce Lightning Web Component (LWC) library for building modern UIs interfacing with the Coveo platform", "author": "coveo.com", "homepage": "https://coveo.com", @@ -46,7 +46,7 @@ }, "dependencies": { "@coveo/bueno": "0.46.1", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "dompurify": "3.1.6", "marked": "12.0.2" }, diff --git a/packages/samples/angular/package.json b/packages/samples/angular/package.json index bbf83b992dc..c44f8513299 100644 --- a/packages/samples/angular/package.json +++ b/packages/samples/angular/package.json @@ -19,7 +19,7 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic-angular": "2.27.0", + "@coveo/atomic-angular": "2.27.2", "rxjs": "7.8.1", "tslib": "2.6.3", "zone.js": "0.14.8" diff --git a/packages/samples/atomic-next/package.json b/packages/samples/atomic-next/package.json index 16261c6835b..b90af090034 100644 --- a/packages/samples/atomic-next/package.json +++ b/packages/samples/atomic-next/package.json @@ -3,9 +3,9 @@ "version": "0.0.0", "private": true, "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/packages/samples/atomic-react/package.json b/packages/samples/atomic-react/package.json index 9d66c59f4f2..9c2c3c94fcc 100644 --- a/packages/samples/atomic-react/package.json +++ b/packages/samples/atomic-react/package.json @@ -4,9 +4,9 @@ "description": "Samples with atomic-react", "private": true, "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "react": "18.3.1", "react-dom": "18.3.1" }, diff --git a/packages/samples/headless-commerce-react/package.json b/packages/samples/headless-commerce-react/package.json index bc099295460..973bb929fa5 100644 --- a/packages/samples/headless-commerce-react/package.json +++ b/packages/samples/headless-commerce-react/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/user-event": "14.5.2", diff --git a/packages/samples/headless-react/package.json b/packages/samples/headless-react/package.json index ec03507bcb2..5f6c85aa425 100644 --- a/packages/samples/headless-react/package.json +++ b/packages/samples/headless-react/package.json @@ -5,7 +5,7 @@ "private": true, "dependencies": { "@coveo/auth": "1.11.22", - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", diff --git a/packages/samples/headless-ssr-commerce/app/_components/did-you-mean.tsx b/packages/samples/headless-ssr-commerce/app/_components/did-you-mean.tsx new file mode 100644 index 00000000000..f772230fb67 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/did-you-mean.tsx @@ -0,0 +1 @@ +// TODO // TODO KIT-3463: implement did you mean in sample diff --git a/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx index ef9b0fa1d30..03c1492ddfe 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx @@ -7,8 +7,11 @@ import { ListingHydratedState, ListingStaticState, } from '../_lib/commerce-engine'; +import Pagination from './pagination'; import {ProductList} from './product-list'; -import {Summary} from './summary'; +// import ShowMore from './show-more'; +import Sort from './sort'; +import Summary from './summary'; export default function ListingPage({ staticState, @@ -36,16 +39,28 @@ export default function ListingPage({ return ( <> - {/* TODO: add UI component here */} - + + + {/* The ShowMore and Pagination components showcase two frequent ways to implement pagination. */} + + {/* */} ); } diff --git a/packages/samples/headless-ssr-commerce/app/_components/pagination.tsx b/packages/samples/headless-ssr-commerce/app/_components/pagination.tsx new file mode 100644 index 00000000000..c2e25737725 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/pagination.tsx @@ -0,0 +1,61 @@ +import { + Pagination as HeadlessPagination, + PaginationState, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState} from 'react'; + +interface IPaginationProps { + staticState: PaginationState; + controller?: HeadlessPagination; +} + +export default function Pagination(props: IPaginationProps) { + const {staticState, controller} = props; + + const [state, setState] = useState(staticState); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + 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-ssr-commerce/app/_components/product-list.tsx b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx index ef5315963d3..643125e4a49 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx @@ -1,7 +1,9 @@ import { + Product, ProductList as ProductListingController, ProductListState, } from '@coveo/headless/ssr-commerce'; +import {useRouter} from 'next/navigation'; import {useEffect, useState, FunctionComponent} from 'react'; interface ProductListProps { @@ -15,16 +17,30 @@ export const ProductList: FunctionComponent = ({ }) => { const [state, setState] = useState(staticState); + const router = useRouter(); + useEffect( () => controller?.subscribe(() => setState({...controller.state})), [controller] ); + const onProductClick = (product: Product) => { + controller?.interactiveProduct({options: {product}}).select(); + router.push( + `/products/${product.ec_product_id}?name=${product.ec_name}&price=${product.ec_price}` + ); + }; + return (
    {state.products.map((product) => (
  • -

    {product.ec_name}

    +
  • ))}
diff --git a/packages/samples/headless-ssr-commerce/app/_components/product-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/product-page.tsx new file mode 100644 index 00000000000..f426375ed61 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/product-page.tsx @@ -0,0 +1,54 @@ +'use client'; + +import {NavigatorContext} from '@coveo/headless/ssr-commerce'; +import {useSearchParams} from 'next/navigation'; +import {useEffect, useState} from 'react'; +import { + searchEngineDefinition, + SearchHydratedState, + SearchStaticState, +} from '../_lib/commerce-engine'; + +interface IProductPageProps { + staticState: SearchStaticState; + navigatorContext: NavigatorContext; + productId: string; +} + +export default function ProductPage(props: IProductPageProps) { + const [hydratedState, setHydratedState] = useState< + SearchHydratedState | undefined + >(undefined); + + const {staticState, navigatorContext, productId} = props; + + const searchParams = useSearchParams(); + + const price = Number(searchParams.get('price')) ?? NaN; + const name = searchParams.get('name') ?? productId; + + // Setting the navigator context provider also in client-side before hydrating the application + searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); + + useEffect(() => { + searchEngineDefinition + .hydrateStaticState({ + searchAction: staticState.searchAction, + }) + .then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + }); + }, [staticState]); + + const controller = hydratedState?.controllers.productView; + + useEffect(() => { + controller?.view({name, productId, price}); + }, [controller, productId, name, price]); + + return ( +

+ {name} ({productId}) - ${price} +

+ ); +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/show-more.tsx b/packages/samples/headless-ssr-commerce/app/_components/show-more.tsx new file mode 100644 index 00000000000..5ba95e57d8f --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/show-more.tsx @@ -0,0 +1,58 @@ +import { + Pagination as HeadlessPagination, + PaginationState, + Summary, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState} from 'react'; + +interface IShowMoreProps { + staticState: PaginationState; + summaryController?: Summary; + controller?: HeadlessPagination; +} + +export default function ShowMore(props: IShowMoreProps) { + const {controller, summaryController, staticState} = props; + + const [state, setState] = useState(staticState); + const [summaryState, setSummaryState] = useState( + props.summaryController?.state + ); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + useEffect(() => { + summaryController?.subscribe(() => + setSummaryState(summaryController.state) + ); + }, [summaryController]); + + const handleFetchMore = () => { + controller?.fetchMoreProducts(); + }; + + const isDisabled = () => { + return ( + !controller || + summaryState?.lastProduct === summaryState?.totalNumberOfProducts + ); + }; + + return ( + <> +
+ Displaying {summaryState?.lastProduct ?? state.pageSize} out of{' '} + {state.totalEntries} products +
+ + + ); +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/sort.tsx b/packages/samples/headless-ssr-commerce/app/_components/sort.tsx new file mode 100644 index 00000000000..c0f0d661adb --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/sort.tsx @@ -0,0 +1,58 @@ +import {SortState} from '@coveo/headless/commerce'; +import { + Sort as HeadlessSort, + SortBy, + SortCriterion, +} from '@coveo/headless/commerce'; +import {useEffect, useState} from 'react'; + +interface ISortProps { + controller?: HeadlessSort; + staticState: SortState; +} + +export default function Sort(props: ISortProps) { + const {controller, staticState} = props; + + const [state, setState] = useState({...staticState}); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + if (state.availableSorts.length === 0) { + return null; + } + + const getSortLabel = (criterion: SortCriterion) => { + switch (criterion.by) { + case SortBy.Relevance: + return 'Relevance'; + case SortBy.Fields: + return criterion.fields.map((field) => field.displayName).join(', '); + } + }; + + return ( +
+ + +
+ ); +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/summary.tsx b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx index 8be36db7fa7..f267bca3300 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/summary.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx @@ -1,48 +1,58 @@ import { + Summary as HeadlessSummary, ProductListingSummaryState, - Summary as SummaryController, + SearchSummaryState, + RecommendationsSummaryState, } from '@coveo/headless/ssr-commerce'; -import {useEffect, useState, FunctionComponent} from 'react'; -import {ListingHydratedState} from '../_lib/commerce-engine'; +import {useEffect, useState} from 'react'; -interface SummaryProps { - hydratedState?: ListingHydratedState; - staticState: ProductListingSummaryState; - controller?: SummaryController; +interface ISummaryProps { + controller?: HeadlessSummary; + staticState: + | ProductListingSummaryState + | SearchSummaryState + | RecommendationsSummaryState; } -export const Summary: FunctionComponent = ({ - staticState, - hydratedState, - controller, -}: SummaryProps) => { +export default function Summary(props: ISummaryProps) { + const {controller, staticState} = props; + const [state, setState] = useState(staticState); - useEffect( - () => controller?.subscribe?.(() => setState({...controller.state})), - [controller] - ); - - return ( - <> -
- Hydrated:{' '} - -
- - Rendered page with {state.totalNumberOfProducts} results + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + 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} -
- Rendered on{' '} - - {new Date().toISOString()} - -
- - ); -}; + ); + }; + + const renderSummary = () => { + return ( +

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

+ ); + }; + + return
{renderSummary()}
; +} diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts index f587b62afd4..f0c865a76e5 100644 --- a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts @@ -4,8 +4,12 @@ import { CommerceEngineDefinitionOptions, CommerceEngine, defineProductList, + defineSummary, + definePagination, + defineSort, + defineProductView, getSampleCommerceEngineConfiguration, - defineQuerySummary, + defineDidYouMean, //defineParameterManager, } from '@coveo/headless/ssr-commerce'; type CommerceEngineConfig = CommerceEngineDefinitionOptions< @@ -15,9 +19,22 @@ type CommerceEngineConfig = CommerceEngineDefinitionOptions< export default { configuration: { ...getSampleCommerceEngineConfiguration(), + context: { + language: 'en', + country: 'US', + currency: 'USD', + view: { + url: 'https://sports.barca.group/browse/promotions/ui-kit-testing', + }, + }, }, controllers: { - summary: defineQuerySummary(), + summary: defineSummary(), productList: defineProductList(), + pagination: definePagination({options: {pageSize: 9}}), + sort: defineSort(), + productView: defineProductView(), + didYouMean: defineDidYouMean(), // TODO KIT-3463: implement did you mean in sample + //parameterManager: defineParameterManager(), // TODO KIT-3462: implement parameter manager in sample }, } satisfies CommerceEngineConfig; diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts index 1abf5d8f9b0..e4519f5e1ba 100644 --- a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts @@ -11,15 +11,13 @@ export const {listingEngineDefinition, searchEngineDefinition} = engineDefinition; export type ListingStaticState = InferStaticState< - typeof searchEngineDefinition + typeof listingEngineDefinition >; export type ListingHydratedState = InferHydratedState< - typeof searchEngineDefinition ->; - -export type SearchStaticState = InferStaticState< typeof listingEngineDefinition >; + +export type SearchStaticState = InferStaticState; export type SearchHydratedState = InferHydratedState< - typeof listingEngineDefinition + typeof searchEngineDefinition >; diff --git a/packages/samples/headless-ssr-commerce/app/products/[productId]/page.tsx b/packages/samples/headless-ssr-commerce/app/products/[productId]/page.tsx new file mode 100644 index 00000000000..dade8bc6550 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/products/[productId]/page.tsx @@ -0,0 +1,32 @@ +import ProductPage from '@/app/_components/product-page'; +import {searchEngineDefinition} from '@/app/_lib/commerce-engine'; +import {NextJsNavigatorContext} from '@/app/_lib/navigatorContextProvider'; +import {headers} from 'next/headers'; +import {Suspense} from 'react'; + +export default async function ProductDescriptionPage({ + params, +}: { + params: {productId: string}; +}) { + // Sets the navigator context provider to use the newly created `navigatorContext` before fetching the app static state + const navigatorContext = new NextJsNavigatorContext(headers()); + searchEngineDefinition.setNavigatorContextProvider(() => navigatorContext); + + // Fetches the static state of the app with initial state (when applicable) + const staticState = await searchEngineDefinition.fetchStaticState(); + return ( + <> +

Product description page

+ Loading...

}> + +
+ + ); +} + +export const dynamic = 'force-dynamic'; diff --git a/packages/samples/headless-ssr-commerce/package.json b/packages/samples/headless-ssr-commerce/package.json index 9955e076938..48843bcd66f 100644 --- a/packages/samples/headless-ssr-commerce/package.json +++ b/packages/samples/headless-ssr-commerce/package.json @@ -11,7 +11,7 @@ "build:next": "next build" }, "dependencies": { - "@coveo/headless": "2.77.0", + "@coveo/headless": "2.79.0", "next": "14.2.5", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/packages/samples/headless-ssr/package.json b/packages/samples/headless-ssr/package.json index 0374aa632d6..ccdd8b90289 100644 --- a/packages/samples/headless-ssr/package.json +++ b/packages/samples/headless-ssr/package.json @@ -8,8 +8,8 @@ "e2e:watch": "cypress open --browser chrome --e2e" }, "dependencies": { - "@coveo/headless-react": "1.1.1", - "@coveo/headless": "2.77.0", + "@coveo/headless-react": "1.1.3", + "@coveo/headless": "2.79.0", "next": "14.2.5", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/packages/samples/iife/package.json b/packages/samples/iife/package.json index f9d8b1255b9..e4d6dc0141a 100644 --- a/packages/samples/iife/package.json +++ b/packages/samples/iife/package.json @@ -12,10 +12,10 @@ }, "dependencies": { "@babel/standalone": "7.25.0", - "@coveo/atomic": "2.77.0", - "@coveo/atomic-hosted-page": "0.6.4", - "@coveo/atomic-react": "2.13.3", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/atomic-hosted-page": "0.6.6", + "@coveo/atomic-react": "2.13.5", + "@coveo/headless": "2.79.0", "react": "18.3.1", "react-dom": "18.3.1" }, diff --git a/packages/samples/stencil/package.json b/packages/samples/stencil/package.json index 3c18baf7f16..3c1be41598c 100644 --- a/packages/samples/stencil/package.json +++ b/packages/samples/stencil/package.json @@ -8,8 +8,8 @@ "e2e:watch": "cypress open --browser chrome --e2e" }, "dependencies": { - "@coveo/atomic": "2.77.0", - "@coveo/headless": "2.77.0", + "@coveo/atomic": "2.77.2", + "@coveo/headless": "2.79.0", "@stencil/core": "4.20.0", "stencil-router-v2": "0.6.0" }, diff --git a/packages/samples/vuejs/package.json b/packages/samples/vuejs/package.json index c0089ac455c..1a281c0c643 100644 --- a/packages/samples/vuejs/package.json +++ b/packages/samples/vuejs/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "vue": "^3.4.15", - "@coveo/atomic": "2.77.0" + "@coveo/atomic": "2.77.2" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.3",