@deneb-ui/ui 2.0.108 → 2.0.110

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.
@@ -6,6 +6,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("react");
7
7
  const EditableProductCard_1 = require("./EditableProductCard");
8
8
  const SiteDataProvider_1 = require("./SiteDataProvider");
9
+ const EditableFilterSidebar_1 = require("./EditableFilterSidebar");
9
10
  /**
10
11
  * EditableProductGrid is an elite, fully responsive e-commerce showcase grid
11
12
  * with interactive category filtering, live Fivora visual editing synchronization,
@@ -14,12 +15,69 @@ const SiteDataProvider_1 = require("./SiteDataProvider");
14
15
  *
15
16
  * Created by Chamika Gayashan & Induranga Kawishwara
16
17
  */
17
- function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone, sectionPath = 'home', listPath = 'products', title = 'Featured Collection', subtitle = 'Just Dropped', products: userProducts, categories = ['All'], cardVariant = 'modern-glass', columns = { mobile: 1, tablet: 2, desktop: 4 }, onQuickView, cardPrefix = 'product', showSearch = true, searchPlaceholder = 'Search products by name, brand, or tag...', enableBackendSearch = true, onSearchChange, enablePagination = true, pageSize = 8, pageSizeOptions = [8, 16, 24, 48], showPageSizeSelector = false, paginationVariant = 'numbers', hidePaginationOnSinglePage = true, currentPage: controlledPage, initialPage = 1, onPageChange, scrollToTopOnPageChange = true, totalProducts, className = '', style, ...props }) {
18
+ function parseProductNumericPrice(p) {
19
+ if (typeof p.minPrice === "number" && !isNaN(p.minPrice))
20
+ return p.minPrice;
21
+ if (typeof p.price === "number" && !isNaN(p.price))
22
+ return p.price;
23
+ const raw = String(p.minPrice || p.price || p.amount || p.cost || "");
24
+ if (!raw)
25
+ return null;
26
+ const cleaned = raw.replace(/,/g, "").match(/\d+(?:\.\d+)?/);
27
+ if (!cleaned)
28
+ return null;
29
+ const val = parseFloat(cleaned[0]);
30
+ return isNaN(val) ? null : val;
31
+ }
32
+ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone, sectionPath = 'home', listPath = 'products', title = 'Featured Collection', subtitle = 'Just Dropped', products: userProducts, categories = ['All'], cardVariant = 'modern-glass', columns = { mobile: 1, tablet: 2, desktop: 4 }, onQuickView, cardPrefix = 'product', showSearch = true, searchPlaceholder = 'Search products by name, brand, or tag...', enableBackendSearch = true, onSearchChange, enablePagination = true, pageSize = 8, pageSizeOptions = [8, 16, 24, 48], showPageSizeSelector = false, paginationVariant = 'numbers', hidePaginationOnSinglePage = true, currentPage: controlledPage, initialPage = 1, onPageChange, scrollToTopOnPageChange = true, totalProducts, filters: controlledFilters, initialFilters, onFilterChange: onFilterChangeProp, showFilterSidebar = false, filterSidebarProps, showDetailLink = true, showDetailButton, detailActionLabel, className = '', style, ...props }) {
18
33
  const sectionRef = (0, react_1.useRef)(null);
19
34
  const siteData = (0, SiteDataProvider_1.useSiteData)();
20
35
  const siteApi = (0, SiteDataProvider_1.useSiteApi)();
21
36
  const liveProducts = (0, SiteDataProvider_1.useProducts)();
22
37
  const baseProducts = userProducts && userProducts.length > 0 ? userProducts : liveProducts;
38
+ const computedMaxPrice = (0, react_1.useMemo)(() => {
39
+ let highest = 100;
40
+ for (const p of baseProducts) {
41
+ const num = parseProductNumericPrice(p);
42
+ if (num !== null && num > highest) {
43
+ highest = Math.ceil(num);
44
+ }
45
+ }
46
+ if (highest > 1000)
47
+ return Math.ceil(highest / 500) * 500;
48
+ if (highest > 100)
49
+ return Math.ceil(highest / 50) * 50;
50
+ return Math.ceil(highest / 10) * 10;
51
+ }, [baseProducts]);
52
+ const effectiveMaxPrice = filterSidebarProps?.maxPrice ?? computedMaxPrice;
53
+ const [internalFilters, setInternalFilters] = (0, react_1.useState)(() => ({
54
+ selectedCategories: initialFilters?.categories || [],
55
+ priceRange: [initialFilters?.priceRange ? initialFilters.priceRange[0] : 0, initialFilters?.maxPrice ?? (initialFilters?.priceRange ? initialFilters.priceRange[1] : effectiveMaxPrice)],
56
+ selectedSizes: initialFilters?.sizes || [],
57
+ inStockOnly: Boolean(initialFilters?.inStockOnly),
58
+ }));
59
+ const activeFilters = (0, react_1.useMemo)(() => {
60
+ return {
61
+ selectedCategories: controlledFilters?.categories !== undefined
62
+ ? controlledFilters.categories
63
+ : internalFilters.selectedCategories,
64
+ maxPrice: controlledFilters?.maxPrice !== undefined
65
+ ? controlledFilters.maxPrice
66
+ : controlledFilters?.priceRange !== undefined
67
+ ? controlledFilters.priceRange[1]
68
+ : internalFilters.priceRange[1],
69
+ selectedSizes: controlledFilters?.sizes !== undefined
70
+ ? controlledFilters.sizes
71
+ : internalFilters.selectedSizes,
72
+ inStockOnly: controlledFilters?.inStockOnly !== undefined
73
+ ? Boolean(controlledFilters.inStockOnly)
74
+ : Boolean(internalFilters.inStockOnly),
75
+ };
76
+ }, [controlledFilters, internalFilters]);
77
+ const handleFilterChange = (nextFilters) => {
78
+ setInternalFilters(nextFilters);
79
+ onFilterChangeProp?.(nextFilters);
80
+ };
23
81
  const resolvedListPath = (0, react_1.useMemo)(() => {
24
82
  if (listPath && listPath !== 'products') {
25
83
  return listPath;
@@ -50,6 +108,7 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
50
108
  const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
51
109
  const [isSearchingBackend, setIsSearchingBackend] = (0, react_1.useState)(false);
52
110
  const [backendProducts, setBackendProducts] = (0, react_1.useState)(null);
111
+ const [backendTotalProducts, setBackendTotalProducts] = (0, react_1.useState)(null);
53
112
  // Pagination internal state
54
113
  const [internalPage, setInternalPage] = (0, react_1.useState)(initialPage);
55
114
  const activePage = controlledPage !== undefined ? controlledPage : internalPage;
@@ -62,37 +121,95 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
62
121
  }
63
122
  }, [pageSize]);
64
123
  // Use backend search results if available, else fallback to baseProducts
65
- const products = backendProducts && backendProducts.length > 0 ? backendProducts : baseProducts;
66
- // Live backend catalog search when query changes
124
+ const products = backendProducts !== null ? backendProducts : baseProducts;
125
+ // Live backend catalog search & filter when query or filters change
67
126
  (0, react_1.useEffect)(() => {
68
- if (!enableBackendSearch || !searchQuery.trim()) {
127
+ if (!enableBackendSearch) {
69
128
  setBackendProducts(null);
129
+ setBackendTotalProducts(null);
70
130
  return;
71
131
  }
132
+ const candidateSlug = siteData?.siteInstance?.slug ||
133
+ siteData?.project?.slug ||
134
+ siteData?.project?.id;
72
135
  const catalogUrl = siteApi?.catalogUrl ||
73
- (siteData?.siteInstance?.slug ? `/site-catalog/${siteData.siteInstance.slug}` : null);
74
- const slug = siteData?.siteInstance?.slug;
75
- const isPreviewMode = typeof window !== 'undefined' &&
76
- (window.location.pathname.includes('/template-preview/') ||
77
- window.location.pathname.includes('/preview/'));
136
+ (candidateSlug && siteApi?.baseUrl
137
+ ? `${siteApi.baseUrl.replace(/\/+$/, '')}/site-catalog/${candidateSlug}/live-data`
138
+ : candidateSlug
139
+ ? `/site-catalog/${candidateSlug}/live-data`
140
+ : null);
141
+ const slug = siteData?.siteInstance?.slug || siteData?.project?.slug;
142
+ const isPreviewMode = (0, SiteDataProvider_1.isPreviewValidation)(siteData) ||
143
+ (typeof window !== 'undefined' &&
144
+ (window.parent !== window ||
145
+ window.location.pathname.includes('/template-preview') ||
146
+ window.location.pathname.includes('/preview')));
78
147
  if (!catalogUrl ||
79
148
  isPreviewMode ||
80
149
  slug === 'template-validation' ||
81
- catalogUrl.includes('/template-validation/')) {
150
+ catalogUrl.includes('/template-validation')) {
151
+ // In Visual Editor preview or template validation, NEVER query remote backend.
152
+ // All search, category filters, price sliders, sizes, and in-stock toggles
153
+ // operate directly in-memory on data.json / visual editor siteData without errors!
154
+ setBackendProducts(null);
155
+ setBackendTotalProducts(null);
82
156
  return;
83
157
  }
158
+ const hasSearch = Boolean(searchQuery.trim());
159
+ const hasCategories = Boolean(activeFilters.selectedCategories && activeFilters.selectedCategories.length > 0);
160
+ const hasMaxPrice = Boolean(activeFilters.maxPrice !== undefined &&
161
+ activeFilters.maxPrice < effectiveMaxPrice);
162
+ const hasSizes = Boolean(activeFilters.selectedSizes && activeFilters.selectedSizes.length > 0);
163
+ const hasInStock = Boolean(activeFilters.inStockOnly);
164
+ const hasAnyFilterOrSearch = hasSearch ||
165
+ hasCategories ||
166
+ hasMaxPrice ||
167
+ hasSizes ||
168
+ hasInStock;
169
+ if (!hasAnyFilterOrSearch) {
170
+ setBackendProducts(null);
171
+ setBackendTotalProducts(null);
172
+ return;
173
+ }
174
+ const params = new URLSearchParams();
175
+ if (hasSearch)
176
+ params.set('q', searchQuery.trim());
177
+ if (hasCategories && activeFilters.selectedCategories) {
178
+ params.set('category', activeFilters.selectedCategories.join(','));
179
+ }
180
+ if (hasMaxPrice && activeFilters.maxPrice !== undefined) {
181
+ params.set('maxPrice', String(activeFilters.maxPrice));
182
+ }
183
+ if (hasSizes && activeFilters.selectedSizes) {
184
+ params.set('sizes', activeFilters.selectedSizes.join(','));
185
+ }
186
+ if (hasInStock) {
187
+ params.set('inStockOnly', 'true');
188
+ }
189
+ if (enablePagination) {
190
+ params.set('page', String(activePage));
191
+ params.set('limit', String(activePageSize));
192
+ }
84
193
  let active = true;
85
194
  const timer = setTimeout(async () => {
86
195
  try {
87
196
  setIsSearchingBackend(true);
88
197
  const sep = catalogUrl.includes('?') ? '&' : '?';
89
- const url = `${catalogUrl}${sep}q=${encodeURIComponent(searchQuery.trim())}`;
198
+ const url = `${catalogUrl}${sep}${params.toString()}`;
90
199
  const res = await fetch(url, { headers: { Accept: 'application/json' } });
91
200
  if (res.ok) {
92
201
  const data = await res.json();
93
202
  const items = Array.isArray(data) ? data : (data.products || data.items || []);
94
- if (active && Array.isArray(items) && items.length > 0) {
203
+ if (active && Array.isArray(items)) {
95
204
  setBackendProducts(items);
205
+ const totalCount = typeof data.totalProducts === 'number'
206
+ ? data.totalProducts
207
+ : typeof data.pagination?.total === 'number'
208
+ ? data.pagination.total
209
+ : null;
210
+ if (totalCount !== null) {
211
+ setBackendTotalProducts(totalCount);
212
+ }
96
213
  }
97
214
  }
98
215
  }
@@ -108,33 +225,98 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
108
225
  active = false;
109
226
  clearTimeout(timer);
110
227
  };
111
- }, [searchQuery, enableBackendSearch, siteApi, siteData]);
228
+ }, [searchQuery, activeFilters, effectiveMaxPrice, enableBackendSearch, activePage, activePageSize, enablePagination, siteApi, siteData]);
112
229
  const resolvedCategories = (0, react_1.useMemo)(() => {
230
+ const rawList = [];
113
231
  if (categories && categories.length > 1) {
114
- return categories;
232
+ rawList.push(...categories);
233
+ }
234
+ else {
235
+ const siteCatList = siteData?.categories || siteData?.content?.categories;
236
+ if (Array.isArray(siteCatList) && siteCatList.length > 0) {
237
+ rawList.push(...siteCatList);
238
+ }
239
+ for (const p of products) {
240
+ if (typeof p.category === "string" && p.category.trim().length > 0) {
241
+ rawList.push(p.category);
242
+ }
243
+ }
244
+ }
245
+ const map = new Map();
246
+ for (const item of rawList) {
247
+ if (typeof item !== "string")
248
+ continue;
249
+ const trimmed = item.trim();
250
+ if (!trimmed || trimmed.toLowerCase() === "all")
251
+ continue;
252
+ const lower = trimmed.toLowerCase();
253
+ if (!map.has(lower)) {
254
+ map.set(lower, trimmed);
255
+ }
115
256
  }
116
- const distinct = Array.from(new Set(products
117
- .map((p) => p.category)
118
- .filter((cat) => typeof cat === 'string' && cat.trim().length > 0)));
119
- return distinct.length > 0 ? ['All', ...distinct] : categories;
257
+ const distinct = Array.from(map.values());
258
+ return distinct.length > 1 ? ["All", ...distinct] : [];
120
259
  }, [categories, products]);
121
260
  const [activeCategory, setActiveCategory] = (0, react_1.useState)(categories[0] || 'All');
122
- // Reset to page 1 whenever category or search query changes
261
+ // Reset to page 1 whenever category, filters, or search query changes
123
262
  (0, react_1.useEffect)(() => {
124
263
  setInternalPage(1);
125
264
  setLoadedCount(activePageSize);
126
265
  onPageChange?.(1);
127
- }, [searchQuery, activeCategory, activePageSize, onPageChange]);
266
+ }, [searchQuery, activeCategory, activeFilters, activePageSize, onPageChange]);
128
267
  const filteredProducts = (0, react_1.useMemo)(() => {
129
268
  let result = products;
130
- // Filter by category
131
- if (activeCategory && activeCategory.toLowerCase() !== 'all') {
269
+ // 1. Filter by top category tab (if not "All")
270
+ if (activeCategory && activeCategory.trim().toLowerCase() !== "all") {
132
271
  result = result.filter((p) => {
133
- const cat = String(p.category || '').toLowerCase();
134
- return cat === activeCategory.toLowerCase();
272
+ const cat = String(p.category || "").trim().toLowerCase();
273
+ return cat === activeCategory.trim().toLowerCase();
274
+ });
275
+ }
276
+ // 2. Filter by sidebar selectedCategories (multi-select)
277
+ if (activeFilters.selectedCategories && activeFilters.selectedCategories.length > 0) {
278
+ result = result.filter((p) => {
279
+ const cat = String(p.category || "").trim().toLowerCase();
280
+ return activeFilters.selectedCategories.some((c) => c.trim().toLowerCase() === cat);
281
+ });
282
+ }
283
+ // 3. Filter by maxPrice (matches numeric price against slider)
284
+ if (activeFilters.maxPrice !== undefined && activeFilters.maxPrice < effectiveMaxPrice) {
285
+ result = result.filter((p) => {
286
+ const num = parseProductNumericPrice(p);
287
+ if (num === null)
288
+ return true;
289
+ return num <= activeFilters.maxPrice;
290
+ });
291
+ }
292
+ // 4. Filter by sizes / options
293
+ if (activeFilters.selectedSizes && activeFilters.selectedSizes.length > 0) {
294
+ result = result.filter((p) => {
295
+ const pSizes = [];
296
+ if (Array.isArray(p.sizes)) {
297
+ pSizes.push(...p.sizes.map((s) => String(s).trim().toLowerCase()));
298
+ }
299
+ else if (typeof p.sizes === "string") {
300
+ pSizes.push(...p.sizes.split(",").map((s) => s.trim().toLowerCase()));
301
+ }
302
+ if (Array.isArray(p.options)) {
303
+ pSizes.push(...p.options.map((o) => String(o).trim().toLowerCase()));
304
+ }
305
+ else if (typeof p.options === "string") {
306
+ pSizes.push(...p.options.split(",").map((o) => o.trim().toLowerCase()));
307
+ }
308
+ return activeFilters.selectedSizes.some((s) => pSizes.includes(s.trim().toLowerCase()));
309
+ });
310
+ }
311
+ // 5. Filter by inStock availability
312
+ if (activeFilters.inStockOnly) {
313
+ result = result.filter((p) => {
314
+ return (p.inStock !== false &&
315
+ p.isAvailable !== false &&
316
+ p.available !== false);
135
317
  });
136
318
  }
137
- // Filter by search query
319
+ // 6. Filter by search query
138
320
  if (searchQuery.trim()) {
139
321
  const q = searchQuery.trim().toLowerCase();
140
322
  result = result.filter((p) => {
@@ -148,22 +330,49 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
148
330
  });
149
331
  }
150
332
  return result;
151
- }, [products, activeCategory, searchQuery]);
152
- // Total count calculation
153
- const totalItems = totalProducts !== undefined ? totalProducts : filteredProducts.length;
333
+ }, [products, activeCategory, activeFilters, effectiveMaxPrice, searchQuery]);
334
+ const activeFiltersCount = (activeCategory && activeCategory.trim().toLowerCase() !== "all" ? 1 : 0) +
335
+ activeFilters.selectedCategories.length +
336
+ (activeFilters.maxPrice < effectiveMaxPrice ? 1 : 0) +
337
+ activeFilters.selectedSizes.length +
338
+ (activeFilters.inStockOnly ? 1 : 0);
339
+ const handleClearAllFilters = () => {
340
+ setActiveCategory("All");
341
+ setSearchQuery("");
342
+ setInternalFilters({
343
+ selectedCategories: [],
344
+ priceRange: [0, effectiveMaxPrice],
345
+ selectedSizes: [],
346
+ inStockOnly: false,
347
+ });
348
+ onFilterChangeProp?.({
349
+ selectedCategories: [],
350
+ priceRange: [0, effectiveMaxPrice],
351
+ selectedSizes: [],
352
+ inStockOnly: false,
353
+ });
354
+ };
355
+ // Total count calculation: prefers explicit totalProducts prop, then remote backend total, else filtered list length
356
+ const totalItems = totalProducts !== undefined
357
+ ? totalProducts
358
+ : (backendTotalProducts !== null ? backendTotalProducts : filteredProducts.length);
154
359
  const totalPages = Math.max(1, Math.ceil(totalItems / activePageSize));
