@akinon/pz-similar-products 1.92.0-rc.19 → 1.92.0-rc.21
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 +12 -0
- package/README.md +286 -20
- package/package.json +1 -1
- package/src/hooks/use-similar-products.ts +18 -12
- package/src/types/index.ts +23 -0
- package/src/utils/image-conversion.ts +44 -0
- package/src/utils/index.ts +6 -1
- package/src/views/filters.tsx +696 -628
- package/src/views/header-image-search-feature.tsx +16 -4
- package/src/views/image-search.tsx +136 -84
- package/src/views/main.tsx +1 -0
- package/src/views/product-image-search-feature.tsx +12 -3
- package/src/views/results.tsx +12 -2
- package/src/views/search-modal.tsx +38 -7
package/src/views/filters.tsx
CHANGED
|
@@ -79,6 +79,38 @@ export function SimilarProductsFilterSidebar({
|
|
|
79
79
|
}: FilterSidebarProps) {
|
|
80
80
|
const { t } = useLocalization();
|
|
81
81
|
|
|
82
|
+
if (settings?.customRenderers?.render?.filterSidebar?.renderSidebar) {
|
|
83
|
+
return settings.customRenderers.render.filterSidebar.renderSidebar({
|
|
84
|
+
isFilterMenuOpen,
|
|
85
|
+
setIsFilterMenuOpen,
|
|
86
|
+
searchResults,
|
|
87
|
+
isLoading,
|
|
88
|
+
handleFacetChange,
|
|
89
|
+
removeFacetFilter,
|
|
90
|
+
currentImageUrl,
|
|
91
|
+
isCropping,
|
|
92
|
+
imageRef,
|
|
93
|
+
fileInputRef,
|
|
94
|
+
crop,
|
|
95
|
+
setCrop,
|
|
96
|
+
completedCrop,
|
|
97
|
+
setCompletedCrop,
|
|
98
|
+
handleCropComplete,
|
|
99
|
+
toggleCropMode,
|
|
100
|
+
processCompletedCrop,
|
|
101
|
+
processManualCrop,
|
|
102
|
+
resetCrop,
|
|
103
|
+
product,
|
|
104
|
+
hasUploadedImage,
|
|
105
|
+
handleFileUpload,
|
|
106
|
+
handleResetToOriginal,
|
|
107
|
+
fileError,
|
|
108
|
+
showResetButton,
|
|
109
|
+
settings,
|
|
110
|
+
className
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
82
114
|
const sizeKey = settings?.commonProductAttributes?.find(
|
|
83
115
|
(item: any) => item.translationKey === 'size'
|
|
84
116
|
)?.key;
|
|
@@ -135,724 +167,760 @@ export function SimilarProductsFilterSidebar({
|
|
|
135
167
|
);
|
|
136
168
|
|
|
137
169
|
return (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const mobileHeaderClassName = twMerge(
|
|
158
|
-
'flex justify-between mb-6 md:hidden',
|
|
159
|
-
settings?.customStyles?.filterSidebarMobileHeader
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<>
|
|
164
|
-
<div className={mobileHeaderClassName}>
|
|
165
|
-
<h3 className="text-2xl font-bold">
|
|
166
|
-
{t('common.product.filters')}
|
|
167
|
-
</h3>
|
|
168
|
-
<Button
|
|
169
|
-
appearance="ghost"
|
|
170
|
-
size="sm"
|
|
171
|
-
onClick={() => setIsFilterMenuOpen(false)}
|
|
172
|
-
className="hover:bg-gray-200 rounded-full p-0 w-6 h-6"
|
|
173
|
-
>
|
|
174
|
-
<Icon name="close" size={16} />
|
|
175
|
-
</Button>
|
|
176
|
-
</div>
|
|
177
|
-
<div className="flex justify-between items-center mb-6 md:hidden">
|
|
178
|
-
<span className="text-sm">
|
|
179
|
-
{searchResults?.pagination?.total_count || 0} items
|
|
180
|
-
</span>
|
|
181
|
-
</div>
|
|
182
|
-
</>
|
|
183
|
-
);
|
|
184
|
-
})()}
|
|
185
|
-
|
|
186
|
-
{(() => {
|
|
187
|
-
if (
|
|
188
|
-
settings?.customRenderers?.render?.filterSidebar?.renderImageSection
|
|
189
|
-
) {
|
|
190
|
-
return settings.customRenderers.render.filterSidebar.renderImageSection(
|
|
191
|
-
{
|
|
192
|
-
currentImageUrl,
|
|
193
|
-
isCropping,
|
|
194
|
-
onToggleCrop: handleToggleCropMode,
|
|
195
|
-
onFileUpload: handleFileChangeWithCropReset,
|
|
196
|
-
onResetToOriginal: handleResetToOriginal,
|
|
197
|
-
fileError,
|
|
198
|
-
isLoading
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const imageSectionClassName = twMerge(
|
|
204
|
-
'relative mb-2 md:mb-6 mt-4',
|
|
205
|
-
settings?.customStyles?.imageSection
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const imageContainerClassName = twMerge(
|
|
209
|
-
'relative bg-white overflow-hidden flex items-center justify-center transition-all duration-300 ease-in-out',
|
|
210
|
-
isCropping ? 'border-2 border-dashed border-gray-300' : '',
|
|
211
|
-
settings?.customStyles?.imageContainer
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
const cropButtonClassName = twMerge(
|
|
215
|
-
`absolute z-10 bottom-3 left-3 p-2 rounded-full bg-white shadow-md w-10 h-10 ${
|
|
216
|
-
isCropping ? 'text-red-500' : 'text-gray-700'
|
|
217
|
-
} ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`,
|
|
218
|
-
settings?.customStyles?.cropButton
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
const renderCropButton = () => {
|
|
222
|
-
if (!settings?.enableCropping || settings.enableCropping === false)
|
|
223
|
-
return null;
|
|
224
|
-
|
|
170
|
+
<>
|
|
171
|
+
{isFilterMenuOpen && (
|
|
172
|
+
<div
|
|
173
|
+
className={twMerge(
|
|
174
|
+
'fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden',
|
|
175
|
+
settings?.customStyles?.filterSidebarMobileOverlay
|
|
176
|
+
)}
|
|
177
|
+
onClick={() => setIsFilterMenuOpen(false)}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
<div
|
|
182
|
+
className={sidebarClassName}
|
|
183
|
+
style={{
|
|
184
|
+
maxHeight: 'calc(100vh - 120px)'
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{(() => {
|
|
225
188
|
if (
|
|
226
|
-
settings?.customRenderers?.render?.filterSidebar?.
|
|
189
|
+
settings?.customRenderers?.render?.filterSidebar?.renderMobileHeader
|
|
227
190
|
) {
|
|
228
|
-
return settings.customRenderers.render.filterSidebar.
|
|
191
|
+
return settings.customRenderers.render.filterSidebar.renderMobileHeader(
|
|
229
192
|
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
193
|
+
title: t('common.product.filters'),
|
|
194
|
+
itemCount: searchResults?.pagination?.total_count || 0,
|
|
195
|
+
onClose: () => setIsFilterMenuOpen(false)
|
|
233
196
|
}
|
|
234
197
|
);
|
|
235
198
|
}
|
|
236
199
|
|
|
200
|
+
const mobileHeaderClassName = twMerge(
|
|
201
|
+
'flex justify-between mb-6 md:hidden',
|
|
202
|
+
settings?.customStyles?.filterSidebarMobileHeader
|
|
203
|
+
);
|
|
204
|
+
|
|
237
205
|
return (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
{isLoading ? (
|
|
246
|
-
<LoaderSpinner className="w-5 h-5" />
|
|
247
|
-
) : isCropping ? (
|
|
248
|
-
<svg
|
|
249
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
250
|
-
width="20"
|
|
251
|
-
height="20"
|
|
252
|
-
viewBox="0 0 24 24"
|
|
253
|
-
fill="none"
|
|
254
|
-
stroke="currentColor"
|
|
255
|
-
strokeWidth="2"
|
|
256
|
-
strokeLinecap="round"
|
|
257
|
-
strokeLinejoin="round"
|
|
206
|
+
<>
|
|
207
|
+
<div className={mobileHeaderClassName}>
|
|
208
|
+
<h3
|
|
209
|
+
className={twMerge(
|
|
210
|
+
'text-2xl font-bold',
|
|
211
|
+
settings?.customStyles?.filterSidebarMobileTitle
|
|
212
|
+
)}
|
|
258
213
|
>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
stroke="currentColor"
|
|
270
|
-
strokeWidth="2"
|
|
271
|
-
strokeLinecap="round"
|
|
272
|
-
strokeLinejoin="round"
|
|
214
|
+
{t('common.product.filters')}
|
|
215
|
+
</h3>
|
|
216
|
+
<Button
|
|
217
|
+
appearance="ghost"
|
|
218
|
+
size="sm"
|
|
219
|
+
onClick={() => setIsFilterMenuOpen(false)}
|
|
220
|
+
className={twMerge(
|
|
221
|
+
'hover:bg-gray-200 rounded-full p-0 w-6 h-6',
|
|
222
|
+
settings?.customStyles?.filterSidebarMobileCloseButton
|
|
223
|
+
)}
|
|
273
224
|
>
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
225
|
+
<Icon name="close" size={16} />
|
|
226
|
+
</Button>
|
|
227
|
+
</div>
|
|
228
|
+
<div
|
|
229
|
+
className={twMerge(
|
|
230
|
+
'flex justify-between items-center mb-6 md:hidden',
|
|
231
|
+
settings?.customStyles?.filterSidebarMobileCounter
|
|
232
|
+
)}
|
|
233
|
+
>
|
|
234
|
+
<span
|
|
235
|
+
className={twMerge(
|
|
236
|
+
'text-sm',
|
|
237
|
+
settings?.customStyles?.filterSidebarMobileCounterText
|
|
238
|
+
)}
|
|
239
|
+
>
|
|
240
|
+
{searchResults?.pagination?.total_count || 0} items
|
|
241
|
+
</span>
|
|
242
|
+
</div>
|
|
243
|
+
</>
|
|
279
244
|
);
|
|
280
|
-
}
|
|
245
|
+
})()}
|
|
281
246
|
|
|
282
|
-
|
|
283
|
-
if (!settings?.enableCropping || settings.enableCropping === false)
|
|
284
|
-
return null;
|
|
247
|
+
{(() => {
|
|
285
248
|
if (
|
|
286
|
-
|
|
287
|
-
!completedCrop ||
|
|
288
|
-
completedCrop.width <= 10 ||
|
|
289
|
-
completedCrop.height <= 10
|
|
290
|
-
)
|
|
291
|
-
return null;
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
settings?.customRenderers?.render?.filterSidebar?.renderTickButton
|
|
249
|
+
settings?.customRenderers?.render?.filterSidebar?.renderImageSection
|
|
295
250
|
) {
|
|
296
|
-
return settings.customRenderers.render.filterSidebar.
|
|
251
|
+
return settings.customRenderers.render.filterSidebar.renderImageSection(
|
|
297
252
|
{
|
|
298
|
-
|
|
299
|
-
|
|
253
|
+
currentImageUrl,
|
|
254
|
+
isCropping,
|
|
255
|
+
onToggleCrop: handleToggleCropMode,
|
|
256
|
+
onFileUpload: handleFileChangeWithCropReset,
|
|
257
|
+
onResetToOriginal: handleResetToOriginal,
|
|
258
|
+
fileError,
|
|
259
|
+
isLoading
|
|
300
260
|
}
|
|
301
261
|
);
|
|
302
262
|
}
|
|
303
263
|
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}`,
|
|
308
|
-
settings?.customStyles?.tickButton
|
|
264
|
+
const imageSectionClassName = twMerge(
|
|
265
|
+
'relative mb-2 md:mb-6 mt-4',
|
|
266
|
+
settings?.customStyles?.imageSection
|
|
309
267
|
);
|
|
310
268
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
onClick={() => {
|
|
316
|
-
if (!isLoading) {
|
|
317
|
-
handleSafeProcessCrop(completedCrop);
|
|
318
|
-
}
|
|
319
|
-
}}
|
|
320
|
-
disabled={isLoading}
|
|
321
|
-
className={tickButtonClassName}
|
|
322
|
-
>
|
|
323
|
-
{isLoading ? (
|
|
324
|
-
<LoaderSpinner className="w-5 h-5" />
|
|
325
|
-
) : (
|
|
326
|
-
<svg
|
|
327
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
328
|
-
width="20"
|
|
329
|
-
height="20"
|
|
330
|
-
viewBox="0 0 24 24"
|
|
331
|
-
fill="none"
|
|
332
|
-
stroke="currentColor"
|
|
333
|
-
strokeWidth="2"
|
|
334
|
-
strokeLinecap="round"
|
|
335
|
-
strokeLinejoin="round"
|
|
336
|
-
>
|
|
337
|
-
<polyline points="20,6 9,17 4,12"></polyline>
|
|
338
|
-
</svg>
|
|
339
|
-
)}
|
|
340
|
-
</Button>
|
|
269
|
+
const imageContainerClassName = twMerge(
|
|
270
|
+
'relative bg-white overflow-hidden flex items-center justify-center transition-all duration-300 ease-in-out',
|
|
271
|
+
isCropping ? 'border-2 border-dashed border-gray-300' : '',
|
|
272
|
+
settings?.customStyles?.imageContainer
|
|
341
273
|
);
|
|
342
|
-
};
|
|
343
274
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
275
|
+
const cropButtonClassName = twMerge(
|
|
276
|
+
`absolute z-10 bottom-3 left-3 p-2 rounded-full bg-white shadow-md w-10 h-10 ${
|
|
277
|
+
isCropping ? 'text-red-500' : 'text-gray-700'
|
|
278
|
+
} ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`,
|
|
279
|
+
settings?.customStyles?.cropButton
|
|
280
|
+
);
|
|
349
281
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
282
|
+
const renderCropButton = () => {
|
|
283
|
+
if (!settings?.enableCropping || settings.enableCropping === false)
|
|
284
|
+
return null;
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
settings?.customRenderers?.render?.filterSidebar?.renderCropButton
|
|
288
|
+
) {
|
|
289
|
+
return settings.customRenderers.render.filterSidebar.renderCropButton(
|
|
290
|
+
{
|
|
291
|
+
isCropping,
|
|
292
|
+
onClick: handleToggleCropMode,
|
|
293
|
+
disabled: isLoading
|
|
362
294
|
}
|
|
295
|
+
);
|
|
296
|
+
}
|
|
363
297
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
298
|
+
return (
|
|
299
|
+
<Button
|
|
300
|
+
appearance="ghost"
|
|
301
|
+
size="sm"
|
|
302
|
+
onClick={handleToggleCropMode}
|
|
303
|
+
disabled={isLoading}
|
|
304
|
+
className={cropButtonClassName}
|
|
305
|
+
>
|
|
306
|
+
{isLoading ? (
|
|
307
|
+
<LoaderSpinner className="w-5 h-5" />
|
|
308
|
+
) : isCropping ? (
|
|
309
|
+
<svg
|
|
310
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
311
|
+
width="20"
|
|
312
|
+
height="20"
|
|
313
|
+
viewBox="0 0 24 24"
|
|
314
|
+
fill="none"
|
|
315
|
+
stroke="currentColor"
|
|
316
|
+
strokeWidth="2"
|
|
317
|
+
strokeLinecap="round"
|
|
318
|
+
strokeLinejoin="round"
|
|
319
|
+
>
|
|
320
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
321
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
322
|
+
</svg>
|
|
323
|
+
) : (
|
|
324
|
+
<svg
|
|
325
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
326
|
+
width="20"
|
|
327
|
+
height="20"
|
|
328
|
+
viewBox="0 0 24 24"
|
|
329
|
+
fill="none"
|
|
330
|
+
stroke="currentColor"
|
|
331
|
+
strokeWidth="2"
|
|
332
|
+
strokeLinecap="round"
|
|
333
|
+
strokeLinejoin="round"
|
|
334
|
+
>
|
|
335
|
+
<path d="M6 2v14a2 2 0 0 0 2 2h14"></path>
|
|
336
|
+
<path d="M18 22V8a2 2 0 0 0-2-2H2"></path>
|
|
337
|
+
</svg>
|
|
338
|
+
)}
|
|
339
|
+
</Button>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
368
342
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}}
|
|
382
|
-
onComplete={handleCropComplete}
|
|
383
|
-
onDragStart={() => {}}
|
|
384
|
-
onDragEnd={() => {}}
|
|
385
|
-
ruleOfThirds={false}
|
|
386
|
-
aspect={settings?.cropAspectRatio}
|
|
387
|
-
className="slider-crop"
|
|
388
|
-
disabled={isLoading}
|
|
389
|
-
keepSelection={true}
|
|
390
|
-
>
|
|
391
|
-
<img
|
|
392
|
-
ref={imageRef}
|
|
393
|
-
src={currentImageUrl || ''}
|
|
394
|
-
alt={product?.name || 'Product image'}
|
|
395
|
-
className="max-w-full max-h-[200px] md:max-h-[280px]"
|
|
396
|
-
style={{ transform: `scale(1) rotate(0deg)` }}
|
|
397
|
-
/>
|
|
398
|
-
</ReactCrop>
|
|
399
|
-
) : (
|
|
400
|
-
<div className="relative w-full h-full flex items-center justify-center">
|
|
401
|
-
<img
|
|
402
|
-
ref={imageRef}
|
|
403
|
-
src={currentImageUrl || ''}
|
|
404
|
-
alt={product?.name || 'Product image'}
|
|
405
|
-
className="max-w-full max-h-[200px] md:max-h-[280px] object-contain"
|
|
406
|
-
/>
|
|
407
|
-
{!isCropping && completedCrop && (
|
|
408
|
-
<div className="hidden md:block absolute inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-in-out">
|
|
409
|
-
<div
|
|
410
|
-
className="absolute transition-all duration-300 ease-in-out"
|
|
411
|
-
style={{
|
|
412
|
-
width: `${completedCrop.width}px`,
|
|
413
|
-
height: `${completedCrop.height}px`,
|
|
414
|
-
left: `${completedCrop.x}px`,
|
|
415
|
-
top: `${completedCrop.y}px`,
|
|
416
|
-
boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.5)',
|
|
417
|
-
border: '2px solid white'
|
|
418
|
-
}}
|
|
419
|
-
></div>
|
|
420
|
-
</div>
|
|
421
|
-
)}
|
|
422
|
-
</div>
|
|
423
|
-
)}
|
|
424
|
-
</div>
|
|
425
|
-
);
|
|
426
|
-
})()}
|
|
427
|
-
</div>
|
|
343
|
+
const renderTickButton = () => {
|
|
344
|
+
if (!settings?.enableCropping || settings.enableCropping === false)
|
|
345
|
+
return null;
|
|
346
|
+
if (
|
|
347
|
+
!isCropping ||
|
|
348
|
+
!completedCrop ||
|
|
349
|
+
completedCrop.width <= 10 ||
|
|
350
|
+
completedCrop.height <= 10
|
|
351
|
+
)
|
|
352
|
+
return null;
|
|
428
353
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
type="file"
|
|
440
|
-
accept="image/*"
|
|
441
|
-
ref={fileInputRef}
|
|
442
|
-
onChange={handleFileChangeWithCropReset}
|
|
443
|
-
className="hidden"
|
|
444
|
-
/>
|
|
445
|
-
{(() => {
|
|
446
|
-
if (
|
|
447
|
-
settings?.customRenderers?.render?.filterSidebar
|
|
448
|
-
?.renderUploadButton
|
|
449
|
-
) {
|
|
450
|
-
return settings.customRenderers.render.filterSidebar.renderUploadButton(
|
|
451
|
-
{
|
|
452
|
-
onClick: handleNewImageClick,
|
|
453
|
-
disabled: isLoading
|
|
454
|
-
}
|
|
455
|
-
);
|
|
456
|
-
}
|
|
354
|
+
if (
|
|
355
|
+
settings?.customRenderers?.render?.filterSidebar?.renderTickButton
|
|
356
|
+
) {
|
|
357
|
+
return settings.customRenderers.render.filterSidebar.renderTickButton(
|
|
358
|
+
{
|
|
359
|
+
onClick: () => handleSafeProcessCrop(completedCrop),
|
|
360
|
+
disabled: isLoading
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
457
364
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
365
|
+
const tickButtonClassName = twMerge(
|
|
366
|
+
`absolute z-10 bottom-3 right-3 p-2 rounded-full bg-white shadow-md w-10 h-10 text-green-600 ${
|
|
367
|
+
isLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
368
|
+
}`,
|
|
369
|
+
settings?.customStyles?.tickButton
|
|
370
|
+
);
|
|
464
371
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
372
|
+
return (
|
|
373
|
+
<Button
|
|
374
|
+
appearance="ghost"
|
|
375
|
+
size="sm"
|
|
376
|
+
onClick={() => {
|
|
377
|
+
if (!isLoading) {
|
|
378
|
+
handleSafeProcessCrop(completedCrop);
|
|
379
|
+
}
|
|
380
|
+
}}
|
|
381
|
+
disabled={isLoading}
|
|
382
|
+
className={tickButtonClassName}
|
|
383
|
+
>
|
|
384
|
+
{isLoading ? (
|
|
385
|
+
<LoaderSpinner className="w-5 h-5" />
|
|
386
|
+
) : (
|
|
387
|
+
<svg
|
|
388
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
389
|
+
width="20"
|
|
390
|
+
height="20"
|
|
391
|
+
viewBox="0 0 24 24"
|
|
392
|
+
fill="none"
|
|
393
|
+
stroke="currentColor"
|
|
394
|
+
strokeWidth="2"
|
|
395
|
+
strokeLinecap="round"
|
|
396
|
+
strokeLinejoin="round"
|
|
397
|
+
>
|
|
398
|
+
<polyline points="20,6 9,17 4,12"></polyline>
|
|
399
|
+
</svg>
|
|
493
400
|
)}
|
|
401
|
+
</Button>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<div className={imageSectionClassName}>
|
|
407
|
+
<div className={imageContainerClassName}>
|
|
408
|
+
{renderCropButton()}
|
|
409
|
+
{renderTickButton()}
|
|
494
410
|
|
|
495
411
|
{(() => {
|
|
496
412
|
if (
|
|
497
413
|
settings?.customRenderers?.render?.filterSidebar
|
|
498
|
-
?.
|
|
414
|
+
?.renderImageContainer
|
|
499
415
|
) {
|
|
500
|
-
return settings.customRenderers.render.filterSidebar.
|
|
416
|
+
return settings.customRenderers.render.filterSidebar.renderImageContainer(
|
|
501
417
|
{
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
showResetButton && (hasUploadedImage || completedCrop)
|
|
418
|
+
imageUrl: currentImageUrl || '',
|
|
419
|
+
productName: product?.name || 'Product image',
|
|
420
|
+
isCropping
|
|
506
421
|
}
|
|
507
422
|
);
|
|
508
423
|
}
|
|
509
424
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const resetButtonClassName = twMerge(
|
|
514
|
-
`flex items-center gap-2 text-xs md:text-sm justify-center bg-blue-100 hover:bg-blue-200 border-blue-200 text-blue-600 ${
|
|
515
|
-
isLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
516
|
-
}`,
|
|
517
|
-
settings?.customStyles?.resetButton
|
|
425
|
+
const imageWrapperClassName = twMerge(
|
|
426
|
+
'w-full h-full flex items-center justify-center',
|
|
427
|
+
settings?.customStyles?.imageWrapper
|
|
518
428
|
);
|
|
519
429
|
|
|
520
430
|
return (
|
|
521
|
-
<
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
431
|
+
<div className={imageWrapperClassName}>
|
|
432
|
+
{isCropping ? (
|
|
433
|
+
<ReactCrop
|
|
434
|
+
crop={crop}
|
|
435
|
+
onChange={(newCrop) => {
|
|
436
|
+
if (!isLoading) {
|
|
437
|
+
setCrop(newCrop);
|
|
438
|
+
if (newCrop?.width > 10 && newCrop?.height > 10) {
|
|
439
|
+
setCompletedCrop(newCrop);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}}
|
|
443
|
+
onComplete={handleCropComplete}
|
|
444
|
+
onDragStart={() => {}}
|
|
445
|
+
onDragEnd={() => {}}
|
|
446
|
+
ruleOfThirds={false}
|
|
447
|
+
aspect={settings?.cropAspectRatio}
|
|
448
|
+
className="slider-crop"
|
|
449
|
+
disabled={isLoading}
|
|
450
|
+
keepSelection={true}
|
|
451
|
+
>
|
|
452
|
+
<img
|
|
453
|
+
ref={imageRef}
|
|
454
|
+
src={currentImageUrl || ''}
|
|
455
|
+
alt={product?.name || 'Product image'}
|
|
456
|
+
className="max-w-full max-h-[200px] md:max-h-[280px]"
|
|
457
|
+
style={{ transform: `scale(1) rotate(0deg)` }}
|
|
458
|
+
/>
|
|
459
|
+
</ReactCrop>
|
|
460
|
+
) : (
|
|
461
|
+
<div className="relative w-full h-full flex items-center justify-center">
|
|
462
|
+
<img
|
|
463
|
+
ref={imageRef}
|
|
464
|
+
src={currentImageUrl || ''}
|
|
465
|
+
alt={product?.name || 'Product image'}
|
|
466
|
+
className="max-w-full max-h-[200px] md:max-h-[280px] object-contain"
|
|
467
|
+
/>
|
|
468
|
+
{!isCropping && completedCrop && (
|
|
469
|
+
<div className="hidden md:block absolute inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-in-out">
|
|
470
|
+
<div
|
|
471
|
+
className="absolute transition-all duration-300 ease-in-out"
|
|
472
|
+
style={{
|
|
473
|
+
width: `${completedCrop.width}px`,
|
|
474
|
+
height: `${completedCrop.height}px`,
|
|
475
|
+
left: `${completedCrop.x}px`,
|
|
476
|
+
top: `${completedCrop.y}px`,
|
|
477
|
+
boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.5)',
|
|
478
|
+
border: '2px solid white'
|
|
479
|
+
}}
|
|
480
|
+
></div>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
)}
|
|
485
|
+
</div>
|
|
547
486
|
);
|
|
548
487
|
})()}
|
|
549
488
|
</div>
|
|
550
|
-
)}
|
|
551
489
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
490
|
+
{!isCropping && (
|
|
491
|
+
<div
|
|
492
|
+
className={twMerge(
|
|
493
|
+
'flex flex-col md:flex-row justify-center mt-3 gap-2',
|
|
494
|
+
settings?.customStyles?.cropControls
|
|
495
|
+
)}
|
|
496
|
+
>
|
|
497
|
+
{settings?.enableFileUpload !== false && (
|
|
498
|
+
<>
|
|
499
|
+
<input
|
|
500
|
+
type="file"
|
|
501
|
+
accept="image/*"
|
|
502
|
+
ref={fileInputRef}
|
|
503
|
+
onChange={handleFileChangeWithCropReset}
|
|
504
|
+
className="hidden"
|
|
505
|
+
/>
|
|
506
|
+
{(() => {
|
|
507
|
+
if (
|
|
508
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
509
|
+
?.renderUploadButton
|
|
510
|
+
) {
|
|
511
|
+
return settings.customRenderers.render.filterSidebar.renderUploadButton(
|
|
512
|
+
{
|
|
513
|
+
onClick: handleNewImageClick,
|
|
514
|
+
disabled: isLoading
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const uploadButtonClassName = twMerge(
|
|
520
|
+
`flex items-center gap-2 text-xs md:text-sm justify-center bg-gray-100 hover:bg-gray-200 border-gray-200 ${
|
|
521
|
+
isLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
522
|
+
}`,
|
|
523
|
+
settings?.customStyles?.uploadButton
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<Button
|
|
528
|
+
appearance="outlined"
|
|
529
|
+
size="sm"
|
|
530
|
+
onClick={handleNewImageClick}
|
|
531
|
+
disabled={isLoading}
|
|
532
|
+
className={uploadButtonClassName}
|
|
533
|
+
>
|
|
534
|
+
<svg
|
|
535
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
536
|
+
width="20"
|
|
537
|
+
height="20"
|
|
538
|
+
viewBox="0 0 24 24"
|
|
539
|
+
fill="none"
|
|
540
|
+
stroke="currentColor"
|
|
541
|
+
strokeWidth="2"
|
|
542
|
+
strokeLinecap="round"
|
|
543
|
+
strokeLinejoin="round"
|
|
544
|
+
className="text-gray-500"
|
|
545
|
+
>
|
|
546
|
+
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
|
|
547
|
+
<circle cx="12" cy="13" r="4"></circle>
|
|
548
|
+
</svg>
|
|
549
|
+
{t('common.product.new_image')}
|
|
550
|
+
</Button>
|
|
551
|
+
);
|
|
552
|
+
})()}
|
|
553
|
+
</>
|
|
554
|
+
)}
|
|
555
|
+
|
|
556
|
+
{(() => {
|
|
557
|
+
if (
|
|
558
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
559
|
+
?.renderResetButton
|
|
560
|
+
) {
|
|
561
|
+
return settings.customRenderers.render.filterSidebar.renderResetButton(
|
|
562
|
+
{
|
|
563
|
+
onClick: handleResetToOriginal,
|
|
564
|
+
disabled: isLoading,
|
|
565
|
+
showButton:
|
|
566
|
+
showResetButton &&
|
|
567
|
+
(hasUploadedImage || completedCrop)
|
|
568
|
+
}
|
|
569
|
+
);
|
|
562
570
|
}
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
571
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
572
|
+
if (
|
|
573
|
+
!showResetButton ||
|
|
574
|
+
(!hasUploadedImage && !completedCrop)
|
|
575
|
+
)
|
|
576
|
+
return null;
|
|
570
577
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
);
|
|
578
|
-
})()}
|
|
579
|
-
</div>
|
|
580
|
-
);
|
|
581
|
-
})()}
|
|
582
|
-
|
|
583
|
-
{/* Filters */}
|
|
584
|
-
<div className="space-y-2 md:space-y-4">
|
|
585
|
-
{searchResults?.facets
|
|
586
|
-
?.filter((facet) => facet.key !== 'category_ids')
|
|
587
|
-
?.map((facet) => {
|
|
588
|
-
if (
|
|
589
|
-
settings?.customRenderers?.render?.filterSidebar
|
|
590
|
-
?.renderFilterGroup
|
|
591
|
-
) {
|
|
592
|
-
return settings.customRenderers.render.filterSidebar.renderFilterGroup(
|
|
593
|
-
{
|
|
594
|
-
facet,
|
|
595
|
-
onFacetChange: handleFacetChange,
|
|
596
|
-
isLoading
|
|
597
|
-
}
|
|
598
|
-
);
|
|
599
|
-
}
|
|
578
|
+
const resetButtonClassName = twMerge(
|
|
579
|
+
`flex items-center gap-2 text-xs md:text-sm justify-center bg-blue-100 hover:bg-blue-200 border-blue-200 text-blue-600 ${
|
|
580
|
+
isLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
581
|
+
}`,
|
|
582
|
+
settings?.customStyles?.resetButton
|
|
583
|
+
);
|
|
600
584
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
585
|
+
return (
|
|
586
|
+
<Button
|
|
587
|
+
appearance="outlined"
|
|
588
|
+
size="sm"
|
|
589
|
+
onClick={handleResetToOriginal}
|
|
590
|
+
disabled={isLoading}
|
|
591
|
+
className={resetButtonClassName}
|
|
592
|
+
>
|
|
593
|
+
<svg
|
|
594
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
595
|
+
width="20"
|
|
596
|
+
height="20"
|
|
597
|
+
viewBox="0 0 24 24"
|
|
598
|
+
fill="none"
|
|
599
|
+
stroke="currentColor"
|
|
600
|
+
strokeWidth="2"
|
|
601
|
+
strokeLinecap="round"
|
|
602
|
+
strokeLinejoin="round"
|
|
603
|
+
className="text-blue-600"
|
|
604
|
+
>
|
|
605
|
+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
|
|
606
|
+
<path d="M21 3v5h-5"></path>
|
|
607
|
+
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
|
|
608
|
+
<path d="M3 21v-5h5"></path>
|
|
609
|
+
</svg>
|
|
610
|
+
{t('common.product.reset_to_original')}
|
|
611
|
+
</Button>
|
|
612
|
+
);
|
|
613
|
+
})()}
|
|
614
|
+
</div>
|
|
615
|
+
)}
|
|
606
616
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
617
|
+
{fileError &&
|
|
618
|
+
!isCropping &&
|
|
619
|
+
(() => {
|
|
620
|
+
if (
|
|
621
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
622
|
+
?.renderErrorMessage
|
|
623
|
+
) {
|
|
624
|
+
return settings.customRenderers.render.filterSidebar.renderErrorMessage(
|
|
625
|
+
{
|
|
626
|
+
error: fileError
|
|
627
|
+
}
|
|
628
|
+
);
|
|
629
|
+
}
|
|
614
630
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
631
|
+
const errorClassName = twMerge(
|
|
632
|
+
'mt-2 px-3 py-2 bg-red-50 border border-red-100 rounded-md',
|
|
633
|
+
settings?.customStyles?.errorMessage
|
|
634
|
+
);
|
|
619
635
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
636
|
+
return (
|
|
637
|
+
<div className={errorClassName}>
|
|
638
|
+
<p className="text-xs text-red-600 font-medium">
|
|
639
|
+
{fileError}
|
|
640
|
+
</p>
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
})()}
|
|
644
|
+
</div>
|
|
645
|
+
);
|
|
646
|
+
})()}
|
|
627
647
|
|
|
628
|
-
|
|
648
|
+
{/* Filters */}
|
|
649
|
+
<div className="space-y-2 md:space-y-4">
|
|
650
|
+
{searchResults?.facets
|
|
651
|
+
?.filter((facet) => facet.key !== 'category_ids')
|
|
652
|
+
?.map((facet) => {
|
|
629
653
|
if (
|
|
630
654
|
settings?.customRenderers?.render?.filterSidebar
|
|
631
|
-
?.
|
|
655
|
+
?.renderFilterGroup
|
|
632
656
|
) {
|
|
633
|
-
return settings.customRenderers.render.filterSidebar.
|
|
657
|
+
return settings.customRenderers.render.filterSidebar.renderFilterGroup(
|
|
634
658
|
{
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
659
|
+
facet,
|
|
660
|
+
onFacetChange: handleFacetChange,
|
|
661
|
+
isLoading
|
|
638
662
|
}
|
|
639
663
|
);
|
|
640
664
|
}
|
|
641
|
-
return null;
|
|
642
|
-
};
|
|
643
665
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
isLoading,
|
|
655
|
-
isSelected: choice.is_selected
|
|
656
|
-
}
|
|
666
|
+
const Component = getComponentByWidgetType(
|
|
667
|
+
facet.widget_type,
|
|
668
|
+
facet.key
|
|
669
|
+
);
|
|
670
|
+
const choices = facet.data.choices || [];
|
|
671
|
+
|
|
672
|
+
if (!Component) {
|
|
673
|
+
console.warn(
|
|
674
|
+
'Component not found for widget type:',
|
|
675
|
+
facet.widget_type
|
|
657
676
|
);
|
|
677
|
+
return null;
|
|
658
678
|
}
|
|
659
679
|
|
|
660
|
-
const
|
|
661
|
-
'filter-
|
|
662
|
-
settings?.customStyles?.
|
|
680
|
+
const filterGroupClassName = twMerge(
|
|
681
|
+
'filter-group',
|
|
682
|
+
settings?.customStyles?.filterGroup
|
|
663
683
|
);
|
|
664
684
|
|
|
665
|
-
const
|
|
666
|
-
'
|
|
667
|
-
|
|
685
|
+
const filterGroupContentClassName = twMerge(
|
|
686
|
+
clsx('flex gap-4', {
|
|
687
|
+
'flex-wrap flex-row': facet.key === sizeKey,
|
|
688
|
+
'flex-col': facet.key !== sizeKey
|
|
689
|
+
}),
|
|
690
|
+
settings?.customStyles?.filterGroupContent
|
|
668
691
|
);
|
|
669
692
|
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
693
|
+
const renderFilterGroupTitle = () => {
|
|
694
|
+
if (
|
|
695
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
696
|
+
?.renderFilterGroupTitle
|
|
697
|
+
) {
|
|
698
|
+
return settings.customRenderers.render.filterSidebar.renderFilterGroupTitle(
|
|
699
|
+
{
|
|
700
|
+
title: facet.name,
|
|
701
|
+
isCollapsed: choices.some((choice) => choice.is_selected),
|
|
702
|
+
onToggle: () => {}
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return null;
|
|
707
|
+
};
|
|
674
708
|
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
709
|
+
const renderFilterItem = (choice: any, index: number) => {
|
|
710
|
+
if (
|
|
711
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
712
|
+
?.renderFilterItem
|
|
713
|
+
) {
|
|
714
|
+
return settings.customRenderers.render.filterSidebar.renderFilterItem(
|
|
715
|
+
{
|
|
716
|
+
choice,
|
|
717
|
+
facetKey: facet.key,
|
|
718
|
+
onFacetChange: handleFacetChange,
|
|
719
|
+
isLoading,
|
|
720
|
+
isSelected: choice.is_selected
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const filterItemClassName = twMerge(
|
|
726
|
+
'filter-item',
|
|
727
|
+
settings?.customStyles?.filterItem
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
const filterItemInputClassName = twMerge(
|
|
731
|
+
'filter-item-input',
|
|
732
|
+
settings?.customStyles?.filterItemInput
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const filterItemLabelClassName = twMerge(
|
|
736
|
+
'filter-item-label',
|
|
737
|
+
settings?.customStyles?.filterItemLabel
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const filterItemCountClassName = twMerge(
|
|
741
|
+
'filter-item-count',
|
|
742
|
+
settings?.customStyles?.filterItemCount
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
return (
|
|
746
|
+
<div key={choice.value} className={filterItemClassName}>
|
|
747
|
+
<Component
|
|
748
|
+
key={choice.label}
|
|
749
|
+
value={choice.value}
|
|
750
|
+
label={choice.label}
|
|
751
|
+
name={facet.key}
|
|
752
|
+
onChange={() => {
|
|
753
|
+
if (!isLoading) {
|
|
754
|
+
handleFacetChange(facet.key, choice.value);
|
|
755
|
+
}
|
|
756
|
+
}}
|
|
757
|
+
onClick={() => {
|
|
758
|
+
if (!isLoading && facet.key === sizeKey) {
|
|
759
|
+
handleFacetChange(facet.key, choice.value);
|
|
760
|
+
}
|
|
761
|
+
}}
|
|
762
|
+
checked={choice.is_selected}
|
|
763
|
+
data-testid={`${choice.label.trim()}`}
|
|
764
|
+
disabled={isLoading}
|
|
765
|
+
className={filterItemInputClassName}
|
|
766
|
+
>
|
|
767
|
+
<span className={filterItemLabelClassName}>
|
|
768
|
+
{choice.label}
|
|
769
|
+
</span>{' '}
|
|
770
|
+
(
|
|
771
|
+
<span
|
|
772
|
+
data-testid={`filter-count-${facet.name.toLowerCase()}-${index}`}
|
|
773
|
+
className={filterItemCountClassName}
|
|
774
|
+
>
|
|
775
|
+
{choice.quantity}
|
|
776
|
+
</span>
|
|
777
|
+
)
|
|
778
|
+
</Component>
|
|
779
|
+
</div>
|
|
780
|
+
);
|
|
781
|
+
};
|
|
679
782
|
|
|
680
783
|
return (
|
|
681
|
-
<div key={
|
|
682
|
-
<
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
name={facet.key}
|
|
687
|
-
onChange={() => {
|
|
688
|
-
if (!isLoading) {
|
|
689
|
-
handleFacetChange(facet.key, choice.value);
|
|
690
|
-
}
|
|
691
|
-
}}
|
|
692
|
-
onClick={() => {
|
|
693
|
-
if (!isLoading && facet.key === sizeKey) {
|
|
694
|
-
handleFacetChange(facet.key, choice.value);
|
|
695
|
-
}
|
|
696
|
-
}}
|
|
697
|
-
checked={choice.is_selected}
|
|
698
|
-
data-testid={`${choice.label.trim()}`}
|
|
699
|
-
disabled={isLoading}
|
|
700
|
-
className={filterItemInputClassName}
|
|
784
|
+
<div key={facet.key} className={filterGroupClassName}>
|
|
785
|
+
<Accordion
|
|
786
|
+
title={renderFilterGroupTitle() || facet.name}
|
|
787
|
+
isCollapse={choices.some((choice) => choice.is_selected)}
|
|
788
|
+
dataTestId={`filter-${facet.name}`}
|
|
701
789
|
>
|
|
702
|
-
<
|
|
703
|
-
{
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
{choice.quantity}
|
|
711
|
-
</span>
|
|
712
|
-
)
|
|
713
|
-
</Component>
|
|
790
|
+
<div className={filterGroupContentClassName}>
|
|
791
|
+
{choices
|
|
792
|
+
.slice(0, 10)
|
|
793
|
+
.map((choice, index) =>
|
|
794
|
+
renderFilterItem(choice, index)
|
|
795
|
+
)}
|
|
796
|
+
</div>
|
|
797
|
+
</Accordion>
|
|
714
798
|
</div>
|
|
715
799
|
);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
<div className={filterGroupContentClassName}>
|
|
726
|
-
{choices
|
|
727
|
-
.slice(0, 10)
|
|
728
|
-
.map((choice, index) => renderFilterItem(choice, index))}
|
|
729
|
-
</div>
|
|
730
|
-
</Accordion>
|
|
731
|
-
</div>
|
|
800
|
+
})}
|
|
801
|
+
</div>
|
|
802
|
+
|
|
803
|
+
{/* Mobile Active Filters and Clear Button */}
|
|
804
|
+
{(() => {
|
|
805
|
+
const hasActiveFilters = searchResults?.facets
|
|
806
|
+
?.filter((facet) => facet.key !== 'category_ids')
|
|
807
|
+
?.some((facet) =>
|
|
808
|
+
facet.data.choices?.some((choice) => choice.is_selected)
|
|
732
809
|
);
|
|
733
|
-
})}
|
|
734
|
-
</div>
|
|
735
|
-
|
|
736
|
-
{/* Mobile Active Filters and Clear Button */}
|
|
737
|
-
{(() => {
|
|
738
|
-
const hasActiveFilters = searchResults?.facets
|
|
739
|
-
?.filter((facet) => facet.key !== 'category_ids')
|
|
740
|
-
?.some((facet) =>
|
|
741
|
-
facet.data.choices?.some((choice) => choice.is_selected)
|
|
742
|
-
);
|
|
743
810
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const activeFilters = searchResults.facets
|
|
747
|
-
.filter((facet) => facet.key !== 'category_ids')
|
|
748
|
-
.flatMap(
|
|
749
|
-
(facet) =>
|
|
750
|
-
facet.data.choices
|
|
751
|
-
?.filter((choice) => choice.is_selected)
|
|
752
|
-
.map((choice) => ({
|
|
753
|
-
key: facet.key,
|
|
754
|
-
value: choice.value.toString(),
|
|
755
|
-
label: choice.label
|
|
756
|
-
})) || []
|
|
757
|
-
);
|
|
811
|
+
if (!hasActiveFilters) return null;
|
|
758
812
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
?.filter((facet) => facet.key !== 'category_ids')
|
|
764
|
-
?.forEach((facet) => {
|
|
813
|
+
const activeFilters = searchResults.facets
|
|
814
|
+
.filter((facet) => facet.key !== 'category_ids')
|
|
815
|
+
.flatMap(
|
|
816
|
+
(facet) =>
|
|
765
817
|
facet.data.choices
|
|
766
818
|
?.filter((choice) => choice.is_selected)
|
|
767
|
-
.
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
819
|
+
.map((choice) => ({
|
|
820
|
+
key: facet.key,
|
|
821
|
+
value: choice.value.toString(),
|
|
822
|
+
label: choice.label
|
|
823
|
+
})) || []
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
const handleClearAll = () => {
|
|
827
|
+
if (!isLoading) {
|
|
828
|
+
const facetsToRemove = [];
|
|
829
|
+
searchResults?.facets
|
|
830
|
+
?.filter((facet) => facet.key !== 'category_ids')
|
|
831
|
+
?.forEach((facet) => {
|
|
832
|
+
facet.data.choices
|
|
833
|
+
?.filter((choice) => choice.is_selected)
|
|
834
|
+
.forEach((choice) => {
|
|
835
|
+
facetsToRemove.push({
|
|
836
|
+
facetKey: facet.key,
|
|
837
|
+
choiceValue: choice.value
|
|
838
|
+
});
|
|
771
839
|
});
|
|
772
|
-
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
facetsToRemove.forEach(({ facetKey, choiceValue }) => {
|
|
843
|
+
removeFacetFilter(facetKey, choiceValue);
|
|
773
844
|
});
|
|
845
|
+
}
|
|
846
|
+
};
|
|
774
847
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
848
|
+
if (
|
|
849
|
+
settings?.customRenderers?.render?.filterSidebar
|
|
850
|
+
?.renderMobileActiveFilters
|
|
851
|
+
) {
|
|
852
|
+
return (
|
|
853
|
+
<div className="md:hidden mt-6">
|
|
854
|
+
{settings.customRenderers.render.filterSidebar.renderMobileActiveFilters(
|
|
855
|
+
{
|
|
856
|
+
filters: activeFilters,
|
|
857
|
+
onRemove: handleFacetChange,
|
|
858
|
+
onClearAll: handleClearAll,
|
|
859
|
+
isLoading
|
|
860
|
+
}
|
|
861
|
+
)}
|
|
862
|
+
</div>
|
|
863
|
+
);
|
|
778
864
|
}
|
|
779
|
-
};
|
|
780
865
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
?.
|
|
784
|
-
) {
|
|
785
|
-
return (
|
|
786
|
-
<div className="md:hidden mt-6">
|
|
787
|
-
{settings.customRenderers.render.filterSidebar.renderMobileActiveFilters(
|
|
788
|
-
{
|
|
789
|
-
filters: activeFilters,
|
|
790
|
-
onRemove: handleFacetChange,
|
|
791
|
-
onClearAll: handleClearAll,
|
|
792
|
-
isLoading
|
|
793
|
-
}
|
|
794
|
-
)}
|
|
795
|
-
</div>
|
|
866
|
+
const mobileActiveFiltersClassName = twMerge(
|
|
867
|
+
'md:hidden mt-6',
|
|
868
|
+
settings?.customStyles?.mobileActiveFilters
|
|
796
869
|
);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
</h4>
|
|
820
|
-
<div className="flex flex-wrap gap-2">
|
|
821
|
-
{activeFilters.map((filter) => (
|
|
822
|
-
<div
|
|
823
|
-
key={`${filter.key}-${filter.value}`}
|
|
824
|
-
className={mobileActiveFilterTagClassName}
|
|
825
|
-
>
|
|
826
|
-
<span>{filter.label}</span>
|
|
827
|
-
<Button
|
|
828
|
-
appearance="ghost"
|
|
829
|
-
size="sm"
|
|
830
|
-
onClick={() => {
|
|
831
|
-
if (!isLoading) {
|
|
832
|
-
handleFacetChange(filter.key, filter.value);
|
|
833
|
-
}
|
|
834
|
-
}}
|
|
835
|
-
disabled={isLoading}
|
|
836
|
-
className="hover:bg-gray-200 rounded-full p-0 w-5 h-5 disabled:opacity-50 disabled:cursor-not-allowed ml-1"
|
|
870
|
+
|
|
871
|
+
const mobileActiveFilterTagClassName = twMerge(
|
|
872
|
+
'flex items-center gap-1 px-2 py-1 bg-gray-100 rounded-full text-xs',
|
|
873
|
+
settings?.customStyles?.mobileActiveFilterTag
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
const mobileClearAllButtonClassName = twMerge(
|
|
877
|
+
'w-full',
|
|
878
|
+
settings?.customStyles?.mobileClearAllButton
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
return (
|
|
882
|
+
<div className={mobileActiveFiltersClassName}>
|
|
883
|
+
<div className="mb-4">
|
|
884
|
+
<h4 className="text-sm font-medium mb-2">
|
|
885
|
+
{t('common.product.active_filters')}
|
|
886
|
+
</h4>
|
|
887
|
+
<div className="flex flex-wrap gap-2">
|
|
888
|
+
{activeFilters.map((filter) => (
|
|
889
|
+
<div
|
|
890
|
+
key={`${filter.key}-${filter.value}`}
|
|
891
|
+
className={mobileActiveFilterTagClassName}
|
|
837
892
|
>
|
|
838
|
-
<
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
893
|
+
<span>{filter.label}</span>
|
|
894
|
+
<Button
|
|
895
|
+
appearance="ghost"
|
|
896
|
+
size="sm"
|
|
897
|
+
onClick={() => {
|
|
898
|
+
if (!isLoading) {
|
|
899
|
+
handleFacetChange(filter.key, filter.value);
|
|
900
|
+
}
|
|
901
|
+
}}
|
|
902
|
+
disabled={isLoading}
|
|
903
|
+
className="hover:bg-gray-200 rounded-full p-0 w-5 h-5 disabled:opacity-50 disabled:cursor-not-allowed ml-1"
|
|
904
|
+
>
|
|
905
|
+
<Icon name="close" size={10} />
|
|
906
|
+
</Button>
|
|
907
|
+
</div>
|
|
908
|
+
))}
|
|
909
|
+
</div>
|
|
842
910
|
</div>
|
|
843
|
-
</div>
|
|
844
911
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
912
|
+
<Button
|
|
913
|
+
appearance="outlined"
|
|
914
|
+
className={mobileClearAllButtonClassName}
|
|
915
|
+
onClick={handleClearAll}
|
|
916
|
+
disabled={isLoading}
|
|
917
|
+
>
|
|
918
|
+
{t('common.product.clear_all_filters')}
|
|
919
|
+
</Button>
|
|
920
|
+
</div>
|
|
921
|
+
);
|
|
922
|
+
})()}
|
|
923
|
+
</div>
|
|
924
|
+
</>
|
|
857
925
|
);
|
|
858
926
|
}
|