@discount-depot/hydrogen 0.1.4

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.
Files changed (42) hide show
  1. package/README.md +337 -0
  2. package/dist/buy-x-get-y-data.d.ts +29 -0
  3. package/dist/buy-x-get-y-data.js +103 -0
  4. package/dist/buy-x-get-y-design.d.ts +2 -0
  5. package/dist/buy-x-get-y-design.js +38 -0
  6. package/dist/buy-x-get-y-styles.d.ts +2 -0
  7. package/dist/buy-x-get-y-styles.js +3 -0
  8. package/dist/buy-x-get-y.d.ts +9 -0
  9. package/dist/buy-x-get-y.js +69 -0
  10. package/dist/cart-goal-data.d.ts +48 -0
  11. package/dist/cart-goal-data.js +63 -0
  12. package/dist/cart-goal-server.d.ts +17 -0
  13. package/dist/cart-goal-server.js +73 -0
  14. package/dist/cart-goal.d.ts +13 -0
  15. package/dist/cart-goal.js +113 -0
  16. package/dist/collection.d.ts +10 -0
  17. package/dist/collection.js +91 -0
  18. package/dist/config.d.ts +4 -0
  19. package/dist/config.js +4 -0
  20. package/dist/countdown-data.d.ts +16 -0
  21. package/dist/countdown-data.js +46 -0
  22. package/dist/countdown-design.d.ts +17 -0
  23. package/dist/countdown-design.js +107 -0
  24. package/dist/countdown-styles.d.ts +1 -0
  25. package/dist/countdown-styles.js +2 -0
  26. package/dist/countdown.d.ts +6 -0
  27. package/dist/countdown.js +86 -0
  28. package/dist/customer-identity.d.ts +15 -0
  29. package/dist/customer-identity.js +32 -0
  30. package/dist/presentation.d.ts +20 -0
  31. package/dist/presentation.js +85 -0
  32. package/dist/preview-session.d.ts +2 -0
  33. package/dist/preview-session.js +21 -0
  34. package/dist/react.d.ts +31 -0
  35. package/dist/react.js +89 -0
  36. package/dist/server.d.ts +53 -0
  37. package/dist/server.js +284 -0
  38. package/dist/volume-table.d.ts +9 -0
  39. package/dist/volume-table.js +94 -0
  40. package/dist/volume.d.ts +27 -0
  41. package/dist/volume.js +82 -0
  42. package/package.json +34 -0