155
360
  // Products to render based on pagination mode
156
361
  const displayedProducts = (0, react_1.useMemo)(() => {
157
362
  if (!enablePagination) {
158
363
  return filteredProducts;
159
364
  }
365
+ // If backend already returned server-paginated items for this activePage, render directly
366
+ if (backendProducts !== null && backendTotalProducts !== null) {
367
+ return filteredProducts;
368
+ }
160
369
  if (paginationVariant === 'load-more') {
161
370
  return filteredProducts.slice(0, loadedCount);
162
371
  }
163
372
  const startIndex = (activePage - 1) * activePageSize;
164
373
  const endIndex = startIndex + activePageSize;
165
374
  return filteredProducts.slice(startIndex, endIndex);
166
- }, [filteredProducts, enablePagination, paginationVariant, loadedCount, activePage, activePageSize]);
375
+ }, [filteredProducts, enablePagination, paginationVariant, loadedCount, activePage, activePageSize, backendProducts, backendTotalProducts]);
167
376
  // Smart page numbers calculation with ellipsis (e.g. 1 ... 4 5 6 ... 10)
168
377
  const visiblePages = (0, react_1.useMemo)(() => {
169
378
  if (totalPages <= 7) {
@@ -203,8 +412,53 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
203
412
  return `${mClass} ${tClass} ${dClass}`;
204
413
  }, [columns]);
205
414
  const shouldShowPagination = enablePagination && !(hidePaginationOnSinglePage && totalPages <= 1);
415
+ const productsGridContent = ((0, jsx_runtime_1.jsx)("div", { ...(resolvedListPath ? { 'data-preview-list-path': resolvedListPath } : {}), className: displayedProducts.length > 0
416
+ ? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
417
+ : 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
418
+ // Determine true index in baseProducts for exact Fivora visual editing focus targeting
419
+ const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
420
+ (bp.name && product.name ? bp.name === product.name : false) ||
421
+ (bp.title && product.title ? bp.title === product.title : false) ||
422
+ (bp.productName && product.productName ? bp.productName === product.productName : false));
423
+ const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
424
+ const itemPath = resolvedListPath
425
+ ? `${resolvedListPath}[${effectiveIndex}]`
426
+ : `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
427
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": itemPath, className: "relative group", children: [(0, jsx_runtime_1.jsx)(EditableProductCard_1.EditableProductCard, { itemPath: itemPath, product: product, cardVariant: cardVariant, currency: currency, whatsappPhone: whatsappPhone || whatsappNumber, showDetailLink: showDetailLink, showDetailButton: showDetailButton, actionLabel: detailActionLabel, className: "h-full" }), onQuickView && ((0, jsx_runtime_1.jsx)("div", { className: "absolute top-4 right-4 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => onQuickView(product, itemPath), className: "p-2.5 rounded-xl bg-slate-950/80 backdrop-blur-md text-white hover:bg-lime-400 hover:text-slate-950 shadow-lg transition-all active:scale-95", title: "Quick View", "aria-label": `Quick View ${product.name || product.title}`, children: (0, jsx_runtime_1.jsxs)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] }) }) }))] }, product.id || idx));
428
+ })) : (
429
+ /* Empty State — strictly inside the listPath container */
430
+ (0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16 px-4 rounded-3xl border border-dashed border-slate-300 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/30 w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 mx-auto mb-4 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-slate-900 dark:text-white", children: "No products found" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 dark:text-slate-400 mt-1 max-w-sm mx-auto", children: searchQuery
431
+ ? `No products matched "${searchQuery}"${activeCategory !== 'All' ? ` in category "${activeCategory}"` : ''}.`
432
+ : activeFiltersCount > 0
433
+ ? "No products matched your active filters."
434
+ : `No products available in category "${activeCategory}".` }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-5 flex items-center justify-center gap-3", children: [searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSearchQuery(''), className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-200 dark:bg-slate-800 text-slate-800 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors cursor-pointer", children: "Clear Search" })), activeCategory !== 'All' && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory('All'), className: "px-4 py-2 rounded-xl text-xs font-bold bg-lime-400 text-slate-950 hover:bg-lime-300 transition-colors shadow-sm cursor-pointer", children: "View All Products" })), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleClearAllFilters, className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-900 text-white dark:bg-white dark:text-slate-950 hover:bg-slate-800 transition-colors shadow-sm cursor-pointer", children: "Clear All Filters" }))] })] })) }));
435
+ /* Pagination Controls */
436
+ const paginationContent = shouldShowPagination ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: paginationVariant === 'load-more' ? (
437
+ /* Progressive "Load More" Variant */
438
+ (0, jsx_runtime_1.jsxs)("div", { className: "mt-12 flex flex-col items-center justify-center gap-3 text-center", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-slate-500 dark:text-slate-400 font-medium", children: ["Showing ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: displayedProducts.length }), " of", ' ', (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: totalItems }), " products"] }), displayedProducts.length < totalItems && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: handleLoadMore, className: "px-8 py-3 rounded-2xl font-bold text-sm bg-slate-900 text-white hover:bg-slate-800 dark:bg-lime-400 dark:text-slate-950 dark:hover:bg-lime-300 shadow-md transition-all active:scale-95 cursor-pointer", children: ["Load More Products (", totalItems - displayedProducts.length, " remaining)"] }))] })) : (
439
+ /* Numbered & Simple Pagination Navigation */
440
+ (0, jsx_runtime_1.jsxs)("nav", { role: "navigation", "aria-label": "Product pagination", className: "mt-12 pt-8 border-t border-slate-200 dark:border-slate-800/80 flex flex-col sm:flex-row items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Showing", ' ', (0, jsx_runtime_1.jsxs)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: [Math.min((activePage - 1) * activePageSize + 1, totalItems), "\u2013", Math.min(activePage * activePageSize, totalItems)] }), ' ', "of ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: totalItems }), " products"] }), showPageSizeSelector && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 ml-2 border-l border-slate-200 dark:border-slate-800 pl-3", children: [(0, jsx_runtime_1.jsx)("span", { children: "Show" }), (0, jsx_runtime_1.jsx)("select", { value: activePageSize, onChange: (e) => {
441
+ const newSize = Number(e.target.value);
442
+ setActivePageSize(newSize);
443
+ setInternalPage(1);
444
+ onPageChange?.(1);
445
+ }, className: "bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg px-2 py-1 text-xs font-semibold text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-1 focus:ring-lime-400", children: pageSizeOptions.map((size) => ((0, jsx_runtime_1.jsx)("option", { value: size, children: size }, size))) }), (0, jsx_runtime_1.jsx)("span", { children: "per page" })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 sm:gap-1.5", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage - 1), disabled: activePage <= 1, "aria-label": "Previous page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage <= 1
446
+ ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
447
+ : 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }), (0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Previous" })] }), paginationVariant === 'numbers' ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: visiblePages.map((pageNum, idx) => {
448
+ if (pageNum === 'ellipsis') {
449
+ return ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "px-2 text-slate-400 dark:text-slate-600 select-none text-xs font-bold", children: "\u2026" }, `ellipsis-${idx}`));
450
+ }
451
+ const isCurrent = pageNum === activePage;
452
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handlePageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": isCurrent ? 'page' : undefined, className: `min-w-[34px] h-[34px] px-2 rounded-xl text-xs font-bold transition-all flex items-center justify-center cursor-pointer ${isCurrent
453
+ ? 'bg-slate-900 text-white dark:bg-lime-400 dark:text-slate-950 font-black shadow-md scale-105'
454
+ : 'bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'}`, children: pageNum }, pageNum));
455
+ }) })) : (
456
+ /* Simple Indicator */
457
+ (0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1.5 text-xs font-bold text-slate-700 dark:text-slate-300", children: ["Page ", activePage, " of ", totalPages] })), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage + 1), disabled: activePage >= totalPages, "aria-label": "Next page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage >= totalPages
458
+ ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
459
+ : 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Next" }), (0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M9 5l7 7-7 7" }) })] })] })] })) })) : null;
206
460
  return ((0, jsx_runtime_1.jsxs)("section", { id: props.id || "products", ref: sectionRef, "data-design-section": props["data-design-section"] || "products", "data-section-id": props["data-section-id"] || "products", "data-preview-page-key": sectionPath, className: `editable-product-grid max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 ${className}`.trim(), style: style, ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col md:flex-row md:items-end justify-between mb-8 gap-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [subtitle && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.gridSubtitle`, className: "text-xs font-black tracking-widest uppercase text-emerald-600 dark:text-lime-400 block mb-2", children: subtitle })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `${sectionPath}.gridTitle`, className: "text-3xl sm:text-4xl font-extrabold tracking-tight text-slate-900 dark:text-white leading-tight", children: title })] }), resolvedCategories.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap items-center gap-2", children: resolvedCategories.map((cat) => {
