@akinon/pz-similar-products 1.92.0-rc.16
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/.gitattributes +15 -0
- package/.prettierrc +13 -0
- package/CHANGELOG.md +3 -0
- package/README.md +1372 -0
- package/package.json +21 -0
- package/src/data/endpoints.ts +122 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/use-image-cropper.ts +264 -0
- package/src/hooks/use-image-search-feature.ts +32 -0
- package/src/hooks/use-similar-products.ts +939 -0
- package/src/index.ts +33 -0
- package/src/types/index.ts +419 -0
- package/src/utils/image-validation.ts +303 -0
- package/src/utils/index.ts +161 -0
- package/src/views/filters.tsx +858 -0
- package/src/views/header-image-search-feature.tsx +68 -0
- package/src/views/image-search-button.tsx +47 -0
- package/src/views/image-search.tsx +152 -0
- package/src/views/main.tsx +200 -0
- package/src/views/product-image-search-feature.tsx +48 -0
- package/src/views/results.tsx +591 -0
- package/src/views/search-button.tsx +38 -0
- package/src/views/search-modal.tsx +422 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { LoaderSpinner } from '@akinon/next/components';
|
|
5
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
6
|
+
import { ResultsGridProps } from '../types';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
import dynamic from 'next/dynamic';
|
|
9
|
+
|
|
10
|
+
const ProductItem = dynamic(
|
|
11
|
+
() => import('@theme/views/product-item').then((mod) => mod.ProductItem),
|
|
12
|
+
{
|
|
13
|
+
ssr: false,
|
|
14
|
+
loading: () => (
|
|
15
|
+
<div className="w-full h-64 bg-gray-100 animate-pulse rounded"></div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function SimilarProductsResultsGrid({
|
|
21
|
+
searchResults,
|
|
22
|
+
resultsKey,
|
|
23
|
+
isLoading,
|
|
24
|
+
handlePageChange,
|
|
25
|
+
handleLoadMore,
|
|
26
|
+
loadedPages,
|
|
27
|
+
settings,
|
|
28
|
+
className
|
|
29
|
+
}: ResultsGridProps) {
|
|
30
|
+
const { t } = useLocalization();
|
|
31
|
+
|
|
32
|
+
const onPageChange = (page: number) => {
|
|
33
|
+
handlePageChange(page);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const gridClassName = twMerge(
|
|
37
|
+
'flex-1 flex flex-col p-3 md:p-4 overflow-y-auto relative',
|
|
38
|
+
className
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderGrid) {
|
|
42
|
+
return settings.customRenderers.render.resultsGrid.renderGrid({
|
|
43
|
+
searchResults,
|
|
44
|
+
resultsKey,
|
|
45
|
+
isLoading,
|
|
46
|
+
handlePageChange,
|
|
47
|
+
handleLoadMore,
|
|
48
|
+
loadedPages,
|
|
49
|
+
settings,
|
|
50
|
+
className: gridClassName
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const renderLoadingState = () => {
|
|
55
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderLoadingState) {
|
|
56
|
+
return settings.customRenderers.render.resultsGrid.renderLoadingState();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const loadingStateClassName = twMerge(
|
|
60
|
+
'flex-1 flex justify-center items-center p-3 md:p-4',
|
|
61
|
+
settings?.customStyles?.loadingSpinner
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const loadingSpinnerClassName = twMerge(
|
|
65
|
+
'h-8 w-8',
|
|
66
|
+
settings?.customStyles?.loadingSpinner
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={loadingStateClassName}>
|
|
71
|
+
<LoaderSpinner className={loadingSpinnerClassName} />
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const renderEmptyState = () => {
|
|
77
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderEmptyState) {
|
|
78
|
+
return settings.customRenderers.render.resultsGrid.renderEmptyState();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const emptyStateClassName = twMerge(
|
|
82
|
+
'flex-1 flex flex-col justify-center items-center',
|
|
83
|
+
settings?.customStyles?.emptyState
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const emptyStateIconClassName = twMerge(
|
|
87
|
+
'mx-auto h-24 w-24 text-gray-400 mb-4',
|
|
88
|
+
settings?.customStyles?.emptyStateIcon
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const emptyStateTextClassName = twMerge(
|
|
92
|
+
'text-lg font-medium text-gray-900 mb-2',
|
|
93
|
+
settings?.customStyles?.emptyStateText
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={emptyStateClassName}>
|
|
98
|
+
<div className="text-center">
|
|
99
|
+
<svg
|
|
100
|
+
className={emptyStateIconClassName}
|
|
101
|
+
fill="none"
|
|
102
|
+
viewBox="0 0 24 24"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
>
|
|
106
|
+
<path
|
|
107
|
+
strokeLinecap="round"
|
|
108
|
+
strokeLinejoin="round"
|
|
109
|
+
strokeWidth={1}
|
|
110
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
111
|
+
/>
|
|
112
|
+
</svg>
|
|
113
|
+
<h3 className={emptyStateTextClassName}>
|
|
114
|
+
{t('common.product.no_products')}
|
|
115
|
+
</h3>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const renderProductItem = (product: any, index: number) => {
|
|
122
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderProductItem) {
|
|
123
|
+
return settings.customRenderers.render.resultsGrid.renderProductItem({
|
|
124
|
+
product,
|
|
125
|
+
index
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const productItemClassName = twMerge(
|
|
130
|
+
'product-item',
|
|
131
|
+
settings?.customStyles?.productItem
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const renderProductImage = () => {
|
|
135
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderProductImage) {
|
|
136
|
+
return settings.customRenderers.render.resultsGrid.renderProductImage({
|
|
137
|
+
product,
|
|
138
|
+
imageUrl: product.productimage_set?.[0]?.image || '',
|
|
139
|
+
alt: product.name,
|
|
140
|
+
index
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const renderProductInfo = () => {
|
|
147
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderProductInfo) {
|
|
148
|
+
return settings.customRenderers.render.resultsGrid.renderProductInfo({
|
|
149
|
+
product,
|
|
150
|
+
index
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const renderProductTitle = () => {
|
|
157
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderProductTitle) {
|
|
158
|
+
return settings.customRenderers.render.resultsGrid.renderProductTitle({
|
|
159
|
+
product,
|
|
160
|
+
title: product.name
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const renderProductPrice = () => {
|
|
167
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderProductPrice) {
|
|
168
|
+
return settings.customRenderers.render.resultsGrid.renderProductPrice({
|
|
169
|
+
product,
|
|
170
|
+
price: product.price,
|
|
171
|
+
oldPrice: product.old_price
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
renderProductImage() ||
|
|
179
|
+
renderProductInfo() ||
|
|
180
|
+
renderProductTitle() ||
|
|
181
|
+
renderProductPrice()
|
|
182
|
+
) {
|
|
183
|
+
const productImageWrapperClassName = twMerge(
|
|
184
|
+
'product-image-wrapper',
|
|
185
|
+
settings?.customStyles?.productImageWrapper
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const productImageClassName = twMerge(
|
|
189
|
+
'product-image',
|
|
190
|
+
settings?.customStyles?.productImage
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const productInfoClassName = twMerge(
|
|
194
|
+
'product-info',
|
|
195
|
+
settings?.customStyles?.productInfo
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const productTitleClassName = twMerge(
|
|
199
|
+
'product-title',
|
|
200
|
+
settings?.customStyles?.productTitle
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const productPriceClassName = twMerge(
|
|
204
|
+
'product-price',
|
|
205
|
+
settings?.customStyles?.productPrice
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const productOldPriceClassName = twMerge(
|
|
209
|
+
'product-old-price',
|
|
210
|
+
settings?.customStyles?.productOldPrice
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div key={product.pk} className={productItemClassName}>
|
|
215
|
+
{renderProductImage() || (
|
|
216
|
+
<div className={productImageWrapperClassName}>
|
|
217
|
+
<img
|
|
218
|
+
src={product.productimage_set?.[0]?.image}
|
|
219
|
+
alt={product.name}
|
|
220
|
+
className={productImageClassName}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{renderProductInfo() || (
|
|
226
|
+
<div className={productInfoClassName}>
|
|
227
|
+
{renderProductTitle() || (
|
|
228
|
+
<h3 className={productTitleClassName}>{product.name}</h3>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{renderProductPrice() || (
|
|
232
|
+
<div className="price-container">
|
|
233
|
+
<span className={productPriceClassName}>
|
|
234
|
+
{product.price} ₺
|
|
235
|
+
</span>
|
|
236
|
+
{product.old_price && (
|
|
237
|
+
<span className={productOldPriceClassName}>
|
|
238
|
+
{product.old_price} ₺
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div key={product.pk} className={productItemClassName}>
|
|
251
|
+
<ProductItem product={product} width={340} height={510} index={index} />
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const renderLoadMore = () => {
|
|
257
|
+
if (!searchResults?.pagination || !handleLoadMore) return null;
|
|
258
|
+
|
|
259
|
+
const hasMore =
|
|
260
|
+
searchResults.pagination.current_page <
|
|
261
|
+
searchResults.pagination.num_pages;
|
|
262
|
+
if (!hasMore) return null;
|
|
263
|
+
|
|
264
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderLoadMore) {
|
|
265
|
+
return settings.customRenderers.render.resultsGrid.renderLoadMore({
|
|
266
|
+
onLoadMore: handleLoadMore,
|
|
267
|
+
hasMore,
|
|
268
|
+
isLoading,
|
|
269
|
+
currentPage: searchResults.pagination.current_page,
|
|
270
|
+
totalPages: searchResults.pagination.num_pages,
|
|
271
|
+
loadedPages: loadedPages?.length || 1
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const loadMoreContainerClassName = twMerge(
|
|
276
|
+
'flex justify-center items-center mt-8',
|
|
277
|
+
settings?.customStyles?.loadMoreContainer
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const renderLoadMoreButton = () => {
|
|
281
|
+
if (
|
|
282
|
+
settings?.customRenderers?.render?.resultsGrid?.renderLoadMoreButton
|
|
283
|
+
) {
|
|
284
|
+
return settings.customRenderers.render.resultsGrid.renderLoadMoreButton(
|
|
285
|
+
{
|
|
286
|
+
onClick: handleLoadMore,
|
|
287
|
+
disabled: isLoading,
|
|
288
|
+
isLoading,
|
|
289
|
+
text: settings?.loadMoreText || 'Load More'
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const loadMoreButtonClassName = twMerge(
|
|
295
|
+
'px-8 py-3 bg-gray-900 text-white rounded-lg hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
|
|
296
|
+
settings?.customStyles?.loadMoreButton
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<button
|
|
301
|
+
onClick={handleLoadMore}
|
|
302
|
+
disabled={isLoading}
|
|
303
|
+
className={loadMoreButtonClassName}
|
|
304
|
+
>
|
|
305
|
+
{isLoading ? (
|
|
306
|
+
<div className="flex items-center space-x-2">
|
|
307
|
+
<LoaderSpinner className="h-4 w-4" />
|
|
308
|
+
<span>Loading...</span>
|
|
309
|
+
</div>
|
|
310
|
+
) : (
|
|
311
|
+
settings?.loadMoreText || 'Load More'
|
|
312
|
+
)}
|
|
313
|
+
</button>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div className={loadMoreContainerClassName}>{renderLoadMoreButton()}</div>
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const renderPagination = () => {
|
|
323
|
+
if (!searchResults?.pagination || searchResults.pagination.num_pages <= 1) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
settings?.paginationType === 'load-more' ||
|
|
329
|
+
settings?.paginationType === 'infinite-scroll'
|
|
330
|
+
) {
|
|
331
|
+
return renderLoadMore();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderPagination) {
|
|
335
|
+
return settings.customRenderers.render.resultsGrid.renderPagination({
|
|
336
|
+
pagination: searchResults.pagination,
|
|
337
|
+
onPageChange,
|
|
338
|
+
isLoading
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const paginationContainerClassName = twMerge(
|
|
343
|
+
'mb-4 mt-8 flex items-center justify-center',
|
|
344
|
+
settings?.customStyles?.paginationContainer
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const paginationInfoClassName = twMerge(
|
|
348
|
+
'pagination-info',
|
|
349
|
+
settings?.customStyles?.paginationInfo
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const renderPaginationInfo = () => {
|
|
353
|
+
if (
|
|
354
|
+
settings?.customRenderers?.render?.resultsGrid?.renderPaginationInfo
|
|
355
|
+
) {
|
|
356
|
+
return settings.customRenderers.render.resultsGrid.renderPaginationInfo(
|
|
357
|
+
{
|
|
358
|
+
currentPage: searchResults.pagination.current_page,
|
|
359
|
+
totalPages: searchResults.pagination.num_pages,
|
|
360
|
+
totalCount: searchResults.pagination.total_count || 0
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const renderPaginationPrevious = () => {
|
|
368
|
+
if (searchResults.pagination.current_page <= 1) return null;
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
settings?.customRenderers?.render?.resultsGrid?.renderPaginationPrevious
|
|
372
|
+
) {
|
|
373
|
+
return settings.customRenderers.render.resultsGrid.renderPaginationPrevious(
|
|
374
|
+
{
|
|
375
|
+
onClick: () =>
|
|
376
|
+
onPageChange(searchResults.pagination.current_page - 1),
|
|
377
|
+
disabled: isLoading
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const paginationPreviousClassName = twMerge(
|
|
383
|
+
'flex cursor-pointer px-2 text-sm items-center disabled:opacity-50 disabled:cursor-not-allowed',
|
|
384
|
+
settings?.customStyles?.paginationPrevious
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<li>
|
|
389
|
+
<button
|
|
390
|
+
onClick={() =>
|
|
391
|
+
onPageChange(searchResults.pagination.current_page - 1)
|
|
392
|
+
}
|
|
393
|
+
disabled={isLoading}
|
|
394
|
+
className={paginationPreviousClassName}
|
|
395
|
+
>
|
|
396
|
+
<span><</span>
|
|
397
|
+
<span className="ms-4 hidden lg:inline-block">
|
|
398
|
+
{t('category.pagination.previous')}
|
|
399
|
+
</span>
|
|
400
|
+
</button>
|
|
401
|
+
</li>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const renderPaginationNext = () => {
|
|
406
|
+
if (
|
|
407
|
+
searchResults.pagination.current_page >=
|
|
408
|
+
searchResults.pagination.num_pages
|
|
409
|
+
)
|
|
410
|
+
return null;
|
|
411
|
+
|
|
412
|
+
if (
|
|
413
|
+
settings?.customRenderers?.render?.resultsGrid?.renderPaginationNext
|
|
414
|
+
) {
|
|
415
|
+
return settings.customRenderers.render.resultsGrid.renderPaginationNext(
|
|
416
|
+
{
|
|
417
|
+
onClick: () =>
|
|
418
|
+
onPageChange(searchResults.pagination.current_page + 1),
|
|
419
|
+
disabled: isLoading
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const paginationNextClassName = twMerge(
|
|
425
|
+
'flex cursor-pointer px-2 text-xs items-center disabled:opacity-50 disabled:cursor-not-allowed',
|
|
426
|
+
settings?.customStyles?.paginationNext
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<li>
|
|
431
|
+
<button
|
|
432
|
+
onClick={() =>
|
|
433
|
+
onPageChange(searchResults.pagination.current_page + 1)
|
|
434
|
+
}
|
|
435
|
+
disabled={isLoading}
|
|
436
|
+
className={paginationNextClassName}
|
|
437
|
+
>
|
|
438
|
+
<span className="me-4 hidden lg:inline-block">
|
|
439
|
+
{t('category.pagination.next')}
|
|
440
|
+
</span>
|
|
441
|
+
<span>></span>
|
|
442
|
+
</button>
|
|
443
|
+
</li>
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const renderPaginationButton = (pageNum: number) => {
|
|
448
|
+
const isCurrentPage = pageNum === searchResults.pagination.current_page;
|
|
449
|
+
|
|
450
|
+
if (
|
|
451
|
+
settings?.customRenderers?.render?.resultsGrid?.renderPaginationButton
|
|
452
|
+
) {
|
|
453
|
+
return settings.customRenderers.render.resultsGrid.renderPaginationButton(
|
|
454
|
+
{
|
|
455
|
+
page: pageNum,
|
|
456
|
+
isActive: isCurrentPage,
|
|
457
|
+
isDisabled: isLoading || isCurrentPage,
|
|
458
|
+
onClick: () => onPageChange(pageNum),
|
|
459
|
+
children: pageNum
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const paginationButtonClassName = twMerge(
|
|
465
|
+
`cursor-pointer px-2 text-xs items-center disabled:cursor-not-allowed`,
|
|
466
|
+
isCurrentPage
|
|
467
|
+
? 'font-semibold text-black-800'
|
|
468
|
+
: 'text-gray-400 hover:text-gray-600',
|
|
469
|
+
settings?.customStyles?.paginationButton
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const activeButtonClassName = twMerge(
|
|
473
|
+
paginationButtonClassName,
|
|
474
|
+
isCurrentPage ? settings?.customStyles?.paginationButtonActive : ''
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const disabledButtonClassName = twMerge(
|
|
478
|
+
activeButtonClassName,
|
|
479
|
+
isLoading || isCurrentPage
|
|
480
|
+
? settings?.customStyles?.paginationButtonDisabled
|
|
481
|
+
: ''
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<li key={pageNum}>
|
|
486
|
+
<button
|
|
487
|
+
onClick={() => onPageChange(pageNum)}
|
|
488
|
+
disabled={isLoading || isCurrentPage}
|
|
489
|
+
className={disabledButtonClassName}
|
|
490
|
+
>
|
|
491
|
+
{pageNum}
|
|
492
|
+
</button>
|
|
493
|
+
</li>
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<div className={paginationContainerClassName}>
|
|
499
|
+
{renderPaginationInfo()}
|
|
500
|
+
<ul
|
|
501
|
+
className={twMerge(
|
|
502
|
+
'flex items-center',
|
|
503
|
+
settings?.customStyles?.pagination
|
|
504
|
+
)}
|
|
505
|
+
>
|
|
506
|
+
{renderPaginationPrevious()}
|
|
507
|
+
|
|
508
|
+
{Array.from(
|
|
509
|
+
{ length: searchResults.pagination.num_pages },
|
|
510
|
+
(_, i) => i + 1
|
|
511
|
+
).map((pageNum) => renderPaginationButton(pageNum))}
|
|
512
|
+
|
|
513
|
+
{renderPaginationNext()}
|
|
514
|
+
</ul>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
if (isLoading && (!searchResults || !searchResults.products)) {
|
|
520
|
+
return renderLoadingState();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const renderLoadingOverlay = () => {
|
|
524
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderLoadingOverlay) {
|
|
525
|
+
return settings.customRenderers.render.resultsGrid.renderLoadingOverlay();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const loadingOverlayClassName = twMerge(
|
|
529
|
+
'absolute inset-0 bg-white bg-opacity-75 flex justify-center items-center z-10',
|
|
530
|
+
settings?.customStyles?.loadingOverlay
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const loadingSpinnerClassName = twMerge(
|
|
534
|
+
'h-8 w-8',
|
|
535
|
+
settings?.customStyles?.loadingSpinner
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<div className={loadingOverlayClassName}>
|
|
540
|
+
<LoaderSpinner className={loadingSpinnerClassName} />
|
|
541
|
+
</div>
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const renderGridContainer = () => {
|
|
546
|
+
if (settings?.customRenderers?.render?.resultsGrid?.renderGridContainer) {
|
|
547
|
+
return settings.customRenderers.render.resultsGrid.renderGridContainer({
|
|
548
|
+
children: searchResults.products.map((product, index) =>
|
|
549
|
+
renderProductItem(product, index)
|
|
550
|
+
),
|
|
551
|
+
resultsKey
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const gridContainerClassName = twMerge(
|
|
556
|
+
'grid gap-x-4 gap-y-12 grid-cols-2 md:grid-cols-3 flex-1',
|
|
557
|
+
settings?.customStyles?.gridContainer
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
return (
|
|
561
|
+
<div key={resultsKey} className={gridContainerClassName}>
|
|
562
|
+
{searchResults.products.map((product, index) =>
|
|
563
|
+
renderProductItem(product, index)
|
|
564
|
+
)}
|
|
565
|
+
</div>
|
|
566
|
+
);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const resultsContainerClassName = twMerge(
|
|
570
|
+
'results-container',
|
|
571
|
+
settings?.customStyles?.resultsContainer
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div className={gridClassName}>
|
|
576
|
+
{isLoading &&
|
|
577
|
+
searchResults &&
|
|
578
|
+
searchResults.products &&
|
|
579
|
+
renderLoadingOverlay()}
|
|
580
|
+
|
|
581
|
+
{searchResults && searchResults.products?.length > 0 ? (
|
|
582
|
+
<div className={resultsContainerClassName}>
|
|
583
|
+
{renderGridContainer()}
|
|
584
|
+
{renderPagination()}
|
|
585
|
+
</div>
|
|
586
|
+
) : (
|
|
587
|
+
renderEmptyState()
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Icon } from '@akinon/next/components';
|
|
5
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
6
|
+
|
|
7
|
+
interface SimilarProductsButtonProps {
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
isLoading?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function SimilarProductsButton({
|
|
15
|
+
onClick,
|
|
16
|
+
className = '',
|
|
17
|
+
isLoading = false,
|
|
18
|
+
disabled = false
|
|
19
|
+
}: SimilarProductsButtonProps) {
|
|
20
|
+
const { t } = useLocalization();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
onClick={onClick}
|
|
25
|
+
disabled={disabled || isLoading}
|
|
26
|
+
className={`flex items-center justify-center p-2 bg-amber-200 rounded-md ${
|
|
27
|
+
disabled || isLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
28
|
+
} ${className}`}
|
|
29
|
+
>
|
|
30
|
+
<div className="flex items-center gap-2">
|
|
31
|
+
<Icon name="search" size={16} className="fill-black" />
|
|
32
|
+
<span className="text-xs font-medium text-black uppercase">
|
|
33
|
+
{t('common.product.view_similar_styles')}
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</button>
|
|
37
|
+
);
|
|
38
|
+
}
|