@akinon/pz-similar-products 1.92.0-rc.38 → 1.92.0-rc.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @akinon/pz-similar-products
2
2
 
3
+ ## 1.92.0-rc.40
4
+
5
+ ## 1.92.0-rc.39
6
+
7
+ ### Minor Changes
8
+
9
+ - 174bf3a: ZERO-3533: Refactor similar products API endpoints to support array search parameters and improve query handling
10
+
3
11
  ## 1.92.0-rc.38
4
12
 
5
13
  ## 1.92.0-rc.37
package/README.md CHANGED
@@ -92,13 +92,13 @@ Change which icons are displayed by specifying different icon names:
92
92
  ```tsx
93
93
  const iconCustomization = {
94
94
  iconNames: {
95
- searchButton: 'magnifier', // Search button (default: 'search')
96
- modalClose: 'x', // Modal close buttons (default: 'close')
97
- filter: 'funnel', // Filter button (default: 'filter')
98
- filterRemove: 'trash', // Filter tag remove buttons (default: 'close')
95
+ searchButton: 'magnifier', // Search button (default: 'search')
96
+ modalClose: 'x', // Modal close buttons (default: 'close')
97
+ filter: 'funnel', // Filter button (default: 'filter')
98
+ filterRemove: 'trash', // Filter tag remove buttons (default: 'close')
99
99
  modalSearch: 'search-outline', // Modal search button (default: 'search')
100
- modalSearchClear: 'clear', // Search input clear button (default: 'close')
101
- imageSearchButton: 'upload' // Image search button (default: 'search')
100
+ modalSearchClear: 'clear', // Search input clear button (default: 'close')
101
+ imageSearchButton: 'upload' // Image search button (default: 'search')
102
102
  }
103
103
  };
104
104
  ```
@@ -133,13 +133,15 @@ const completeIconSettings = {
133
133
  modalSearchClear: 'clear-alt',
134
134
  imageSearchButton: 'upload-cloud'
135
135
  },
136
-
136
+
137
137
  // Style the icons
138
138
  customStyles: {
139
- searchButtonIcon: 'fill-indigo-500 hover:fill-indigo-600 transition-all duration-200',
139
+ searchButtonIcon:
140
+ 'fill-indigo-500 hover:fill-indigo-600 transition-all duration-200',
140
141
  modalCloseIcon: 'text-gray-500 hover:text-gray-700 w-5 h-5',
141
142
  filterIcon: 'text-blue-500 hover:text-blue-600 w-4 h-4',
142
- filterRemoveIcon: 'text-red-400 hover:text-red-600 w-3 h-3 transition-colors',
143
+ filterRemoveIcon:
144
+ 'text-red-400 hover:text-red-600 w-3 h-3 transition-colors',
143
145
  modalSearchIcon: 'text-green-500 hover:text-green-600',
144
146
  modalSearchClearIcon: 'text-gray-400 hover:text-gray-600',
145
147
  imageSearchButtonIcon: 'text-indigo-500 hover:text-indigo-600 w-5 h-5'
@@ -154,7 +156,7 @@ const completeIconSettings = {
154
156
  activeIndex,
155
157
  settings: completeIconSettings
156
158
  }}
157
- />
159
+ />;
158
160
  ```
159
161
 
160
162
  ### Available Icon Customization Points
@@ -288,7 +290,7 @@ interface SimilarProductsSettings {
288
290
  itemCount?: string;
289
291
  sortDropdown?: string;
290
292
  filterToggleButton?: string;
291
-
293
+
292
294
  // Text search input
293
295
  modalSearchInput?: string;
294
296
  modalSearchContainer?: string;
@@ -296,7 +298,7 @@ interface SimilarProductsSettings {
296
298
  modalSearchIcon?: string;
297
299
  modalSearchClearButton?: string;
298
300
  modalSearchClearIcon?: string;
299
-
301
+
300
302
  // Icon customization
301
303
  searchButtonIcon?: string; // Search button icon styling
302
304
  modalCloseIcon?: string; // Modal close button icons styling
@@ -476,13 +478,16 @@ The plugin features a built-in text search functionality that allows users to se
476
478
  ```tsx