207
- const isActive = activeCategory.toLowerCase() === cat.toLowerCase();
461
+ const isActive = activeCategory.trim().toLowerCase() === cat.trim().toLowerCase();
208
462
  return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory(cat), className: `px-4 py-2 rounded-xl text-xs font-bold transition-all duration-200 ${isActive
209
463
  ? 'bg-slate-900 text-white dark:bg-white dark:text-slate-950 shadow-md scale-102 font-extrabold'
210
464
  : 'bg-slate-100 text-slate-600 hover:text-slate-900 border border-slate-200 hover:bg-slate-200/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:text-white dark:border-slate-800/80 dark:hover:bg-slate-850'}`, children: cat }, cat));
@@ -214,47 +468,7 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
214
468
  }, placeholder: searchPlaceholder, className: "w-full pl-10 pr-10 py-2.5 rounded-2xl text-sm bg-slate-100/90 dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-lime-400 dark:focus:ring-lime-400 transition-all shadow-sm" }), searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
215
469
  setSearchQuery('');
216
470
  onSearchChange?.('');
217
- }, className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors", title: "Clear search", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [isSearchingBackend && ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1.5 text-emerald-600 dark:text-lime-400 font-medium animate-pulse", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-lime-400" }), "Searching live catalog..."] })), (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold bg-slate-100 dark:bg-slate-900/80 px-3 py-1.5 rounded-xl border border-slate-200/80 dark:border-slate-800", children: ["Showing ", (0, jsx_runtime_1.jsx)("span", { className: "text-slate-900 dark:text-white font-bold", children: filteredProducts.length }), " of ", products.length, " products"] })] })] })), (0, jsx_runtime_1.jsx)("div", { ...(resolvedListPath ? { 'data-preview-list-path': resolvedListPath } : {}), className: displayedProducts.length > 0
218
- ? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
219
- : 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
220
- // Determine true index in baseProducts for exact Fivora visual editing focus targeting
221
- const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
222
- (bp.name && product.name ? bp.name === product.name : false) ||
223
- (bp.title && product.title ? bp.title === product.title : false) ||
224
- (bp.productName && product.productName ? bp.productName === product.productName : false));
225
- const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
226
- const itemPath = resolvedListPath
227
- ? `${resolvedListPath}[${effectiveIndex}]`
228
- : `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
229
- return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": itemPath, className: "relative group", children: [(0, jsx_runtime_1.jsx)(EditableProductCard_1.EditableProductCard, { itemPath: itemPath, product: product, cardVariant: cardVariant, currency: currency, whatsappPhone: whatsappPhone || whatsappNumber, className: "h-full" }), onQuickView && ((0, jsx_runtime_1.jsx)("div", { className: "absolute top-4 right-4 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => onQuickView(product, itemPath), className: "p-2.5 rounded-xl bg-slate-950/80 backdrop-blur-md text-white hover:bg-lime-400 hover:text-slate-950 shadow-lg transition-all active:scale-95", title: "Quick View", "aria-label": `Quick View ${product.name || product.title}`, children: (0, jsx_runtime_1.jsxs)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] }) }) }))] }, product.id || idx));
230
- })) : (
231
- /* Empty State — strictly inside the listPath container */
232
- (0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16 px-4 rounded-3xl border border-dashed border-slate-300 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/30 w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 mx-auto mb-4 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-slate-900 dark:text-white", children: "No products found" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 dark:text-slate-400 mt-1 max-w-sm mx-auto", children: searchQuery
233
- ? `No products matched "${searchQuery}"${activeCategory !== 'All' ? ` in category "${activeCategory}"` : ''}.`
234
- : `No products available in category "${activeCategory}".` }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-5 flex items-center justify-center gap-3", children: [searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSearchQuery(''), className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-200 dark:bg-slate-800 text-slate-800 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors", children: "Clear Search" })), activeCategory !== 'All' && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory('All'), className: "px-4 py-2 rounded-xl text-xs font-bold bg-lime-400 text-slate-950 hover:bg-lime-300 transition-colors shadow-sm", children: "View All Products" }))] })] })) }), shouldShowPagination && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: paginationVariant === 'load-more' ? (
235
- /* Progressive "Load More" Variant */
236
- (0, jsx_runtime_1.jsxs)("div", { className: "mt-12 flex flex-col items-center justify-center gap-3 text-center", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-slate-500 dark:text-slate-400 font-medium", children: ["Showing ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: displayedProducts.length }), " of", ' ', (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: totalItems }), " products"] }), displayedProducts.length < totalItems && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: handleLoadMore, className: "px-8 py-3 rounded-2xl font-bold text-sm bg-slate-900 text-white hover:bg-slate-800 dark:bg-lime-400 dark:text-slate-950 dark:hover:bg-lime-300 shadow-md transition-all active:scale-95 cursor-pointer", children: ["Load More Products (", totalItems - displayedProducts.length, " remaining)"] }))] })) : (
237
- /* Numbered & Simple Pagination Navigation */
238
- (0, jsx_runtime_1.jsxs)("nav", { role: "navigation", "aria-label": "Product pagination", className: "mt-12 pt-8 border-t border-slate-200 dark:border-slate-800/80 flex flex-col sm:flex-row items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Showing", ' ', (0, jsx_runtime_1.jsxs)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: [Math.min((activePage - 1) * activePageSize + 1, totalItems), "\u2013", Math.min(activePage * activePageSize, totalItems)] }), ' ', "of ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: totalItems }), " products"] }), showPageSizeSelector && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 ml-2 border-l border-slate-200 dark:border-slate-800 pl-3", children: [(0, jsx_runtime_1.jsx)("span", { children: "Show" }), (0, jsx_runtime_1.jsx)("select", { value: activePageSize, onChange: (e) => {
239
- const newSize = Number(e.target.value);
240
- setActivePageSize(newSize);
241
- setInternalPage(1);
242
- onPageChange?.(1);
243
- }, className: "bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg px-2 py-1 text-xs font-semibold text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-1 focus:ring-lime-400", children: pageSizeOptions.map((size) => ((0, jsx_runtime_1.jsx)("option", { value: size, children: size }, size))) }), (0, jsx_runtime_1.jsx)("span", { children: "per page" })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 sm:gap-1.5", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage - 1), disabled: activePage <= 1, "aria-label": "Previous page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage <= 1
244
- ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
245
- : 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }), (0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Previous" })] }), paginationVariant === 'numbers' ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: visiblePages.map((pageNum, idx) => {
246
- if (pageNum === 'ellipsis') {
247
- return ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "px-2 text-slate-400 dark:text-slate-600 select-none text-xs font-bold", children: "\u2026" }, `ellipsis-${idx}`));
248
- }
249
- const isCurrent = pageNum === activePage;
250
- return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handlePageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": isCurrent ? 'page' : undefined, className: `min-w-[34px] h-[34px] px-2 rounded-xl text-xs font-bold transition-all flex items-center justify-center cursor-pointer ${isCurrent
251
- ? 'bg-slate-900 text-white dark:bg-lime-400 dark:text-slate-950 font-black shadow-md scale-105'
252
- : 'bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'}`, children: pageNum }, pageNum));
253
- }) })) : (
254
- /* Simple Indicator */
255
- (0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1.5 text-xs font-bold text-slate-700 dark:text-slate-300", children: ["Page ", activePage, " of ", totalPages] })), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage + 1), disabled: activePage >= totalPages, "aria-label": "Next page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage >= totalPages
256
- ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
257
- : 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Next" }), (0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M9 5l7 7-7 7" }) })] })] })] })) }))] }));
471
+ }, className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors", title: "Clear search", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [isSearchingBackend && ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1.5 text-emerald-600 dark:text-lime-400 font-medium animate-pulse", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-lime-400" }), "Searching live catalog..."] })), (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold bg-slate-100 dark:bg-slate-900/80 px-3 py-1.5 rounded-xl border border-slate-200/80 dark:border-slate-800", children: ["Showing ", (0, jsx_runtime_1.jsx)("span", { className: "text-slate-900 dark:text-white font-bold", children: filteredProducts.length }), " of ", products.length, " products"] })] })] })), showFilterSidebar ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col lg:flex-row gap-8 items-start w-full", children: [(0, jsx_runtime_1.jsx)("aside", { className: "w-full lg:w-72 shrink-0", children: (0, jsx_runtime_1.jsx)(EditableFilterSidebar_1.EditableFilterSidebar, { categories: categories.length > 1 ? categories : resolvedCategories.filter((c) => c !== "All"), minPrice: filterSidebarProps?.minPrice ?? 0, maxPrice: effectiveMaxPrice, currency: currency, initialFilters: activeFilters, onFilterChange: handleFilterChange, ...filterSidebarProps }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 w-full min-w-0", children: [productsGridContent, paginationContent] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [productsGridContent, paginationContent] }))] }));
258
472
  }
259
473
  // Canonical alias
260
474
  exports.ProductGrid = EditableProductGrid;
@@ -19,6 +19,8 @@ export interface ProductShowcaseItem {
19
19
  badge?: string;
20
20
  colors?: Array<ProductShowcaseColor | string>;
21
21
  isAvailable?: boolean;
22
+ href?: string;
23
+ detailHref?: string;
22
24
  [key: string]: unknown;
23
25
  }
24
26
  export interface EditableProductShowcaseProps extends React.HTMLAttributes<HTMLElement> {
@@ -6,6 +6,7 @@ exports.ProductShowcase = EditableProductShowcase;
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
8
  const SiteDataProvider_1 = require("./SiteDataProvider");
9
+ const PlatformProductDetail_1 = require("./PlatformProductDetail");
9
10
  const DEFAULT_PRODUCTS = [
10
11
  {
11
12
  id: 'phone-iphone-16-pro',
@@ -194,8 +195,8 @@ function EditableProductShowcase({ sectionPath = 'home', listPath = 'featuredPho
194
195
  const activeColorIdx = selectedColors[phoneId] ?? 0;
195
196
  const colorsList = phone.colors || [];
196
197
  const activeColor = colorsList[activeColorIdx] || colorsList[0] || { name: 'Default', hex: '#9d9890' };
197
- const detailUrl = `${productDetailRoutePrefix}/?id=${encodeURIComponent(phoneId)}`;
198
- return ((0, jsx_runtime_1.jsxs)("li", { "data-preview-item-path": `${sectionPath}.${listPath}[${idx}]`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].card`, "data-preview-style-type": "card", className: "group relative flex flex-col justify-between p-6 sm:p-7 rounded-3xl border border-black/[0.08] bg-white/95 hover:border-cyan-400/60 transition-all duration-300 hover:-translate-y-1.5 shadow-[0_10px_30px_rgba(0,0,0,0.04)] hover:shadow-[0_20px_40px_rgba(0,240,255,0.12)]", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-bold uppercase tracking-widest text-cyan-600 font-mono", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].brand`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].brand`, "data-preview-style-type": "text", children: phone.brand }), (0, jsx_runtime_1.jsx)("h3", { className: "mt-1 text-xl font-bold text-zinc-950 tracking-tight group-hover:text-cyan-600 transition-colors", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].name`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].name`, "data-preview-style-type": "text", children: (0, jsx_runtime_1.jsx)("a", { href: detailUrl, className: "hover:underline", children: phone.name }) }), phone.subtitle && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].subtitle`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].subtitle`, "data-preview-style-type": "text", children: phone.subtitle }))] }), phone.badge && ((0, jsx_runtime_1.jsx)("span", { className: "shrink-0 rounded-full border border-black/[0.08] bg-black/[0.03] px-2.5 py-0.5 text-[10px] font-semibold text-zinc-700 backdrop-blur-md", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].badge`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].badge`, "data-preview-style-type": "text", children: phone.badge }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "relative my-6 h-60 w-full overflow-clip rounded-2xl border border-black/[0.06] bg-slate-100 flex items-center justify-center", children: [(0, jsx_runtime_1.jsx)("img", { src: phone.image, alt: phone.name || 'Product Image', "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].image`, className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-108" }), (0, jsx_runtime_1.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent opacity-30" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between pt-2 border-t border-black/[0.06]", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", "data-preview-list-path": `${sectionPath}.${listPath}[${idx}].colors`, children: [colorsList.map((c, colorIdx) => ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `${sectionPath}.${listPath}[${idx}].colors[${colorIdx}]`, className: "inline-flex items-center", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
198
+ const detailUrl = phone.href || phone.detailHref || (0, PlatformProductDetail_1.platformProductDetailHref)(phoneId, productDetailRoutePrefix);
199
+ return ((0, jsx_runtime_1.jsxs)("li", { "data-preview-item-path": `${sectionPath}.${listPath}[${idx}]`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].card`, "data-preview-style-type": "card", className: "group relative flex flex-col justify-between p-6 sm:p-7 rounded-3xl border border-black/[0.08] bg-white/95 hover:border-cyan-400/60 transition-all duration-300 hover:-translate-y-1.5 shadow-[0_10px_30px_rgba(0,0,0,0.04)] hover:shadow-[0_20px_40px_rgba(0,240,255,0.12)]", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-bold uppercase tracking-widest text-cyan-600 font-mono", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].brand`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].brand`, "data-preview-style-type": "text", children: phone.brand }), (0, jsx_runtime_1.jsx)("h3", { className: "mt-1 text-xl font-bold text-zinc-950 tracking-tight group-hover:text-cyan-600 transition-colors", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].name`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].name`, "data-preview-style-type": "text", children: (0, jsx_runtime_1.jsx)("a", { href: detailUrl, className: "hover:underline", "data-preview-static": "Open the stable live product detail page", children: phone.name }) }), phone.subtitle && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].subtitle`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].subtitle`, "data-preview-style-type": "text", children: phone.subtitle }))] }), phone.badge && ((0, jsx_runtime_1.jsx)("span", { className: "shrink-0 rounded-full border border-black/[0.08] bg-black/[0.03] px-2.5 py-0.5 text-[10px] font-semibold text-zinc-700 backdrop-blur-md", "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].badge`, "data-preview-style-target": `${sectionPath}.${listPath}[${idx}].badge`, "data-preview-style-type": "text", children: phone.badge }))] }), (0, jsx_runtime_1.jsxs)("a", { href: detailUrl, "data-preview-static": "Open the stable live product detail page", className: "relative my-6 h-60 w-full overflow-clip rounded-2xl border border-black/[0.06] bg-slate-100 flex items-center justify-center block", children: [(0, jsx_runtime_1.jsx)("img", { src: phone.image, alt: phone.name || 'Product Image', "data-preview-field-path": `${sectionPath}.${listPath}[${idx}].image`, className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-108" }), (0, jsx_runtime_1.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent opacity-30" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between pt-2 border-t border-black/[0.06]", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", "data-preview-list-path": `${sectionPath}.${listPath}[${idx}].colors`, children: [colorsList.map((c, colorIdx) => ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `${sectionPath}.${listPath}[${idx}].colors[${colorIdx}]`, className: "inline-flex items-center", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
199
200
  e.stopPropagation();
200
201
  setSelectedColors((prev) => ({ ...prev, [phoneId]: colorIdx }));
201
202
  }, "aria-label": c.name, title: c.name, className: `h-4 w-4 rounded-full transition-all cursor-pointer ${activeColorIdx === colorIdx
@@ -208,6 +209,6 @@ function EditableProductShowcase({ sectionPath = 'home', listPath = 'featuredPho
208
209
  // eslint-disable-next-line @next/next/no-location-assign-relative-destination
209
210
  window.location.href = detailUrl;
210
211
  }
211
- }, className: "flex h-9 w-9 items-center justify-center rounded-full border border-black/10 bg-black/[0.02] text-zinc-600 hover:text-cyan-600 hover:border-cyan-400/50 hover:bg-cyan-50/50 transition-all cursor-pointer", title: "Quick View", "aria-label": "Quick View Product", children: (0, jsx_runtime_1.jsxs)("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] }) })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => handleWhatsAppOrder(phone, activeColor.name, e), className: "w-full flex items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-cyan-400 via-sky-400 to-blue-600 py-3 px-4 text-xs font-bold text-white shadow-[0_4px_15px_rgba(0,240,255,0.25)] hover:shadow-[0_6px_25px_rgba(0,240,255,0.45)] transition-all hover:scale-[1.01] active:scale-[0.99] cursor-pointer", children: [(0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4 fill-white", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M12.031 6.172c-3.181 0-5.767 2.586-5.768 5.766-.001 1.298.38 2.27 1.019 3.287l-.582 2.128 2.182-.573c.978.58 1.911.928 3.145.929 3.178 0 5.767-2.587 5.768-5.766.001-3.187-2.575-5.771-5.764-5.771zm3.392 8.244c-.144.405-.837.774-1.17.824-.312.045-.634.055-.992-.061-.635-.206-1.464-.567-2.527-1.63-1.341-1.341-1.849-2.531-2.006-2.986-.157-.455-.015-.81.212-1.037.202-.202.455-.253.606-.253.152 0 .303.004.436.012.141.008.329-.053.515.394.192.465.657 1.602.714 1.719.057.116.096.253.019.405-.076.152-.114.248-.228.38-.114.133-.24.296-.343.397-.114.114-.233.238-.1.467.133.228.591.976 1.269 1.58 1.077.959 1.83 1.157 2.058 1.271.228.114.362.096.495-.057.133-.152.571-.666.723-.894.152-.228.303-.19.515-.114.212.076 1.346.635 1.575.749.228.114.38.171.436.266.057.095.057.553-.087.958z" }) }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-target": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-type": "text", children: whatsappOrderLabel })] })] })] }, phoneId));
212
+ }, className: "flex h-9 w-9 items-center justify-center rounded-full border border-black/10 bg-black/[0.02] text-zinc-600 hover:text-cyan-600 hover:border-cyan-400/50 hover:bg-cyan-50/50 transition-all cursor-pointer", title: "Quick View", "aria-label": "Quick View Product", "data-preview-static": "Open the product quick view or detail page", children: (0, jsx_runtime_1.jsxs)("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] }) })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => handleWhatsAppOrder(phone, activeColor.name, e), className: "w-full flex items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-cyan-400 via-sky-400 to-blue-600 py-3 px-4 text-xs font-bold text-white shadow-[0_4px_15px_rgba(0,240,255,0.25)] hover:shadow-[0_6px_25px_rgba(0,240,255,0.45)] transition-all hover:scale-[1.01] active:scale-[0.99] cursor-pointer", children: [(0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4 fill-white", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M12.031 6.172c-3.181 0-5.767 2.586-5.768 5.766-.001 1.298.38 2.27 1.019 3.287l-.582 2.128 2.182-.573c.978.58 1.911.928 3.145.929 3.178 0 5.767-2.587 5.768-5.766.001-3.187-2.575-5.771-5.764-5.771zm3.392 8.244c-.144.405-.837.774-1.17.824-.312.045-.634.055-.992-.061-.635-.206-1.464-.567-2.527-1.63-1.341-1.341-1.849-2.531-2.006-2.986-.157-.455-.015-.81.212-1.037.202-.202.455-.253.606-.253.152 0 .303.004.436.012.141.008.329-.053.515.394.192.465.657 1.602.714 1.719.057.116.096.253.019.405-.076.152-.114.248-.228.38-.114.133-.24.296-.343.397-.114.114-.233.238-.1.467.133.228.591.976 1.269 1.58 1.077.959 1.83 1.157 2.058 1.271.228.114.362.096.495-.057.133-.152.571-.666.723-.894.152-.228.303-.19.515-.114.212.076 1.346.635 1.575.749.228.114.38.171.436.266.057.095.057.553-.087.958z" }) }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-target": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-type": "text", children: whatsappOrderLabel })] })] })] }, phoneId));
212
213
  }) })] })] }));
