@deneb-ui/ui 2.0.110 → 2.0.112

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.
@@ -102,16 +102,7 @@ export declare const DenebDataProvider: typeof SiteDataProvider;
102
102
  export declare const useDenebData: typeof useSiteData;
103
103
  export declare const DenebDataContext: React.Context<SiteData | typeof UNINITIALIZED_SITE_DATA>;
104
104
  export type DenebData = SiteData;
105
- /**
106
- * Hook to retrieve products cleanly from SiteData, supporting both
107
- * top-level content.products and nested content.home.products.
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
- */
105
+ export declare function formatWeeklyHoursToString(value: unknown): string;
115
106
  export declare function normalizeProductItem(p: unknown): ProductItem;
116
107
  /**
117
108
  * Universal Service Normalizer
@@ -18,6 +18,7 @@ exports.contentNumber = contentNumber;
18
18
  exports.parseFieldPath = parseFieldPath;
19
19
  exports.getFieldStyle = getFieldStyle;
20
20
  exports.useFieldStyle = useFieldStyle;
21
+ exports.formatWeeklyHoursToString = formatWeeklyHoursToString;
21
22
  exports.normalizeProductItem = normalizeProductItem;
22
23
  exports.normalizeServiceItem = normalizeServiceItem;
23
24
  exports.useProducts = useProducts;
@@ -356,9 +357,11 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
356
357
  }
357
358
  }
358
359
  if (live.shop.openingHours) {
360
+ const hoursStr = formatWeeklyHoursToString(live.shop.openingHours);
361
+ const liveText = hoursStr || (typeof live.shop.openingHours === "string" ? live.shop.openingHours : "");
359
362
  nextContact.hours = live.shop.openingHours;
360
- nextContact.openingHours = live.shop.openingHours;
361
- nextCommon.openingHours = live.shop.openingHours;
363
+ nextContact.openingHours = liveText;
364
+ nextCommon.openingHours = liveText;
362
365
  nextCommon.hours = live.shop.openingHours;
363
366
  nextHome.businessHours = live.shop.openingHours;
364
367
  }
@@ -718,6 +721,78 @@ exports.DenebDataContext = exports.SiteDataContext;
718
721
  * with clean numbers and resolved image URLs, so any template component code works flawlessly
719
722
  * regardless of whether the developer wrote .title or .name, .cost or .price, .image or .imageUrl.
720
723
  */