477
479
  const textSearchSettings = {
478
480
  enableTextSearch: true, // Enable text search input
479
-
481
+
480
482
  customStyles: {
481
483
  modalSearchContainer: 'relative flex items-center',
482
- modalSearchInput: 'h-10 px-4 pr-20 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500',
483
- modalSearchButton: 'absolute right-2 top-1/2 -translate-y-1/2 p-2 hover:bg-gray-100 rounded-md',
484
+ modalSearchInput:
485
+ 'h-10 px-4 pr-20 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500',
486
+ modalSearchButton:
487
+ 'absolute right-2 top-1/2 -translate-y-1/2 p-2 hover:bg-gray-100 rounded-md',
484
488
  modalSearchIcon: 'text-gray-500',
485
- modalSearchClearButton: 'absolute right-12 top-1/2 -translate-y-1/2 p-2 hover:bg-gray-100 rounded-md',
489
+ modalSearchClearButton:
490
+ 'absolute right-12 top-1/2 -translate-y-1/2 p-2 hover:bg-gray-100 rounded-md',
486
491
  modalSearchClearIcon: 'text-gray-400 hover:text-gray-600'
487
492
  }
488
493
  };
@@ -493,22 +498,33 @@ const textSearchSettings = {
493
498
  ```tsx
494
499
  const customIconSettings = {
495
500
  enableTextSearch: true,
496
-
501
+
497
502
  customRenderers: {
498
503
  render: {
499
504
  modal: {
500
505
  renderSearchIcon: ({ disabled, onClick }) => (
501
- <svg
502
- className="w-5 h-5 text-blue-500 cursor-pointer hover:text-blue-700"
506
+ <svg
507
+ className="w-5 h-5 text-blue-500 cursor-pointer hover:text-blue-700"
503
508
  onClick={onClick}
504
- fill="none"
505
- stroke="currentColor"
509
+ fill="none"
510
+ stroke="currentColor"
506
511
  viewBox="0 0 24 24"
507
512
  >
508
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
513
+ <path
514
+ strokeLinecap="round"
515
+ strokeLinejoin="round"
516
+ strokeWidth={2}
517
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
518
+ />
509
519
  </svg>
510
520
  ),
511
- renderModalSearchInput: ({ searchText, setSearchText, isLoading, placeholder, onSearch }) => (
521
+ renderModalSearchInput: ({
522
+ searchText,
523
+ setSearchText,
524
+ isLoading,
525
+ placeholder,
526
+ onSearch
527
+ }) => (
512
528
  <div className="relative">
513
529
  <input
514
530
  type="text"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-similar-products",
3
- "version": "1.92.0-rc.38",
3
+ "version": "1.92.0-rc.40",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "peerDependencies": {
@@ -27,7 +27,12 @@ export const similarProductsApi = api.injectEndpoints({
27
27
  endpoints: (build) => ({
28
28
  getSimilarProductsByUrl: build.query<
29
29
  SimilarProductsResponse,
30
- { url: string; limit?: number; excluded_product_ids?: number[]; text?: string }
30
+ {
31
+ url: string;
32
+ limit?: number;
33
+ excluded_product_ids?: number[];
34
+ text?: string;
35
+ }
31
36
  >({
32
37
  query: ({ url, limit = 20, excluded_product_ids, text }) => {
33
38
  const params = new URLSearchParams();
@@ -54,7 +59,12 @@ export const similarProductsApi = api.injectEndpoints({
54
59
 
55
60
  getSimilarProductsByImage: build.mutation<
56
61
  SimilarProductsResponse,
57
- { image: string; limit?: number; excluded_product_ids?: number[]; text?: string }
62
+ {
63
+ image: string;
64
+ limit?: number;
65
+ excluded_product_ids?: number[];
66
+ text?: string;
67
+ }
58
68
  >({
59
69
  query: ({ image, limit = 20, excluded_product_ids, text }) => {
60
70
  const params = new URLSearchParams();
@@ -81,12 +91,21 @@ export const similarProductsApi = api.injectEndpoints({
81
91
  SimilarProductsListResponse,
82
92
  {
83
93
  filter: string;
84
- searchParams?: Record<string, string>;
94
+ searchParams?: Record<string, string | string[]>;
85
95
  isExclude?: boolean;
86
96
  }
87
97
  >({
88
98
  query: ({ filter, searchParams = {}, isExclude = false }) => {
89
- const params = new URLSearchParams(searchParams);
99
+ const params = new URLSearchParams();
100
+
101
+ Object.entries(searchParams).forEach(([key, value]) => {
102
+ if (Array.isArray(value)) {
103
+ value.forEach((v) => params.append(key, v));
104
+ } else {
105
+ params.append(key, value);
106
+ }
107
+ });
108
+
90
109
  const queryString = params.toString();
91
110
 
92
111
  const headerName = isExclude
@@ -107,7 +126,7 @@ export const similarProductsApi = api.injectEndpoints({
107
126
  .reduce((acc, key) => {
108
127
  acc[key] = searchParams[key];
109
128
  return acc;
110
- }, {} as Record<string, string>);
129
+ }, {} as Record<string, string | string[]>);
111
130
 
112
131
  return JSON.stringify({
113
132
  filter,
@@ -128,4 +147,4 @@ export const {
128
147
  useLazyGetSimilarProductsListQuery
129
148
  } = similarProductsApi;
130
149
 
131
- export type { Product, Facet, FacetChoice, SortOption, Pagination };
150
+ export type { Product, Facet, FacetChoice, SortOption, Pagination };
@@ -1,3 +1,4 @@
1
1
  export { useSimilarProducts } from './use-similar-products';
2
2
  export { useImageCropper } from './use-image-cropper';
3
3
  export { useImageSearchFeature } from './use-image-search-feature';
4
+ export { useTextSearchFeature } from './use-text-search-feature';
@@ -18,7 +18,7 @@ import { debounce } from '../utils';
18
18
  type SearchResults = SimilarProductsListResponse;
19
19
 
20
20
  interface SearchParams {
21
- [key: string]: string;
21
+ [key: string]: string | string[];
22
22
  }
23
23
 
24
24
  export function useSimilarProducts(product: Product) {
@@ -115,7 +115,7 @@ export function useSimilarProducts(product: Product) {
115
115
  String(choice.value)
116
116
  );
117
117
  const searchKey = String(facet.search_key || facet.key);
118
- searchParams[searchKey] = values.join(',');
118
+ searchParams[searchKey] = values.length === 1 ? values[0] : values;
119
119
  }
120
120
  });
121
121
  }
@@ -350,7 +350,9 @@ export function useSimilarProducts(product: Product) {
350
350
  const productIds = extractProductIds(data);
351
351
 
352
352
  if (productIds.length > 0) {
353
- const result = await executeProductSearch(productIds);
353
+ const searchParams = buildSearchParams(searchResults?.facets);
354
+
355
+ const result = await executeProductSearch(productIds, searchParams);
354
356
  if (result) {
355
357
  const resultWithCorrectSorterState = {
356
358
  ...result,
@@ -369,7 +371,11 @@ export function useSimilarProducts(product: Product) {
369
371
  executeProductSearch,
370
372
  updateSorterState,
371
373
  updateResultsAndKey,
372
- createEmptySearchResults
374
+ createEmptySearchResults,
375
+ searchText,
376
+ searchTextRef,
377
+ buildSearchParams,
378
+ searchResults
373
379
  ]
374
380
  );
375
381
 
@@ -974,7 +980,11 @@ export function useSimilarProducts(product: Product) {
974
980
  const textToUse = searchTextRef.current || searchText;
975
981
 
976
982
  if (hasCroppedImage && currentImageBase64) {
977
- await fetchSimilarProductsByBase64(currentImageBase64, textToUse, !hasUploadedImage);
983
+ await fetchSimilarProductsByBase64(
984
+ currentImageBase64,
985
+ textToUse,
986
+ !hasUploadedImage
987
+ );
978
988
  } else if (hasUploadedImage && currentImageUrl) {
979
989
  await fetchSimilarProductsByBase64(currentImageUrl, textToUse, false);
980
990
  } else if (currentImageUrl) {
@@ -995,7 +1005,11 @@ export function useSimilarProducts(product: Product) {
995
1005
  setSearchTextWithRef('');
996
1006
 
997
1007
  if (hasCroppedImage && currentImageBase64) {
998
- await fetchSimilarProductsByBase64(currentImageBase64, '', !hasUploadedImage);
1008
+ await fetchSimilarProductsByBase64(
1009
+ currentImageBase64,
1010
+ '',
1011
+ !hasUploadedImage
1012
+ );
999
1013
  } else if (hasUploadedImage && currentImageUrl) {
1000
1014
  await fetchSimilarProductsByBase64(currentImageUrl, '', false);
1001
1015
  } else if (currentImageUrl) {
@@ -0,0 +1,41 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ const TEXT_SEARCH_STORAGE_KEY = 'enable_image_search_text';
4
+
5
+ interface UseTextSearchFeatureProps {
6
+ enableTextSearch?: boolean;
7
+ }
8
+
9
+ export function useTextSearchFeature(props?: UseTextSearchFeatureProps) {
10
+ const [isEnabled, setIsEnabled] = useState(false);
11
+ const [isLoading, setIsLoading] = useState(true);
12
+
13
+ useEffect(() => {
14
+ const envEnabled = process.env.NEXT_PUBLIC_ENABLE_TEXT_SEARCH === 'true';
15
+
16
+ if (envEnabled) {
17
+ setIsEnabled(true);
18
+ setIsLoading(false);
19
+ return;
20
+ }
21
+
22
+ try {
23
+ const localStorageValue = localStorage.getItem(TEXT_SEARCH_STORAGE_KEY);
24
+
25
+ if (localStorageValue === 'true') {
26
+ setIsEnabled(true);
27
+ } else {
28
+ setIsEnabled(!!props?.enableTextSearch);
29
+ }
30
+ } catch (error) {
31
+ setIsEnabled(false);
32
+ }
33
+
34
+ setIsLoading(false);
35
+ }, [props?.enableTextSearch]);
36
+
37
+ return {
38
+ isEnabled,
39
+ isLoading
40
+ };
41
+ }
@@ -3,6 +3,7 @@
3
3
  import React, { useState, useRef } from 'react';
4
4
  import { Product } from '@akinon/next/types';
5
5
  import { useImageSearchFeature } from '../hooks/use-image-search-feature';
6
+ import { useTextSearchFeature } from '../hooks/use-text-search-feature';
6
7
  import { ImageSearchButton } from './image-search-button';
7
8
  import { SimilarProductsPlugin } from './main';
8
9
 
@@ -20,6 +21,9 @@ export function HeaderImageSearchFeature({
20
21
  enableTextSearch = false
21
22
  }: HeaderImageSearchFeatureProps) {
22
23
  const { isEnabled: hookIsEnabled, isLoading } = useImageSearchFeature();
24
+ const { isEnabled: textSearchEnabled } = useTextSearchFeature({
25
+ enableTextSearch
26
+ });
23
27
 
24
28
  const envEnabled = process.env.NEXT_PUBLIC_ENABLE_IMAGE_SEARCH === 'true';
25
29
  const finalIsEnabled = envEnabled
@@ -31,10 +35,10 @@ export function HeaderImageSearchFeature({
31
35
  const [isImageSearchModalOpen, setIsImageSearchModalOpen] = useState(false);
32
36
  const [isResultsModalOpen, setIsResultsModalOpen] = useState(false);
33
37
  const [uploadedImageFile, setUploadedImageFile] = useState<File | null>(null);
34
-
38
+
35
39
  const settings = {
36
40
  ...userSettings,
37
- enableTextSearch
41
+ enableTextSearch: textSearchEnabled
38
42
  };
39
43
 
40
44
  if (isLoading || !finalIsEnabled) {
@@ -6,6 +6,7 @@ import { SimilarProductsButton } from './search-button';
6
6
  import { SimilarProductsPlugin } from './main';
7
7
  import { mergeSettings } from '../utils';
8
8
  import { useImageSearchFeature } from '../hooks/use-image-search-feature';
9
+ import { useTextSearchFeature } from '../hooks/use-text-search-feature';
9
10
 
10
11
  interface ProductImageSearchFeatureProps {
11
12
  product: Product;
@@ -24,12 +25,16 @@ export function ProductImageSearchFeature({
24
25
  isEnabled: isEnabledProp,
25
26
  enableTextSearch = false
26
27
  }: ProductImageSearchFeatureProps) {
28
+ const [isModalOpen, setIsModalOpen] = useState(false);
29
+ const { isEnabled: hookIsEnabled } = useImageSearchFeature();
30
+ const { isEnabled: textSearchEnabled } = useTextSearchFeature({
31
+ enableTextSearch
32
+ });
33
+
27
34
  const settings = mergeSettings({
28
35
  ...userSettings,
29
- enableTextSearch
36
+ enableTextSearch: textSearchEnabled
30
37
  });
31
- const [isModalOpen, setIsModalOpen] = useState(false);
32
- const { isEnabled: hookIsEnabled } = useImageSearchFeature();
33
38
 
34
39
  const envEnabled = process.env.NEXT_PUBLIC_ENABLE_IMAGE_SEARCH === 'true';
35
40
  const finalIsEnabled = envEnabled
@@ -46,7 +51,11 @@ export function ProductImageSearchFeature({
46
51
  <>
47
52
  {finalIsEnabled && (
48
53
  <>
49
- <SimilarProductsButton onClick={handleClick} className={className} settings={settings} />
54
+ <SimilarProductsButton
55
+ onClick={handleClick}
56
+ className={className}
57
+ settings={settings}
58
+ />
50
59
 
51
60
  <SimilarProductsPlugin
52
61
  product={product}