213
214
  }
@@ -13,6 +13,8 @@ export interface ProductQuickViewItem {
13
13
  isAvailable?: boolean;
14
14
  rating?: number;
15
15
  reviewCount?: number;
16
+ href?: string;
17
+ detailHref?: string;
16
18
  [key: string]: unknown;
17
19
  }
18
20
  export interface ProductQuickViewProps {
@@ -27,6 +29,8 @@ export interface ProductQuickViewProps {
27
29
  */
28
30
  itemPath?: string;
29
31
  addToCartLabel?: string;
32
+ showDetailLink?: boolean;
33
+ detailLinkText?: string;
30
34
  }
31
35
  /**
32
36
  * DENEB UI — Product Quick View Modal
@@ -37,4 +41,4 @@ export interface ProductQuickViewProps {
37
41
  *
38
42
  * Created by Chamika Gayashan & Induranga Kawishwara
39
43
  */
40
- export declare function ProductQuickView({ product, isOpen, onClose, onAddToCart, className, itemPath, addToCartLabel, }: ProductQuickViewProps): React.JSX.Element | null;
44
+ export declare function ProductQuickView({ product, isOpen, onClose, onAddToCart, className, itemPath, addToCartLabel, showDetailLink, detailLinkText, }: ProductQuickViewProps): React.JSX.Element | null;