@@ -0,0 +1,107 @@
1
+ import { safePriceColor } from './presentation.js';
2
+ // Only color stops and a degree direction, never URLs, CSS variables or arbitrary functions.
3
+ export function safeCountdownBackground(value) {
4
+ const color = safePriceColor(value);
5
+ if (color)
6
+ return color;
7
+ const match = /^linear-gradient\((.*)\)$/i.exec(value.trim());
8
+ if (!match)
9
+ return undefined;
10
+ const parts = match[1].split(/,(?![^()]*\))/).map((part) => part.trim());
11
+ if (/^-?\d+(?:\.\d+)?deg$/.test(parts[0]))
12
+ parts.shift();
13
+ if (parts.length < 2 || parts.length > 8)
14
+ return undefined;
15
+ return parts.every((part) => {
16
+ const stop = /^(.*?)(?:\s+(-?\d+(?:\.\d+)?%))?$/.exec(part);
17
+ return stop && safePriceColor(stop[1]);
18
+ })
19
+ ? value
20
+ : undefined;
21
+ }
22
+ export function resolveCountdownDesign(design) {
23
+ const field = (suffix) => {
24
+ const camel = 'countdown' +
25
+ suffix
26
+ .split('_')
27
+ .map((word) => word[0].toUpperCase() + word.slice(1))
28
+ .join('');
29
+ return (design[`vd_countdown_${suffix}`] ??
30
+ design[`countdown_${suffix}`] ??
31
+ design[camel]);
32
+ };
33
+ const text = (name, fallback) => {
34
+ const value = field(name);
35
+ return typeof value === 'string' && value.trim()
36
+ ? value.slice(0, 500)
37
+ : fallback;
38
+ };
39
+ const requestedTheme = text('theme', 'classic');
40
+ const theme = requestedTheme === 'minimal'
41
+ ? 'coupon'
42
+ : ['classic', 'banner', 'coupon'].includes(requestedTheme)
43
+ ? requestedTheme
44
+ : 'classic';
45
+ const preset = theme === 'banner'
46
+ ? {
47
+ bg: 'linear-gradient(135deg, #0f172a 0%, #334155 100%)',
48
+ border: 'rgba(255,255,255,0.16)',
49
+ color: '#e2e8f0',
50
+ timeBg: 'rgba(255,255,255,0.08)',
51
+ timeColor: '#f8fafc',
52
+ size: 18,
53
+ radius: 18,
54
+ pad: 18,
55
+ timePad: 10,
56
+ }
57
+ : theme === 'coupon'
58
+ ? {
59
+ bg: 'linear-gradient(135deg, #ebffe4 0%, #c9f8c3 100%)',
60
+ border: 'rgba(111,205,118,0.22)',
61
+ color: '#425343',
62
+ timeBg: '#59c95f',
63
+ timeColor: '#f7fff7',
64
+ size: 15,
65
+ radius: 8,
66
+ pad: 10,
67
+ timePad: 0,
68
+ }
69
+ : {
70
+ bg: 'linear-gradient(135deg, #d89cff 0%, #8f39ff 100%)',
71
+ border: 'rgba(255,255,255,0.12)',
72
+ color: '#2b1238',
73
+ timeBg: 'transparent',
74
+ timeColor: '#ffffff',
75
+ size: 20,
76
+ radius: 8,
77
+ pad: 24,
78
+ timePad: 0,
79
+ };
80
+ const size = (name, fallback) => {
81
+ const value = field(name);
82
+ return value !== undefined &&
83
+ typeof value !== 'boolean' &&
84
+ String(value).trim() !== '' &&
85
+ Number.isFinite(Number(value))
86
+ ? Math.min(100, Math.max(0, Number(value)))
87
+ : fallback;
88
+ };
89
+ return {
90
+ theme,
91
+ format: text('format', 'dhms'),
92
+ title: text('title', 'Limited time offer'),
93
+ label: text('label', 'Sale ends in:'),
94
+ labels: ['days', 'hours', 'minutes', 'seconds'].map((unit, i) => text(`${unit}_label`, ['Days', 'Hrs', 'Mins', 'Secs'][i])),
95
+ bg: safeCountdownBackground(text('background_color', preset.bg)) ?? preset.bg,
96
+ border: safePriceColor(text('border_color', preset.border)) ?? preset.border,
97
+ color: safePriceColor(text('text_color', preset.color)) ?? preset.color,
98
+ timeBg: safeCountdownBackground(text('timer_background_color', preset.timeBg)) ??
99
+ preset.timeBg,
100
+ timeColor: safePriceColor(text('timer_text_color', preset.timeColor)) ??
101
+ preset.timeColor,
102
+ size: size('font_size', preset.size),
103
+ radius: size('border_radius', preset.radius),
104
+ pad: size('padding', preset.pad),
105
+ timePad: size('timer_padding', preset.timePad),
106
+ };
107
+ }
@@ -0,0 +1 @@
1
+ export declare const countdownStyles = ".dd-hydrogen-countdown-root {\r\n width: 100%;\r\n}\r\n\r\n.dd-hydrogen-countdown-root[hidden] {\r\n display: none !important;\r\n}\r\n\r\n.dd-hydrogen-countdown-sticky-host {\r\n position: fixed;\r\n left: max(16px, env(safe-area-inset-left));\r\n right: max(16px, env(safe-area-inset-right));\r\n bottom: max(16px, env(safe-area-inset-bottom));\r\n z-index: 9999;\r\n display: flex;\r\n justify-content: center;\r\n pointer-events: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-sticky-host[hidden] {\r\n display: none !important;\r\n}\r\n\r\n.dd-hydrogen-countdown-card {\r\n width: 100%;\r\n max-width: var(--dd-max-width, 100%);\r\n box-sizing: border-box;\r\n padding: var(--dd-pad, 24px);\r\n border: 1px solid var(--dd-border, rgba(255, 255, 255, 0.12));\r\n border-radius: var(--dd-radius, 8px);\r\n background: var(--dd-bg, linear-gradient(135deg, #d89cff 0%, #8f39ff 100%));\r\n color: var(--dd-text, #2b1238);\r\n font-size: var(--dd-size, 20px);\r\n line-height: 1.3;\r\n margin: 0 auto;\r\n pointer-events: auto;\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-classic {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 14px;\r\n text-align: center;\r\n box-shadow: 0 14px 32px rgba(15, 23, 42, 0.12);\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-banner {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr) auto;\r\n align-items: center;\r\n gap: 14px 18px;\r\n text-align: left;\r\n box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-coupon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 16px;\r\n min-height: 74px;\r\n box-shadow: 0 16px 36px rgba(36, 83, 52, 0.12);\r\n}\r\n\r\n.dd-hydrogen-countdown-title {\r\n display: block;\r\n width: 100%;\r\n color: inherit;\r\n font-size: max(calc(var(--dd-size, 20px) * 1.1), 16px);\r\n line-height: 1.2;\r\n font-weight: 600;\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-title {\r\n grid-column: 1 / -1;\r\n text-align: left;\r\n}\r\n\r\n.dd-theme-coupon .dd-hydrogen-countdown-title {\r\n display: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-label {\r\n display: block;\r\n color: inherit;\r\n font-size: max(calc(var(--dd-size, 20px) * 0.7), 12px);\r\n line-height: 1.3;\r\n font-weight: 500;\r\n}\r\n\r\n.dd-theme-classic .dd-hydrogen-countdown-label {\r\n width: 100%;\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-label {\r\n width: auto;\r\n justify-self: start;\r\n text-align: left;\r\n}\r\n\r\n.dd-theme-coupon .dd-hydrogen-countdown-label {\r\n display: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-time {\r\n box-sizing: border-box;\r\n}\r\n\r\n.dd-hydrogen-countdown-time--split {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 2px;\r\n width: 100%;\r\n min-height: var(--dd-time-min-height, 60px);\r\n padding: var(--dd-time-pad, 0);\r\n border-radius: max(calc(var(--dd-radius, 8px) / 1.5), 8px);\r\n background: var(--dd-time-bg, transparent);\r\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: auto;\r\n min-height: 0;\r\n justify-self: end;\r\n}\r\n\r\n.dd-hydrogen-countdown-segment {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n text-align: center;\r\n flex: var(--dd-segment-flex, 0 0 auto);\r\n width: var(--dd-segment-width, auto);\r\n min-width: var(--dd-segment-min-width, 52px);\r\n}\r\n\r\n.dd-hydrogen-countdown-value {\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-time-value-size, max(calc(var(--dd-size, 20px) * 0.95), 16px));\r\n line-height: 0.95;\r\n font-weight: 400;\r\n letter-spacing: -0.04em;\r\n}\r\n\r\n.dd-hydrogen-countdown-unit {\r\n margin-top: var(--dd-unit-margin-top, 6px);\r\n color: var(--dd-text, #2b1238);\r\n font-size: var(--dd-time-unit-size, max(calc(var(--dd-size, 20px) * 0.75), 12px));\r\n font-weight: 500;\r\n white-space: nowrap;\r\n}\r\n\r\n.dd-hydrogen-countdown-separator {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-time-value-size, max(calc(var(--dd-size, 20px) * 0.95), 16px));\r\n line-height: 1;\r\n font-weight: 300;\r\n padding-inline: var(--dd-separator-pad, 4px);\r\n}\r\n\r\n.dd-hydrogen-countdown-coupon-lead {\r\n display: flex;\r\n align-items: center;\r\n gap: 12px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dd-hydrogen-countdown-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex: 0 0 auto;\r\n font-size: max(calc(var(--dd-size, 15px) * 1.6), 28px);\r\n line-height: 1;\r\n}\r\n\r\n.dd-hydrogen-countdown-icon svg {\r\n display: block;\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.dd-hydrogen-countdown-coupon-message {\r\n display: block;\r\n min-width: 0;\r\n color: inherit;\r\n font-size: max(var(--dd-size, 15px), 15px);\r\n font-weight: 650;\r\n line-height: 1.15;\r\n white-space: normal;\r\n}\r\n\r\n.dd-hydrogen-countdown-time--coupon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-start;\r\n gap: var(--dd-coupon-gap, 12px);\r\n flex-wrap: nowrap;\r\n width: var(--dd-coupon-width, auto);\r\n min-width: fit-content;\r\n max-width: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-chip {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n box-sizing: border-box;\r\n min-width: var(--dd-chip-min-width, max(calc(var(--dd-size, 15px) * 5.2), 82px));\r\n height: var(--dd-chip-height, 50px);\r\n padding: 0 max(var(--dd-time-pad, 0), 10px);\r\n border-radius: max(var(--dd-radius, 8px), 8px);\r\n background: var(--dd-time-bg, transparent);\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-chip-font-size, max(var(--dd-size, 15px), 15px));\r\n font-weight: 700;\r\n line-height: 1;\r\n white-space: nowrap;\r\n box-shadow: 0 10px 18px rgba(36, 83, 52, 0.1);\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner {\r\n grid-template-columns: minmax(0, 1fr);\r\n text-align: center;\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-title,\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-label {\r\n text-align: center;\r\n justify-self: center;\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: 100%;\r\n justify-self: stretch;\r\n}\r\n\r\n.dd-layout-coupon-compact.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: auto minmax(0, 1fr);\r\n align-items: center;\r\n gap: 8px 12px;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-coupon-lead {\r\n display: contents;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-icon {\r\n grid-column: 1 / 2;\r\n grid-row: 1 / 2;\r\n justify-self: center;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-coupon-message {\r\n grid-column: 2 / 3;\r\n grid-row: 1 / 2;\r\n text-align: left;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-time--coupon {\r\n grid-column: 1 / -1;\r\n grid-row: 2 / 3;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.dd-layout-coupon-stack.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr);\r\n justify-content: initial;\r\n min-height: 0;\r\n gap: 8px;\r\n text-align: center;\r\n}\r\n\r\n.dd-layout-coupon-stack .dd-hydrogen-countdown-coupon-lead {\r\n justify-content: center;\r\n}\r\n\r\n.dd-layout-coupon-stack .dd-hydrogen-countdown-time--coupon {\r\n width: 100%;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n}\r\n\r\n@media (max-width: 767px) {\r\n .dd-hydrogen-countdown-card.dd-theme-banner {\r\n grid-template-columns: minmax(0, 1fr);\r\n text-align: center;\r\n }\r\n\r\n .dd-theme-banner .dd-hydrogen-countdown-title,\r\n .dd-theme-banner .dd-hydrogen-countdown-label {\r\n text-align: center;\r\n justify-self: center;\r\n }\r\n\r\n .dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: 100%;\r\n justify-self: stretch;\r\n }\r\n}\r\n\r\n@media (max-width: 520px) {\r\n .dd-hydrogen-countdown-card.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr);\r\n justify-content: initial;\r\n min-height: 0;\r\n gap: 8px;\r\n text-align: center;\r\n }\r\n\r\n .dd-hydrogen-countdown-coupon-lead {\r\n justify-content: center;\r\n }\r\n\r\n .dd-hydrogen-countdown-time--coupon {\r\n width: 100%;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n }\r\n\r\n .dd-hydrogen-countdown-chip {\r\n min-width: var(--dd-chip-min-width, max(calc(var(--dd-size, 15px) * 4.8), 72px));\r\n height: var(--dd-chip-height, 44px);\r\n font-size: var(--dd-chip-font-size, max(calc(var(--dd-size, 15px) * 0.93), 14px));\r\n }\r\n}\r\n";
@@ -0,0 +1,2 @@
1
+ // Scoped styles ported from the Discount Depot countdown app block.
2
+ export const countdownStyles = '.dd-hydrogen-countdown-root {\r\n width: 100%;\r\n}\r\n\r\n.dd-hydrogen-countdown-root[hidden] {\r\n display: none !important;\r\n}\r\n\r\n.dd-hydrogen-countdown-sticky-host {\r\n position: fixed;\r\n left: max(16px, env(safe-area-inset-left));\r\n right: max(16px, env(safe-area-inset-right));\r\n bottom: max(16px, env(safe-area-inset-bottom));\r\n z-index: 9999;\r\n display: flex;\r\n justify-content: center;\r\n pointer-events: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-sticky-host[hidden] {\r\n display: none !important;\r\n}\r\n\r\n.dd-hydrogen-countdown-card {\r\n width: 100%;\r\n max-width: var(--dd-max-width, 100%);\r\n box-sizing: border-box;\r\n padding: var(--dd-pad, 24px);\r\n border: 1px solid var(--dd-border, rgba(255, 255, 255, 0.12));\r\n border-radius: var(--dd-radius, 8px);\r\n background: var(--dd-bg, linear-gradient(135deg, #d89cff 0%, #8f39ff 100%));\r\n color: var(--dd-text, #2b1238);\r\n font-size: var(--dd-size, 20px);\r\n line-height: 1.3;\r\n margin: 0 auto;\r\n pointer-events: auto;\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-classic {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 14px;\r\n text-align: center;\r\n box-shadow: 0 14px 32px rgba(15, 23, 42, 0.12);\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-banner {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr) auto;\r\n align-items: center;\r\n gap: 14px 18px;\r\n text-align: left;\r\n box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);\r\n}\r\n\r\n.dd-hydrogen-countdown-card.dd-theme-coupon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 16px;\r\n min-height: 74px;\r\n box-shadow: 0 16px 36px rgba(36, 83, 52, 0.12);\r\n}\r\n\r\n.dd-hydrogen-countdown-title {\r\n display: block;\r\n width: 100%;\r\n color: inherit;\r\n font-size: max(calc(var(--dd-size, 20px) * 1.1), 16px);\r\n line-height: 1.2;\r\n font-weight: 600;\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-title {\r\n grid-column: 1 / -1;\r\n text-align: left;\r\n}\r\n\r\n.dd-theme-coupon .dd-hydrogen-countdown-title {\r\n display: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-label {\r\n display: block;\r\n color: inherit;\r\n font-size: max(calc(var(--dd-size, 20px) * 0.7), 12px);\r\n line-height: 1.3;\r\n font-weight: 500;\r\n}\r\n\r\n.dd-theme-classic .dd-hydrogen-countdown-label {\r\n width: 100%;\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-label {\r\n width: auto;\r\n justify-self: start;\r\n text-align: left;\r\n}\r\n\r\n.dd-theme-coupon .dd-hydrogen-countdown-label {\r\n display: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-time {\r\n box-sizing: border-box;\r\n}\r\n\r\n.dd-hydrogen-countdown-time--split {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 2px;\r\n width: 100%;\r\n min-height: var(--dd-time-min-height, 60px);\r\n padding: var(--dd-time-pad, 0);\r\n border-radius: max(calc(var(--dd-radius, 8px) / 1.5), 8px);\r\n background: var(--dd-time-bg, transparent);\r\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: auto;\r\n min-height: 0;\r\n justify-self: end;\r\n}\r\n\r\n.dd-hydrogen-countdown-segment {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n text-align: center;\r\n flex: var(--dd-segment-flex, 0 0 auto);\r\n width: var(--dd-segment-width, auto);\r\n min-width: var(--dd-segment-min-width, 52px);\r\n}\r\n\r\n.dd-hydrogen-countdown-value {\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-time-value-size, max(calc(var(--dd-size, 20px) * 0.95), 16px));\r\n line-height: 0.95;\r\n font-weight: 400;\r\n letter-spacing: -0.04em;\r\n}\r\n\r\n.dd-hydrogen-countdown-unit {\r\n margin-top: var(--dd-unit-margin-top, 6px);\r\n color: var(--dd-text, #2b1238);\r\n font-size: var(--dd-time-unit-size, max(calc(var(--dd-size, 20px) * 0.75), 12px));\r\n font-weight: 500;\r\n white-space: nowrap;\r\n}\r\n\r\n.dd-hydrogen-countdown-separator {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-time-value-size, max(calc(var(--dd-size, 20px) * 0.95), 16px));\r\n line-height: 1;\r\n font-weight: 300;\r\n padding-inline: var(--dd-separator-pad, 4px);\r\n}\r\n\r\n.dd-hydrogen-countdown-coupon-lead {\r\n display: flex;\r\n align-items: center;\r\n gap: 12px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dd-hydrogen-countdown-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex: 0 0 auto;\r\n font-size: max(calc(var(--dd-size, 15px) * 1.6), 28px);\r\n line-height: 1;\r\n}\r\n\r\n.dd-hydrogen-countdown-icon svg {\r\n display: block;\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.dd-hydrogen-countdown-coupon-message {\r\n display: block;\r\n min-width: 0;\r\n color: inherit;\r\n font-size: max(var(--dd-size, 15px), 15px);\r\n font-weight: 650;\r\n line-height: 1.15;\r\n white-space: normal;\r\n}\r\n\r\n.dd-hydrogen-countdown-time--coupon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-start;\r\n gap: var(--dd-coupon-gap, 12px);\r\n flex-wrap: nowrap;\r\n width: var(--dd-coupon-width, auto);\r\n min-width: fit-content;\r\n max-width: none;\r\n}\r\n\r\n.dd-hydrogen-countdown-chip {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n box-sizing: border-box;\r\n min-width: var(--dd-chip-min-width, max(calc(var(--dd-size, 15px) * 5.2), 82px));\r\n height: var(--dd-chip-height, 50px);\r\n padding: 0 max(var(--dd-time-pad, 0), 10px);\r\n border-radius: max(var(--dd-radius, 8px), 8px);\r\n background: var(--dd-time-bg, transparent);\r\n color: var(--dd-time-text, #ffffff);\r\n font-size: var(--dd-chip-font-size, max(var(--dd-size, 15px), 15px));\r\n font-weight: 700;\r\n line-height: 1;\r\n white-space: nowrap;\r\n box-shadow: 0 10px 18px rgba(36, 83, 52, 0.1);\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner {\r\n grid-template-columns: minmax(0, 1fr);\r\n text-align: center;\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-title,\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-label {\r\n text-align: center;\r\n justify-self: center;\r\n}\r\n\r\n.dd-layout-banner-narrow.dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: 100%;\r\n justify-self: stretch;\r\n}\r\n\r\n.dd-layout-coupon-compact.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: auto minmax(0, 1fr);\r\n align-items: center;\r\n gap: 8px 12px;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-coupon-lead {\r\n display: contents;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-icon {\r\n grid-column: 1 / 2;\r\n grid-row: 1 / 2;\r\n justify-self: center;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-coupon-message {\r\n grid-column: 2 / 3;\r\n grid-row: 1 / 2;\r\n text-align: left;\r\n}\r\n\r\n.dd-layout-coupon-compact .dd-hydrogen-countdown-time--coupon {\r\n grid-column: 1 / -1;\r\n grid-row: 2 / 3;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.dd-layout-coupon-stack.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr);\r\n justify-content: initial;\r\n min-height: 0;\r\n gap: 8px;\r\n text-align: center;\r\n}\r\n\r\n.dd-layout-coupon-stack .dd-hydrogen-countdown-coupon-lead {\r\n justify-content: center;\r\n}\r\n\r\n.dd-layout-coupon-stack .dd-hydrogen-countdown-time--coupon {\r\n width: 100%;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n}\r\n\r\n@media (max-width: 767px) {\r\n .dd-hydrogen-countdown-card.dd-theme-banner {\r\n grid-template-columns: minmax(0, 1fr);\r\n text-align: center;\r\n }\r\n\r\n .dd-theme-banner .dd-hydrogen-countdown-title,\r\n .dd-theme-banner .dd-hydrogen-countdown-label {\r\n text-align: center;\r\n justify-self: center;\r\n }\r\n\r\n .dd-theme-banner .dd-hydrogen-countdown-time--split {\r\n width: 100%;\r\n justify-self: stretch;\r\n }\r\n}\r\n\r\n@media (max-width: 520px) {\r\n .dd-hydrogen-countdown-card.dd-theme-coupon {\r\n display: grid;\r\n grid-template-columns: minmax(0, 1fr);\r\n justify-content: initial;\r\n min-height: 0;\r\n gap: 8px;\r\n text-align: center;\r\n }\r\n\r\n .dd-hydrogen-countdown-coupon-lead {\r\n justify-content: center;\r\n }\r\n\r\n .dd-hydrogen-countdown-time--coupon {\r\n width: 100%;\r\n justify-content: center;\r\n flex-wrap: wrap;\r\n gap: 8px;\r\n }\r\n\r\n .dd-hydrogen-countdown-chip {\r\n min-width: var(--dd-chip-min-width, max(calc(var(--dd-size, 15px) * 4.8), 72px));\r\n height: var(--dd-chip-height, 44px);\r\n font-size: var(--dd-chip-font-size, max(calc(var(--dd-size, 15px) * 0.93), 14px));\r\n }\r\n}\r\n';
@@ -0,0 +1,6 @@
1
+ export type { DepotCountdown } from './countdown-data.js';
2
+ /** Uses an absolute deadline; rerenders, variant changes and refreshes never restart it. */
3
+ export declare function DiscountDepotCountdown({ countdown, design, }: {
4
+ countdown: unknown;
5
+ design?: Record<string, string | number | boolean>;
6
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment, useEffect, useState } from 'react';
3
+ import { resolveCountdownDesign } from './countdown-design.js';
4
+ import { countdownStyles } from './countdown-styles.js';
5
+ import { normalizeCountdown, countdownDeadline } from './countdown-data.js';
6
+ /** Uses an absolute deadline; rerenders, variant changes and refreshes never restart it. */
7
+ export function DiscountDepotCountdown({ countdown, design = {}, }) {
8
+ const data = normalizeCountdown(countdown);
9
+ const start = data ? Date.parse(data.startsAt) : 0;
10
+ const end = data ? Date.parse(data.endsAt) : 0;
11
+ const restart = data?.expiryAction === 'restart';
12
+ const cutoff = data?.ruleEndsAt ? Date.parse(data.ruleEndsAt) : Infinity;
13
+ const [clock, setClock] = useState(null);
14
+ useEffect(() => {
15
+ if (!start || !end)
16
+ return;
17
+ let interval;
18
+ const tick = () => {
19
+ const now = Date.now();
20
+ setClock({ start, end, now });
21
+ if ((now >= cutoff || (!restart && now >= end)) && interval !== undefined)
22
+ clearInterval(interval);
23
+ };
24
+ tick();
25
+ if (Date.now() < cutoff && (restart || Date.now() < end))
26
+ interval = setInterval(tick, 1000);
27
+ return () => {
28
+ if (interval !== undefined)
29
+ clearInterval(interval);
30
+ };
31
+ }, [start, end, restart, cutoff]);
32
+ if (!data ||
33
+ !clock ||
34
+ clock.start !== start ||
35
+ clock.end !== end ||
36
+ clock.now < start ||
37
+ clock.now >= countdownDeadline(data, clock.now))
38
+ return null;
39
+ const total = Math.ceil((countdownDeadline(data, clock.now) - clock.now) / 1000);
40
+ const days = Math.floor(total / 86400);
41
+ const hours = Math.floor(total / 3600) % 24;
42
+ const minutes = Math.floor(total / 60) % 60;
43
+ const seconds = total % 60;
44
+ const pad = (value) => String(value).padStart(2, '0');
45
+ const text = `${days ? `${days}d ` : ''}${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
46
+ const saved = resolveCountdownDesign({ ...design, ...data.design });
47
+ const allParts = [days, hours, minutes, seconds];
48
+ const parts = saved.format === 'ms'
49
+ ? [
50
+ [Math.floor(total / 60), saved.labels[2]],
51
+ [seconds, saved.labels[3]],
52
+ ]
53
+ : saved.format === 'hms'
54
+ ? [
55
+ [Math.floor(total / 3600), saved.labels[1]],
56
+ [minutes, saved.labels[2]],
57
+ [seconds, saved.labels[3]],
58
+ ]
59
+ : allParts.map((value, index) => [value, saved.labels[index]]);
60
+ const coupon = saved.theme === 'coupon';
61
+ const title = data.title ?? saved.title;
62
+ const label = data.label ?? saved.label;
63
+ const vars = {
64
+ '--dd-bg': saved.bg,
65
+ '--dd-border': saved.border,
66
+ '--dd-text': saved.color,
67
+ '--dd-time-bg': saved.timeBg,
68
+ '--dd-time-text': saved.timeColor,
69
+ '--dd-size': `${saved.size}px`,
70
+ '--dd-radius': `${saved.radius}px`,
71
+ '--dd-pad': `${saved.pad}px`,
72
+ '--dd-time-pad': `${saved.timePad}px`,
73
+ '--dd-max-width': coupon
74
+ ? '1040px'
75
+ : saved.theme === 'banner'
76
+ ? '520px'
77
+ : '360px',
78
+ '--dd-time-value-size': `${Math.max(parts.length > 3 ? Math.round(saved.size * 0.9) : saved.size, 16)}px`,
79
+ '--dd-time-unit-size': `${Math.max(Math.round(saved.size * (parts.length > 3 ? 0.65 : 0.75)), 11)}px`,
80
+ '--dd-segment-min-width': parts.length > 3 ? (saved.theme === 'banner' ? '48px' : '56px') : '52px',
81
+ '--dd-separator-pad': saved.theme === 'banner' ? '1px' : '2px',
82
+ };
83
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: countdownStyles }), _jsx("section", { className: coupon
84
+ ? 'dd-hydrogen-countdown-sticky-host'
85
+ : 'dd-hydrogen-countdown-root', "aria-label": "Offer countdown", style: coupon ? undefined : { marginTop: 12 }, children: _jsxs("div", { className: `dd-hydrogen-countdown-card dd-theme-${saved.theme}${saved.theme === 'banner' ? ' dd-layout-banner-narrow' : ''}`, style: vars, children: [coupon ? (_jsxs("span", { className: "dd-hydrogen-countdown-coupon-lead", children: [_jsx("span", { "aria-hidden": "true", className: "dd-hydrogen-countdown-icon", children: _jsxs("svg", { viewBox: "0 0 64 64", width: "1em", height: "1em", focusable: "false", children: [_jsx("circle", { cx: "32", cy: "35", r: "19", fill: "#ffedf2" }), _jsx("circle", { cx: "32", cy: "35", r: "15", fill: "#d8e7ff" }), _jsx("path", { d: "M20 8c-4 0-7 3-7 7 0 3 2 5 4 6l7-7c-1-3-2-6-4-6ZM44 14l7 7c3-1 5-3 5-6 0-4-3-7-7-7-2 0-3 1-5 6Z", fill: "#ffd24d" }), _jsx("path", { d: "M18 18c5-6 12-9 20-9s15 3 20 9", fill: "none", stroke: "#ff6b8b", strokeWidth: "5", strokeLinecap: "round" }), _jsx("circle", { cx: "32", cy: "35", r: "17", fill: "none", stroke: "#ff5c7d", strokeWidth: "5" }), _jsx("path", { d: "M32 35V25M32 35l8 5M18 55l7-7M46 55l-7-7", stroke: "#31435a", strokeWidth: "4", strokeLinecap: "round" }), _jsx("circle", { cx: "32", cy: "35", r: "2.5", fill: "#31435a" })] }) }), _jsx("span", { className: "dd-hydrogen-countdown-coupon-message", children: [title, label].filter(Boolean).join(' ') })] })) : (_jsxs(_Fragment, { children: [_jsx("strong", { className: "dd-hydrogen-countdown-title", children: title }), _jsx("span", { className: "dd-hydrogen-countdown-label", children: label })] })), _jsx("div", { role: "timer", "aria-live": "off", "aria-label": text, className: `dd-hydrogen-countdown-time dd-hydrogen-countdown-time--${coupon ? 'coupon' : 'split'}`, children: parts.map(([value, unit], index) => coupon ? (_jsxs("span", { className: "dd-hydrogen-countdown-chip", children: [Number(value), " ", unit] }, index)) : (_jsxs(Fragment, { children: [_jsxs("span", { className: "dd-hydrogen-countdown-segment", children: [_jsx("span", { className: "dd-hydrogen-countdown-value", children: pad(Number(value)) }), _jsx("span", { className: "dd-hydrogen-countdown-unit", children: unit })] }), index < parts.length - 1 && (_jsx("span", { className: "dd-hydrogen-countdown-separator", "aria-hidden": "true", children: ":" }))] }, index))) })] }) })] }));
86
+ }
@@ -0,0 +1,15 @@
1
+ import type { CustomerAccount } from '@shopify/hydrogen';
2
+ export interface CustomerIdentityContext {
3
+ customerAccount?: Pick<CustomerAccount, 'isLoggedIn' | 'getAccessToken'>;
4
+ }
5
+ export type CustomerPreviewCredentials = {
6
+ state: 'authenticated';
7
+ accessToken: string;
8
+ origin: string;
9
+ } | {
10
+ state: 'guest' | 'unknown';
11
+ };
12
+ /** Read only Hydrogen's server session; browser JSON never supplies credentials. */
13
+ export declare function resolvePreviewCustomer(context: CustomerIdentityContext, request: Request): Promise<CustomerPreviewCredentials>;
14
+ /** Customer access tokens stay on the server and are never part of JSON output. */
15
+ export declare function customerPreviewHeaders(credentials?: CustomerPreviewCredentials): Record<string, string>;
@@ -0,0 +1,32 @@
1
+ /** Read only Hydrogen's server session; browser JSON never supplies credentials. */
2
+ export async function resolvePreviewCustomer(context, request) {
3
+ if (!context.customerAccount)
4
+ return { state: 'unknown' };
5
+ try {
6
+ if (!(await context.customerAccount.isLoggedIn()))
7
+ return { state: 'guest' };
8
+ // Hydrogen checks expiry and refreshes the token using its existing session.
9
+ const accessToken = await context.customerAccount.getAccessToken();
10
+ if (!accessToken || accessToken.length > 16384 || /[^\x21-\x7e]/.test(accessToken))
11
+ return { state: 'unknown' };
12
+ const origin = new URL(request.url);
13
+ // Match Hydrogen's Customer Account client, including local development.
14
+ origin.protocol = 'https:';
15
+ return { state: 'authenticated', accessToken, origin: origin.origin };
16
+ }
17
+ catch {
18
+ return { state: 'unknown' };
19
+ }
20
+ }
21
+ /** Customer access tokens stay on the server and are never part of JSON output. */
22
+ export function customerPreviewHeaders(credentials) {
23
+ const headers = { 'Content-Type': 'application/json' };
24
+ if (credentials?.state === 'authenticated') {
25
+ headers['X-Discount-Depot-Customer-Access-Token'] = credentials.accessToken;
26
+ headers['X-Discount-Depot-Customer-Origin'] = credentials.origin;
27
+ }
28
+ else if (credentials?.state === 'guest') {
29
+ headers['X-Discount-Depot-Customer-State'] = 'guest';
30
+ }
31
+ return headers;
32
+ }
@@ -0,0 +1,20 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+ import type { DepotDiscount } from './server.js';
3
+ export interface PricedVariant {
4
+ id: string;
5
+ price: {
6
+ amount: string;
7
+ currencyCode: string;
8
+ };
9
+ }
10
+ export declare function isValidDiscount(discount: unknown, variant?: PricedVariant | null): discount is DepotDiscount;
11
+ export declare function safePriceColor(value?: string): string | undefined;
12
+ export declare function savedLabelStyle(cssText?: string): CSSProperties | undefined;
13
+ /** Presentational only: product cards never initiate requests. */
14
+ export declare function DiscountDepotPricePreview({ variant, discount, children, locale, className, }: {
15
+ variant?: PricedVariant | null;
16
+ discount?: unknown;
17
+ children: ReactNode;
18
+ locale?: string;
19
+ className?: string;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,85 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function isValidDiscount(discount, variant) {
3
+ if (!variant || !discount || typeof discount !== 'object')
4
+ return false;
5
+ const value = discount;
6
+ return (value.eligible === true &&
7
+ value.currencyCode === variant.price.currencyCode &&
8
+ typeof value.originalPrice === 'string' &&
9
+ value.originalPrice.trim() !== '' &&
10
+ Number(value.originalPrice) === Number(variant.price.amount) &&
11
+ typeof value.displayPrice === 'string' &&
12
+ value.displayPrice.trim() !== '' &&
13
+ Number.isFinite(Number(value.displayPrice)) &&
14
+ Number(value.displayPrice) >= 0 &&
15
+ Number(value.displayPrice) <= Number(variant.price.amount));
16
+ }
17
+ const CSS_PROPERTIES = {
18
+ 'background-color': 'backgroundColor',
19
+ background: 'background',
20
+ color: 'color',
21
+ display: 'display',
22
+ border: 'border',
23
+ 'border-color': 'borderColor',
24
+ 'border-radius': 'borderRadius',
25
+ 'border-width': 'borderWidth',
26
+ 'border-style': 'borderStyle',
27
+ 'font-size': 'fontSize',
28
+ 'font-weight': 'fontWeight',
29
+ 'font-style': 'fontStyle',
30
+ 'letter-spacing': 'letterSpacing',
31
+ 'line-height': 'lineHeight',
32
+ padding: 'padding',
33
+ 'margin-left': 'marginLeft',
34
+ 'margin-right': 'marginRight',
35
+ 'margin-top': 'marginTop',
36
+ 'margin-bottom': 'marginBottom',
37
+ 'text-align': 'textAlign',
38
+ 'text-transform': 'textTransform',
39
+ 'word-break': 'wordBreak',
40
+ };
41
+ export function safePriceColor(value) {
42
+ if (typeof value !== 'string')
43
+ return undefined;
44
+ const color = value.trim();
45
+ return /^(?:#[\da-f]{3,4}|#[\da-f]{6}|#[\da-f]{8}|[a-z]+|(?:rgb|hsl)a?\([\d\s.,%/+-]+\))$/i.test(color)
46
+ ? color
47
+ : undefined;
48
+ }
49
+ export function savedLabelStyle(cssText) {
50
+ if (typeof cssText !== 'string')
51
+ return undefined;
52
+ const style = {};
53
+ for (const declaration of cssText.split(';')) {
54
+ const separator = declaration.indexOf(':');
55
+ if (separator === -1)
56
+ continue;
57
+ const property = declaration.slice(0, separator).trim().toLowerCase();
58
+ const key = Object.hasOwn(CSS_PROPERTIES, property)
59
+ ? CSS_PROPERTIES[property]
60
+ : undefined;
61
+ const value = declaration.slice(separator + 1).trim();
62
+ // Only simple values and color functions: no URLs, escapes, custom variables,
63
+ // !important, external resources, positioning or arbitrary CSS functions.
64
+ if (!key ||
65
+ !value ||
66
+ !/^[#a-z\d\s.,%()/+-]+$/i.test(value) ||
67
+ /[a-z-]+\s*\(/i.test(value.replace(/(?:rgb|hsl)a?\([\d\s.,%/+-]+\)/gi, '')))
68
+ continue;
69
+ style[key] = value;
70
+ }
71
+ return Object.keys(style).length ? style : undefined;
72
+ }
73
+ /** Presentational only: product cards never initiate requests. */
74
+ export function DiscountDepotPricePreview({ variant, discount, children, locale, className, }) {
75
+ if (!isValidDiscount(discount, variant))
76
+ return _jsx(_Fragment, { children: children });
77
+ const formatter = new Intl.NumberFormat(locale ?? 'en', {
78
+ style: 'currency',
79
+ currency: discount.currencyCode,
80
+ });
81
+ const label = (typeof discount.ruleLabel === 'string' && discount.ruleLabel) ||
82
+ (typeof discount.message === 'string' ? discount.message : '');
83
+ const color = safePriceColor(discount.priceColor);
84
+ return (_jsxs("div", { className: className ?? 'discount-depot-price', "aria-label": "Price", role: "group", "aria-live": "polite", children: [discount.showOriginalPrice === true ? (_jsxs(_Fragment, { children: [_jsx("s", { children: formatter.format(Number(discount.originalPrice)) }), ' '] })) : null, _jsx("strong", { style: color ? { color } : undefined, children: formatter.format(Number(discount.displayPrice)) }), label ? (_jsx("p", { style: savedLabelStyle(discount.ruleLabelCss), children: label })) : null] }));
85
+ }
@@ -0,0 +1,2 @@
1
+ /** Drop personalized preview caches when a shopper returns after login/logout. */
2
+ export declare function usePreviewSessionRevision(): number;
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react';
2
+ /** Drop personalized preview caches when a shopper returns after login/logout. */
3
+ export function usePreviewSessionRevision() {
4
+ const [revision, setRevision] = useState(0);
5
+ useEffect(() => {
6
+ if (typeof window === 'undefined' || typeof document === 'undefined')
7
+ return;
8
+ const refresh = () => setRevision(value => value + 1);
9
+ const visible = () => { if (document.visibilityState === 'visible')
10
+ refresh(); };
11
+ window.addEventListener('focus', refresh);
12
+ window.addEventListener('pageshow', refresh);
13
+ document.addEventListener('visibilitychange', visible);
14
+ return () => {
15
+ window.removeEventListener('focus', refresh);
16
+ window.removeEventListener('pageshow', refresh);
17
+ document.removeEventListener('visibilitychange', visible);
18
+ };
19
+ }, []);
20
+ return revision;
21
+ }
@@ -0,0 +1,31 @@
1
+ export { DiscountDepotBuyXGetY } from './buy-x-get-y.js';
2
+ export type { DepotBuyXGetYOffer } from './buy-x-get-y-data.js';
3
+ export { DiscountDepotVolumeTable } from './volume-table.js';
4
+ export { DiscountDepotCartGoal, DiscountDepotCartGoalPreview } from './cart-goal.js';
5
+ export type { DepotCartGoal, GoalCart } from './cart-goal-data.js';
6
+ export { DiscountDepotCountdown } from './countdown.js';
7
+ export type { DepotCountdown } from './countdown.js';
8
+ export type { DiscountDepotVolumeTableProps } from './volume-table.js';
9
+ export type { DepotVolumeTable } from './volume.js';
10
+ export { DiscountDepotPricePreview, savedLabelStyle, safePriceColor, } from './presentation.js';
11
+ export { DiscountDepotCollection } from './collection.js';
12
+ import { type ReactNode } from 'react';
13
+ export interface DiscountDepotPriceProps {
14
+ variant?: {
15
+ id: string;
16
+ price: {
17
+ amount: string;
18
+ currencyCode: string;
19
+ };
20
+ } | null;
21
+ /** Your existing price UI, used while loading or if no automatic discount applies. */
22
+ children: ReactNode;
23
+ /** Override only when mounting the resource route at a different path. */
24
+ endpoint?: string;
25
+ locale?: string;
26
+ className?: string;
27
+ /** Fetch and render volume tiers with the same PDP request. */
28
+ showVolumeTable?: boolean;
29
+ showBuyXGetY?: boolean;
30
+ }
31
+ export declare function DiscountDepotPrice({ variant, children, endpoint, locale, className, showVolumeTable, showBuyXGetY, }: DiscountDepotPriceProps): import("react").JSX.Element;
package/dist/react.js ADDED
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { usePreviewSessionRevision } from './preview-session.js';
3
+ import { DiscountDepotBuyXGetY } from './buy-x-get-y.js';
4
+ import { normalizeBuyXGetY } from './buy-x-get-y-data.js';
5
+ export { DiscountDepotBuyXGetY } from './buy-x-get-y.js';
6
+ import { DiscountDepotPricePreview } from './presentation.js';
7
+ import { DiscountDepotVolumeTable } from './volume-table.js';
8
+ import { DiscountDepotCountdown } from './countdown.js';
9
+ import { normalizeVolumeTable } from './volume.js';
10
+ export { DiscountDepotVolumeTable } from './volume-table.js';
11
+ export { DiscountDepotCartGoal, DiscountDepotCartGoalPreview } from './cart-goal.js';
12
+ export { DiscountDepotCountdown } from './countdown.js';
13
+ export { DiscountDepotPricePreview, savedLabelStyle, safePriceColor, } from './presentation.js';
14
+ export { DiscountDepotCollection } from './collection.js';
15
+ import { useEffect, useState } from 'react';
16
+ import { useLocation } from 'react-router';
17
+ export function DiscountDepotPrice({ variant, children, endpoint, locale, className, showVolumeTable = false, showBuyXGetY = false, }) {
18
+ const { pathname } = useLocation();
19
+ const id = variant?.id;
20
+ const amount = variant?.price.amount;
21
+ const currency = variant?.price.currencyCode;
22
+ const prefix = pathname.match(/^\/[a-z]{2}-[a-z]{2}(?=\/|$)/i)?.[0] ?? '';
23
+ const url = endpoint ?? `${prefix}/api/discount-depot`;
24
+ const sessionRevision = usePreviewSessionRevision();
25
+ const key = JSON.stringify([id, amount, currency, url, showVolumeTable, showBuyXGetY, sessionRevision]);
26
+ const [result, setResult] = useState(null);
27
+ useEffect(() => {
28
+ if (!id || !amount || !currency)
29
+ return;
30
+ const controller = new AbortController();
31
+ // Keep the request in the same market as a localized Hydrogen PDP.
32
+ let active = true;
33
+ setResult(null);
34
+ const timeout = setTimeout(() => controller.abort(), 15000);
35
+ void (async () => {
36
+ try {
37
+ const response = await fetch(url, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({
41
+ variantId: id,
42
+ ...(showVolumeTable ? { includeVolumeTable: true } : {}),
43
+ ...(showBuyXGetY ? { includeBuyXGetY: true } : {}),
44
+ }),
45
+ signal: controller.signal,
46
+ });
47
+ if (!response.ok)
48
+ return;
49
+ const data = await response.json();
50
+ if (!active || data?.variantId !== id)
51
+ return;
52
+ const discount = data.discount;
53
+ const buyXGetY = showBuyXGetY ? normalizeBuyXGetY(data.buyXGetY, currency) : [];
54
+ const volumeTable = showVolumeTable
55
+ ? normalizeVolumeTable(data.volumeTable, {
56
+ amount,
57
+ currencyCode: currency,
58
+ })
59
+ : null;
60
+ if (active &&
61
+ data.variantId === id &&
62
+ discount?.eligible &&
63
+ discount.currencyCode === currency &&
64
+ Number(discount.originalPrice) === Number(amount) &&
65
+ Number.isFinite(Number(discount.displayPrice)) &&
66
+ Number(discount.displayPrice) >= 0 &&
67
+ Number(discount.displayPrice) <= Number(amount)) {
68
+ setResult({ key, discount, volumeTable, buyXGetY });
69
+ }
70
+ else {
71
+ setResult({ key, discount: null, volumeTable, buyXGetY });
72
+ }
73
+ }
74
+ catch {
75
+ // Preserve the merchant's normal price when a request fails or is cancelled.
76
+ }
77
+ finally {
78
+ clearTimeout(timeout);
79
+ }
80
+ })();
81
+ return () => {
82
+ active = false;
83
+ controller.abort();
84
+ clearTimeout(timeout);
85
+ };
86
+ }, [id, amount, currency, url, key, showVolumeTable, showBuyXGetY]);
87
+ const discount = result?.key === key ? result.discount : null;
88
+ return (_jsxs(_Fragment, { children: [_jsx(DiscountDepotPricePreview, { variant: variant, discount: discount, locale: locale, className: className, children: children }), _jsx(DiscountDepotCountdown, { countdown: discount?.countdown }), showBuyXGetY && currency && _jsx(DiscountDepotBuyXGetY, { allowProductActions: true, offers: result?.key === key ? result.buyXGetY : [], currencyCode: currency, locale: locale }), showVolumeTable && (_jsx(DiscountDepotVolumeTable, { variant: variant, table: result?.key === key ? result.volumeTable : null, locale: locale }))] }));
89
+ }
@@ -0,0 +1,53 @@
1
+ import type { Storefront } from '@shopify/hydrogen';
2
+ export type { DepotBuyXGetYOffer } from './buy-x-get-y-data.js';
3
+ export { createDiscountDepotCartGoalRoute } from './cart-goal-server.js';
4
+ export type { DepotCartGoal } from './cart-goal-data.js';
5
+ import { type DepotCountdown } from './countdown-data.js';
6
+ import { type CustomerIdentityContext, type CustomerPreviewCredentials } from './customer-identity.js';
7
+ export type { DepotVolumeTable } from './volume.js';
8
+ export interface DiscountContext extends CustomerIdentityContext {
9
+ storefront: Storefront;
10
+ }
11
+ /** All stores use the endpoint shipped in this package. */
12
+ export declare function createDiscountDepotRoute({ apiUrl, batchApiUrl, }?: {
13
+ apiUrl?: string;
14
+ batchApiUrl?: string;
15
+ }): {
16
+ loader: (args?: {
17
+ request: Request;
18
+ }) => Response;
19
+ action: ({ request, context, }: {
20
+ request: Request;
21
+ context: DiscountContext;
22
+ }) => Promise<Response>;
23
+ };
24
+ export interface DiscountInput {
25
+ productId: string;
26
+ variantId: string;
27
+ price: string;
28
+ currencyCode: string;
29
+ country: string;
30
+ }
31
+ export interface DepotDiscount {
32
+ countdown?: DepotCountdown | null;
33
+ eligible: boolean;
34
+ originalPrice: string;
35
+ displayPrice: string;
36
+ currencyCode: string;
37
+ message?: string;
38
+ ruleLabel?: string;
39
+ ruleLabelCss?: string;
40
+ priceColor?: string;
41
+ showOriginalPrice?: boolean;
42
+ }
43
+ /** Read-only price preview. Shopify remains responsible for cart/checkout totals. */
44
+ export declare function getDepotDiscount(shop: string, input: DiscountInput, apiUrl?: string): Promise<DepotDiscount | null>;
45
+ export declare function getDepotProductPreview(shop: string, input: DiscountInput, apiUrl?: string, includeVolumeTable?: boolean, credentials?: CustomerPreviewCredentials, includeBuyXGetY?: boolean): Promise<{
46
+ discount: DepotDiscount | null;
47
+ volumeTable: import("./volume.js").DepotVolumeTable | null;
48
+ buyXGetY: import("./buy-x-get-y-data.js").DepotBuyXGetYOffer[];
49
+ }>;
50
+ export declare function getDepotDiscounts(shop: string, inputs: DiscountInput[], apiUrl?: string, credentials?: CustomerPreviewCredentials): Promise<{
51
+ variantId: string;
52
+ discount: DepotDiscount | null;
53
+ }[]>;