@deneb-ui/ui 2.0.126 → 2.0.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/EditableProductCard.d.ts +1 -0
- package/dist/EditableProductCard.js +2 -8
- package/dist/EditableProductDetail.d.ts +1 -0
- package/dist/EditableProductDetail.js +6 -8
- package/dist/EditableServiceCard.d.ts +3 -0
- package/dist/EditableServiceCard.js +3 -1
- package/dist/EditableUniversalCard.d.ts +5 -3
- package/dist/PlatformProductDetail.js +3 -8
- package/dist/ProductQuickView.d.ts +2 -0
- package/dist/ProductQuickView.js +3 -2
- package/dist/SiteDataProvider.d.ts +1 -0
- package/dist/SiteDataProvider.js +87 -3
- package/dist/utils/catalogMedia.d.ts +18 -0
- package/dist/utils/catalogMedia.js +43 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -637,6 +637,12 @@ export function PromoSection() {
|
|
|
637
637
|
|
|
638
638
|
DENEB UI commerce components natively support both **Single Fixed Prices** and **Dynamic Price Ranges**, as well as apparel clothing options (color swatches with image swapping and size chips).
|
|
639
639
|
|
|
640
|
+
Fivora uses one canonical catalog contract: `ProductCard` is the commerce
|
|
641
|
+
renderer, `ServiceCard` is the service/quote renderer, and `UniversalCard` is
|
|
642
|
+
reserved for other generic entity listings. Keep `imageUrl` as the primary
|
|
643
|
+
image and `additionalImages` as the ordered gallery; DENEB normalizes them to
|
|
644
|
+
`images`/`gallery` for detail and quick-view components.
|
|
645
|
+
|
|
640
646
|
Keep `price`, `compareAtPrice`, `minPrice`, `maxPrice`, and variant prices as
|
|
641
647
|
JSON numbers. Keep `currency` or a currency symbol as separate text. A formatted
|
|
642
648
|
`priceRange` string is display-only; numeric filters and sorting must use the
|
|
@@ -653,7 +659,8 @@ import { ProductCard } from "@deneb-ui/ui";
|
|
|
653
659
|
price: 3500,
|
|
654
660
|
compareAtPrice: 4500,
|
|
655
661
|
currency: "LKR",
|
|
656
|
-
imageUrl: "/images/shirt.jpg"
|
|
662
|
+
imageUrl: "/images/shirt.jpg",
|
|
663
|
+
additionalImages: ["/images/shirt-back.jpg", "/images/shirt-detail.jpg"]
|
|
657
664
|
}}
|
|
658
665
|
itemPath="products[0]"
|
|
659
666
|
/>
|
|
@@ -673,6 +680,7 @@ import { ProductCard } from "@deneb-ui/ui";
|
|
|
673
680
|
isPriceRange: true,
|
|
674
681
|
currency: "LKR",
|
|
675
682
|
imageUrl: "/images/tunic-navy.jpg",
|
|
683
|
+
additionalImages: ["/images/tunic-back.jpg", "/images/tunic-detail.jpg"],
|
|
676
684
|
colors: [
|
|
677
685
|
{ name: "Navy Blue", hex: "#1e3a8a", imageUrl: "/images/tunic-navy.jpg" },
|
|
678
686
|
{ name: "Olive Green", hex: "#3f6212", imageUrl: "/images/tunic-olive.jpg" },
|
|
@@ -9,6 +9,7 @@ const EditableImage_1 = require("./EditableImage");
|
|
|
9
9
|
const useCart_1 = require("./cart/useCart");
|
|
10
10
|
const productOptions_1 = require("./utils/productOptions");
|
|
11
11
|
const PlatformProductDetail_1 = require("./PlatformProductDetail");
|
|
12
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
12
13
|
const COLOR_HEX_MAP = {
|
|
13
14
|
black: '#111827',
|
|
14
15
|
white: '#ffffff',
|
|
@@ -166,14 +167,7 @@ function EditableProductCard({ itemPath, product: productProp, item: itemProp, c
|
|
|
166
167
|
const badge = String(product?.badge || product?.tag || '');
|
|
167
168
|
const hasBadgeField = Boolean(product && ('badge' in product || 'tag' in product));
|
|
168
169
|
const fallbackImage = imageFallback || 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=800&q=80';
|
|
169
|
-
const
|
|
170
|
-
product?.image ||
|
|
171
|
-
product?.photo ||
|
|
172
|
-
product?.thumbnail ||
|
|
173
|
-
product?.productImage ||
|
|
174
|
-
(Array.isArray(product?.gallery) && product.gallery[0]) ||
|
|
175
|
-
(Array.isArray(product?.images) && product.images[0]);
|
|
176
|
-
const baseImageUrl = String(rawImage || fallbackImage);
|
|
170
|
+
const baseImageUrl = (0, catalogMedia_1.resolveCatalogPrimaryImage)(product, fallbackImage);
|
|
177
171
|
// Active image: clicked color image -> resolved variant image -> product base image
|
|
178
172
|
const currentImage = colorImage || resolvedPrice.variantImage || baseImageUrl;
|
|
179
173
|
const imageKey = 'image' in (product || {}) && !('imageUrl' in (product || {}) && product.imageUrl)
|
|
@@ -9,6 +9,7 @@ const react_1 = require("react");
|
|
|
9
9
|
const utils_1 = require("./utils");
|
|
10
10
|
const productOptions_1 = require("./utils/productOptions");
|
|
11
11
|
const urls_1 = require("./utils/urls");
|
|
12
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
12
13
|
/**
|
|
13
14
|
* EditableProductDetail is an elite, fully calibrated Single Product View component
|
|
14
15
|
* engineered for high-conversion commerce and 100% compliant with Fivora Visual Editing.
|
|
@@ -28,14 +29,11 @@ function EditableProductDetail({ sectionPath = 'product', product, sizes, colors
|
|
|
28
29
|
const productRecord = (product || {});
|
|
29
30
|
const productPhoto = productRecord.photo;
|
|
30
31
|
const productThumbnail = productRecord.thumbnail;
|
|
31
|
-
const validImages = (0, react_1.useMemo)(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
: [product.featuredImage || product.imageUrl || product.image || productPhoto || productThumbnail || '/products/vanta-aero-x.jpg'];
|
|
37
|
-
return rawList.filter(Boolean);
|
|
38
|
-
}, [product.gallery, product.images, product.featuredImage, product.imageUrl, product.image, productPhoto, productThumbnail]);
|
|
32
|
+
const validImages = (0, react_1.useMemo)(() => (0, catalogMedia_1.resolveCatalogImages)({
|
|
33
|
+
...product,
|
|
34
|
+
photo: productPhoto,
|
|
35
|
+
thumbnail: productThumbnail,
|
|
36
|
+
}, '/products/vanta-aero-x.jpg'), [product, productPhoto, productThumbnail]);
|
|
39
37
|
// 2. Resolve Options (Sizes, Volumes, Weights, Counts)
|
|
40
38
|
const resolved = (0, productOptions_1.resolveProductOptions)(product);
|
|
41
39
|
const effectiveOptions = (0, react_1.useMemo)(() => {
|
|
@@ -4,6 +4,7 @@ exports.EditableServiceCard = EditableServiceCard;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const EditableText_1 = require("./EditableText");
|
|
6
6
|
const EditableImage_1 = require("./EditableImage");
|
|
7
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
7
8
|
/**
|
|
8
9
|
* EditableServiceCard standardizes service presentation across Home and Services pages.
|
|
9
10
|
* Supports service name, description, image, and repeatable features checklist.
|
|
@@ -28,7 +29,8 @@ function EditableServiceCard({ itemPath, service, imageFallback = '/placeholder.
|
|
|
28
29
|
? String(rawPriceCandidate)
|
|
29
30
|
: 'LKR ' + String(rawPriceCandidate)
|
|
30
31
|
: '';
|
|
31
|
-
const
|
|
32
|
+
const serviceImages = (0, catalogMedia_1.resolveCatalogImages)(service);
|
|
33
|
+
const imageUrl = serviceImages[0] || '';
|
|
32
34
|
const features = Array.isArray(service?.features) ? service.features : [];
|
|
33
35
|
const mergedStyle = {
|
|
34
36
|
textAlign: align,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* EditableUniversalCard — DENEB UI Framework
|
|
3
3
|
*
|
|
4
|
-
* Universal, multi-industry listing
|
|
4
|
+
* Universal, multi-industry listing renderer for non-catalog entities. Fivora
|
|
5
|
+
* products and services should use EditableProductCard/EditableServiceCard so
|
|
6
|
+
* their backend catalog behavior is preserved:
|
|
5
7
|
* - 🚗 Automotive & Equipment: Make/model, year & condition, mileage, fuel, gearbox, price
|
|
6
8
|
* - 📚 Bookshops & Publishers: Cover photo, book title, author, published year, pages, price
|
|
7
9
|
* - 🏋️ Gyms & Fitness: Membership tier, duration, meal plan, 24/7 access, trainer, price
|
|
8
|
-
* - 🍽️
|
|
10
|
+
* - 🍽️ Editorial Menus: Dish photo, title, dietary tags, ingredients, prep time, price
|
|
9
11
|
* - 🏠 Real Estate & Rentals: Property image, location, beds, baths, sqft, price
|
|
10
|
-
* - 💼
|
|
12
|
+
* - 💼 Agency Portfolios: Offering title, duration, deliverable checklist, pricing
|
|
11
13
|
*
|
|
12
14
|
* Designed with strict editability architecture:
|
|
13
15
|
* 1. Automatically resolves fields from `useSiteData()` via `itemPath` or explicit field paths.
|
|
@@ -9,6 +9,7 @@ const react_1 = require("react");
|
|
|
9
9
|
const EditableProductDetail_1 = require("./EditableProductDetail");
|
|
10
10
|
const SiteDataProvider_1 = require("./SiteDataProvider");
|
|
11
11
|
const utils_1 = require("./utils");
|
|
12
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
12
13
|
const DEFAULT_QUERY_PARAM = 'id';
|
|
13
14
|
const DEFAULT_DETAIL_ROUTE = '/products/detail/';
|
|
14
15
|
const DEFAULT_RETRY_DELAYS = [0, 750, 2_000];
|
|
@@ -93,13 +94,7 @@ function normalizeProductForDetail(product, siteData) {
|
|
|
93
94
|
(typeof mergedProduct.thumbnail === 'string' && mergedProduct.thumbnail) ||
|
|
94
95
|
undefined;
|
|
95
96
|
const price = mergedProduct.price ?? mergedProduct.cost ?? mergedProduct.amount ?? mergedProduct.productPrice;
|
|
96
|
-
const images =
|
|
97
|
-
? mergedProduct.gallery
|
|
98
|
-
: Array.isArray(mergedProduct.images)
|
|
99
|
-
? mergedProduct.images
|
|
100
|
-
: Array.isArray(mergedProduct.additionalImages)
|
|
101
|
-
? mergedProduct.additionalImages.filter((value) => typeof value === 'string' && Boolean(value.trim()))
|
|
102
|
-
: undefined;
|
|
97
|
+
const images = (0, catalogMedia_1.resolveCatalogImages)(mergedProduct);
|
|
103
98
|
const compareAtPrice = mergedProduct.compareAtPrice ?? mergedProduct.originalPrice;
|
|
104
99
|
const shop = (0, SiteDataProvider_1.isRecord)(siteData.shop) ? siteData.shop : {};
|
|
105
100
|
const merchant = (0, SiteDataProvider_1.isRecord)(siteData.merchant) ? siteData.merchant : {};
|
|
@@ -122,7 +117,7 @@ function normalizeProductForDetail(product, siteData) {
|
|
|
122
117
|
? { featuredImage: image, imageUrl: image, image }
|
|
123
118
|
: {}),
|
|
124
119
|
...(price !== undefined ? { price } : {}),
|
|
125
|
-
...(images ? { gallery: images, images } : {}),
|
|
120
|
+
...(images.length > 0 ? { gallery: images, images } : {}),
|
|
126
121
|
...(compareAtPrice !== undefined
|
|
127
122
|
? { compareAtPrice, originalPrice: compareAtPrice }
|
|
128
123
|
: {}),
|
package/dist/ProductQuickView.js
CHANGED
|
@@ -5,6 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
7
|
const PlatformProductDetail_1 = require("./PlatformProductDetail");
|
|
8
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
8
9
|
/**
|
|
9
10
|
* DENEB UI — Product Quick View Modal
|
|
10
11
|
*
|
|
@@ -20,7 +21,7 @@ function ProductQuickView({ product, isOpen, onClose, onAddToCart, className = '
|
|
|
20
21
|
(0, react_1.useEffect)(() => {
|
|
21
22
|
if (product) {
|
|
22
23
|
setQuantity(1);
|
|
23
|
-
setSelectedImage(
|
|
24
|
+
setSelectedImage((0, catalogMedia_1.resolveCatalogImages)(product)[0] || '');
|
|
24
25
|
}
|
|
25
26
|
}, [product]);
|
|
26
27
|
// Handle ESC key to close
|
|
@@ -42,7 +43,7 @@ function ProductQuickView({ product, isOpen, onClose, onAddToCart, className = '
|
|
|
42
43
|
if (!isOpen || !product) {
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
45
|
-
const images =
|
|
46
|
+
const images = (0, catalogMedia_1.resolveCatalogImages)(product);
|
|
46
47
|
const currency = product.currency || '$';
|
|
47
48
|
const isAvailable = product.isAvailable !== false && product.inStock !== false;
|
|
48
49
|
const handleAddToCart = () => {
|
|
@@ -11,6 +11,7 @@ export declare const LEGACY_PREVIEW_READY_MESSAGE: string;
|
|
|
11
11
|
export declare const DENEB_PREVIEW_FOCUS_MESSAGE = "DENEB_PREVIEW_FOCUS_PAGE";
|
|
12
12
|
export declare const PREVIEW_FOCUS_MESSAGE = "FIVORA_PREVIEW_FOCUS_PAGE";
|
|
13
13
|
export declare const LEGACY_PREVIEW_FOCUS_MESSAGE: string;
|
|
14
|
+
export declare const DENEB_REFRESH_LIVE_CATALOG_EVENT = "deneb:refresh-live-catalog";
|
|
14
15
|
export declare const PREVIEW_FIELD_ATTRIBUTE = "data-preview-field-path";
|
|
15
16
|
export { STYLE_PATCH_MESSAGE, DENEB_STYLE_PATCH_MESSAGE } from '@deneb-ui/core';
|
|
16
17
|
export type GenericRecord = Record<string, any>;
|
package/dist/SiteDataProvider.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
'use client';
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.DUMMY_PREVIEW_SHOP_NAME = exports.DUMMY_PREVIEW_ADDRESS = exports.DUMMY_PREVIEW_EMAIL = exports.DUMMY_PREVIEW_WHATSAPP = exports.DUMMY_PREVIEW_PHONE = exports.DenebDataContext = exports.useDenebData = exports.DenebDataProvider = exports.resolveAsset = exports.withBasePath = exports.SiteDataContext = exports.DENEB_STYLE_PATCH_MESSAGE = exports.STYLE_PATCH_MESSAGE = exports.PREVIEW_FIELD_ATTRIBUTE = exports.LEGACY_PREVIEW_FOCUS_MESSAGE = exports.PREVIEW_FOCUS_MESSAGE = exports.DENEB_PREVIEW_FOCUS_MESSAGE = exports.LEGACY_PREVIEW_READY_MESSAGE = exports.PREVIEW_READY_MESSAGE = exports.DENEB_PREVIEW_READY_MESSAGE = exports.LEGACY_PREVIEW_DATA_MESSAGE = exports.PREVIEW_DATA_MESSAGE = exports.DENEB_PREVIEW_DATA_MESSAGE = void 0;
|
|
4
|
+
exports.DUMMY_PREVIEW_SHOP_NAME = exports.DUMMY_PREVIEW_ADDRESS = exports.DUMMY_PREVIEW_EMAIL = exports.DUMMY_PREVIEW_WHATSAPP = exports.DUMMY_PREVIEW_PHONE = exports.DenebDataContext = exports.useDenebData = exports.DenebDataProvider = exports.resolveAsset = exports.withBasePath = exports.SiteDataContext = exports.DENEB_STYLE_PATCH_MESSAGE = exports.STYLE_PATCH_MESSAGE = exports.PREVIEW_FIELD_ATTRIBUTE = exports.DENEB_REFRESH_LIVE_CATALOG_EVENT = exports.LEGACY_PREVIEW_FOCUS_MESSAGE = exports.PREVIEW_FOCUS_MESSAGE = exports.DENEB_PREVIEW_FOCUS_MESSAGE = exports.LEGACY_PREVIEW_READY_MESSAGE = exports.PREVIEW_READY_MESSAGE = exports.DENEB_PREVIEW_READY_MESSAGE = exports.LEGACY_PREVIEW_DATA_MESSAGE = exports.PREVIEW_DATA_MESSAGE = exports.DENEB_PREVIEW_DATA_MESSAGE = void 0;
|
|
5
5
|
exports.isRecord = isRecord;
|
|
6
6
|
exports.isDarkColor = isDarkColor;
|
|
7
7
|
exports.syncThemeToDocument = syncThemeToDocument;
|
|
@@ -43,6 +43,7 @@ const ThemeStyles_1 = require("./ThemeStyles");
|
|
|
43
43
|
const utils_1 = require("./utils");
|
|
44
44
|
Object.defineProperty(exports, "withBasePath", { enumerable: true, get: function () { return utils_1.withBasePath; } });
|
|
45
45
|
Object.defineProperty(exports, "resolveAsset", { enumerable: true, get: function () { return utils_1.resolveAsset; } });
|
|
46
|
+
const catalogMedia_1 = require("./utils/catalogMedia");
|
|
46
47
|
exports.DENEB_PREVIEW_DATA_MESSAGE = 'DENEB_PREVIEW_SITE_DATA';
|
|
47
48
|
exports.PREVIEW_DATA_MESSAGE = 'FIVORA_PREVIEW_SITE_DATA';
|
|
48
49
|
const PREVIOUS_PREVIEW_PREFIX = `${['MARKET', 'PLACE'].join('')}_PREVIEW_`;
|
|
@@ -55,6 +56,7 @@ exports.LEGACY_PREVIEW_READY_MESSAGE = previousPreviewMessage('READY');
|
|
|
55
56
|
exports.DENEB_PREVIEW_FOCUS_MESSAGE = 'DENEB_PREVIEW_FOCUS_PAGE';
|
|
56
57
|
exports.PREVIEW_FOCUS_MESSAGE = 'FIVORA_PREVIEW_FOCUS_PAGE';
|
|
57
58
|
exports.LEGACY_PREVIEW_FOCUS_MESSAGE = previousPreviewMessage('FOCUS_PAGE');
|
|
59
|
+
exports.DENEB_REFRESH_LIVE_CATALOG_EVENT = 'deneb:refresh-live-catalog';
|
|
58
60
|
exports.PREVIEW_FIELD_ATTRIBUTE = 'data-preview-field-path';
|
|
59
61
|
var core_2 = require("@deneb-ui/core");
|
|
60
62
|
Object.defineProperty(exports, "STYLE_PATCH_MESSAGE", { enumerable: true, get: function () { return core_2.STYLE_PATCH_MESSAGE; } });
|
|
@@ -366,6 +368,9 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
|
|
|
366
368
|
}
|
|
367
369
|
return {
|
|
368
370
|
...current,
|
|
371
|
+
liveCatalogUpdatedAt: typeof live.updatedAt === 'string'
|
|
372
|
+
? live.updatedAt
|
|
373
|
+
: new Date().toISOString(),
|
|
369
374
|
shop: nextShop,
|
|
370
375
|
merchant: nextMerchant,
|
|
371
376
|
reviews: nextReviews ?? current.reviews,
|
|
@@ -388,6 +393,36 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
|
|
|
388
393
|
...(currentContent?.store && typeof currentContent.store === 'object' && nextProducts !== undefined
|
|
389
394
|
? { store: { ...currentContent.store, products: nextProducts } }
|
|
390
395
|
: {}),
|
|
396
|
+
...(currentContent?.servicesPage &&
|
|
397
|
+
typeof currentContent.servicesPage === 'object' &&
|
|
398
|
+
nextServices !== undefined
|
|
399
|
+
? {
|
|
400
|
+
servicesPage: {
|
|
401
|
+
...currentContent.servicesPage,
|
|
402
|
+
services: nextServices,
|
|
403
|
+
},
|
|
404
|
+
}
|
|
405
|
+
: {}),
|
|
406
|
+
...(currentContent?.company &&
|
|
407
|
+
typeof currentContent.company === 'object' &&
|
|
408
|
+
nextServices !== undefined
|
|
409
|
+
? {
|
|
410
|
+
company: {
|
|
411
|
+
...currentContent.company,
|
|
412
|
+
services: nextServices,
|
|
413
|
+
},
|
|
414
|
+
}
|
|
415
|
+
: {}),
|
|
416
|
+
...(currentContent?.business &&
|
|
417
|
+
typeof currentContent.business === 'object' &&
|
|
418
|
+
nextServices !== undefined
|
|
419
|
+
? {
|
|
420
|
+
business: {
|
|
421
|
+
...currentContent.business,
|
|
422
|
+
services: nextServices,
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
: {}),
|
|
391
426
|
...(currentHome || Object.keys(nextHome).length > 0
|
|
392
427
|
? {
|
|
393
428
|
home: {
|
|
@@ -458,9 +493,19 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
|
|
|
458
493
|
void loadLiveCatalog();
|
|
459
494
|
}
|
|
460
495
|
};
|
|
496
|
+
const refreshNow = () => {
|
|
497
|
+
if (document.visibilityState !== 'visible')
|
|
498
|
+
return;
|
|
499
|
+
if (retryTimer !== null)
|
|
500
|
+
window.clearTimeout(retryTimer);
|
|
501
|
+
retryTimer = null;
|
|
502
|
+
void loadLiveCatalog();
|
|
503
|
+
};
|
|
461
504
|
void loadLiveCatalog();
|
|
462
505
|
window.addEventListener("focus", refreshIfStale);
|
|
463
506
|
window.addEventListener("online", refreshIfStale);
|
|
507
|
+
window.addEventListener('pageshow', refreshIfStale);
|
|
508
|
+
window.addEventListener(exports.DENEB_REFRESH_LIVE_CATALOG_EVENT, refreshNow);
|
|
464
509
|
document.addEventListener("visibilitychange", refreshIfStale);
|
|
465
510
|
return () => {
|
|
466
511
|
stopped = true;
|
|
@@ -469,6 +514,8 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
|
|
|
469
514
|
window.clearTimeout(retryTimer);
|
|
470
515
|
window.removeEventListener("focus", refreshIfStale);
|
|
471
516
|
window.removeEventListener("online", refreshIfStale);
|
|
517
|
+
window.removeEventListener('pageshow', refreshIfStale);
|
|
518
|
+
window.removeEventListener(exports.DENEB_REFRESH_LIVE_CATALOG_EVENT, refreshNow);
|
|
472
519
|
document.removeEventListener("visibilitychange", refreshIfStale);
|
|
473
520
|
};
|
|
474
521
|
}, [liveCatalogEndpoint, siteSlug, initialSiteData]);
|
|
@@ -875,7 +922,9 @@ function normalizeProductItem(p) {
|
|
|
875
922
|
p.src ??
|
|
876
923
|
p.productImage ??
|
|
877
924
|
(Array.isArray(p.gallery) ? p.gallery[0] : undefined) ??
|
|
878
|
-
(Array.isArray(p.images) ? p.images[0] : undefined)
|
|
925
|
+
(Array.isArray(p.images) ? p.images[0] : undefined) ??
|
|
926
|
+
(Array.isArray(p.additionalImages) ? p.additionalImages[0] : undefined);
|
|
927
|
+
const catalogImages = (0, catalogMedia_1.resolveCatalogImages)(p);
|
|
879
928
|
const badgeCandidate = p.badge ?? p.tag ?? p.label ?? p.badgeText ?? p.chip;
|
|
880
929
|
const descCandidate = p.description ?? p.desc ?? p.details ?? p.shortDescription ?? p.subtitle;
|
|
881
930
|
const explicitMin = p.minPrice ?? customData.minPrice;
|
|
@@ -918,6 +967,13 @@ function normalizeProductItem(p) {
|
|
|
918
967
|
productImage: imageCandidate,
|
|
919
968
|
}
|
|
920
969
|
: {}),
|
|
970
|
+
...(catalogImages.length > 0
|
|
971
|
+
? {
|
|
972
|
+
gallery: catalogImages,
|
|
973
|
+
images: catalogImages,
|
|
974
|
+
additionalImages: catalogImages.slice(1),
|
|
975
|
+
}
|
|
976
|
+
: {}),
|
|
921
977
|
...(badgeCandidate !== undefined
|
|
922
978
|
? {
|
|
923
979
|
badge: badgeCandidate,
|
|
@@ -943,7 +999,15 @@ function normalizeServiceItem(s) {
|
|
|
943
999
|
const nameCandidate = s.name ?? s.title ?? s.serviceName ?? s.heading;
|
|
944
1000
|
const titleCandidate = s.title ?? s.name ?? s.serviceName ?? s.heading;
|
|
945
1001
|
const descCandidate = s.description ?? s.desc ?? s.details ?? s.shortDescription;
|
|
946
|
-
const imageCandidate = s.imageUrl ??
|
|
1002
|
+
const imageCandidate = s.imageUrl ??
|
|
1003
|
+
s.image ??
|
|
1004
|
+
s.photo ??
|
|
1005
|
+
s.thumbnail ??
|
|
1006
|
+
s.picture ??
|
|
1007
|
+
(Array.isArray(s.gallery) ? s.gallery[0] : undefined) ??
|
|
1008
|
+
(Array.isArray(s.images) ? s.images[0] : undefined) ??
|
|
1009
|
+
(Array.isArray(s.additionalImages) ? s.additionalImages[0] : undefined);
|
|
1010
|
+
const catalogImages = (0, catalogMedia_1.resolveCatalogImages)(s);
|
|
947
1011
|
return {
|
|
948
1012
|
...customData,
|
|
949
1013
|
...s,
|
|
@@ -951,6 +1015,13 @@ function normalizeServiceItem(s) {
|
|
|
951
1015
|
title: titleCandidate,
|
|
952
1016
|
...(descCandidate !== undefined ? { description: descCandidate, desc: descCandidate, details: descCandidate } : {}),
|
|
953
1017
|
...(imageCandidate !== undefined ? { imageUrl: imageCandidate, image: imageCandidate, photo: imageCandidate } : {}),
|
|
1018
|
+
...(catalogImages.length > 0
|
|
1019
|
+
? {
|
|
1020
|
+
gallery: catalogImages,
|
|
1021
|
+
images: catalogImages,
|
|
1022
|
+
additionalImages: catalogImages.slice(1),
|
|
1023
|
+
}
|
|
1024
|
+
: {}),
|
|
954
1025
|
};
|
|
955
1026
|
}
|
|
956
1027
|
function useProducts(fallback = []) {
|
|
@@ -958,6 +1029,13 @@ function useProducts(fallback = []) {
|
|
|
958
1029
|
const content = isRecord(siteData?.content) ? siteData.content : null;
|
|
959
1030
|
if (!content)
|
|
960
1031
|
return fallback;
|
|
1032
|
+
// Once the public catalog endpoint has answered, its top-level collection is
|
|
1033
|
+
// authoritative even when it is empty. This prevents deleted products from
|
|
1034
|
+
// falling back to stale products embedded in the generated site bundle.
|
|
1035
|
+
if (typeof siteData.liveCatalogUpdatedAt === 'string' &&
|
|
1036
|
+
Array.isArray(content.products)) {
|
|
1037
|
+
return content.products.map(normalizeProductItem);
|
|
1038
|
+
}
|
|
961
1039
|
let rawList = null;
|
|
962
1040
|
const candidates = [
|
|
963
1041
|
content.products,
|
|
@@ -990,6 +1068,12 @@ function useServices(fallback = []) {
|
|
|
990
1068
|
const content = isRecord(siteData?.content) ? siteData.content : null;
|
|
991
1069
|
if (!content)
|
|
992
1070
|
return fallback;
|
|
1071
|
+
// An empty live response is meaningful: all services were removed. Do not
|
|
1072
|
+
// resurrect build-time services from a nested legacy collection or fallback.
|
|
1073
|
+
if (typeof siteData.liveCatalogUpdatedAt === 'string' &&
|
|
1074
|
+
Array.isArray(content.services)) {
|
|
1075
|
+
return content.services.map(normalizeServiceItem);
|
|
1076
|
+
}
|
|
993
1077
|
const candidates = [
|
|
994
1078
|
content.services,
|
|
995
1079
|
content.servicesPage?.services,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CatalogMediaItem {
|
|
2
|
+
featuredImage?: unknown;
|
|
3
|
+
imageUrl?: unknown;
|
|
4
|
+
image?: unknown;
|
|
5
|
+
photo?: unknown;
|
|
6
|
+
thumbnail?: unknown;
|
|
7
|
+
productImage?: unknown;
|
|
8
|
+
gallery?: unknown;
|
|
9
|
+
images?: unknown;
|
|
10
|
+
additionalImages?: unknown;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves Fivora's catalog media contract while retaining legacy template
|
|
15
|
+
* aliases. The primary image stays first and duplicate URLs are removed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveCatalogImages(item?: CatalogMediaItem | null, fallbackImage?: string): string[];
|
|
18
|
+
export declare function resolveCatalogPrimaryImage(item?: CatalogMediaItem | null, fallbackImage?: string): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCatalogImages = resolveCatalogImages;
|
|
4
|
+
exports.resolveCatalogPrimaryImage = resolveCatalogPrimaryImage;
|
|
5
|
+
function appendImage(images, seen, candidate) {
|
|
6
|
+
if (typeof candidate !== 'string')
|
|
7
|
+
return;
|
|
8
|
+
const normalized = candidate.trim();
|
|
9
|
+
if (!normalized || seen.has(normalized))
|
|
10
|
+
return;
|
|
11
|
+
seen.add(normalized);
|
|
12
|
+
images.push(normalized);
|
|
13
|
+
}
|
|
14
|
+
function appendImageList(images, seen, candidate) {
|
|
15
|
+
if (!Array.isArray(candidate))
|
|
16
|
+
return;
|
|
17
|
+
candidate.forEach((value) => appendImage(images, seen, value));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves Fivora's catalog media contract while retaining legacy template
|
|
21
|
+
* aliases. The primary image stays first and duplicate URLs are removed.
|
|
22
|
+
*/
|
|
23
|
+
function resolveCatalogImages(item, fallbackImage) {
|
|
24
|
+
const images = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
const source = item || {};
|
|
27
|
+
appendImage(images, seen, source.featuredImage);
|
|
28
|
+
appendImage(images, seen, source.imageUrl);
|
|
29
|
+
appendImage(images, seen, source.image);
|
|
30
|
+
appendImage(images, seen, source.photo);
|
|
31
|
+
appendImage(images, seen, source.thumbnail);
|
|
32
|
+
appendImage(images, seen, source.productImage);
|
|
33
|
+
appendImageList(images, seen, source.gallery);
|
|
34
|
+
appendImageList(images, seen, source.images);
|
|
35
|
+
appendImageList(images, seen, source.additionalImages);
|
|
36
|
+
if (images.length === 0) {
|
|
37
|
+
appendImage(images, seen, fallbackImage);
|
|
38
|
+
}
|
|
39
|
+
return images;
|
|
40
|
+
}
|
|
41
|
+
function resolveCatalogPrimaryImage(item, fallbackImage) {
|
|
42
|
+
return resolveCatalogImages(item, fallbackImage)[0] || '';
|
|
43
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.127",
|
|
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.
|
|
53
|
+
"@deneb-ui/core": "^2.0.127"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"react": "^18.0.0 || ^19.0.0",
|