@deneb-ui/ui 2.0.86 → 2.0.87

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.
@@ -4,16 +4,29 @@ export interface ProductItem {
4
4
  id?: string | number;
5
5
  name?: string;
6
6
  title?: string;
7
+ productName?: string;
8
+ itemTitle?: string;
7
9
  brand?: string;
8
10
  price?: string | number;
11
+ cost?: string | number;
12
+ amount?: string | number;
13
+ productPrice?: string | number;
14
+ compareAtPrice?: string | number;
9
15
  originalPrice?: string | number;
10
16
  currency?: string;
11
17
  description?: string;
18
+ desc?: string;
19
+ details?: string;
12
20
  category?: string;
13
21
  imageUrl?: string;
14
22
  image?: string;
23
+ photo?: string;
24
+ thumbnail?: string;
25
+ productImage?: string;
26
+ gallery?: string[];
15
27
  images?: string[];
16
28
  badge?: string;
29
+ tag?: string;
17
30
  rating?: number | string;
18
31
  reviewsCount?: number | string;
19
32
  isNew?: boolean;
@@ -20,18 +20,43 @@ function CartIcon({ className = '', style = {} }) {
20
20
  */
21
21
  function EditableProductCard({ itemPath, product, cardVariant = 'modern-glass', imageFallback, as: Component = 'article', currency = 'LKR', showBrand = true, showPrice = true, showDescription = true, showCategory = true, whatsappNumber = '94770000000', storeName, showWhatsAppButton = true, showAddToCartButton = true, whatsappActionLabel = 'Inquire on WhatsApp', addToCartLabel = 'Add to Cart', actionSlot, actionLabel, actionLabelPath, onAddToCart, onWhatsAppClick, className = '', style, ...props }) {
22
22
  const cart = (0, useCart_1.useOptionalCart)();
23
- const name = String(product?.name || product?.title || 'Untitled Product');
23
+ const titleKey = 'title' in (product || {}) && !('name' in (product || {}) && product.name)
24
+ ? 'title'
25
+ : ('productName' in (product || {}) && !('name' in (product || {}) && product.name)
26
+ ? 'productName'
27
+ : 'name');
28
+ const name = String(product?.name || product?.title || product?.productName || product?.itemTitle || 'Untitled Product');
24
29
  const brand = String(product?.brand || '');
25
- const hasPrice = showPrice && product?.price !== undefined && product?.price !== null && String(product.price).trim() !== '';
26
- const rawPrice = product?.price;
30
+ const rawPriceCandidate = product?.price ?? product?.cost ?? product?.amount ?? product?.productPrice;
31
+ const hasPrice = showPrice && rawPriceCandidate !== undefined && rawPriceCandidate !== null && String(rawPriceCandidate).trim() !== '';
32
+ const rawPrice = rawPriceCandidate;
27
33
  const priceNum = typeof rawPrice === 'number' ? rawPrice : parseFloat(String(rawPrice || '').replace(/[^0-9.]/g, '')) || 0;
28
34
  const formattedPrice = hasPrice ? (typeof rawPrice === 'string' && rawPrice.includes('LKR') ? rawPrice : (0, useCart_1.formatCurrency)(priceNum, currency)) : '';
29
- const originalPrice = product?.originalPrice !== undefined ? String(product.originalPrice) : '';
30
- const description = String(product?.description || '');
35
+ const priceKey = 'cost' in (product || {}) && !('price' in (product || {}) && product.price !== undefined)
36
+ ? 'cost'
37
+ : ('amount' in (product || {}) && !('price' in (product || {}) && product.price !== undefined)
38
+ ? 'amount'
39
+ : 'price');
40
+ const rawOriginalPrice = product?.originalPrice ?? product?.compareAtPrice;
41
+ const originalPrice = rawOriginalPrice !== undefined && rawOriginalPrice !== null ? String(rawOriginalPrice) : '';
42
+ const originalPriceKey = 'compareAtPrice' in (product || {}) && !('originalPrice' in (product || {}) && product.originalPrice !== undefined)
43
+ ? 'compareAtPrice'
44
+ : 'originalPrice';
45
+ const descriptionKey = 'desc' in (product || {}) && !('description' in (product || {}) && product.description)
46
+ ? 'desc'
47
+ : 'description';
48
+ const description = String(product?.description || product?.desc || product?.details || '');
31
49
  const category = String(product?.category || '');
32
- const badge = String(product?.badge || '');
50
+ const badgeKey = 'tag' in (product || {}) && !('badge' in (product || {}) && product.badge) ? 'tag' : 'badge';
51
+ const badge = String(product?.badge || product?.tag || '');
33
52
  const fallbackImage = imageFallback || 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=800&q=80';
34
- const imageUrl = String(product?.imageUrl || product?.image || fallbackImage);
53
+ const rawImage = product?.imageUrl || product?.image || product?.photo || product?.thumbnail || product?.productImage || (Array.isArray(product?.gallery) && product.gallery[0]) || (Array.isArray(product?.images) && product.images[0]);
54
+ const imageUrl = String(rawImage || fallbackImage);
55
+ const imageKey = 'image' in (product || {}) && !('imageUrl' in (product || {}) && product.imageUrl)
56
+ ? 'image'
57
+ : ('photo' in (product || {}) && !('imageUrl' in (product || {}) && product.imageUrl)
58
+ ? 'photo'
59
+ : 'imageUrl');
35
60
  // Resolved phone number for WhatsApp
36
61
  const resolvedPhone = String(product?.whatsappNumber || whatsappNumber || '94770000000');
37
62
  const resolvedWhatsAppText = String(product?.whatsappButtonText || whatsappActionLabel);
@@ -113,7 +138,7 @@ function EditableProductCard({ itemPath, product, cardVariant = 'modern-glass',
113
138
  overflow: 'hidden',
114
139
  width: isHorizontal ? '40%' : '100%',
115
140
  flexShrink: 0,
116
- }, children: [(0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.imageUrl`, "data-preview-field-path": `${itemPath}.imageUrl`, src: imageUrl, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } }), badge && ((0, jsx_runtime_1.jsx)("div", { style: { position: 'absolute', top: '10px', left: '10px', zIndex: 10 }, children: (0, jsx_runtime_1.jsx)(EditableText_2.EditableBadge, { id: `${itemPath}.badge`, "data-preview-field-path": `${itemPath}.badge`, defaultValue: badge, badgeVariant: "primary" }) }))] }), (0, jsx_runtime_1.jsxs)("div", { style: {
141
+ }, children: [(0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, "data-preview-field-path": `${itemPath}.${imageKey}`, src: imageUrl, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } }), badge && ((0, jsx_runtime_1.jsx)("div", { style: { position: 'absolute', top: '10px', left: '10px', zIndex: 10 }, children: (0, jsx_runtime_1.jsx)(EditableText_2.EditableBadge, { id: `${itemPath}.${badgeKey}`, "data-preview-field-path": `${itemPath}.${badgeKey}`, defaultValue: badge, badgeVariant: "primary" }) }))] }), (0, jsx_runtime_1.jsxs)("div", { style: {
117
142
  padding: '1.25rem',
118
143
  display: 'flex',
119
144
  flexDirection: 'column',
@@ -135,13 +160,13 @@ function EditableProductCard({ itemPath, product, cardVariant = 'modern-glass',
135
160
  backgroundColor: 'var(--tag-bg, var(--color-secondary, rgba(241, 245, 249, 0.9)))',
136
161
  padding: '2px 8px',
137
162
  borderRadius: '6px',
138
- } })) : null] }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "h3", id: `${itemPath}.name`, "data-preview-field-path": `${itemPath}.name`, defaultValue: name, style: {
163
+ } })) : null] }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "h3", id: `${itemPath}.${titleKey}`, "data-preview-field-path": `${itemPath}.${titleKey}`, defaultValue: name, style: {
139
164
  fontSize: '1.125rem',
140
165
  fontWeight: 700,
141
166
  lineHeight: 1.35,
142
167
  color: 'var(--heading-color, #ffffff)',
143
168
  marginBottom: '0.375rem',
144
- } }), showDescription && description ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "p", id: `${itemPath}.description`, "data-preview-field-path": `${itemPath}.description`, defaultValue: description, style: {
169
+ } }), showDescription && description ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "p", id: `${itemPath}.${descriptionKey}`, "data-preview-field-path": `${itemPath}.${descriptionKey}`, defaultValue: description, style: {
145
170
  fontSize: '0.875rem',
146
171
  color: 'var(--muted-text, #94a3b8)',
147
172
  lineHeight: 1.5,
@@ -157,12 +182,12 @@ function EditableProductCard({ itemPath, product, cardVariant = 'modern-glass',
157
182
  display: 'flex',
158
183
  flexDirection: 'column',
159
184
  gap: '0.75rem',
160
- }, children: [hasPrice ? ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'baseline', gap: '0.5rem', flexWrap: 'wrap' }, children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "span", id: `${itemPath}.price`, "data-preview-field-path": `${itemPath}.price`, defaultValue: formattedPrice, style: {
185
+ }, children: [hasPrice ? ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'baseline', gap: '0.5rem', flexWrap: 'wrap' }, children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "span", id: `${itemPath}.${priceKey}`, "data-preview-field-path": `${itemPath}.${priceKey}`, defaultValue: formattedPrice, style: {
161
186
  fontSize: '1.3rem',
162
187
  fontWeight: 800,
163
188
  color: 'var(--brand-color, #ffffff)',
164
189
  letterSpacing: '-0.02em',
165
- } }), originalPrice ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "span", id: `${itemPath}.originalPrice`, "data-preview-field-path": `${itemPath}.originalPrice`, defaultValue: originalPrice, style: {
190
+ } }), originalPrice ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "span", id: `${itemPath}.${originalPriceKey}`, "data-preview-field-path": `${itemPath}.${originalPriceKey}`, defaultValue: originalPrice, style: {
166
191
  fontSize: '0.875rem',
167
192
  textDecoration: 'line-through',
168
193
  color: 'var(--muted-text, #94a3b8)',
@@ -29,9 +29,9 @@ function EditableProductDetail({ sectionPath = 'product', product, sizes, colors
29
29
  ? product.gallery
30
30
  : (product.images && product.images.length > 0)
31
31
  ? product.images
32
- : [product.featuredImage || product.imageUrl || product.image || '/products/vanta-aero-x.jpg'];
32
+ : [product.featuredImage || product.imageUrl || product.image || product.photo || product.thumbnail || '/products/vanta-aero-x.jpg'];
33
33
  return rawList.filter(Boolean);
34
- }, [product.gallery, product.images, product.featuredImage, product.imageUrl, product.image]);
34
+ }, [product.gallery, product.images, product.featuredImage, product.imageUrl, product.image, product.photo, product.thumbnail]);
35
35
  // 2. Resolve Options (Sizes, Volumes, Weights, Counts)
36
36
  const resolved = (0, productOptions_1.resolveProductOptions)(product);
37
37
  const effectiveOptions = (0, react_1.useMemo)(() => {
@@ -106,8 +106,9 @@ function EditableProductDetail({ sectionPath = 'product', product, sizes, colors
106
106
  setActiveImage(c.image);
107
107
  }
108
108
  };
109
- const name = String(product.name || product.title || 'Product Title');
110
- const price = product.price !== undefined ? String(product.price) : 'LKR 0';
109
+ const name = String(product.name || product.title || product.productName || product.itemTitle || 'Product Title');
110
+ const rawPriceCandidate = product.price ?? product.cost ?? product.amount ?? product.productPrice;
111
+ const price = rawPriceCandidate !== undefined && rawPriceCandidate !== null ? String(rawPriceCandidate) : 'LKR 0';
111
112
  const originalPrice = product.originalPrice ?? product.compareAtPrice;
112
113
  const isAvailable = product.isAvailable !== false;
113
114
  const description = String(product.description || '');
@@ -20,6 +20,33 @@ function EditableProductGrid({ sectionPath = 'home', listPath = 'products', titl
20
20
  const siteApi = (0, SiteDataProvider_1.useSiteApi)();
21
21
  const liveProducts = (0, SiteDataProvider_1.useProducts)();
22
22
  const baseProducts = userProducts && userProducts.length > 0 ? userProducts : liveProducts;
23
+ const resolvedListPath = (0, react_1.useMemo)(() => {
24
+ if (listPath && listPath !== 'products') {
25
+ return listPath;
26
+ }
27
+ const content = (0, SiteDataProvider_1.isRecord)(siteData?.content) ? siteData.content : null;
28
+ if (!content)
29
+ return listPath || 'products';
30
+ if (Array.isArray(content.products) && content.products.length > 0)
31
+ return 'products';
32
+ if ((0, SiteDataProvider_1.isRecord)(content.shop) && Array.isArray(content.shop.products))
33
+ return 'shop.products';
34
+ if ((0, SiteDataProvider_1.isRecord)(content.home) && Array.isArray(content.home.products))
35
+ return 'home.products';
36
+ if ((0, SiteDataProvider_1.isRecord)(content.home) && Array.isArray(content.home.featuredProducts))
37
+ return 'home.featuredProducts';
38
+ if ((0, SiteDataProvider_1.isRecord)(content.catalog) && Array.isArray(content.catalog.products))
39
+ return 'catalog.products';
40
+ if ((0, SiteDataProvider_1.isRecord)(content.menu) && Array.isArray(content.menu.items))
41
+ return 'menu.items';
42
+ if ((0, SiteDataProvider_1.isRecord)(content.menu) && Array.isArray(content.menu.products))
43
+ return 'menu.products';
44
+ if ((0, SiteDataProvider_1.isRecord)(content.store) && Array.isArray(content.store.products))
45
+ return 'store.products';
46
+ if ((0, SiteDataProvider_1.isRecord)(content.shop) && Array.isArray(content.shop.items))
47
+ return 'shop.items';
48
+ return listPath || 'products';
49
+ }, [listPath, siteData]);
23
50
  const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
24
51
  const [isSearchingBackend, setIsSearchingBackend] = (0, react_1.useState)(false);
25
52
  const [backendProducts, setBackendProducts] = (0, react_1.useState)(null);
@@ -103,12 +130,13 @@ function EditableProductGrid({ sectionPath = 'home', listPath = 'products', titl
103
130
  if (searchQuery.trim()) {
104
131
  const q = searchQuery.trim().toLowerCase();
105
132
  result = result.filter((p) => {
106
- const name = String(p.name || p.title || '').toLowerCase();
107
- const desc = String(p.description || '').toLowerCase();
133
+ const name = String(p.name || p.title || p.productName || p.itemTitle || '').toLowerCase();
134
+ const desc = String(p.description || p.desc || p.details || '').toLowerCase();
108
135
  const brand = String(p.brand || '').toLowerCase();
109
136
  const cat = String(p.category || '').toLowerCase();
110
137
  const tags = Array.isArray(p.tags) ? p.tags.join(' ').toLowerCase() : '';
111
- return name.includes(q) || desc.includes(q) || brand.includes(q) || cat.includes(q) || tags.includes(q);
138
+ const badge = String(p.badge || p.tag || '').toLowerCase();
139
+ return name.includes(q) || desc.includes(q) || brand.includes(q) || cat.includes(q) || tags.includes(q) || badge.includes(q);
112
140
  });
113
141
  }
114
142
  return result;
@@ -178,16 +206,17 @@ function EditableProductGrid({ sectionPath = 'home', listPath = 'products', titl
178
206
  }, 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: () => {
179
207
  setSearchQuery('');
180
208
  onSearchChange?.('');
181
- }, 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", { ...(listPath ? { 'data-preview-list-path': listPath } : {}), className: displayedProducts.length > 0
209
+ }, 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
182
210
  ? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
183
211
  : 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
184
212
  // Determine true index in baseProducts for exact Fivora visual editing focus targeting
185
213
  const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
186
214
  (bp.name && product.name ? bp.name === product.name : false) ||
187
- (bp.title && product.title ? bp.title === product.title : false));
215
+ (bp.title && product.title ? bp.title === product.title : false) ||
216
+ (bp.productName && product.productName ? bp.productName === product.productName : false));
188
217
  const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
189
- const itemPath = listPath
190
- ? `${listPath}[${effectiveIndex}]`
218
+ const itemPath = resolvedListPath
219
+ ? `${resolvedListPath}[${effectiveIndex}]`
191
220
  : `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
192
221
  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, 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));
193
222
  })) : (
@@ -99,23 +99,34 @@ function EditableProductShowcase({ sectionPath = 'home', listPath = 'featuredPho
99
99
  return { name: `Color ${cIdx + 1}`, hex: c };
100
100
  return { name: c?.name || `Color ${cIdx + 1}`, hex: c?.hex || '#9d9890' };
101
101
  });
102
- const resolvedImage = item.image && typeof item.image === 'string' && item.image.trim() !== ''
103
- ? item.image
104
- : (item.imageUrl && typeof item.imageUrl === 'string' && item.imageUrl.trim() !== ''
105
- ? item.imageUrl
106
- : (def.image || '/images/showcase-phone.jpg'));
102
+ const rawImage = item.image || item.imageUrl || item.photo || item.thumbnail || (Array.isArray(item.images) && item.images[0]);
103
+ const resolvedImage = typeof rawImage === 'string' && rawImage.trim() !== ''
104
+ ? rawImage
105
+ : (def.image || '/images/showcase-phone.jpg');
106
+ const rawPrice = item.price ?? item.cost ?? item.amount ?? item.productPrice;
107
+ const parsedPrice = typeof rawPrice === 'number'
108
+ ? rawPrice
109
+ : typeof rawPrice === 'string'
110
+ ? parseFloat(rawPrice.replace(/[^0-9.]/g, ''))
111
+ : NaN;
112
+ const finalPrice = Number.isFinite(parsedPrice) ? parsedPrice : def.price;
113
+ const rawOrig = item.originalPrice ?? item.compareAtPrice;
114
+ const parsedOrig = typeof rawOrig === 'number'
115
+ ? rawOrig
116
+ : typeof rawOrig === 'string'
117
+ ? parseFloat(rawOrig.replace(/[^0-9.]/g, ''))
118
+ : NaN;
119
+ const finalOrig = Number.isFinite(parsedOrig) ? parsedOrig : def.originalPrice;
107
120
  return {
108
121
  ...def,
109
122
  ...item,
110
123
  _index: idx,
111
124
  id: item.id || `product-${idx}`,
112
- name: item.name || item.title || def.name,
125
+ name: item.name || item.title || item.productName || item.itemTitle || def.name,
113
126
  brand: item.brand || item.category || def.brand || 'Store',
114
127
  subtitle: item.subtitle || def.subtitle || '',
115
- price: item.price != null ? Number(item.price) : def.price,
116
- originalPrice: item.originalPrice != null
117
- ? Number(item.originalPrice)
118
- : (item.compareAtPrice != null ? Number(item.compareAtPrice) : def.originalPrice),
128
+ price: finalPrice,
129
+ originalPrice: finalOrig,
119
130
  condition: item.condition || def.condition || 'Brand New',
120
131
  category: item.category || item.brand || def.category || 'All',
121
132
  badge: item.badge || def.badge || '',
@@ -70,11 +70,16 @@ function normalizeProductForDetail(product, siteData) {
70
70
  };
71
71
  const name = (typeof mergedProduct.name === 'string' && mergedProduct.name) ||
72
72
  (typeof mergedProduct.title === 'string' && mergedProduct.title) ||
73
+ (typeof mergedProduct.productName === 'string' && mergedProduct.productName) ||
74
+ (typeof mergedProduct.itemTitle === 'string' && mergedProduct.itemTitle) ||
73
75
  undefined;
74
76
  const image = (typeof mergedProduct.featuredImage === 'string' && mergedProduct.featuredImage) ||
75
77
  (typeof mergedProduct.imageUrl === 'string' && mergedProduct.imageUrl) ||
76
78
  (typeof mergedProduct.image === 'string' && mergedProduct.image) ||
79
+ (typeof mergedProduct.photo === 'string' && mergedProduct.photo) ||
80
+ (typeof mergedProduct.thumbnail === 'string' && mergedProduct.thumbnail) ||
77
81
  undefined;
82
+ const price = mergedProduct.price ?? mergedProduct.cost ?? mergedProduct.amount ?? mergedProduct.productPrice;
78
83
  const images = Array.isArray(mergedProduct.gallery)
79
84
  ? mergedProduct.gallery
80
85
  : Array.isArray(mergedProduct.images)
@@ -103,6 +108,7 @@ function normalizeProductForDetail(product, siteData) {
103
108
  ...(image
104
109
  ? { featuredImage: image, imageUrl: image, image }
105
110
  : {}),
111
+ ...(price !== undefined ? { price } : {}),
106
112
  ...(images ? { gallery: images, images } : {}),
107
113
  ...(compareAtPrice !== undefined
108
114
  ? { compareAtPrice, originalPrice: compareAtPrice }
@@ -106,6 +106,17 @@ export type DenebData = SiteData;
106
106
  * Hook to retrieve products cleanly from SiteData, supporting both
107
107
  * top-level content.products and nested content.home.products.
108
108
  */
109
+ /**
110
+ * Universal Bidirectional Product Normalizer:
111
+ * Ensures every product object has both canonical and alias keys simultaneously populated
112
+ * with clean numbers and resolved image URLs, so any template component code works flawlessly
113
+ * regardless of whether the developer wrote .title or .name, .cost or .price, .image or .imageUrl.
114
+ */
115
+ export declare function normalizeProductItem(p: unknown): ProductItem;
116
+ /**
117
+ * Universal Service Normalizer
118
+ */
119
+ export declare function normalizeServiceItem(s: unknown): ServiceItem;
109
120
  export declare function useProducts(fallback?: ProductItem[]): ProductItem[];
110
121
  /**
111
122
  * Hook to retrieve services cleanly from SiteData, supporting both
@@ -18,6 +18,8 @@ exports.contentNumber = contentNumber;
18
18
  exports.parseFieldPath = parseFieldPath;
19
19
  exports.getFieldStyle = getFieldStyle;
20
20
  exports.useFieldStyle = useFieldStyle;
21
+ exports.normalizeProductItem = normalizeProductItem;
22
+ exports.normalizeServiceItem = normalizeServiceItem;
21
23
  exports.useProducts = useProducts;
22
24
  exports.useServices = useServices;
23
25
  exports.useSiteApi = useSiteApi;
@@ -263,20 +265,8 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
263
265
  const currentCommon = isRecord(currentContent.common) ? currentContent.common : {};
264
266
  const currentContact = isRecord(currentContent.contact) ? currentContent.contact : {};
265
267
  const rawLiveProducts = Array.isArray(live.products) ? live.products : currentContent.products;
266
- const nextProducts = Array.isArray(rawLiveProducts)
267
- ? rawLiveProducts.map((p) => {
268
- if (!isRecord(p))
269
- return p;
270
- const customData = isRecord(p.customData) ? p.customData : {};
271
- return {
272
- ...customData,
273
- ...p,
274
- };
275
- })
276
- : rawLiveProducts;
277
- const nextServices = Array.isArray(live.services)
278
- ? live.services
279
- : currentContent.services;
268
+ const nextProducts = Array.isArray(rawLiveProducts) ? rawLiveProducts.map(normalizeProductItem) : rawLiveProducts;
269
+ const nextServices = Array.isArray(live.services) ? live.services.map(normalizeServiceItem) : currentContent.services;
280
270
  const nextReviews = Array.isArray(live.reviews)
281
271
  ? live.reviews
282
272
  : null;
@@ -296,6 +286,7 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
296
286
  nextContact.phone = contact.phone;
297
287
  nextContact.contactNumber = contact.phone;
298
288
  nextCommon.contactNumber = contact.phone;
289
+ nextCommon.phone = contact.phone;
299
290
  }
300
291
  if (typeof contact.whatsapp === 'string' && contact.whatsapp.trim()) {
301
292
  nextContact.whatsapp = contact.whatsapp;
@@ -304,6 +295,7 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
304
295
  if (cleanWa) {
305
296
  nextContact.whatsappNumber = cleanWa;
306
297
  nextCommon.whatsappNumber = cleanWa;
298
+ nextHome.whatsappNumber = cleanWa;
307
299
  // Dynamically update any hardcoded or static wa.me URLs across home and common
308
300
  const waRegex = /(https?:\/\/(?:wa\.me|api\.whatsapp\.com\/send\?phone=))\d+/gi;
309
301
  for (const key of ['whatsappCtaUrl', 'whatsappOrderUrl', 'whatsappUrl']) {
@@ -341,6 +333,7 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
341
333
  nextContact.hours = live.shop.openingHours;
342
334
  nextContact.openingHours = live.shop.openingHours;
343
335
  nextCommon.openingHours = live.shop.openingHours;
336
+ nextCommon.hours = live.shop.openingHours;
344
337
  nextHome.businessHours = live.shop.openingHours;
345
338
  }
346
339
  if (typeof live.shop.businessName === 'string' && live.shop.businessName.trim()) {
@@ -368,6 +361,18 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
368
361
  ...(nextProducts !== undefined ? { products: nextProducts } : {}),
369
362
  ...(nextServices !== undefined ? { services: nextServices } : {}),
370
363
  ...(nextReviews !== null ? { reviews: nextReviews, testimonials: nextReviews, feedbacks: nextReviews } : {}),
364
+ ...(currentContent?.shop && typeof currentContent.shop === 'object' && nextProducts !== undefined
365
+ ? { shop: { ...currentContent.shop, products: nextProducts } }
366
+ : {}),
367
+ ...(currentContent?.catalog && typeof currentContent.catalog === 'object' && nextProducts !== undefined
368
+ ? { catalog: { ...currentContent.catalog, products: nextProducts } }
369
+ : {}),
370
+ ...(currentContent?.menu && typeof currentContent.menu === 'object' && nextProducts !== undefined
371
+ ? { menu: { ...currentContent.menu, items: nextProducts, products: nextProducts } }
372
+ : {}),
373
+ ...(currentContent?.store && typeof currentContent.store === 'object' && nextProducts !== undefined
374
+ ? { store: { ...currentContent.store, products: nextProducts } }
375
+ : {}),
371
376
  ...(currentHome || Object.keys(nextHome).length > 0
372
377
  ? {
373
378
  home: {
@@ -681,37 +686,162 @@ exports.DenebDataContext = exports.SiteDataContext;
681
686
  * Hook to retrieve products cleanly from SiteData, supporting both
682
687
  * top-level content.products and nested content.home.products.
683
688
  */
689
+ /**
690
+ * Universal Bidirectional Product Normalizer:
691
+ * Ensures every product object has both canonical and alias keys simultaneously populated
692
+ * with clean numbers and resolved image URLs, so any template component code works flawlessly
693
+ * regardless of whether the developer wrote .title or .name, .cost or .price, .image or .imageUrl.
694
+ */
695
+ function normalizeProductItem(p) {
696
+ if (!isRecord(p))
697
+ return p;
698
+ const customData = isRecord(p.customData) ? p.customData : {};
699
+ const nameCandidate = p.name ?? p.title ?? p.productName ?? p.itemTitle ?? p.heading ?? p.label;
700
+ const titleCandidate = p.title ?? p.name ?? p.itemTitle ?? p.productName ?? p.heading ?? p.label;
701
+ const rawPrice = p.price !== undefined
702
+ ? p.price
703
+ : p.cost !== undefined
704
+ ? p.cost
705
+ : p.amount !== undefined
706
+ ? p.amount
707
+ : p.productPrice !== undefined
708
+ ? p.productPrice
709
+ : p.currentPrice !== undefined
710
+ ? p.currentPrice
711
+ : p.rate;
712
+ let numPrice = rawPrice;
713
+ if (typeof rawPrice === "string" && rawPrice.trim()) {
714
+ const match = rawPrice.match(/-?\d+(?:,\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?/);
715
+ if (match) {
716
+ const parsed = parseFloat(match[0].replace(/,/g, ""));
717
+ if (!isNaN(parsed) && Number.isFinite(parsed)) {
718
+ numPrice = parsed;
719
+ }
720
+ }
721
+ }
722
+ const rawCompare = p.compareAtPrice !== undefined
723
+ ? p.compareAtPrice
724
+ : p.originalPrice !== undefined
725
+ ? p.originalPrice
726
+ : p.oldPrice !== undefined
727
+ ? p.oldPrice
728
+ : p.regularPrice !== undefined
729
+ ? p.regularPrice
730
+ : p.strikePrice;
731
+ let numCompare = rawCompare;
732
+ if (typeof rawCompare === "string" && rawCompare.trim()) {
733
+ const match = rawCompare.match(/-?\d+(?:,\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?/);
734
+ if (match) {
735
+ const parsed = parseFloat(match[0].replace(/,/g, ""));
736
+ if (!isNaN(parsed) && Number.isFinite(parsed)) {
737
+ numCompare = parsed;
738
+ }
739
+ }
740
+ }
741
+ const imageCandidate = p.imageUrl ??
742
+ p.image ??
743
+ p.photo ??
744
+ p.thumbnail ??
745
+ p.picture ??
746
+ p.img ??
747
+ p.src ??
748
+ p.productImage ??
749
+ (Array.isArray(p.gallery) ? p.gallery[0] : undefined) ??
750
+ (Array.isArray(p.images) ? p.images[0] : undefined);
751
+ const badgeCandidate = p.badge ?? p.tag ?? p.label ?? p.badgeText ?? p.chip;
752
+ const descCandidate = p.description ?? p.desc ?? p.details ?? p.shortDescription ?? p.subtitle;
753
+ return {
754
+ ...customData,
755
+ ...p,
756
+ name: nameCandidate,
757
+ title: titleCandidate,
758
+ productName: (titleCandidate ?? nameCandidate),
759
+ itemTitle: (titleCandidate ?? nameCandidate),
760
+ ...(numPrice !== undefined
761
+ ? {
762
+ price: numPrice,
763
+ cost: numPrice,
764
+ amount: numPrice,
765
+ productPrice: numPrice,
766
+ }
767
+ : {}),
768
+ ...(numCompare !== undefined
769
+ ? {
770
+ compareAtPrice: numCompare,
771
+ originalPrice: numCompare,
772
+ }
773
+ : {}),
774
+ ...(imageCandidate !== undefined
775
+ ? {
776
+ imageUrl: imageCandidate,
777
+ image: imageCandidate,
778
+ photo: imageCandidate,
779
+ thumbnail: imageCandidate,
780
+ productImage: imageCandidate,
781
+ }
782
+ : {}),
783
+ ...(badgeCandidate !== undefined
784
+ ? {
785
+ badge: badgeCandidate,
786
+ tag: badgeCandidate,
787
+ }
788
+ : {}),
789
+ ...(descCandidate !== undefined
790
+ ? {
791
+ description: descCandidate,
792
+ desc: descCandidate,
793
+ details: descCandidate,
794
+ }
795
+ : {}),
796
+ };
797
+ }
798
+ /**
799
+ * Universal Service Normalizer
800
+ */
801
+ function normalizeServiceItem(s) {
802
+ if (!isRecord(s))
803
+ return s;
804
+ const customData = isRecord(s.customData) ? s.customData : {};
805
+ const nameCandidate = s.name ?? s.title ?? s.serviceName ?? s.heading;
806
+ const titleCandidate = s.title ?? s.name ?? s.serviceName ?? s.heading;
807
+ const descCandidate = s.description ?? s.desc ?? s.details ?? s.shortDescription;
808
+ const imageCandidate = s.imageUrl ?? s.image ?? s.photo ?? s.thumbnail ?? s.picture;
809
+ return {
810
+ ...customData,
811
+ ...s,
812
+ name: nameCandidate,
813
+ title: titleCandidate,
814
+ ...(descCandidate !== undefined ? { description: descCandidate, desc: descCandidate, details: descCandidate } : {}),
815
+ ...(imageCandidate !== undefined ? { imageUrl: imageCandidate, image: imageCandidate, photo: imageCandidate } : {}),
816
+ };
817
+ }
684
818
  function useProducts(fallback = []) {
685
819
  const siteData = useSiteData();
686
820
  const content = isRecord(siteData?.content) ? siteData.content : null;
687
821
  if (!content)
688
822
  return fallback;
689
823
  let rawList = null;
690
- if (Array.isArray(content.products) && content.products.length > 0) {
691
- rawList = content.products;
692
- }
693
- else {
694
- const home = isRecord(content.home) ? content.home : null;
695
- if (home) {
696
- if (Array.isArray(home.products) && home.products.length > 0) {
697
- rawList = home.products;
698
- }
699
- else if (Array.isArray(home.featuredProducts) && home.featuredProducts.length > 0) {
700
- rawList = home.featuredProducts;
701
- }
824
+ const candidates = [
825
+ content.products,
826
+ content.shop?.products,
827
+ content.home?.products,
828
+ content.home?.featuredProducts,
829
+ content.catalog?.products,
830
+ content.menu?.items,
831
+ content.menu?.products,
832
+ content.store?.products,
833
+ content.shop?.items,
834
+ siteData.shop?.products,
835
+ ];
836
+ for (const candidate of candidates) {
837
+ if (Array.isArray(candidate) && candidate.length > 0) {
838
+ rawList = candidate;
839
+ break;
702
840
  }
703
841
  }
704
842
  if (!rawList)
705
843
  return fallback;
706
- return rawList.map((p) => {
707
- if (!isRecord(p))
708
- return p;
709
- const customData = isRecord(p.customData) ? p.customData : {};
710
- return {
711
- ...customData,
712
- ...p,
713
- };
714
- });
844
+ return rawList.map(normalizeProductItem);
715
845
  }
716
846
  /**
717
847
  * Hook to retrieve services cleanly from SiteData, supporting both
@@ -722,16 +852,17 @@ function useServices(fallback = []) {
722
852
  const content = isRecord(siteData?.content) ? siteData.content : null;
723
853
  if (!content)
724
854
  return fallback;
725
- if (Array.isArray(content.services) && content.services.length > 0) {
726
- return content.services;
727
- }
728
- const home = isRecord(content.home) ? content.home : null;
729
- if (home) {
730
- if (Array.isArray(home.services) && home.services.length > 0) {
731
- return home.services;
732
- }
733
- if (Array.isArray(home.featuredServices) && home.featuredServices.length > 0) {
734
- return home.featuredServices;
855
+ const candidates = [
856
+ content.services,
857
+ content.servicesPage?.services,
858
+ content.home?.services,
859
+ content.home?.featuredServices,
860
+ content.company?.services,
861
+ content.business?.services,
862
+ ];
863
+ for (const candidate of candidates) {
864
+ if (Array.isArray(candidate) && candidate.length > 0) {
865
+ return candidate.map(normalizeServiceItem);
735
866
  }
736
867
  }
737
868
  return fallback;
@@ -12,6 +12,7 @@ export interface CartItem {
12
12
  sku?: string;
13
13
  metadata?: Record<string, unknown>;
14
14
  }
15
+ export declare function parseNumericPrice(value: unknown): number;
15
16
  export declare function formatCurrency(amount: number, currency?: string): string;
16
17
  export declare function getProductWhatsAppUrl(product: {
17
18
  name?: string;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  'use client';
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.parseNumericPrice = parseNumericPrice;
4
5
  exports.formatCurrency = formatCurrency;
5
6
  exports.getProductWhatsAppUrl = getProductWhatsAppUrl;
6
7
  exports.CartProvider = CartProvider;
@@ -8,6 +9,17 @@ exports.useOptionalCart = useOptionalCart;
8
9
  exports.useCart = useCart;
9
10
  const jsx_runtime_1 = require("react/jsx-runtime");
10
11
  const react_1 = require("react");
12
+ function parseNumericPrice(value) {
13
+ if (typeof value === "number" && Number.isFinite(value))
14
+ return value;
15
+ if (typeof value !== "string" || !value.trim())
16
+ return 0;
17
+ const match = value.match(/-?\d+(?:,\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?/);
18
+ if (!match)
19
+ return 0;
20
+ const parsed = parseFloat(match[0].replace(/,/g, ""));
21
+ return Number.isFinite(parsed) ? parsed : 0;
22
+ }
11
23
  function formatCurrency(amount, currency = 'LKR') {
12
24
  const formatted = (amount || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
13
25
  if (currency === 'LKR' || currency === 'Rs.' || currency === 'Rs') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.86",
3
+ "version": "2.0.87",
4
4
  "description": "Visual-first React component library for editable commerce storefronts. Built for Next.js and Fivora.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  ],
51
51
  "license": "MIT",
52
52
  "dependencies": {
53
- "@deneb-ui/core": "^2.0.86"
53
+ "@deneb-ui/core": "^2.0.87"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",