{this.renderLabel()}
{this.renderValues()}
diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx
index 9e049738495..2478c398f26 100644
--- a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx
+++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx
@@ -16,6 +16,9 @@ import {
SearchStatusState,
FacetValueRequest,
CategoryFacetValueRequest,
+ buildTabManager,
+ TabManager,
+ TabManagerState,
} from '@coveo/headless';
import {Component, Element, h, Listen, Prop, State} from '@stencil/core';
import {FocusTargetController} from '../../../../utils/accessibility-utils';
@@ -24,9 +27,10 @@ import {
InitializableComponent,
InitializeBindings,
} from '../../../../utils/initialization-utils';
-import {MapProp} from '../../../../utils/props-utils';
+import {ArrayProp, MapProp} from '../../../../utils/props-utils';
import {parseDependsOn} from '../../../common/facets/depends-on';
import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder';
+import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils';
import {TimeframeFacetCommon} from '../../../common/facets/timeframe-facet-common';
import {Bindings} from '../../atomic-search-interface/atomic-search-interface';
@@ -68,6 +72,7 @@ export class AtomicTimeframeFacet implements InitializableComponent {
private timeframeFacetCommon?: TimeframeFacetCommon;
public filter?: DateFilter;
public searchStatus!: SearchStatus;
+ public tabManager!: TabManager;
@Element() private host!: HTMLElement;
@BindStateToController('facetForDateRange')
@@ -82,6 +87,9 @@ export class AtomicTimeframeFacet implements InitializableComponent {
@BindStateToController('searchStatus')
@State()
public searchStatusState!: SearchStatusState;
+ @BindStateToController('tabManager')
+ @State()
+ public tabManagerState!: TabManagerState;
@State() public error!: Error;
/**
@@ -97,6 +105,32 @@ export class AtomicTimeframeFacet implements InitializableComponent {
* The field whose values you want to display in the facet.
*/
@Prop({reflect: true}) public field = 'date';
+ /**
+ * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`.
+ *
+ * Set this property as a stringified JSON array, e.g.,
+ * ```html
+ *
+ * ```
+ * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs.
+ */
+ @ArrayProp()
+ @Prop({reflect: true, mutable: true})
+ public tabsIncluded: string[] | string = '[]';
+
+ /**
+ * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`.
+ *
+ * Set this property as a stringified JSON array, e.g.,
+ * ```html
+ *
+ * ```
+ * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs.
+ */
+ @ArrayProp()
+ @Prop({reflect: true, mutable: true})
+ public tabsExcluded: string[] | string = '[]';
+
/**
* Whether this facet should contain an datepicker allowing users to set custom ranges.
*/
@@ -181,6 +215,14 @@ export class AtomicTimeframeFacet implements InitializableComponent {
}
public initialize() {
+ if (
+ [...this.tabsIncluded].length > 0 &&
+ [...this.tabsExcluded].length > 0
+ ) {
+ console.warn(
+ 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.'
+ );
+ }
this.timeframeFacetCommon = new TimeframeFacetCommon({
facetId: this.facetId,
host: this.host,
@@ -211,6 +253,16 @@ export class AtomicTimeframeFacet implements InitializableComponent {
sortCriteria: this.sortCriteria,
});
this.searchStatus = buildSearchStatus(this.bindings.engine);
+ this.tabManager = buildTabManager(this.bindings.engine);
+ }
+
+ public componentShouldUpdate(): void {
+ updateFacetVisibilityForActiveTab(
+ [...this.tabsIncluded],
+ [...this.tabsExcluded],
+ this.tabManagerState?.activeTab,
+ this.facetForDateRange
+ );
}
public disconnectedCallback() {
diff --git a/packages/atomic/src/utils/tab-utils.spec.ts b/packages/atomic/src/utils/tab-utils.spec.ts
new file mode 100644
index 00000000000..de64504fac9
--- /dev/null
+++ b/packages/atomic/src/utils/tab-utils.spec.ts
@@ -0,0 +1,76 @@
+import {shouldDisplayOnCurrentTab} from './tab-utils';
+
+describe('tab-utils', () => {
+ let activeTab: string;
+ let includeTabs: string[];
+ let excludeTabs: string[];
+
+ beforeEach(() => {
+ activeTab = '';
+ includeTabs = [];
+ excludeTabs = [];
+ });
+
+ describe('shouldDisplayOnCurrentTab', () => {
+ describe('Given a tab is active', () => {
+ beforeEach(() => {
+ activeTab = 'tab1';
+ });
+
+ it('returns true when active tab is included and not excluded', () => {
+ includeTabs = ['tab1'];
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(true);
+ });
+
+ it('returns false when active tab is excluded', () => {
+ excludeTabs = ['tab1'];
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(false);
+ });
+
+ it('returns false when active tab is both included and excluded', () => {
+ includeTabs = ['tab1'];
+ excludeTabs = ['tab1'];
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(false);
+ });
+
+ it('returns false when tabs are included, no tabs are excluded, and the active tab is different', () => {
+ includeTabs = ['tab2', 'tab3'];
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(false);
+ });
+
+ it('returns true when tabs are excluded, no tabs are included, and the active tab is different', () => {
+ excludeTabs = ['tab2', 'tab3'];
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(true);
+ });
+ });
+ describe('Given no tab is active', () => {
+ test.each([
+ ['not included or excluded', [], []],
+ ['included', ['tab1', 'tab2', 'tab3'], []],
+ ['excluded', [], ['tab1', 'tab2', 'tab3']],
+ [
+ 'excluded and included',
+ ['tab1', 'tab2', 'tab3'],
+ ['tab1', 'tab2', 'tab3'],
+ ],
+ ])(
+ 'returns true when no tab is active and tabs are %s',
+ (_, includeTabs, excludeTabs) => {
+ expect(
+ shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab)
+ ).toBe(true);
+ }
+ );
+ });
+ });
+});
diff --git a/packages/atomic/src/utils/tab-utils.ts b/packages/atomic/src/utils/tab-utils.ts
new file mode 100644
index 00000000000..21c900ac2d5
--- /dev/null
+++ b/packages/atomic/src/utils/tab-utils.ts
@@ -0,0 +1,24 @@
+/**
+ * Determines whether the component should be displayed on the current tab.
+ *
+ * @param tabsIncluded - An array of tab names that should include the facet.
+ * @param tabsExcluded - An array of tab names that should exclude the facet.
+ * @param activeTab - The name of the currently active tab.
+ * @returns A boolean indicating whether the component should be displayed on the current tab.
+ */
+export function shouldDisplayOnCurrentTab(
+ includeTabs: string[],
+ excludeTabs: string[],
+ activeTab: string
+) {
+ if (!activeTab) {
+ return true;
+ }
+
+ const isIncluded =
+ includeTabs.length === 0 || includeTabs.includes(activeTab);
+ const isNotExcluded =
+ excludeTabs.length === 0 || !excludeTabs.includes(activeTab);
+
+ return isIncluded && isNotExcluded;
+}
From c26ce47f7f6a3c05b6f929ace8a205e39400b2b4 Mon Sep 17 00:00:00 2001
From: ylakhdar
Date: Thu, 1 Aug 2024 12:43:26 -0400
Subject: [PATCH 11/23] chore(headless SSR): add commerce ssr samples (#4228)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR introduces the headless-ssr-commerce sample project, utilizing
the @coveo/headless/ssr-commerce package. The project serves as a demo
application, currently featuring a product listing page. Although a
search feature is planned, it is not yet implemented.
Additionally, I’ve created and exposed two commerce controllers: summary
and product list. These controllers are included to provide content for
the listing page, but they are subject to change in the near future.
Here is what you will get at this stage after going to
http://localhost:3000/listing
![image](https://github.com/user-attachments/assets/73aebab6-3b38-412b-ae09-acdd49bc4853)
https://coveord.atlassian.net/browse/KIT-3441
---------
Co-authored-by: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com>
---
package-lock.json | 1040 +++++++----------
package.json | 2 -
.../headless-sub-controller.ssr.ts | 22 +
.../headless-product-listing.ssr.ts | 23 +
packages/headless/src/ssr-commerce.index.ts | 14 +-
.../headless-ssr-commerce/.eslintrc.json | 6 +
.../samples/headless-ssr-commerce/.gitignore | 36 +
.../samples/headless-ssr-commerce/README.md | 24 +
.../app/_components/listing-page.tsx | 43 +
.../app/_components/product-list.tsx | 32 +
.../app/_components/summary.tsx | 48 +
.../app/_lib/commerce-engine-config.ts | 39 +
.../app/_lib/commerce-engine.ts | 14 +
.../headless-ssr-commerce/app/layout.tsx | 13 +
.../app/listing/page.tsx | 15 +
.../headless-ssr-commerce/app/page.tsx | 14 +
.../headless-ssr-commerce/app/search/page.tsx | 3 +
.../headless-ssr-commerce/next.config.mjs | 4 +
.../headless-ssr-commerce/package.json | 27 +
.../headless-ssr-commerce/project.json | 17 +
.../headless-ssr-commerce/tsconfig.json | 27 +
.../headless-ssr/app-router/package.json | 8 +-
.../headless-ssr/app-router/tsconfig.json | 3 +-
packages/samples/headless-ssr/package.json | 8 +-
.../headless-ssr/pages-router/package.json | 8 +-
.../headless-ssr/pages-router/tsconfig.json | 4 +-
packages/samples/headless-ssr/tsconfig.json | 11 +-
27 files changed, 866 insertions(+), 639 deletions(-)
create mode 100644 packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts
create mode 100644 packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts
create mode 100644 packages/samples/headless-ssr-commerce/.eslintrc.json
create mode 100644 packages/samples/headless-ssr-commerce/.gitignore
create mode 100644 packages/samples/headless-ssr-commerce/README.md
create mode 100644 packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/_components/product-list.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/_components/summary.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts
create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts
create mode 100644 packages/samples/headless-ssr-commerce/app/layout.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/listing/page.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/page.tsx
create mode 100644 packages/samples/headless-ssr-commerce/app/search/page.tsx
create mode 100644 packages/samples/headless-ssr-commerce/next.config.mjs
create mode 100644 packages/samples/headless-ssr-commerce/package.json
create mode 100644 packages/samples/headless-ssr-commerce/project.json
create mode 100644 packages/samples/headless-ssr-commerce/tsconfig.json
diff --git a/package-lock.json b/package-lock.json
index 87f5282430d..ca2201ee909 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,8 +83,6 @@
"nx": "19.0.4",
"patch-package": "8.0.0",
"prettier": "3.3.3",
- "react": "18.3.1",
- "react-dom": "18.3.1",
"react-syntax-highlighter": "15.5.0",
"rimraf": "5.0.9",
"semver": "7.6.3",
@@ -5475,6 +5473,10 @@
"resolved": "packages/samples/headless-react",
"link": true
},
+ "node_modules/@coveo/headless-ssr-commerce-samples": {
+ "resolved": "packages/samples/headless-ssr-commerce",
+ "link": true
+ },
"node_modules/@coveo/headless-ssr-samples-app-router": {
"resolved": "packages/samples/headless-ssr/app-router",
"link": true
@@ -8144,6 +8146,177 @@
"tar-stream": "^2.1.4"
}
},
+ "node_modules/@next/env": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
+ "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA=="
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz",
+ "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==",
+ "dev": true,
+ "dependencies": {
+ "glob": "10.3.10"
+ }
+ },
+ "node_modules/@next/eslint-plugin-next/node_modules/glob": {
+ "version": "10.3.10",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+ "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.5",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+ "path-scurry": "^1.10.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
+ "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
+ "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
+ "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
+ "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
+ "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
+ "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
+ "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
+ "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
+ "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -30034,6 +30207,147 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint-config-next": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz",
+ "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==",
+ "dev": true,
+ "dependencies": {
+ "@next/eslint-plugin-next": "14.2.5",
+ "@rushstack/eslint-patch": "^1.3.3",
+ "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
+ },
+ "peerDependencies": {
+ "eslint": "^7.23.0 || ^8.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
+ "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.2.0",
+ "@typescript-eslint/types": "7.2.0",
+ "@typescript-eslint/typescript-estree": "7.2.0",
+ "@typescript-eslint/visitor-keys": "7.2.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz",
+ "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "7.2.0",
+ "@typescript-eslint/visitor-keys": "7.2.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz",
+ "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==",
+ "dev": true,
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz",
+ "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "7.2.0",
+ "@typescript-eslint/visitor-keys": "7.2.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "9.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz",
+ "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "7.2.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
@@ -41957,6 +42271,82 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/next": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
+ "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
+ "dependencies": {
+ "@next/env": "14.2.5",
+ "@swc/helpers": "0.5.5",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.1"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "14.2.5",
+ "@next/swc-darwin-x64": "14.2.5",
+ "@next/swc-linux-arm64-gnu": "14.2.5",
+ "@next/swc-linux-arm64-musl": "14.2.5",
+ "@next/swc-linux-x64-gnu": "14.2.5",
+ "@next/swc-linux-x64-musl": "14.2.5",
+ "@next/swc-win32-arm64-msvc": "14.2.5",
+ "@next/swc-win32-ia32-msvc": "14.2.5",
+ "@next/swc-win32-x64-msvc": "14.2.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/ng-packagr": {
"version": "17.3.0",
"resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-17.3.0.tgz",
@@ -60652,146 +61042,6 @@
"typescript": "5.4.5"
}
},
- "packages/samples/atomic-next/node_modules/@next/env": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
- "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA=="
- },
- "packages/samples/atomic-next/node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
- "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-darwin-x64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
- "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
- "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
- "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
- "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
- "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
- "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
- "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/atomic-next/node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
- "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"packages/samples/atomic-next/node_modules/cypress-repeat": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/cypress-repeat/-/cypress-repeat-2.3.5.tgz",
@@ -60836,82 +61086,6 @@
"node": ">=8"
}
},
- "packages/samples/atomic-next/node_modules/next": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
- "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
- "dependencies": {
- "@next/env": "14.2.5",
- "@swc/helpers": "0.5.5",
- "busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001579",
- "graceful-fs": "^4.2.11",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.1"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": ">=18.17.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "14.2.5",
- "@next/swc-darwin-x64": "14.2.5",
- "@next/swc-linux-arm64-gnu": "14.2.5",
- "@next/swc-linux-arm64-musl": "14.2.5",
- "@next/swc-linux-x64-gnu": "14.2.5",
- "@next/swc-linux-x64-musl": "14.2.5",
- "@next/swc-win32-arm64-msvc": "14.2.5",
- "@next/swc-win32-ia32-msvc": "14.2.5",
- "@next/swc-win32-x64-msvc": "14.2.5"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.41.2",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "packages/samples/atomic-next/node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"packages/samples/atomic-react": {
"name": "@coveo/atomic-react-samples",
"version": "0.0.0",
@@ -67064,13 +67238,13 @@
"@coveo/headless": "2.75.0",
"@coveo/headless-react": "1.0.21",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"cypress": "13.13.1",
"cypress-repeat": "2.3.5",
"cypress-web-vitals": "4.1.2",
@@ -67082,173 +67256,42 @@
"node": "^18 || ^20"
}
},
+ "packages/samples/headless-ssr-commerce": {
+ "name": "@coveo/headless-ssr-commerce-samples",
+ "version": "0.0.0",
+ "dependencies": {
+ "@coveo/headless": "^2.74.0",
+ "next": "14.2.5",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "20.14.12",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "eslint": "8.57",
+ "eslint-config-next": "14.2.5",
+ "typescript": "5.4.5"
+ }
+ },
"packages/samples/headless-ssr/app-router": {
"name": "@coveo/headless-ssr-samples-app-router",
"version": "0.0.0",
"dependencies": {
"@coveo/headless-ssr-samples-common": "0.0.0",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.5",
"typescript": "5.4.5"
}
},
- "packages/samples/headless-ssr/node_modules/@next/env": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
- "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA=="
- },
- "packages/samples/headless-ssr/node_modules/@next/eslint-plugin-next": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz",
- "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==",
- "dev": true,
- "dependencies": {
- "glob": "10.3.10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
- "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-darwin-x64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
- "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
- "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
- "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
- "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
- "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
- "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
- "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "packages/samples/headless-ssr/node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
- "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"packages/samples/headless-ssr/node_modules/@types/react": {
"version": "18.2.21",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
@@ -67269,109 +67312,6 @@
"@types/react": "*"
}
},
- "packages/samples/headless-ssr/node_modules/@typescript-eslint/parser": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
- "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/scope-manager": "7.2.0",
- "@typescript-eslint/types": "7.2.0",
- "@typescript-eslint/typescript-estree": "7.2.0",
- "@typescript-eslint/visitor-keys": "7.2.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.56.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "packages/samples/headless-ssr/node_modules/@typescript-eslint/scope-manager": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz",
- "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "7.2.0",
- "@typescript-eslint/visitor-keys": "7.2.0"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "packages/samples/headless-ssr/node_modules/@typescript-eslint/types": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz",
- "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==",
- "dev": true,
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "packages/samples/headless-ssr/node_modules/@typescript-eslint/typescript-estree": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz",
- "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "7.2.0",
- "@typescript-eslint/visitor-keys": "7.2.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "minimatch": "9.0.3",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "packages/samples/headless-ssr/node_modules/@typescript-eslint/visitor-keys": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz",
- "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "7.2.0",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
"packages/samples/headless-ssr/node_modules/cypress-repeat": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/cypress-repeat/-/cypress-repeat-2.3.5.tgz",
@@ -67416,155 +67356,19 @@
"node": ">=8"
}
},
- "packages/samples/headless-ssr/node_modules/eslint-config-next": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz",
- "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==",
- "dev": true,
- "dependencies": {
- "@next/eslint-plugin-next": "14.2.5",
- "@rushstack/eslint-patch": "^1.3.3",
- "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.28.1",
- "eslint-plugin-jsx-a11y": "^6.7.1",
- "eslint-plugin-react": "^7.33.2",
- "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
- },
- "peerDependencies": {
- "eslint": "^7.23.0 || ^8.0.0",
- "typescript": ">=3.3.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "packages/samples/headless-ssr/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "packages/samples/headless-ssr/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
- "dev": true,
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
- "minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "packages/samples/headless-ssr/node_modules/next": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
- "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
- "dependencies": {
- "@next/env": "14.2.5",
- "@swc/helpers": "0.5.5",
- "busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001579",
- "graceful-fs": "^4.2.11",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.1"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": ">=18.17.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "14.2.5",
- "@next/swc-darwin-x64": "14.2.5",
- "@next/swc-linux-arm64-gnu": "14.2.5",
- "@next/swc-linux-arm64-musl": "14.2.5",
- "@next/swc-linux-x64-gnu": "14.2.5",
- "@next/swc-linux-x64-musl": "14.2.5",
- "@next/swc-win32-arm64-msvc": "14.2.5",
- "@next/swc-win32-ia32-msvc": "14.2.5",
- "@next/swc-win32-x64-msvc": "14.2.5"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.41.2",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "packages/samples/headless-ssr/node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"packages/samples/headless-ssr/pages-router": {
"name": "@coveo/headless-ssr-samples-pages-router",
"version": "0.0.0",
"dependencies": {
"@coveo/headless-ssr-samples-common": "0.0.0",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.5",
"typescript": "5.4.5"
diff --git a/package.json b/package.json
index ddec47b8c0a..e7031a4eb17 100644
--- a/package.json
+++ b/package.json
@@ -81,8 +81,6 @@
"nx": "19.0.4",
"patch-package": "8.0.0",
"prettier": "3.3.3",
- "react": "18.3.1",
- "react-dom": "18.3.1",
"react-syntax-highlighter": "15.5.0",
"rimraf": "5.0.9",
"semver": "7.6.3",
diff --git a/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts
new file mode 100644
index 00000000000..b085699e9eb
--- /dev/null
+++ b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts
@@ -0,0 +1,22 @@
+import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine';
+import {ControllerDefinitionWithoutProps} from '../../../../app/ssr-engine/types/common';
+import {buildProductListing} from '../../product-listing/headless-product-listing';
+import {ProductListingSummaryState} from '../../product-listing/summary/headless-product-listing-summary';
+import {Summary} from '../summary/headless-core-summary';
+
+export type {ProductListingSummaryState, Summary};
+
+export interface SummaryDefinition
+ extends ControllerDefinitionWithoutProps<
+ CommerceEngine,
+ Summary
+ > {}
+
+/**
+ * @internal
+ */
+export function defineQuerySummary(): SummaryDefinition {
+ return {
+ build: (engine) => buildProductListing(engine).summary(),
+ };
+}
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
new file mode 100644
index 00000000000..6808cbb4352
--- /dev/null
+++ b/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts
@@ -0,0 +1,23 @@
+import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine';
+import {ControllerDefinitionWithoutProps} from '../../../app/ssr-engine/types/common';
+import {ProductListing, buildProductListing} from './headless-product-listing';
+
+export type {ProductListingState as ProductListState} from './headless-product-listing';
+export type ProductList = Pick;
+
+export interface ProductListDefinition
+ extends ControllerDefinitionWithoutProps {}
+
+/**
+ * Defines a `ProductListing` controller instance.
+ *
+ * @param props - The configurable `ProductListing` properties.
+ * @returns The `ProductListing` controller definition.
+ *
+ * @internal
+ * */
+export function defineProductList(): ProductListDefinition {
+ return {
+ build: (engine) => buildProductListing(engine),
+ };
+}
diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts
index 78594b4ee68..80c5b56748d 100644
--- a/packages/headless/src/ssr-commerce.index.ts
+++ b/packages/headless/src/ssr-commerce.index.ts
@@ -21,8 +21,6 @@ export type {
AnalyticsRuntimeEnvironment,
} from './app/engine-configuration';
export type {
- ControllerDefinitionWithoutProps,
- ControllerDefinitionWithProps,
ControllerDefinitionsMap,
InferControllerFromDefinition,
InferControllersMapFromDefinition,
@@ -66,6 +64,18 @@ export type {
} from './controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet';
export type {RegularFacet} from './controllers/commerce/core/facets/regular/headless-commerce-regular-facet';
+export type {
+ ProductList,
+ ProductListState,
+} from './controllers/commerce/product-listing/headless-product-listing.ssr';
+export {defineProductList} from './controllers/commerce/product-listing/headless-product-listing.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';
+
// TODO: KIT-3391 - export other SSR commerce controllers
//#endregion
diff --git a/packages/samples/headless-ssr-commerce/.eslintrc.json b/packages/samples/headless-ssr-commerce/.eslintrc.json
new file mode 100644
index 00000000000..dccec630f68
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
+ "parser": "@typescript-eslint/parser",
+ "plugins": ["@typescript-eslint"],
+ "root": true
+}
diff --git a/packages/samples/headless-ssr-commerce/.gitignore b/packages/samples/headless-ssr-commerce/.gitignore
new file mode 100644
index 00000000000..fd3dbb571a1
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/packages/samples/headless-ssr-commerce/README.md b/packages/samples/headless-ssr-commerce/README.md
new file mode 100644
index 00000000000..a749fa88c3f
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/README.md
@@ -0,0 +1,24 @@
+# Server side rendering examples
+
+- Demonstrates usage of the framework agnostic `@coveo/headless/ssr-commerce` utils for Server-Side Rendering with headless using Next.js in a commerce app.
+- Although Next.js is used to demonstrate SSR usage for convenience, the utils are not specific to Next.js.
+
+## Getting Started
+
+- Run dev server
+
+```bash
+npm run dev
+```
+
+- Run prod
+
+```bash
+npm run build && npm run prod
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+- Run tests
+
+TODO: add tests
diff --git a/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx
new file mode 100644
index 00000000000..1a8a966a904
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import {useEffect, useState} from 'react';
+import {
+ hydrateStaticState,
+ ListingHydratedState,
+ ListingStaticState,
+} from '../_lib/commerce-engine';
+import {ProductList} from './product-list';
+import {Summary} from './summary';
+
+export default function ListingPage({
+ staticState,
+}: {
+ staticState: ListingStaticState;
+}) {
+ const [hydratedState, setHydratedState] = useState<
+ ListingHydratedState | undefined
+ >(undefined);
+
+ useEffect(() => {
+ hydrateStaticState({
+ searchAction: staticState.searchAction,
+ }).then(({engine, controllers}) => {
+ setHydratedState({engine, controllers});
+ });
+ }, [staticState]);
+
+ return (
+ <>
+ {/* TODO: add UI component here */}
+
+
+ >
+ );
+}
diff --git a/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx
new file mode 100644
index 00000000000..ef5315963d3
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx
@@ -0,0 +1,32 @@
+import {
+ ProductList as ProductListingController,
+ ProductListState,
+} from '@coveo/headless/ssr-commerce';
+import {useEffect, useState, FunctionComponent} from 'react';
+
+interface ProductListProps {
+ staticState: ProductListState;
+ controller?: ProductListingController;
+}
+
+export const ProductList: FunctionComponent = ({
+ staticState,
+ controller,
+}) => {
+ const [state, setState] = useState(staticState);
+
+ useEffect(
+ () => controller?.subscribe(() => setState({...controller.state})),
+ [controller]
+ );
+
+ return (
+
+ {state.products.map((product) => (
+ -
+
{product.ec_name}
+
+ ))}
+
+ );
+};
diff --git a/packages/samples/headless-ssr-commerce/app/_components/summary.tsx b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx
new file mode 100644
index 00000000000..8be36db7fa7
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx
@@ -0,0 +1,48 @@
+import {
+ ProductListingSummaryState,
+ Summary as SummaryController,
+} from '@coveo/headless/ssr-commerce';
+import {useEffect, useState, FunctionComponent} from 'react';
+import {ListingHydratedState} from '../_lib/commerce-engine';
+
+interface SummaryProps {
+ hydratedState?: ListingHydratedState;
+ staticState: ProductListingSummaryState;
+ controller?: SummaryController;
+}
+
+export const Summary: FunctionComponent = ({
+ staticState,
+ hydratedState,
+ controller,
+}: SummaryProps) => {
+ const [state, setState] = useState(staticState);
+
+ useEffect(
+ () => controller?.subscribe?.(() => setState({...controller.state})),
+ [controller]
+ );
+
+ return (
+ <>
+
+ Hydrated:{' '}
+
+
+
+ Rendered page with {state.totalNumberOfProducts} results
+
+
+ Rendered on{' '}
+
+ {new Date().toISOString()}
+
+
+ >
+ );
+};
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
new file mode 100644
index 00000000000..dbfe55679b6
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts
@@ -0,0 +1,39 @@
+import {
+ Controller,
+ ControllerDefinitionsMap,
+ CommerceEngineDefinitionOptions,
+ CommerceEngine,
+ defineProductList,
+ getSampleCommerceEngineConfiguration,
+ defineQuerySummary,
+} from '@coveo/headless/ssr-commerce';
+
+type CommerceEngineConfig = CommerceEngineDefinitionOptions<
+ ControllerDefinitionsMap
+>;
+
+const configuration = {
+ ...getSampleCommerceEngineConfiguration(),
+ analytics: {
+ trackingId: 'sports-ui-samples',
+ enabled: false, // TODO: enable analytics
+ },
+};
+
+export default {
+ configuration: {
+ ...configuration,
+ context: {
+ country: 'US',
+ currency: 'USD',
+ language: 'en',
+ view: {
+ url: 'https://sports.barca.group/browse/promotions/skis-boards/surfboards',
+ },
+ },
+ },
+ controllers: {
+ summary: defineQuerySummary(),
+ productList: defineProductList(),
+ },
+} 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
new file mode 100644
index 00000000000..b1193e6bbdd
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts
@@ -0,0 +1,14 @@
+import {
+ defineCommerceEngine,
+ InferStaticState,
+ InferHydratedState,
+} from '@coveo/headless/ssr-commerce';
+import engineConfig from './commerce-engine-config';
+
+const engineDefinition = defineCommerceEngine(engineConfig);
+
+// TODO: only supporting Listing static state for now (KIT-3394)
+export type ListingStaticState = InferStaticState;
+export type ListingHydratedState = InferHydratedState;
+
+export const {fetchStaticState, hydrateStaticState} = engineDefinition;
diff --git a/packages/samples/headless-ssr-commerce/app/layout.tsx b/packages/samples/headless-ssr-commerce/app/layout.tsx
new file mode 100644
index 00000000000..a85570e84cf
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/layout.tsx
@@ -0,0 +1,13 @@
+export const metadata = {
+ title: 'Headless SSR examples',
+ description:
+ 'Examples of using framework agnostic @coveo/headless/ssr utils and @coveo/headless-react/ssr',
+};
+
+export default function RootLayout({children}: {children: React.ReactNode}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/samples/headless-ssr-commerce/app/listing/page.tsx b/packages/samples/headless-ssr-commerce/app/listing/page.tsx
new file mode 100644
index 00000000000..516ef72bf6b
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/listing/page.tsx
@@ -0,0 +1,15 @@
+import ListingPage from '../_components/listing-page';
+import {fetchStaticState} from '../_lib/commerce-engine';
+
+/**
+ * This file defines a List component that uses the Coveo Headless SSR commerce library to manage its state.
+ *
+ * The Listing function is the entry point for server-side rendering (SSR).
+ */
+export default async function Listing() {
+ const staticState = await fetchStaticState();
+
+ return ;
+}
+
+export const dynamic = 'force-dynamic';
diff --git a/packages/samples/headless-ssr-commerce/app/page.tsx b/packages/samples/headless-ssr-commerce/app/page.tsx
new file mode 100644
index 00000000000..5c25b69d2d3
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/page.tsx
@@ -0,0 +1,14 @@
+import Link from 'next/link';
+
+export default function Home() {
+ return (
+
+ -
+ Surfboard Listing Page
+
+ -
+ Search Page
+
+
+ );
+}
diff --git a/packages/samples/headless-ssr-commerce/app/search/page.tsx b/packages/samples/headless-ssr-commerce/app/search/page.tsx
new file mode 100644
index 00000000000..058db5bf727
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/search/page.tsx
@@ -0,0 +1,3 @@
+export default async function Search() {
+ return Not Supported Yet
;
+}
diff --git a/packages/samples/headless-ssr-commerce/next.config.mjs b/packages/samples/headless-ssr-commerce/next.config.mjs
new file mode 100644
index 00000000000..4678774e6d6
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/packages/samples/headless-ssr-commerce/package.json b/packages/samples/headless-ssr-commerce/package.json
new file mode 100644
index 00000000000..0dd961e1379
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@coveo/headless-ssr-commerce-samples",
+ "description": "Examples of framework agnostic @coveo/headless/ssr utils and @coveo/headless-react/ssr using Next.js app router",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "nx build",
+ "prod": "next start",
+ "lint": "next lint",
+ "build:next": "next build"
+ },
+ "dependencies": {
+ "@coveo/headless": "^2.74.0",
+ "next": "14.2.5",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "20.14.12",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "eslint": "8.57",
+ "eslint-config-next": "14.2.5",
+ "typescript": "5.4.5"
+ }
+}
diff --git a/packages/samples/headless-ssr-commerce/project.json b/packages/samples/headless-ssr-commerce/project.json
new file mode 100644
index 00000000000..481e49aa2ed
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/project.json
@@ -0,0 +1,17 @@
+{
+ "name": "headless-ssr-commerce-samples",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "targets": {
+ "cached:build": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "npm run build:next",
+ "cwd": "packages/samples/headless-ssr-commerce"
+ }
+ },
+ "build": {
+ "dependsOn": ["cached:build"],
+ "executor": "nx:noop"
+ }
+ }
+}
diff --git a/packages/samples/headless-ssr-commerce/tsconfig.json b/packages/samples/headless-ssr-commerce/tsconfig.json
new file mode 100644
index 00000000000..a97dd95edd2
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"],
+ "react": ["./node_modules/@types/react"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/samples/headless-ssr/app-router/package.json b/packages/samples/headless-ssr/app-router/package.json
index 8b4da1ca982..d50bf2cfa3f 100644
--- a/packages/samples/headless-ssr/app-router/package.json
+++ b/packages/samples/headless-ssr/app-router/package.json
@@ -15,13 +15,13 @@
"dependencies": {
"@coveo/headless-ssr-samples-common": "0.0.0",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.5",
"typescript": "5.4.5"
diff --git a/packages/samples/headless-ssr/app-router/tsconfig.json b/packages/samples/headless-ssr/app-router/tsconfig.json
index b5f251262fd..c970cb2086c 100644
--- a/packages/samples/headless-ssr/app-router/tsconfig.json
+++ b/packages/samples/headless-ssr/app-router/tsconfig.json
@@ -20,7 +20,8 @@
}
],
"paths": {
- "@/common/*": ["../common/*"]
+ "@/common/*": ["../common/*"],
+ "react": ["../node_modules/@types/react"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
diff --git a/packages/samples/headless-ssr/package.json b/packages/samples/headless-ssr/package.json
index 902cc96616b..40a3025f6fa 100644
--- a/packages/samples/headless-ssr/package.json
+++ b/packages/samples/headless-ssr/package.json
@@ -11,13 +11,13 @@
"@coveo/headless-react": "1.0.21",
"@coveo/headless": "2.75.0",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "8.57.0",
"eslint-config-react-app": "7.0.1",
"typescript": "5.4.5",
diff --git a/packages/samples/headless-ssr/pages-router/package.json b/packages/samples/headless-ssr/pages-router/package.json
index 098902e0d87..186a214049d 100644
--- a/packages/samples/headless-ssr/pages-router/package.json
+++ b/packages/samples/headless-ssr/pages-router/package.json
@@ -15,13 +15,13 @@
"dependencies": {
"@coveo/headless-ssr-samples-common": "0.0.0",
"next": "14.2.5",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.14.12",
- "@types/react": "18.3.3",
- "@types/react-dom": "18.3.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.5",
"typescript": "5.4.5"
diff --git a/packages/samples/headless-ssr/pages-router/tsconfig.json b/packages/samples/headless-ssr/pages-router/tsconfig.json
index 1641607b4c8..f57179a6377 100644
--- a/packages/samples/headless-ssr/pages-router/tsconfig.json
+++ b/packages/samples/headless-ssr/pages-router/tsconfig.json
@@ -1,6 +1,5 @@
{
"compilerOptions": {
- "target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@@ -14,7 +13,8 @@
"jsx": "preserve",
"incremental": true,
"paths": {
- "@/common/*": ["../common/*"]
+ "@/common/*": ["../common/*"],
+ "react": ["../node_modules/@types/react"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
diff --git a/packages/samples/headless-ssr/tsconfig.json b/packages/samples/headless-ssr/tsconfig.json
index 9c758e237ae..267f8fdbde6 100644
--- a/packages/samples/headless-ssr/tsconfig.json
+++ b/packages/samples/headless-ssr/tsconfig.json
@@ -4,7 +4,14 @@
// For Cypress https://github.com/cypress-io/cypress/issues/27448
"compilerOptions": {
"module": "es2015",
- "moduleResolution": "node"
+ "moduleResolution": "node",
+ "jsx": "react-jsx"
}
- }
+ },
+ "compilerOptions": {
+ "paths": {
+ "react": ["./node_modules/@types/react"]
+ }
+ },
+ "include": ["**/*.ts", "**/*.tsx", "**/**/*.ts", "**/**/*.tsx"]
}
From e8ebb095978794b625caec8aed3e985646513469 Mon Sep 17 00:00:00 2001
From: Nico Labarre
Date: Thu, 1 Aug 2024 15:51:02 -0400
Subject: [PATCH 12/23] fix(commerce): fix field suggestions state update
(#4245)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Field suggestion values weren't updating when the query changed. To fix
this, we fetch the facet search state using a selector, just like we do
for facets (thanks @fbeaudoincoveo!).
I've also fixed a few other things:
- Load the proper reducers in field suggestions controllers
- Add the reducer to the commerce engine
- Type the reducer loaders
- Replace the `Subscribable` type to `Controller` on field suggestions
controllers
- Adjusted the field suggestions integration test to rely on controllers
instead of engine state updates. This was actually a clue that the
current behavior was broken, as it was not possible to await state
changes on the field suggestions controllers 😅
[CAPI-1201]
[CAPI-1201]:
https://coveord.atlassian.net/browse/CAPI-1201?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
---
.../app/commerce-engine/commerce-engine.ts | 2 +
packages/headless/src/commerce.index.ts | 5 +-
...eadless-category-field-suggestions.test.ts | 11 +++++
.../headless-category-field-suggestions.ts | 48 ++++++++++++++++---
...adless-field-suggestions-generator.test.ts | 6 ---
.../headless-field-suggestions-generator.ts | 18 ++-----
.../headless-field-suggestions.test.ts | 11 +++++
.../headless-field-suggestions.ts | 40 +++++++++++++---
.../src/integration-tests/commerce.test.ts | 6 +--
9 files changed, 110 insertions(+), 37 deletions(-)
diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ts
index 55369eddc8f..b027271bd5a 100644
--- a/packages/headless/src/app/commerce-engine/commerce-engine.ts
+++ b/packages/headless/src/app/commerce-engine/commerce-engine.ts
@@ -9,6 +9,7 @@ import {setContext} from '../../features/commerce/context/context-actions';
import {contextReducer} from '../../features/commerce/context/context-slice';
import {didYouMeanReducer} from '../../features/commerce/did-you-mean/did-you-mean-slice';
import {commerceFacetSetReducer} from '../../features/commerce/facets/facet-set/facet-set-slice';
+import {fieldSuggestionsOrderReducer} from '../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {manualNumericFacetReducer} from '../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice';
import {paginationReducer} from '../../features/commerce/pagination/pagination-slice';
import {productListingReducer} from '../../features/commerce/product-listing/product-listing-slice';
@@ -45,6 +46,7 @@ const commerceEngineReducers = {
commercePagination: paginationReducer,
commerceSort: sortReducer,
facetOrder: facetOrderReducer,
+ fieldSuggestionsOrder: fieldSuggestionsOrderReducer,
facetSearchSet: specificFacetSearchSetReducer,
categoryFacetSearchSet: categoryFacetSearchSetReducer,
commerceFacetSet: commerceFacetSetReducer,
diff --git a/packages/headless/src/commerce.index.ts b/packages/headless/src/commerce.index.ts
index 3f00217da79..ff7e9318e05 100644
--- a/packages/headless/src/commerce.index.ts
+++ b/packages/headless/src/commerce.index.ts
@@ -298,7 +298,10 @@ export type {
CategoryFieldSuggestions,
CategoryFieldSuggestionsState,
} from './controllers/commerce/field-suggestions/headless-category-field-suggestions';
-export type {FieldSuggestionsGenerator} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator';
+export type {
+ FieldSuggestionsGenerator,
+ GeneratedFieldSuggestionsControllers,
+} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator';
export type {FieldSuggestionsFacet} from './features/commerce/facets/field-suggestions-order/field-suggestions-order-state.ts';
export {buildFieldSuggestionsGenerator} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator';
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
index 42d5e7e9e70..a0bbfa9d5da 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
@@ -1,5 +1,8 @@
import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
+import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {CategoryFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request';
+import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
+import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice';
import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions';
import {CommerceAppState} from '../../../state/commerce-app-state';
import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search';
@@ -58,6 +61,14 @@ describe('categoryFieldSuggestions', () => {
initFacet();
});
+ it('adds correct reducers to engine', () => {
+ expect(engine.addReducers).toHaveBeenCalledWith({
+ fieldSuggestionsOrder,
+ categoryFacetSearchSet,
+ commerceFacetSet,
+ });
+ });
+
it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => {
fieldSuggestions.updateText('foo');
expect(updateFacetSearch).toHaveBeenCalledWith({
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
index 52b75b7de3b..531851bccc3 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
@@ -1,11 +1,23 @@
+import {createSelector} from '@reduxjs/toolkit';
import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response';
-import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine';
+import {
+ CommerceEngine,
+ CommerceEngineState,
+} from '../../../app/commerce-engine/commerce-engine';
import {stateKey} from '../../../app/state-key';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
+import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
+import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice';
+import {
+ CategoryFacetSearchSection,
+ CommerceFacetSetSection,
+ FacetSearchSection,
+ FieldSuggestionsOrderSection,
+} from '../../../state/state-sections';
import {loadReducerError} from '../../../utils/errors';
import {
buildController,
- Subscribable,
+ Controller,
} from '../../controller/headless-controller';
import {
CategoryFieldSuggestionsState as CoreCategoryFieldSuggestionsState,
@@ -28,7 +40,7 @@ export type CategoryFieldSuggestionsState = CoreCategoryFieldSuggestionsState &
* This controller is a wrapper around the basic category facet controller search functionality, and thus exposes similar options and properties.
*/
export interface CategoryFieldSuggestions
- extends Subscribable,
+ extends Controller,
FacetControllerType<'hierarchical'> {
/**
* Requests field suggestions based on a query.
@@ -81,13 +93,26 @@ export function buildCategoryFieldSuggestions(
isForFieldSuggestions: true,
});
+ const getState = () => engine[stateKey];
+
const getFacetForFieldSuggestions = (facetId: string) => {
- return engine[stateKey].fieldSuggestionsOrder!.find(
+ return getState().fieldSuggestionsOrder.find(
(facet) => facet.facetId === facetId
)!;
};
const controller = buildController(engine);
+
+ const facetSearchStateSelector = createSelector(
+ (state: CommerceEngineState) =>
+ state.categoryFacetSearchSet[options.facetId],
+ (facetSearch) => ({
+ isLoading: facetSearch.isLoading,
+ moreValuesAvailable: facetSearch.response.moreValuesAvailable,
+ query: facetSearch.options.query,
+ values: facetSearch.response.values,
+ })
+ );
return {
...controller,
...facetSearch,
@@ -103,7 +128,7 @@ export function buildCategoryFieldSuggestions(
displayName: facet.displayName,
field: facet.field,
facetId: facet.facetId,
- ...facetSearch.state,
+ ...facetSearchStateSelector(getState()),
};
},
@@ -113,7 +138,16 @@ export function buildCategoryFieldSuggestions(
function loadFieldSuggestionsReducers(
engine: CommerceEngine
-): engine is CommerceEngine {
- engine.addReducers({commerceFacetSet});
+): engine is CommerceEngine<
+ FieldSuggestionsOrderSection &
+ CommerceFacetSetSection &
+ CategoryFacetSearchSection &
+ FacetSearchSection
+> {
+ engine.addReducers({
+ fieldSuggestionsOrder,
+ categoryFacetSearchSet,
+ commerceFacetSet,
+ });
return true;
}
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
index c0dad1dca0d..f20d4dddc17 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
@@ -1,9 +1,6 @@
import {FacetSearchType} from '../../../api/commerce/facet-search/facet-search-request';
import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response';
-import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
-import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice';
-import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
import {CommerceAppState} from '../../../state/commerce-app-state';
import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search';
import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request';
@@ -71,9 +68,6 @@ describe('fieldSuggestionsGenerator', () => {
it('adds correct reducers to engine', () => {
expect(engine.addReducers).toHaveBeenCalledWith({
fieldSuggestionsOrder,
- commerceFacetSet,
- facetSearchSet,
- categoryFacetSearchSet,
});
});
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts
index 9a2a2e5bafc..92719e8fe1e 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts
@@ -5,12 +5,10 @@ import {
CommerceEngineState,
} from '../../../app/commerce-engine/commerce-engine';
import {stateKey} from '../../../app/state-key';
-import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {FieldSuggestionsFacet} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-state';
import {executeSearch} from '../../../features/commerce/search/search-actions';
-import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice';
-import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
+import {FieldSuggestionsOrderSection} from '../../../state/state-sections';
import {loadReducerError} from '../../../utils/errors';
import {
buildController,
@@ -67,11 +65,8 @@ export function buildFieldSuggestionsGenerator(
const controller = buildController(engine);
const createFieldSuggestionsControllers = createSelector(
- (state: CommerceEngineState) => state.fieldSuggestionsOrder!,
- (state: CommerceEngineState) => state.commerceFacetSet,
- (state: CommerceEngineState) => state.facetSearchSet,
- (state: CommerceEngineState) => state.categoryFacetSearchSet,
- (facetOrder, _commerceFacetSet, _facetSearchSet, _categoryFacetSearchSet) =>
+ (state: CommerceEngineState) => state.fieldSuggestionsOrder,
+ (facetOrder) =>
facetOrder.map(({type, facetId}) => {
switch (type) {
case 'hierarchical':
@@ -94,19 +89,16 @@ export function buildFieldSuggestionsGenerator(
},
get state() {
- return engine[stateKey].fieldSuggestionsOrder!;
+ return engine[stateKey].fieldSuggestionsOrder;
},
};
}
function loadFieldSuggestionsGeneratorReducers(
engine: CommerceEngine
-): engine is CommerceEngine {
+): engine is CommerceEngine {
engine.addReducers({
fieldSuggestionsOrder,
- commerceFacetSet,
- facetSearchSet,
- categoryFacetSearchSet,
});
return true;
}
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
index 5d22f2f65cb..be836448924 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
@@ -1,6 +1,9 @@
import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
+import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {RegularFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request';
+import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions';
+import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
import {CommerceAppState} from '../../../state/commerce-app-state';
import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request';
import {buildMockCommerceFacetSlice} from '../../../test/mock-commerce-facet-slice';
@@ -60,6 +63,14 @@ describe('fieldSuggestions', () => {
initFacet();
});
+ it('adds correct reducers to engine', () => {
+ expect(engine.addReducers).toHaveBeenCalledWith({
+ fieldSuggestionsOrder,
+ commerceFacetSet,
+ facetSearchSet,
+ });
+ });
+
it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => {
fieldSuggestions.updateText('foo');
expect(updateFacetSearch).toHaveBeenCalled();
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
index 548d9d0c072..6f0ed218cce 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
@@ -1,12 +1,23 @@
+import {createSelector} from '@reduxjs/toolkit';
import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response';
import {SpecificFacetSearchResult} from '../../../api/search/facet-search/specific-facet-search/specific-facet-search-response';
-import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine';
+import {
+ CommerceEngine,
+ CommerceEngineState,
+} from '../../../app/commerce-engine/commerce-engine';
import {stateKey} from '../../../app/state-key';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
+import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
+import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
+import {
+ CommerceFacetSetSection,
+ FacetSearchSection,
+ FieldSuggestionsOrderSection,
+} from '../../../state/state-sections';
import {loadReducerError} from '../../../utils/errors';
import {
buildController,
- Subscribable,
+ Controller,
} from '../../controller/headless-controller';
import {FacetControllerType} from '../core/facets/headless-core-commerce-facet';
import {RegularFacetOptions} from '../core/facets/regular/headless-commerce-regular-facet';
@@ -28,7 +39,7 @@ export type FieldSuggestionsState = RegularFacetSearchState &
* This controller is a wrapper around the basic facet controller search functionality, and thus exposes similar options and properties.
*/
export interface FieldSuggestions
- extends Subscribable,
+ extends Controller,
FacetControllerType<'regular'> {
/**
* Requests field suggestions based on a query.
@@ -99,13 +110,26 @@ export function buildFieldSuggestions(
isForFieldSuggestions: true,
});
+ const getState = () => engine[stateKey];
+
const getFacetForFieldSuggestions = (facetId: string) => {
- return engine[stateKey].fieldSuggestionsOrder!.find(
+ return getState().fieldSuggestionsOrder.find(
(facet) => facet.facetId === facetId
)!;
};
const controller = buildController(engine);
+
+ const facetSearchStateSelector = createSelector(
+ (state: CommerceEngineState) => state.facetSearchSet[options.facetId],
+ (facetSearch) => ({
+ isLoading: facetSearch.isLoading,
+ moreValuesAvailable: facetSearch.response.moreValuesAvailable,
+ query: facetSearch.options.query,
+ values: facetSearch.response.values,
+ })
+ );
+
return {
...controller,
...facetSearch,
@@ -121,7 +145,7 @@ export function buildFieldSuggestions(
displayName: facet.displayName,
field: facet.field,
facetId: facet.facetId,
- ...facetSearch.state,
+ ...facetSearchStateSelector(getState()),
};
},
@@ -131,7 +155,9 @@ export function buildFieldSuggestions(
function loadFieldSuggestionsReducers(
engine: CommerceEngine
-): engine is CommerceEngine {
- engine.addReducers({commerceFacetSet});
+): engine is CommerceEngine<
+ FieldSuggestionsOrderSection & CommerceFacetSetSection & FacetSearchSection
+> {
+ engine.addReducers({fieldSuggestionsOrder, commerceFacetSet, facetSearchSet});
return true;
}
diff --git a/packages/headless/src/integration-tests/commerce.test.ts b/packages/headless/src/integration-tests/commerce.test.ts
index 5782367d9e0..9cf5b318fdf 100644
--- a/packages/headless/src/integration-tests/commerce.test.ts
+++ b/packages/headless/src/integration-tests/commerce.test.ts
@@ -154,9 +154,9 @@ describe.skip('commerce', () => {
expect(generator.fieldSuggestions).toHaveLength(3);
for (const controller of generator.fieldSuggestions) {
- await waitForNextStateChange(engine, {
+ await waitForNextStateChange(controller, {
action: () => controller.updateText('can'),
- expectedSubscriberCalls: 3,
+ expectedSubscriberCalls: 2,
});
}
@@ -169,7 +169,7 @@ describe.skip('commerce', () => {
await search(box, 'acc');
for (const controller of generator.fieldSuggestions) {
- await waitForNextStateChange(engine, {
+ await waitForNextStateChange(controller, {
action: () => controller.updateText('acc'),
expectedSubscriberCalls: 3,
});
From 99bbef1e52cde79e06b1b5f7815d2a6fba7d474b Mon Sep 17 00:00:00 2001
From: ylakhdar
Date: Fri, 2 Aug 2024 15:20:54 -0400
Subject: [PATCH 13/23] feat(headless SSR): support navigator context in both
Engine and fetch/hydrate functions (#4231)
Introducing a method for configuring the navigator context provider.
Users are alerted with a warning if the navigator context is not
established on both the client and server sides.
I also updated the samples to demonstrate the navigator's application
within a Next.js framework, though the approach is not exclusive to this
framework.
There are two methods for setting the navigator context:
1. During engine configuration.
2. While initializing the app's static state and hydrating the app
state, if the first method is impractical.
The example in the SSR samples highlights a scenario where, due to the
framework's limitations, setting the navigator context directly within
the engine configuration is not feasible because headers are not yet
available. In such cases, the second method is recommended. However,
this approach necessitates invoking `setNavigatorContextProvider` on
both the server and client sides, increasing the potential for user
errors. To address this, a warning is issued if the setup is not
correctly implemented.
---
.../src/ssr/search-engine.test.tsx | 7 +++
.../commerce-engine/commerce-engine.ssr.ts | 26 +++++++-
.../app/search-engine/search-engine.ssr.ts | 28 ++++++++-
.../src/app/ssr-engine/types/core-engine.ts | 10 +++
packages/headless/src/ssr-commerce.index.ts | 1 +
.../app/_lib/navigatorContextProvider.ts | 63 +++++++++++++++++++
.../headless-ssr-commerce/app/middleware.ts | 10 +++
7 files changed, 141 insertions(+), 4 deletions(-)
create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts
create mode 100644 packages/samples/headless-ssr-commerce/app/middleware.ts
diff --git a/packages/headless-react/src/ssr/search-engine.test.tsx b/packages/headless-react/src/ssr/search-engine.test.tsx
index a5f7538ffb9..26ebd2ef3f1 100644
--- a/packages/headless-react/src/ssr/search-engine.test.tsx
+++ b/packages/headless-react/src/ssr/search-engine.test.tsx
@@ -12,6 +12,7 @@ import {defineSearchEngine} from './search-engine.js';
describe('Headless react SSR utils', () => {
let errorSpy: jest.SpyInstance;
+ const mockedNavigatorContextProvider = jest.fn();
const sampleConfig = {
...getSampleSearchEngineConfiguration(),
analytics: {enabled: false}, // TODO: KIT-2585 Remove after analytics SSR support is added
@@ -34,6 +35,7 @@ describe('Headless react SSR utils', () => {
controllers,
StaticStateProvider,
HydratedStateProvider,
+ setNavigatorContextProvider,
...rest
} = defineSearchEngine({
configuration: sampleConfig,
@@ -46,6 +48,7 @@ describe('Headless react SSR utils', () => {
useEngine,
StaticStateProvider,
HydratedStateProvider,
+ setNavigatorContextProvider,
].forEach((returnValue) => expect(typeof returnValue).toBe('function'));
expect(controllers).toEqual({});
@@ -81,6 +84,7 @@ describe('Headless react SSR utils', () => {
StaticStateProvider,
HydratedStateProvider,
controllers,
+ setNavigatorContextProvider,
useEngine,
} = engineDefinition;
@@ -131,6 +135,7 @@ describe('Headless react SSR utils', () => {
});
test('should render with StaticStateProvider', async () => {
+ setNavigatorContextProvider(mockedNavigatorContextProvider);
const staticState = await fetchStaticState();
render(
@@ -142,6 +147,7 @@ describe('Headless react SSR utils', () => {
});
test('should hydrate results with HydratedStateProvider', async () => {
+ setNavigatorContextProvider(mockedNavigatorContextProvider);
const staticState = await fetchStaticState();
const {engine, controllers} = await hydrateStaticState(staticState);
@@ -159,6 +165,7 @@ describe('Headless react SSR utils', () => {
let hydratedState: InferHydratedState;
beforeEach(async () => {
+ setNavigatorContextProvider(mockedNavigatorContextProvider);
staticState = await fetchStaticState();
hydratedState = await hydrateStaticState(staticState);
});
diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts
index 2f7e55e9283..73e9ae811d6 100644
--- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts
+++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts
@@ -6,6 +6,7 @@ import {stateKey} from '../../app/state-key';
import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing';
import type {Controller} from '../../controllers/controller/headless-controller';
import {createWaitForActionMiddleware} from '../../utils/utils';
+import {NavigatorContextProvider} from '../navigatorContextProvider';
import {
buildControllerDefinitions,
composeFunction,
@@ -117,13 +118,21 @@ export function defineCommerceEngine<
type HydrateStaticStateFromBuildResultParameters =
Parameters;
- const getOpts = () => {
+ const getOptions = () => {
return engineOptions;
};
+ const setNavigatorContextProvider = (
+ navigatorContextProvider: NavigatorContextProvider
+ ) => {
+ engineOptions.navigatorContextProvider = navigatorContextProvider;
+ };
+
const build: BuildFunction = async (...[buildOptions]: BuildParameters) => {
const engine = buildSSRCommerceEngine(
- buildOptions?.extend ? await buildOptions.extend(getOpts()) : getOpts()
+ buildOptions?.extend
+ ? await buildOptions.extend(getOptions())
+ : getOptions()
);
const controllers = buildControllerDefinitions({
definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions,
@@ -140,6 +149,12 @@ export function defineCommerceEngine<
const fetchStaticState: FetchStaticStateFunction = composeFunction(
async (...params: FetchStaticStateParameters) => {
+ if (!getOptions().navigatorContextProvider) {
+ // TODO: KIT-3409 - implement a logger to log SSR warnings/errors
+ console.warn(
+ '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()'
+ );
+ }
const buildResult = await build(...params);
const staticState = await fetchStaticState.fromBuildResult({
buildResult,
@@ -170,6 +185,12 @@ export function defineCommerceEngine<
const hydrateStaticState: HydrateStaticStateFunction = composeFunction(
async (...params: HydrateStaticStateParameters) => {
+ if (!getOptions().navigatorContextProvider) {
+ // TODO: KIT-3409 - implement a logger to log SSR warnings/errors
+ console.warn(
+ '[WARNING] Missing navigator context in client-side code. Make sure to set it with `setNavigatorContextProvider` before calling hydrateStaticState()'
+ );
+ }
const buildResult = await build(...(params as BuildParameters));
const staticState = await hydrateStaticState.fromBuildResult({
buildResult,
@@ -198,5 +219,6 @@ export function defineCommerceEngine<
build,
fetchStaticState,
hydrateStaticState,
+ setNavigatorContextProvider,
};
}
diff --git a/packages/headless/src/app/search-engine/search-engine.ssr.ts b/packages/headless/src/app/search-engine/search-engine.ssr.ts
index 35ad3cd17be..56003a36cca 100644
--- a/packages/headless/src/app/search-engine/search-engine.ssr.ts
+++ b/packages/headless/src/app/search-engine/search-engine.ssr.ts
@@ -5,6 +5,7 @@ import {UnknownAction} from '@reduxjs/toolkit';
import type {Controller} from '../../controllers/controller/headless-controller';
import {LegacySearchAction} from '../../features/analytics/analytics-utils';
import {createWaitForActionMiddleware} from '../../utils/utils';
+import {NavigatorContextProvider} from '../navigatorContextProvider';
import {
buildControllerDefinitions,
composeFunction,
@@ -108,11 +109,21 @@ export function defineSearchEngine<
type HydrateStaticStateFromBuildResultParameters =
Parameters;
+ const getOptions = () => {
+ return engineOptions;
+ };
+
+ const setNavigatorContextProvider = (
+ navigatorContextProvider: NavigatorContextProvider
+ ) => {
+ engineOptions.navigatorContextProvider = navigatorContextProvider;
+ };
+
const build: BuildFunction = async (...[buildOptions]: BuildParameters) => {
const engine = buildSSRSearchEngine(
buildOptions?.extend
- ? await buildOptions.extend(engineOptions)
- : engineOptions
+ ? await buildOptions.extend(getOptions())
+ : getOptions()
);
const controllers = buildControllerDefinitions({
definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions,
@@ -129,6 +140,12 @@ export function defineSearchEngine<
const fetchStaticState: FetchStaticStateFunction = composeFunction(
async (...params: FetchStaticStateParameters) => {
+ if (!getOptions().navigatorContextProvider) {
+ // TODO: KIT-3409 - implement a logger to log SSR warnings/errors
+ console.warn(
+ '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()'
+ );
+ }
const buildResult = await build(...params);
const staticState = await fetchStaticState.fromBuildResult({
buildResult,
@@ -156,6 +173,12 @@ export function defineSearchEngine<
const hydrateStaticState: HydrateStaticStateFunction = composeFunction(
async (...params: HydrateStaticStateParameters) => {
+ if (!getOptions().navigatorContextProvider) {
+ // TODO: KIT-3409 - implement a logger to log SSR warnings/errors
+ console.warn(
+ '[WARNING] Missing navigator context in client-side code. Make sure to set it with `setNavigatorContextProvider` before calling hydrateStaticState()'
+ );
+ }
const buildResult = await build(...(params as BuildParameters));
const staticState = await hydrateStaticState.fromBuildResult({
buildResult,
@@ -184,5 +207,6 @@ export function defineSearchEngine<
build,
fetchStaticState,
hydrateStaticState,
+ setNavigatorContextProvider,
};
}
diff --git a/packages/headless/src/app/ssr-engine/types/core-engine.ts b/packages/headless/src/app/ssr-engine/types/core-engine.ts
index 2a688ea99ae..ffabf9c9766 100644
--- a/packages/headless/src/app/ssr-engine/types/core-engine.ts
+++ b/packages/headless/src/app/ssr-engine/types/core-engine.ts
@@ -2,6 +2,7 @@ import {AnyAction} from '@reduxjs/toolkit';
import type {Controller} from '../../../controllers/controller/headless-controller';
import {CoreEngine, CoreEngineNext} from '../../engine';
import {EngineConfiguration} from '../../engine-configuration';
+import {NavigatorContextProvider} from '../../navigatorContextProvider';
import {Build} from './build';
import {
ControllerDefinitionsMap,
@@ -49,6 +50,15 @@ export interface EngineDefinition<
AnyAction,
InferControllerPropsMapFromDefinitions
>;
+ /**
+ * Sets the navigator context provider.
+ * This provider is essential for retrieving navigation-related data such as referrer, userAgent, location, and clientId, which are crucial for handling both server-side and client-side API requests effectively.
+ *
+ * Note: The implementation specifics of the navigator context provider depend on the Node.js framework being utilized. It is the developer's responsibility to appropriately define and implement the navigator context provider to ensure accurate navigation context is available throughout the application. If the user fails to provide a navigator context provider, a warning will be logged either on the server or the browser console.
+ */
+ setNavigatorContextProvider: (
+ navigatorContextProvider: NavigatorContextProvider
+ ) => void;
/**
* Builds an engine and its controllers from an engine definition.
*/
diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts
index 80c5b56748d..554c0c9f4bc 100644
--- a/packages/headless/src/ssr-commerce.index.ts
+++ b/packages/headless/src/ssr-commerce.index.ts
@@ -36,6 +36,7 @@ export type {
InferBuildResult,
} from './app/ssr-engine/types/core-engine';
export type {LoggerOptions} from './app/logger';
+export type {NavigatorContext} from './app/navigatorContextProvider';
export type {LogLevel} from './app/logger';
diff --git a/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts b/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts
new file mode 100644
index 00000000000..cb5ffa03b7e
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts
@@ -0,0 +1,63 @@
+import {NavigatorContext} from '@coveo/headless/ssr-commerce';
+import type {ReadonlyHeaders} from 'next/dist/server/web/spec-extension/adapters/headers';
+
+/**
+ * This class implements the NavigatorContext interface from Coveo's SSR commerce sub-package.
+ * It is designed to work within a Next.js environment, providing a way to extract
+ * navigation-related context from Next.js request headers. This context will then be
+ * pass to subsequent search requests.
+ */
+export class NextJsNavigatorContext implements NavigatorContext {
+ /**
+ * Initializes a new instance of the NextJsNavigatorContext class.
+ * @param headers The readonly headers from a Next.js request, providing access to request-specific data.
+ */
+ constructor(private headers: ReadonlyHeaders) {}
+
+ /**
+ * Retrieves the referrer URL from the request headers.
+ * Some browsers use 'referer' while others may use 'referrer'.
+ * @returns The referrer URL if available, otherwise undefined.
+ */
+ get referrer() {
+ return this.headers.get('referer') || this.headers.get('referrer');
+ }
+
+ /**
+ * Retrieves the user agent string from the request headers.
+ * @returns The user agent string if available, otherwise undefined.
+ */
+ get userAgent() {
+ return this.headers.get('user-agent');
+ }
+
+ /**
+ * Placeholder for the location property. Needs to be implemented based on the application's requirements.
+ * @returns Currently returns a 'TODO:' string.
+ */
+ get location() {
+ return 'TODO:';
+ }
+
+ /**
+ * Fetches the unique client ID that was generated earlier by the middleware.
+ * @returns The client ID.
+ */
+ get clientId() {
+ const clientId = this.headers.get('x-coveo-client-id');
+ return clientId!;
+ }
+
+ /**
+ * Marshals the navigation context into a format that can be used by Coveo's headless library.
+ * @returns An object containing clientId, location, referrer, and userAgent properties.
+ */
+ get marshal(): NavigatorContext {
+ return {
+ clientId: this.clientId,
+ location: this.location,
+ referrer: this.referrer,
+ userAgent: this.userAgent,
+ };
+ }
+}
diff --git a/packages/samples/headless-ssr-commerce/app/middleware.ts b/packages/samples/headless-ssr-commerce/app/middleware.ts
new file mode 100644
index 00000000000..297df469aef
--- /dev/null
+++ b/packages/samples/headless-ssr-commerce/app/middleware.ts
@@ -0,0 +1,10 @@
+import {NextRequest, NextResponse} from 'next/server';
+
+export default function middleware(request: NextRequest) {
+ const response = NextResponse.next();
+ const requestHeaders = new Headers(request.headers);
+ const uuid = crypto.randomUUID();
+ requestHeaders.set('x-coveo-client-id', uuid);
+ response.headers.set('x-coveo-client-id', uuid);
+ return response;
+}
From 1db110c138d8f43a87f2e530fee00312e9c4c980 Mon Sep 17 00:00:00 2001
From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com>
Date: Mon, 5 Aug 2024 09:50:22 -0400
Subject: [PATCH 14/23] ci: fix error in publish-pr-review-site action (#4242)
https://coveord.atlassian.net/browse/KIT-3451
---
.../actions/publish-pr-review-site/action.yml | 27 +++++++++++++++----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/.github/actions/publish-pr-review-site/action.yml b/.github/actions/publish-pr-review-site/action.yml
index be4993cbc9f..0cd3f8b7ce0 100644
--- a/.github/actions/publish-pr-review-site/action.yml
+++ b/.github/actions/publish-pr-review-site/action.yml
@@ -19,12 +19,29 @@ runs:
token: ${{inputs.token}}
- name: 'Setup branch'
run: |
- if [[ -z $(git ls-remote --heads origin refs/heads/${{github.event.pull_request.number}} | tr -s '[:blank:]') ]]; then
- git switch -c "${{github.event.pull_request.number}}"
+ BRANCH_NAME="${{ github.event.pull_request.number }}"
+
+ git fetch origin
+
+ # Check if the branch exists on the remote
+ if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
+ echo "Branch $BRANCH_NAME exists remotely. Checking out..."
+
+ # If the branch exists locally, switch to it; otherwise, create a tracking branch
+ if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
+ git switch "$BRANCH_NAME"
+ else
+ git switch --track "origin/$BRANCH_NAME"
+ fi
+
+ # Reset the branch to match the latest commit from the main branch
+ git reset --hard origin/main
else
- git fetch origin "refs/heads/${{github.event.pull_request.number}}"
- git switch "${{github.event.pull_request.number}}"
- git reset --hard main
+ echo "Branch $BRANCH_NAME does not exist remotely. Creating a new branch..."
+
+ # Create a new branch locally and push it to the remote
+ git switch -c "$BRANCH_NAME"
+ git push -u origin "$BRANCH_NAME"
fi
working-directory: prs
shell: bash
From a26184e4a4c17015b20f6e9271cbb8faf960b06a Mon Sep 17 00:00:00 2001
From: Nico Labarre
Date: Mon, 5 Aug 2024 10:22:38 -0400
Subject: [PATCH 15/23] fix(commerce): namespace field suggestions to prevent
clash with facet search (#4247)
Since facets in a global search configuration are used to power both
field suggestions and facets, it's possible that field suggestions for a
field be active at the same time that a facet search for the same field
is. This means that updating the text on a field suggestions controller
would cause a facet's facet search values to be updated as well. This is
because both the field suggestions and facet search were powered by the
same state.
To prevent this issue, I namespace the `facetId` used for field
suggestions **only** on the facet search reducers (the regular, and
category one).
I opted for this approach instead of reversing the dependency on the
facet search controller to read state from selectors since it would've
lead to much more changes in the controllers.
[CAPI-1201]
[CAPI-1201]:
https://coveord.atlassian.net/browse/CAPI-1201?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
---
...eadless-category-field-suggestions.test.ts | 15 ++++--
.../headless-category-field-suggestions.ts | 8 ++-
...adless-field-suggestions-generator.test.ts | 25 +++++----
.../headless-field-suggestions.test.ts | 23 ++++----
.../headless-field-suggestions.ts | 8 ++-
...egory-facet-search-request-builder.test.ts | 54 +++++++++++++++----
...e-category-facet-search-request-builder.ts | 8 ++-
.../commerce-facet-search-actions.ts | 18 +++++++
...ce-regular-facet-search-request-builder.ts | 3 +-
.../facets/facet-set/facet-set-slice.test.ts | 12 +++--
.../facets/facet-set/facet-set-slice.ts | 15 ++++--
.../category-facet-search-set-slice.ts | 6 ++-
.../facet-search-reducer-helpers.ts | 20 +++++--
.../specific-facet-search-set-slice.ts | 32 +++++++----
14 files changed, 178 insertions(+), 69 deletions(-)
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
index a0bbfa9d5da..342a7e0a898 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts
@@ -1,4 +1,7 @@
-import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
+import {
+ executeCommerceFieldSuggest,
+ getFacetIdWithCommerceFieldSuggestionNamespace,
+} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {CategoryFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
@@ -33,7 +36,7 @@ describe('categoryFieldSuggestions', () => {
let fieldSuggestions: CategoryFieldSuggestions;
let options: CategoryFacetOptions;
- function initFacet() {
+ function initFieldSuggestions() {
engine = buildMockCommerceEngine(state);
fieldSuggestions = buildCategoryFieldSuggestions(engine, options);
}
@@ -41,7 +44,9 @@ describe('categoryFieldSuggestions', () => {
function setFacetRequest(config: Partial = {}) {
const request = buildMockCommerceFacetRequest({facetId, ...config});
state.commerceFacetSet[facetId] = buildMockCommerceFacetSlice({request});
- state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({
+ state.categoryFacetSearchSet[
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId)
+ ] = buildMockCategoryFacetSearch({
initialNumberOfValues: 5,
});
}
@@ -58,7 +63,7 @@ describe('categoryFieldSuggestions', () => {
state = buildMockCommerceState();
setFacetRequest();
- initFacet();
+ initFieldSuggestions();
});
it('adds correct reducers to engine', () => {
@@ -72,7 +77,7 @@ describe('categoryFieldSuggestions', () => {
it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => {
fieldSuggestions.updateText('foo');
expect(updateFacetSearch).toHaveBeenCalledWith({
- facetId,
+ facetId: getFacetIdWithCommerceFieldSuggestionNamespace(facetId),
query: 'foo',
numberOfValues: 5,
});
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
index 531851bccc3..091e1627d5a 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts
@@ -5,6 +5,7 @@ import {
CommerceEngineState,
} from '../../../app/commerce-engine/commerce-engine';
import {stateKey} from '../../../app/state-key';
+import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice';
@@ -81,9 +82,12 @@ export function buildCategoryFieldSuggestions(
const {dispatch} = engine;
+ const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace(
+ options.facetId
+ );
const facetSearch = buildCategoryFacetSearch(engine, {
options: {
- facetId: options.facetId,
+ facetId: namespacedFacetId,
...options.facetSearch,
numberOfValues: 10,
},
@@ -105,7 +109,7 @@ export function buildCategoryFieldSuggestions(
const facetSearchStateSelector = createSelector(
(state: CommerceEngineState) =>
- state.categoryFacetSearchSet[options.facetId],
+ state.categoryFacetSearchSet[namespacedFacetId],
(facetSearch) => ({
isLoading: facetSearch.isLoading,
moreValuesAvailable: facetSearch.response.moreValuesAvailable,
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
index f20d4dddc17..b75ac8d9ff8 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts
@@ -1,5 +1,6 @@
import {FacetSearchType} from '../../../api/commerce/facet-search/facet-search-request';
import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response';
+import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {CommerceAppState} from '../../../state/commerce-app-state';
import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search';
@@ -38,16 +39,22 @@ describe('fieldSuggestionsGenerator', () => {
function setFacetState(config: FieldSuggestionsFacet[] = []) {
for (const facet of config) {
+ const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace(
+ facet.facetId
+ );
state.fieldSuggestionsOrder.push(facet);
- state.commerceFacetSet[facet.facetId] = {
- request: buildMockCommerceFacetRequest({
- facetId: facet.facetId,
- type: facet.type,
- }),
- };
- state.facetSearchSet[facet.facetId] = buildMockFacetSearch();
- state.categoryFacetSearchSet[facet.facetId] =
- buildMockCategoryFacetSearch();
+ if (facet.type === 'regular') {
+ state.facetSearchSet[namespacedFacetId] = buildMockFacetSearch();
+ } else {
+ state.commerceFacetSet[facet.facetId] = {
+ request: buildMockCommerceFacetRequest({
+ facetId: facet.facetId,
+ type: facet.type,
+ }),
+ };
+ state.categoryFacetSearchSet[namespacedFacetId] =
+ buildMockCategoryFacetSearch();
+ }
}
}
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
index be836448924..52bd0fd8b90 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts
@@ -1,12 +1,12 @@
-import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
+import {
+ executeCommerceFieldSuggest,
+ getFacetIdWithCommerceFieldSuggestionNamespace,
+} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
-import {RegularFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions';
import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
import {CommerceAppState} from '../../../state/commerce-app-state';
-import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request';
-import {buildMockCommerceFacetSlice} from '../../../test/mock-commerce-facet-slice';
import {buildMockCommerceState} from '../../../test/mock-commerce-state';
import {
buildMockCommerceEngine,
@@ -33,16 +33,15 @@ describe('fieldSuggestions', () => {
let fieldSuggestions: FieldSuggestions;
let options: RegularFacetOptions;
- function initFacet() {
+ function initFieldSuggestions() {
engine = buildMockCommerceEngine(state);
fieldSuggestions = buildFieldSuggestions(engine, options);
}
- function setFacetRequest(config: Partial = {}) {
- state.commerceFacetSet[facetId] = buildMockCommerceFacetSlice({
- request: buildMockCommerceFacetRequest({facetId, ...config}),
- });
- state.facetSearchSet[facetId] = buildMockFacetSearch({
+ function setFacetSearchRequest() {
+ state.facetSearchSet[
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId)
+ ] = buildMockFacetSearch({
initialNumberOfValues: 5,
});
}
@@ -58,9 +57,9 @@ describe('fieldSuggestions', () => {
};
state = buildMockCommerceState();
- setFacetRequest();
+ setFacetSearchRequest();
- initFacet();
+ initFieldSuggestions();
});
it('adds correct reducers to engine', () => {
diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
index 6f0ed218cce..5a68ad16e5f 100644
--- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
+++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts
@@ -6,6 +6,7 @@ import {
CommerceEngineState,
} from '../../../app/commerce-engine/commerce-engine';
import {stateKey} from '../../../app/state-key';
+import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions';
import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice';
import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice';
import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice';
@@ -99,8 +100,11 @@ export function buildFieldSuggestions(
const {dispatch} = engine;
+ const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace(
+ options.facetId
+ );
const facetSearch = buildRegularFacetSearch(engine, {
- options: {facetId: options.facetId, ...options.facetSearch},
+ options: {facetId: namespacedFacetId, ...options.facetSearch},
select: () => {
dispatch(options.fetchProductsActionCreator());
},
@@ -121,7 +125,7 @@ export function buildFieldSuggestions(
const controller = buildController(engine);
const facetSearchStateSelector = createSelector(
- (state: CommerceEngineState) => state.facetSearchSet[options.facetId],
+ (state: CommerceEngineState) => state.facetSearchSet[namespacedFacetId],
(facetSearch) => ({
isLoading: facetSearch.isLoading,
moreValuesAvailable: facetSearch.response.moreValuesAvailable,
diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts
index 67b71df5232..58d2a0f17ac 100644
--- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts
+++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts
@@ -9,21 +9,25 @@ import {buildMockCommerceState} from '../../../../../test/mock-commerce-state';
import {buildMockFacetSearchRequestOptions} from '../../../../../test/mock-facet-search-request-options';
import {buildMockNavigatorContextProvider} from '../../../../../test/mock-navigator-context-provider';
import {CategoryFacetValueRequest} from '../../facet-set/interfaces/request';
+import {
+ getFacetIdWithCommerceFieldSuggestionNamespace,
+ getFacetIdWithoutCommerceFieldSuggestionNamespace,
+} from '../commerce-facet-search-actions';
import {buildCategoryFacetSearchRequest} from './commerce-category-facet-search-request-builder';
describe('#buildCategoryFacetSearchRequest', () => {
let state: CommerceAppState;
let navigatorContext: NavigatorContext;
- let facetId: string;
+ const facetId = '1';
let query: string;
let buildCommerceAPIRequestMock: jest.SpyInstance;
beforeEach(() => {
jest.clearAllMocks();
- facetId = '1';
query = 'test';
state = buildMockCommerceState();
+
state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({
options: {...buildMockFacetSearchRequestOptions(), query},
});
@@ -64,7 +68,28 @@ describe('#buildCategoryFacetSearchRequest', () => {
);
});
- describe('returned object #ignorePaths property', () => {
+ describe.each([
+ {
+ facetId: 'a_non_namespaced_facet_id',
+ },
+ {
+ facetId: getFacetIdWithCommerceFieldSuggestionNamespace(
+ 'a_namespaced_facet_id'
+ ),
+ },
+ ])('returned object #ignorePaths property', ({facetId}) => {
+ beforeEach(() => {
+ state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({
+ options: {...buildMockFacetSearchRequestOptions(), query},
+ });
+
+ state.commerceFacetSet[
+ getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId)
+ ] = buildMockCommerceFacetSlice({
+ request: buildMockCommerceFacetRequest({type: 'hierarchical'}),
+ });
+ });
+
it('when the facet request has no selected value, is an empty array', () => {
const request = buildCategoryFacetSearchRequest(
facetId,
@@ -77,8 +102,12 @@ describe('#buildCategoryFacetSearchRequest', () => {
});
it('when the facet request has a selected value with no ancestry, is an array with a single array containing the selected value', () => {
- state.commerceFacetSet[facetId].request.values[0] =
- buildMockCategoryFacetValue({state: 'selected', value: 'test'});
+ state.commerceFacetSet[
+ getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId)
+ ].request.values[0] = buildMockCategoryFacetValue({
+ state: 'selected',
+ value: 'test',
+ });
const request = buildCategoryFacetSearchRequest(
facetId,
state,
@@ -89,15 +118,18 @@ describe('#buildCategoryFacetSearchRequest', () => {
expect(request.ignorePaths).toStrictEqual([
[
(
- state.commerceFacetSet[facetId].request
- .values[0] as CategoryFacetValueRequest
+ state.commerceFacetSet[
+ getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId)
+ ].request.values[0] as CategoryFacetValueRequest
).value,
],
]);
});
it('when the facet request has a selected value with ancestry, is an array with a single array containing the selected value and its ancestors', () => {
- state.commerceFacetSet[facetId].request.values[0] =
+ const nonNamespacedId =
+ getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId);
+ state.commerceFacetSet[nonNamespacedId].request.values[0] =
buildMockCategoryFacetValue({
value: 'test',
children: [
@@ -122,15 +154,15 @@ describe('#buildCategoryFacetSearchRequest', () => {
expect(request.ignorePaths).toStrictEqual([
[
(
- state.commerceFacetSet[facetId].request
+ state.commerceFacetSet[nonNamespacedId].request
.values[0] as CategoryFacetValueRequest
).value,
(
- state.commerceFacetSet[facetId].request
+ state.commerceFacetSet[nonNamespacedId].request
.values[0] as CategoryFacetValueRequest
).children[0].value,
(
- state.commerceFacetSet[facetId].request
+ state.commerceFacetSet[nonNamespacedId].request
.values[0] as CategoryFacetValueRequest
).children[0].children[0].value,
],
diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts
index 7c0dbc55b75..fd8c124a4d7 100644
--- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts
+++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts
@@ -5,6 +5,7 @@ import {
AnyFacetRequest,
CategoryFacetRequest,
} from '../../facet-set/interfaces/request';
+import {getFacetIdWithoutCommerceFieldSuggestionNamespace} from '../commerce-facet-search-actions';
import {StateNeededForCategoryFacetSearch} from './commerce-category-facet-search-state';
export const buildCategoryFacetSearchRequest = (
@@ -15,7 +16,10 @@ export const buildCategoryFacetSearchRequest = (
): CategoryFacetSearchRequest => {
const baseFacetQuery = state.categoryFacetSearchSet[facetId]!.options.query;
const facetQuery = `*${baseFacetQuery}*`;
- const categoryFacet = state.commerceFacetSet[facetId]?.request;
+ const categoryFacet =
+ state.commerceFacetSet[
+ getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId)
+ ]?.request;
const path =
categoryFacet && isCategoryFacetRequest(categoryFacet)
? categoryFacet && getPathToSelectedCategoryFacetItem(categoryFacet)
@@ -40,7 +44,7 @@ export const buildCategoryFacetSearchRequest = (
url,
accessToken,
organizationId,
- facetId,
+ facetId: getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId),
facetQuery,
ignorePaths,
trackingId,
diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts
index 1b873983d75..e5adce226b2 100644
--- a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts
+++ b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts
@@ -114,3 +114,21 @@ export const isRegularFieldSuggestionsState = (
(facet) => facet.facetId === facetId && facet.type === 'regular'
);
};
+
+const commerceFieldSuggestionNamespace = 'field_suggestion:';
+
+export function getFacetIdWithoutCommerceFieldSuggestionNamespace(
+ facetId: string
+) {
+ return facetId.startsWith(commerceFieldSuggestionNamespace)
+ ? facetId.slice(commerceFieldSuggestionNamespace.length)
+ : facetId;
+}
+
+export function getFacetIdWithCommerceFieldSuggestionNamespace(
+ facetId: string
+): string {
+ return facetId.startsWith(commerceFieldSuggestionNamespace)
+ ? facetId
+ : `${commerceFieldSuggestionNamespace}${facetId}`;
+}
diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts
index ee88c1dcfc1..3ef3c04d7b3 100644
--- a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts
+++ b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts
@@ -1,6 +1,7 @@
import {CommerceFacetSearchRequest} from '../../../../../api/commerce/facet-search/facet-search-request';
import {NavigatorContext} from '../../../../../app/navigatorContextProvider';
import {buildCommerceAPIRequest} from '../../../common/actions';
+import {getFacetIdWithoutCommerceFieldSuggestionNamespace} from '../commerce-facet-search-actions';
import {StateNeededForRegularFacetSearch} from './commerce-regular-facet-search-state';
export const buildFacetSearchRequest = (
@@ -30,7 +31,7 @@ export const buildFacetSearchRequest = (
url,
accessToken,
organizationId,
- facetId,
+ facetId: getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId),
facetQuery,
trackingId,
language,
diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts
index 9aaa2a720dc..d88d05f6f13 100644
--- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts
+++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts
@@ -64,6 +64,7 @@ import {
toggleSelectDateFacetValue,
updateDateFacetValues,
} from '../date-facet/date-facet-actions';
+import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../facet-search-set/commerce-facet-search-actions';
import {
toggleExcludeNumericFacetValue,
toggleSelectNumericFacetValue,
@@ -688,16 +689,17 @@ describe('commerceFacetSetReducer', () => {
)
);
expect(finalState).toEqual({
- regular_field: {
+ [getFacetIdWithCommerceFieldSuggestionNamespace('regular_field')]: {
request: {
initialNumberOfValues: 10,
},
},
- hierarchical_field: {
- request: {
- initialNumberOfValues: 10,
+ [getFacetIdWithCommerceFieldSuggestionNamespace('hierarchical_field')]:
+ {
+ request: {
+ initialNumberOfValues: 10,
+ },
},
- },
});
});
});
diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts
index 57acc5397ee..1b35caf06af 100644
--- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts
+++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts
@@ -40,7 +40,10 @@ import {
toggleSelectDateFacetValue,
updateDateFacetValues,
} from '../date-facet/date-facet-actions';
-import {executeCommerceFieldSuggest} from '../facet-search-set/commerce-facet-search-actions';
+import {
+ executeCommerceFieldSuggest,
+ getFacetIdWithCommerceFieldSuggestionNamespace,
+} from '../facet-search-set/commerce-facet-search-actions';
import {
toggleExcludeNumericFacetValue,
toggleSelectNumericFacetValue,
@@ -81,7 +84,10 @@ export const commerceFacetSetReducer = createReducer(
.addCase(fetchProductListing.fulfilled, handleQueryFulfilled)
.addCase(executeSearch.fulfilled, handleQueryFulfilled)
.addCase(executeCommerceFieldSuggest.fulfilled, (state, action) =>
- handleFieldSuggestionsFulfilled(state, action.payload.facetId)
+ handleFieldSuggestionsFulfilled(
+ state,
+ getFacetIdWithCommerceFieldSuggestionNamespace(action.payload.facetId)
+ )
)
.addCase(fetchQuerySuggestions.fulfilled, (state, action) => {
if (!action.payload.fieldSuggestionsFacets) {
@@ -89,7 +95,10 @@ export const commerceFacetSetReducer = createReducer(
}
for (const {facetId} of action.payload.fieldSuggestionsFacets) {
- handleFieldSuggestionsFulfilled(state, facetId);
+ handleFieldSuggestionsFulfilled(
+ state,
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId)
+ );
}
})
.addCase(toggleSelectFacetValue, (state, action) => {
diff --git a/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts b/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts
index 7dd249202db..24a8ff93f78 100644
--- a/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts
+++ b/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts
@@ -3,6 +3,7 @@ import {CategoryFacetSearchResponse} from '../../../../api/search/facet-search/c
import {
executeCommerceFacetSearch,
executeCommerceFieldSuggest,
+ getFacetIdWithCommerceFieldSuggestionNamespace,
} from '../../../commerce/facets/facet-search-set/commerce-facet-search-actions';
import {fetchProductListing} from '../../../commerce/product-listing/product-listing-actions';
import {fetchQuerySuggestions} from '../../../commerce/query-suggest/query-suggest-actions';
@@ -57,7 +58,10 @@ export const categoryFacetSearchSetReducer = createReducer(
})
.addCase(executeCommerceFieldSuggest.rejected, (state, action) => {
const {facetId} = action.meta.arg;
- handleFacetSearchRejected(state, facetId);
+ handleFacetSearchRejected(
+ state,
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId)
+ );
})
.addCase(executeFacetSearch.rejected, (state, action) => {
const facetId = action.meta.arg;
diff --git a/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts b/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts
index 3ca7c7785fc..024ca1e69b6 100644
--- a/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts
+++ b/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts
@@ -1,6 +1,7 @@
import {CommerceAPIResponse} from '../../../api/commerce/commerce-api-client';
import {FacetSearchRequestOptions} from '../../../api/search/facet-search/base/base-facet-search-request';
import {FacetSearchResponse} from '../../../api/search/facet-search/facet-search-response';
+import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../commerce/facets/facet-search-set/commerce-facet-search-actions';
import {FieldSuggestionsFacet} from '../../commerce/facets/field-suggestions-order/field-suggestions-order-state';
import {FacetSearchOptions} from './facet-search-request-options';
@@ -156,11 +157,17 @@ export function handleCommerceFacetFieldSuggestionsFulfilled<
buildEmptyResponse: () => T
) {
const {facetId, response} = payload;
- let search = state[facetId];
+ const namespacedFacetId =
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId);
+ let search = state[namespacedFacetId];
if (!search) {
- handleFacetSearchRegistration(state, {facetId}, buildEmptyResponse);
- search = state[facetId];
+ handleFacetSearchRegistration(
+ state,
+ {facetId: namespacedFacetId},
+ buildEmptyResponse
+ );
+ search = state[namespacedFacetId];
} else if (search.requestId !== requestId) {
return;
}
@@ -223,14 +230,17 @@ export function handleCommerceFetchQuerySuggestionsFulfilledForCategoryFacet<
}
for (const fieldSuggestionFacet of payload.fieldSuggestionsFacets) {
+ const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace(
+ fieldSuggestionFacet.facetId
+ );
if (
- fieldSuggestionFacet.facetId in state ||
+ namespacedFacetId in state ||
fieldSuggestionFacet.type !== 'hierarchical'
) {
continue;
}
- state[fieldSuggestionFacet.facetId] = {
+ state[namespacedFacetId] = {
options: {
...defaultFacetSearchOptions,
query: payload.query ?? '',
diff --git a/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts b/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts
index 322abb686f9..98aadee0cc4 100644
--- a/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts
+++ b/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts
@@ -4,29 +4,32 @@ import {setView} from '../../../commerce/context/context-actions';
import {
executeCommerceFacetSearch,
executeCommerceFieldSuggest,
+ getFacetIdWithCommerceFieldSuggestionNamespace,
} from '../../../commerce/facets/facet-search-set/commerce-facet-search-actions';
import {fetchProductListing} from '../../../commerce/product-listing/product-listing-actions';
import {fetchQuerySuggestions} from '../../../commerce/query-suggest/query-suggest-actions';
import {executeSearch as executeCommerceSearch} from '../../../commerce/search/search-actions';
import {executeSearch} from '../../../search/search-actions';
import {
- handleFacetSearchRegistration,
- handleFacetSearchUpdate,
+ handleCommerceFacetFieldSuggestionsFulfilled,
+ handleCommerceFacetSearchFulfilled,
+ handleCommerceFetchQuerySuggestionsFulfilledForRegularFacet,
+ handleFacetSearchClear,
+ handleFacetSearchFulfilled,
handleFacetSearchPending,
+ handleFacetSearchRegistration,
handleFacetSearchRejected,
- handleFacetSearchFulfilled,
- handleFacetSearchClear,
handleFacetSearchSetClear,
- handleCommerceFacetSearchFulfilled,
- handleCommerceFacetFieldSuggestionsFulfilled,
- handleCommerceFetchQuerySuggestionsFulfilledForRegularFacet,
+ handleFacetSearchUpdate,
} from '../facet-search-reducer-helpers';
import {
clearFacetSearch,
executeFacetSearch,
} from '../generic/generic-facet-search-actions';
-import {registerFacetSearch} from './specific-facet-search-actions';
-import {updateFacetSearch} from './specific-facet-search-actions';
+import {
+ registerFacetSearch,
+ updateFacetSearch,
+} from './specific-facet-search-actions';
import {getFacetSearchSetInitialState} from './specific-facet-search-set-state';
export const specificFacetSearchSetReducer = createReducer(
@@ -46,7 +49,11 @@ export const specificFacetSearchSetReducer = createReducer(
})
.addCase(executeCommerceFieldSuggest.pending, (state, action) => {
const {facetId} = action.meta.arg;
- handleFacetSearchPending(state, facetId, action.meta.requestId);
+ handleFacetSearchPending(
+ state,
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId),
+ action.meta.requestId
+ );
})
.addCase(executeFacetSearch.pending, (state, action) => {
const facetId = action.meta.arg;
@@ -58,7 +65,10 @@ export const specificFacetSearchSetReducer = createReducer(
})
.addCase(executeCommerceFieldSuggest.rejected, (state, action) => {
const {facetId} = action.meta.arg;
- handleFacetSearchRejected(state, facetId);
+ handleFacetSearchRejected(
+ state,
+ getFacetIdWithCommerceFieldSuggestionNamespace(facetId)
+ );
})
.addCase(executeFacetSearch.rejected, (state, action) => {
const facetId = action.meta.arg;
From afcbe2017de8c1a557dca5325de328b92d0884c7 Mon Sep 17 00:00:00 2001
From: Felix Perron-Brault
Date: Mon, 5 Aug 2024 14:46:04 -0400
Subject: [PATCH 16/23] chore: add automatic sorting for tailwind classes
(#4233)
This PR adds `prettier-plugin-tailwindcss`, which is the [recommended
solution](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier)
to sort tailwind classes.
https://coveord.atlassian.net/browse/KIT-3439
---------
Co-authored-by: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com>
---
.prettierrc.js | 5 +-
package-lock.json | 75 +++++++++++++++++++
package.json | 1 +
.../atomic-commerce-products-per-page.pcss | 2 +-
.../atomic-commerce-search-box.tsx | 4 +-
.../atomic-commerce-category-facet.pcss | 4 +-
.../atomic-commerce-facet-number-input.tsx | 4 +-
.../atomic-product-children.tsx | 2 +-
.../atomic-product-description.tsx | 2 +-
.../atomic-product-price.tsx | 4 +-
.../atomic-product-rating.tsx | 2 +-
.../common/atomic-modal/atomic-modal.pcss | 2 +-
.../common/atomic-modal/atomic-modal.tsx | 15 ++--
.../common/atomic-rating/atomic-rating.tsx | 2 +-
.../common/breadbox/breadcrumb-clear-all.tsx | 2 +-
.../common/breadbox/breadcrumb-container.tsx | 6 +-
.../common/breadbox/breadcrumb-content.tsx | 2 +-
.../common/breadbox/breadcrumb-show-less.tsx | 2 +-
.../common/breadbox/breadcrumb-show-more.tsx | 2 +-
.../atomic/src/components/common/carousel.tsx | 4 +-
.../facets/category-facet/search-value.tsx | 8 +-
.../common/facets/facet-common.pcss | 4 +-
.../facet-container/facet-container.tsx | 2 +-
.../facet-date-input/facet-date-input.tsx | 4 +-
.../facets/facet-header/facet-header.tsx | 8 +-
.../facet-number-input/facet-number-input.tsx | 4 +-
.../facet-placeholder/facet-placeholder.tsx | 6 +-
.../facet-search/facet-search-input.tsx | 8 +-
.../facet-search/facet-search-matches.tsx | 4 +-
.../facet-value-box/facet-value-box.pcss | 2 +-
.../facet-value-box/facet-value-box.tsx | 4 +-
.../facet-value-checkbox.pcss | 4 +-
.../facet-value-exclude.tsx | 2 +-
.../facet-value-label-highlight.tsx | 2 +-
.../facet-value-link/facet-value-link.tsx | 2 +-
.../atomic-citation/atomic-citation.tsx | 14 ++--
...atomic-generated-answer-feedback-modal.tsx | 16 ++--
.../common/generated-answer/copy-button.tsx | 2 +-
.../generated-answer/feedback-button.tsx | 2 +-
.../generated-answer-common.tsx | 8 +-
.../generated-markdown-content.pcss | 14 ++--
.../generated-markdown-content.tsx | 2 +-
.../generated-text-content.tsx | 2 +-
.../generated-answer/rephrase-buttons.tsx | 8 +-
.../common/generated-answer/retry-prompt.tsx | 4 +-
.../common/generated-answer/show-button.tsx | 4 +-
.../generated-answer/source-citations.tsx | 4 +-
.../styles/generated-answer.pcss | 6 +-
.../src/components/common/iconButton.tsx | 6 +-
.../image-carousel-indicators.tsx | 10 +--
.../common/image-carousel/image-carousel.tsx | 2 +-
.../common/items-per-page/label.tsx | 2 +-
.../components/common/load-more/button.tsx | 2 +-
.../common/load-more/progress-bar.tsx | 4 +-
.../components/common/load-more/summary.tsx | 2 +-
.../src/components/common/no-items/cancel.tsx | 2 +-
.../components/common/no-items/container.tsx | 2 +-
.../common/no-items/magnifying-glass.tsx | 2 +-
.../components/common/no-items/no-items.tsx | 4 +-
.../src/components/common/no-items/tips.tsx | 2 +-
.../components/common/pager/pager-buttons.tsx | 6 +-
.../query-correction/trigger-correction.tsx | 4 +-
.../common/query-error/container.tsx | 2 +-
.../common/query-error/description.tsx | 2 +-
.../components/common/query-error/details.tsx | 2 +-
.../components/common/query-error/link.tsx | 2 +-
.../common/query-error/show-more.tsx | 2 +-
.../components/common/query-error/title.tsx | 2 +-
.../components/common/query-summary/guard.tsx | 2 +-
.../components/common/refine-modal/body.tsx | 2 +-
.../components/common/refine-modal/button.tsx | 2 +-
.../common/refine-modal/filters.tsx | 4 +-
.../components/common/refine-modal/guard.tsx | 2 +-
.../components/common/refine-modal/modal.tsx | 6 +-
.../components/common/refine-modal/sort.tsx | 8 +-
.../common/search-box/clear-button.tsx | 4 +-
.../common/search-box/search-box.pcss | 4 +-
.../common/search-box/search-input.tsx | 6 +-
.../common/search-box/search-text-area.tsx | 8 +-
.../common/search-box/submit-button.tsx | 4 +-
.../search-box/text-area-clear-button.tsx | 6 +-
.../search-box/text-area-submit-button.tsx | 6 +-
...atomic-smart-snippet-collapse-wrapper.pcss | 2 +-
.../atomic-smart-snippet-collapse-wrapper.tsx | 4 +-
...tomic-smart-snippet-expandable-answer.pcss | 2 +-
...atomic-smart-snippet-expandable-answer.tsx | 4 +-
.../atomic-smart-snippet-feedback-banner.tsx | 4 +-
.../atomic-smart-snippet-feedback-modal.pcss | 4 +-
.../smart-snippet-feedback-modal-common.tsx | 8 +-
.../smart-snippet-suggestions-common.tsx | 10 +--
.../smart-snippet-common.tsx | 2 +-
.../src/components/common/sort/container.tsx | 2 +-
.../src/components/common/sort/guard.tsx | 2 +-
.../src/components/common/sort/label.tsx | 2 +-
.../src/components/common/sort/select.tsx | 2 +-
.../common/suggestions/query-suggestions.tsx | 6 +-
.../common/suggestions/recent-queries.tsx | 8 +-
.../common/tab-manager/tab-button.tsx | 2 +-
.../common/tab-manager/tab-dropdown.tsx | 4 +-
.../src/components/common/tabs/tab-bar.tsx | 2 +-
.../components/common/tabs/tab-popover.tsx | 8 +-
.../atomic-insight-full-search-button.pcss | 4 +-
.../atomic-insight-query-summary.tsx | 2 +-
.../atomic-insight-refine-modal.tsx | 4 +-
.../atomic-insight-result-action-bar.pcss | 4 +-
.../atomic-insight-search-box.pcss | 10 +--
.../atomic-insight-search-box.tsx | 4 +-
.../atomic-insight-tab.pcss | 2 +-
.../result-lists/styles/list-display.pcss | 2 +-
.../ipx/atomic-ipx-body/atomic-ipx-body.pcss | 4 +-
.../ipx/atomic-ipx-body/atomic-ipx-body.tsx | 4 +-
.../atomic-ipx-button/atomic-ipx-button.pcss | 4 +-
.../atomic-ipx-refine-modal.tsx | 4 +-
.../atomic-ipx-refine-toggle.pcss | 4 +-
.../ipx/atomic-ipx-tab/atomic-ipx-tab.pcss | 2 +-
.../atomic-notifications.tsx | 6 +-
.../atomic-relevance-inspector.tsx | 4 +-
.../atomic-result-table-placeholder.tsx | 14 ++--
.../atomic-results-per-page.pcss | 2 +-
.../atomic-search-box/atomic-search-box.pcss | 2 +-
.../atomic-search-box/atomic-search-box.tsx | 4 +-
.../atomic-category-facet.pcss | 4 +-
.../atomic-color-facet/atomic-color-facet.tsx | 2 +-
.../facets/atomic-popover/atomic-popover.tsx | 14 ++--
.../atomic-rating-range-facet.tsx | 2 +-
.../atomic-segmented-facet-scrollable.tsx | 6 +-
.../atomic-segmented-facet.tsx | 4 +-
.../color-facet-checkbox.pcss | 2 +-
.../facet-segmented-value.tsx | 4 +-
.../atomic-quickview-modal.pcss | 2 +-
.../atomic-quickview-modal.tsx | 6 +-
.../atomic-quickview-sidebar.tsx | 14 ++--
.../atomic-quickview/atomic-quickview.tsx | 2 +-
.../atomic-result-badge.tsx | 4 +-
.../quickview-iframe/quickview-iframe.tsx | 2 +-
.../atomic-tab-manager/atomic-tab-manager.tsx | 2 +-
packages/atomic/src/global/global.pcss | 24 +++---
.../pages/examples/commerce-website/cart.html | 2 +-
.../examples/commerce-website/homepage.html | 2 +-
.../commerce-website/listing-pants.html | 2 +-
.../listing-surf-accessories.html | 2 +-
.../commerce-website/listing-towels.html | 2 +-
.../examples/commerce-website/search.html | 6 +-
.../src/pages/CommerceSearchPage.tsx | 2 +-
144 files changed, 384 insertions(+), 310 deletions(-)
diff --git a/.prettierrc.js b/.prettierrc.js
index ef1aec7470c..1f0b0cf9eef 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -5,7 +5,10 @@ const decoratorPlugin = JSON.stringify([
/** @type {import('prettier').Config} */
module.exports = {
- plugins: ['@trivago/prettier-plugin-sort-imports'],
+ plugins: [
+ '@trivago/prettier-plugin-sort-imports',
+ 'prettier-plugin-tailwindcss',
+ ],
bracketSpacing: false,
singleQuote: true,
trailingComma: 'es5',
diff --git a/package-lock.json b/package-lock.json
index ca2201ee909..f7dac3070c0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,6 +83,7 @@
"nx": "19.0.4",
"patch-package": "8.0.0",
"prettier": "3.3.3",
+ "prettier-plugin-tailwindcss": "0.6.5",
"react-syntax-highlighter": "15.5.0",
"rimraf": "5.0.9",
"semver": "7.6.3",
@@ -47308,6 +47309,80 @@
"prettier": "^3.0.0"
}
},
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz",
+ "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig-melody": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig-melody": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
diff --git a/package.json b/package.json
index e7031a4eb17..44bd9beabe0 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"nx": "19.0.4",
"patch-package": "8.0.0",
"prettier": "3.3.3",
+ "prettier-plugin-tailwindcss": "0.6.5",
"react-syntax-highlighter": "15.5.0",
"rimraf": "5.0.9",
"semver": "7.6.3",
diff --git a/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss b/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss
index ba9ae1fb155..d8f29ba58f2 100644
--- a/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss
+++ b/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss
@@ -1,5 +1,5 @@
@import '../../../global/global.pcss';
.btn-page {
- @apply w-10 h-10;
+ @apply h-10 w-10;
}
diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx
index cb3c8ffbd3e..2e5f573745d 100644
--- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx
+++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx
@@ -535,7 +535,7 @@ export class AtomicCommerceSearchBox
? 'suggestions-double-list'
: 'suggestions-single-list'
}`}
- class={`flex w-full z-10 absolute left-0 top-full rounded-md bg-background border border-neutral ${
+ class={`bg-background border-neutral absolute left-0 top-full z-10 flex w-full rounded-md border ${
this.suggestionManager.hasSuggestions &&
this.isExpanded &&
!this.isSearchDisabledForEndUser(this.searchBoxState.value)
@@ -604,7 +604,7 @@ export class AtomicCommerceSearchBox
);
diff --git a/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss b/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss
index ceeb35f0691..b7a0b473a13 100644
--- a/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss
+++ b/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss
@@ -11,9 +11,9 @@
[part~='all-categories-button'],
[part~='parent-button'] {
- @apply w-full py-2.5 pr-2 pl-7 text-left relative flex items-center;
+ @apply relative flex w-full items-center py-2.5 pl-7 pr-2 text-left;
}
[part~='back-arrow'] {
- @apply h-5 w-5 absolute left-1;
+ @apply absolute left-1 h-5 w-5;
}
diff --git a/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx b/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx
index 714956e069b..757d7d504a4 100644
--- a/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx
+++ b/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx
@@ -79,7 +79,7 @@ export class FacetNumberInput {
return (