724
+ function formatTime12(timeStr) {
725
+ if (!timeStr)
726
+ return "";
727
+ const clean = timeStr.trim();
728
+ if (/am|pm/i.test(clean))
729
+ return clean;
730
+ const [hourStr, minStr = "00"] = clean.split(":");
731
+ const hour = parseInt(hourStr, 10);
732
+ if (isNaN(hour))
733
+ return clean;
734
+ const ampm = hour >= 12 ? "PM" : "AM";
735
+ const formattedHour = hour % 12 === 0 ? 12 : hour % 12;
736
+ return `${formattedHour}:${minStr.padStart(2, "0")} ${ampm}`;
737
+ }
738
+ function formatWeeklyHoursToString(value) {
739
+ if (typeof value === "string")
740
+ return value.trim();
741
+ if (!value || typeof value !== "object" || Array.isArray(value))
742
+ return "";
743
+ const days = [
744
+ ["monday", "Mon"],
745
+ ["tuesday", "Tue"],
746
+ ["wednesday", "Wed"],
747
+ ["thursday", "Thu"],
748
+ ["friday", "Fri"],
749
+ ["saturday", "Sat"],
750
+ ["sunday", "Sun"],
751
+ ];
752
+ const entries = [];
753
+ const rec = value;
754
+ for (const [key, label] of days) {
755
+ const day = rec[key];
756
+ if (!day)
757
+ continue;
758
+ if (typeof day === "string") {
759
+ const trimmed = day.trim();
760
+ if (trimmed)
761
+ entries.push({ label, hours: trimmed });
762
+ continue;
763
+ }
764
+ if (day.closed === true) {
765
+ entries.push({ label, hours: "Closed" });
766
+ continue;
767
+ }
768
+ const open = typeof day.open === "string" ? formatTime12(day.open) : "";
769
+ const close = typeof day.close === "string" ? formatTime12(day.close) : "";
770
+ if (open && close) {
771
+ entries.push({ label, hours: `${open} – ${close}` });
772
+ }
773
+ else if (open) {
774
+ entries.push({ label, hours: `From ${open}` });
775
+ }
776
+ }
777
+ if (entries.length === 0)
778
+ return "";
779
+ const groups = [];
780
+ for (const entry of entries) {
781
+ const prev = groups[groups.length - 1];
782
+ if (prev && prev.hours === entry.hours) {
783
+ prev.last = entry.label;
784
+ }
785
+ else {
786
+ groups.push({ first: entry.label, last: entry.label, hours: entry.hours });
787
+ }
788
+ }
789
+ const activeGroups = groups.filter((g) => g.hours && g.hours !== "Closed");
790
+ if (activeGroups.length === 0)
791
+ return "Closed";
792
+ return activeGroups
793
+ .map((g) => (g.first === g.last ? `${g.first}: ${g.hours}` : `${g.first} – ${g.last}: ${g.hours}`))
794
+ .join(", ");
795
+ }
721
796
  function normalizeProductItem(p) {
722
797
  if (!isRecord(p))
723
798
  return p;
@@ -791,7 +866,9 @@ function normalizeProductItem(p) {
791
866
  productName: (titleCandidate ?? nameCandidate),
792
867
  itemTitle: (titleCandidate ?? nameCandidate),
793
868
  minPrice: explicitMin !== undefined ? explicitMin : undefined,
869
+ priceMin: (explicitMin ?? p.priceMin) !== undefined ? (explicitMin ?? p.priceMin) : undefined,
794
870
  maxPrice: explicitMax !== undefined ? explicitMax : undefined,
871
+ priceMax: (explicitMax ?? p.priceMax) !== undefined ? (explicitMax ?? p.priceMax) : undefined,
795
872
  priceRange: explicitRange !== undefined ? explicitRange : undefined,
796
873
  isPriceRange: isRange,
797
874
  ...(numPrice !== undefined
@@ -133,11 +133,26 @@ function isSafeExternalLink(url) {
133
133
  * `window.location` navigation only.
134
134
  */
135
135
  function withBasePath(value) {
136
- const url = value?.trim() ?? '';
136
+ let url = value?.trim() ?? '';
137
137
  if (!url || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url))
138
138
  return url;
139
+ // Deduplicate any repeated generated-sites prefix segments
140
+ const generatedSitesMatch = url.match(/^(\/uploads\/generated-sites\/(?:template-preview|preview|live|candidate)\/[^/]+)/i);
141
+ if (generatedSitesMatch) {
142
+ const root = generatedSitesMatch[1];
143
+ let rest = url.slice(root.length);
144
+ while (rest.toLowerCase().startsWith(root.toLowerCase())) {
145
+ rest = rest.slice(root.length);
146
+ }
147
+ url = `${root}${rest}`;
148
+ }
139
149
  const basePath = (typeof process !== 'undefined' ? process.env?.NEXT_PUBLIC_SITE_BASE_PATH ?? '' : '').replace(/\/$/, '');
150
+ if (!basePath)
151
+ return url.startsWith('/') ? url : `/${url}`;
140
152
  const path = url.startsWith('/') ? url : `/${url}`;
153
+ if (path === basePath || path.startsWith(`${basePath}/`) || path.startsWith(`${basePath}?`)) {
154
+ return path;
155
+ }
141
156
  return `${basePath}${path}`;
142
157
  }
143
158
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.110",
3
+ "version": "2.0.112",
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.110"
53
+ "@deneb-ui/core": "^2.0.112"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",