create-brainerce-store 1.72.0 → 1.74.0

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 (88) hide show
  1. package/dist/index.js +37 -4
  2. package/messages/en.json +30 -1
  3. package/messages/he.json +30 -1
  4. package/package.json +1 -1
  5. package/templates/nextjs/base/AGENTS.md.ejs +63 -16
  6. package/templates/nextjs/base/AI-GUIDE.md +17 -4
  7. package/templates/nextjs/base/CLAUDE.md.ejs +67 -20
  8. package/templates/nextjs/base/TRANSLATIONS.md +207 -207
  9. package/templates/nextjs/base/src/app/account/page.tsx +168 -122
  10. package/templates/nextjs/base/src/app/category/[slug]/page.tsx +10 -1
  11. package/templates/nextjs/base/src/app/checkout/page.tsx +1179 -1074
  12. package/templates/nextjs/base/src/app/layout.tsx.ejs +48 -4
  13. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +98 -3
  14. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +14 -2
  15. package/templates/nextjs/base/src/components/account/loyalty-panel.tsx +226 -0
  16. package/templates/nextjs/base/src/components/account/order-history.tsx +422 -371
  17. package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +85 -85
  18. package/templates/nextjs/base/src/components/account/saved-payment-methods.tsx +104 -0
  19. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +618 -612
  20. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +354 -306
  21. package/templates/nextjs/base/src/components/checkout/tax-display.tsx +141 -74
  22. package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +218 -127
  23. package/templates/nextjs/base/src/core/hooks/use-home-data.ts +63 -51
  24. package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +379 -295
  25. package/templates/nextjs/base/src/core/hooks/use-product-page.ts +33 -1
  26. package/templates/nextjs/base/src/core/lib/capabilities.ts +65 -0
  27. package/templates/nextjs/base/src/core/lib/display-price.ts +139 -0
  28. package/templates/nextjs/base/src/core/lib/region.server.ts +112 -0
  29. package/templates/nextjs/base/src/core/lib/region.ts +112 -0
  30. package/templates/nextjs/base/src/core/lib/store-info.ts +19 -0
  31. package/templates/nextjs/base/src/core/providers/store-provider.tsx.ejs +423 -243
  32. package/templates/nextjs/base/src/ui/cart/cart-item.tsx +174 -164
  33. package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +109 -110
  34. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +219 -176
  35. package/templates/nextjs/base/src/ui/cart/gift-card-input.tsx +191 -0
  36. package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +137 -137
  37. package/templates/nextjs/base/src/ui/cart/tax-estimate-line.tsx +108 -0
  38. package/templates/nextjs/base/src/ui/home/home-client.tsx +27 -29
  39. package/templates/nextjs/base/src/ui/layout/header-search.tsx +249 -0
  40. package/templates/nextjs/base/src/ui/layout/region-switcher.tsx +59 -0
  41. package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +171 -152
  42. package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +197 -191
  43. package/templates/nextjs/base/src/ui/product/product-card.tsx +25 -52
  44. package/templates/nextjs/base/src/ui/product/product-client-section.tsx +12 -3
  45. package/templates/nextjs/base/src/ui/product/product-listing.tsx +460 -446
  46. package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +108 -102
  47. package/templates/nextjs/base/src/ui/product/review-form.tsx +33 -11
  48. package/templates/nextjs/base/src/ui/product/stock-badge.tsx +15 -3
  49. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +48 -4
  50. package/templates/nextjs/designs/atelier/messages-patch/en.json +21 -25
  51. package/templates/nextjs/designs/atelier/messages-patch/he.json +21 -25
  52. package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +177 -181
  53. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +168 -158
  54. package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +106 -107
  55. package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +229 -184
  56. package/templates/nextjs/designs/atelier/ui/cart/reservation-countdown.tsx +131 -131
  57. package/templates/nextjs/designs/atelier/ui/cart/tax-estimate-line.tsx +108 -0
  58. package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +10 -1
  59. package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +178 -174
  60. package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +4 -6
  61. package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +150 -78
  62. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +79 -46
  63. package/templates/nextjs/designs/atelier/ui/layout/header-search.tsx +264 -0
  64. package/templates/nextjs/designs/atelier/ui/layout/region-switcher.tsx +44 -0
  65. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +5 -0
  66. package/templates/nextjs/designs/atelier/ui/product/frequently-bought-together.tsx +202 -196
  67. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +27 -52
  68. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +12 -3
  69. package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +332 -260
  70. package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +101 -95
  71. package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +30 -10
  72. package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +17 -2
  73. package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +9 -0
  74. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +137 -137
  75. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +98 -99
  76. package/templates/nextjs/ui-canvas/cart/cart-view.tsx +140 -140
  77. package/templates/nextjs/ui-canvas/cart/gift-card-input.tsx +142 -0
  78. package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +124 -124
  79. package/templates/nextjs/ui-canvas/cart/tax-estimate-line.tsx +104 -0
  80. package/templates/nextjs/ui-canvas/home/home-client.tsx +24 -26
  81. package/templates/nextjs/ui-canvas/layout/region-switcher.tsx +51 -0
  82. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +3 -0
  83. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +174 -168
  84. package/templates/nextjs/ui-canvas/product/product-card.tsx +9 -51
  85. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +5 -3
  86. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +105 -99
  87. package/templates/nextjs/ui-canvas/product/review-form.tsx +9 -1
  88. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +17 -2
@@ -1,168 +1,174 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
- import type { Product, ProductRecommendation } from 'brainerce';
6
- import { formatPrice } from 'brainerce';
7
- import { useCart, useStoreInfo } from '@/core/providers/store-provider';
8
- import { useCurrency } from '@/core/lib/use-currency';
9
- import { useTranslations } from '@/core/lib/translations';
10
- import { cn } from '@/core/lib/utils';
11
-
12
- interface FrequentlyBoughtTogetherProps {
13
- items: ProductRecommendation[];
14
- currentProduct: Product;
15
- className?: string;
16
- }
17
-
18
- function getEffectivePrice(item: { basePrice: string; salePrice?: string | null }): number {
19
- const sale = item.salePrice ? parseFloat(item.salePrice) : null;
20
- const base = parseFloat(item.basePrice);
21
- return sale != null && sale < base ? sale : base;
22
- }
23
-
24
- function ProductThumb({
25
- name,
26
- imageUrl,
27
- price,
28
- currency,
29
- checked,
30
- onToggle,
31
- disabled,
32
- }: {
33
- name: string;
34
- imageUrl: string | null;
35
- price: number;
36
- currency: string;
37
- checked: boolean;
38
- onToggle?: () => void;
39
- disabled?: boolean;
40
- }) {
41
- return (
42
- <label className="inline-flex items-center gap-2">
43
- {onToggle && <input type="checkbox" checked={checked} onChange={onToggle} disabled={disabled} />}
44
- {/* `relative` + fixed box are layout-critical for next/image fill */}
45
- <span className="relative block h-20 w-20">
46
- {imageUrl ? <Image src={imageUrl} alt={name} fill sizes="80px" /> : null}
47
- </span>
48
- <span>{name}</span>
49
- <span>{formatPrice(price, { currency }) as string}</span>
50
- </label>
51
- );
52
- }
53
-
54
- /**
55
- * DESIGN ME "frequently bought together" bundle on the product page:
56
- * current product + selectable cross-sells + combined total + add-all button;
57
- * items come from useProductPage().recommendations.crossSells, the feature
58
- * gate from useStoreInfo().upsell.
59
- *
60
- * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
61
- */
62
- export function FrequentlyBoughtTogether({
63
- items,
64
- currentProduct,
65
- className,
66
- }: FrequentlyBoughtTogetherProps) {
67
- // Hooks must be called unconditionally and in the same order on every
68
- // render — keep all of them above any early `return null` branch.
69
- const { storeInfo } = useStoreInfo();
70
- const { refreshCart } = useCart();
71
- const t = useTranslations('productDetail');
72
- const currency = useCurrency();
73
-
74
- // Only show up to 3 cross-sells
75
- const crossSells = items.slice(0, 3);
76
-
77
- const [selected, setSelected] = useState<Set<string>>(() => new Set(crossSells.map((i) => i.id)));
78
- const [adding, setAdding] = useState(false);
79
-
80
- if (!storeInfo?.upsell?.frequentlyBoughtTogetherEnabled) return null;
81
- if (crossSells.length === 0) return null;
82
-
83
- const currentPrice = getEffectivePrice(currentProduct);
84
- const currentImage = currentProduct.images?.[0];
85
- const currentImageUrl = currentImage
86
- ? typeof currentImage === 'string'
87
- ? currentImage
88
- : currentImage.url
89
- : null;
90
-
91
- const totalPrice = crossSells
92
- .filter((item) => selected.has(item.id))
93
- .reduce((sum, item) => sum + getEffectivePrice(item), currentPrice);
94
-
95
- const toggleItem = (id: string) => {
96
- setSelected((prev) => {
97
- const next = new Set(prev);
98
- if (next.has(id)) {
99
- next.delete(id);
100
- } else {
101
- next.add(id);
102
- }
103
- return next;
104
- });
105
- };
106
-
107
- async function handleAddAll() {
108
- if (adding || selected.size === 0) return;
109
- try {
110
- setAdding(true);
111
- const { getClient } = await import('@/core/lib/brainerce');
112
- const client = getClient();
113
- const selectedItems = crossSells.filter((item) => selected.has(item.id));
114
- for (const item of selectedItems) {
115
- await client.smartAddToCart({ productId: item.id, quantity: 1 });
116
- }
117
- await refreshCart();
118
- } catch (err) {
119
- console.error('Failed to add items to cart:', err);
120
- } finally {
121
- setAdding(false);
122
- }
123
- }
124
-
125
- return (
126
- <section className={cn(className)}>
127
- <h2>{t('frequentlyBoughtTogether')}</h2>
128
-
129
- <div className="flex flex-wrap items-center gap-3">
130
- {/* Current product (always included, no checkbox) */}
131
- <ProductThumb
132
- name={currentProduct.name}
133
- imageUrl={currentImageUrl}
134
- price={currentPrice}
135
- currency={currency}
136
- checked={true}
137
- disabled
138
- />
139
-
140
- {crossSells.map((item) => {
141
- const img = item.images?.[0];
142
- const imgUrl = img ? (typeof img === 'string' ? img : img.url) : null;
143
- return (
144
- <div key={item.id} className="flex items-center gap-3">
145
- <span aria-hidden="true">+</span>
146
- <ProductThumb
147
- name={item.name}
148
- imageUrl={imgUrl}
149
- price={getEffectivePrice(item)}
150
- currency={currency}
151
- checked={selected.has(item.id)}
152
- onToggle={() => toggleItem(item.id)}
153
- />
154
- </div>
155
- );
156
- })}
157
- </div>
158
-
159
- {/* Total + Add button */}
160
- <div className="flex flex-wrap items-center gap-4">
161
- <span>{t('totalPrice', { price: formatPrice(totalPrice, { currency }) as string })}</span>
162
- <button type="button" onClick={handleAddAll} disabled={adding || selected.size === 0}>
163
- {adding ? t('addingAll') : t('addSelectedToCart')}
164
- </button>
165
- </div>
166
- </section>
167
- );
168
- }
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { CdnImage as Image } from '@/ui/shared/cdn-image';
5
+ import type { Product, ProductRecommendation } from 'brainerce';
6
+ import { formatPrice } from 'brainerce';
7
+ import { useCart, useStoreInfo } from '@/core/providers/store-provider';
8
+ import { useCurrency } from '@/core/lib/use-currency';
9
+ import { useTranslations } from '@/core/lib/translations';
10
+ import { cn } from '@/core/lib/utils';
11
+
12
+ interface FrequentlyBoughtTogetherProps {
13
+ items: ProductRecommendation[];
14
+ currentProduct: Product;
15
+ className?: string;
16
+ }
17
+
18
+ // STORE CURRENCY THROUGHOUT, deliberately. The cross-sells are
19
+ // `ProductRecommendation`, a trimmed shape with no displayPrice/displayCurrency,
20
+ // so there is nothing to convert them to. Converting only `currentProduct`
21
+ // (which is a full Product and does carry FX fields) would add a euro amount to
22
+ // two dollar amounts and print the result as one total. One currency wins, and
23
+ // it has to be the one the cart actually charges.
24
+ function getEffectivePrice(item: { basePrice: string; salePrice?: string | null }): number {
25
+ const sale = item.salePrice ? parseFloat(item.salePrice) : null;
26
+ const base = parseFloat(item.basePrice);
27
+ return sale != null && sale < base ? sale : base;
28
+ }
29
+
30
+ function ProductThumb({
31
+ name,
32
+ imageUrl,
33
+ price,
34
+ currency,
35
+ checked,
36
+ onToggle,
37
+ disabled,
38
+ }: {
39
+ name: string;
40
+ imageUrl: string | null;
41
+ price: number;
42
+ currency: string;
43
+ checked: boolean;
44
+ onToggle?: () => void;
45
+ disabled?: boolean;
46
+ }) {
47
+ return (
48
+ <label className="inline-flex items-center gap-2">
49
+ {onToggle && <input type="checkbox" checked={checked} onChange={onToggle} disabled={disabled} />}
50
+ {/* `relative` + fixed box are layout-critical for next/image fill */}
51
+ <span className="relative block h-20 w-20">
52
+ {imageUrl ? <Image src={imageUrl} alt={name} fill sizes="80px" /> : null}
53
+ </span>
54
+ <span>{name}</span>
55
+ <span>{formatPrice(price, { currency }) as string}</span>
56
+ </label>
57
+ );
58
+ }
59
+
60
+ /**
61
+ * DESIGN ME — "frequently bought together" bundle on the product page:
62
+ * current product + selectable cross-sells + combined total + add-all button;
63
+ * items come from useProductPage().recommendations.crossSells, the feature
64
+ * gate from useStoreInfo().upsell.
65
+ *
66
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
67
+ */
68
+ export function FrequentlyBoughtTogether({
69
+ items,
70
+ currentProduct,
71
+ className,
72
+ }: FrequentlyBoughtTogetherProps) {
73
+ // Hooks must be called unconditionally and in the same order on every
74
+ // render keep all of them above any early `return null` branch.
75
+ const { storeInfo } = useStoreInfo();
76
+ const { refreshCart } = useCart();
77
+ const t = useTranslations('productDetail');
78
+ const currency = useCurrency();
79
+
80
+ // Only show up to 3 cross-sells
81
+ const crossSells = items.slice(0, 3);
82
+
83
+ const [selected, setSelected] = useState<Set<string>>(() => new Set(crossSells.map((i) => i.id)));
84
+ const [adding, setAdding] = useState(false);
85
+
86
+ if (!storeInfo?.upsell?.frequentlyBoughtTogetherEnabled) return null;
87
+ if (crossSells.length === 0) return null;
88
+
89
+ const currentPrice = getEffectivePrice(currentProduct);
90
+ const currentImage = currentProduct.images?.[0];
91
+ const currentImageUrl = currentImage
92
+ ? typeof currentImage === 'string'
93
+ ? currentImage
94
+ : currentImage.url
95
+ : null;
96
+
97
+ const totalPrice = crossSells
98
+ .filter((item) => selected.has(item.id))
99
+ .reduce((sum, item) => sum + getEffectivePrice(item), currentPrice);
100
+
101
+ const toggleItem = (id: string) => {
102
+ setSelected((prev) => {
103
+ const next = new Set(prev);
104
+ if (next.has(id)) {
105
+ next.delete(id);
106
+ } else {
107
+ next.add(id);
108
+ }
109
+ return next;
110
+ });
111
+ };
112
+
113
+ async function handleAddAll() {
114
+ if (adding || selected.size === 0) return;
115
+ try {
116
+ setAdding(true);
117
+ const { getClient } = await import('@/core/lib/brainerce');
118
+ const client = getClient();
119
+ const selectedItems = crossSells.filter((item) => selected.has(item.id));
120
+ for (const item of selectedItems) {
121
+ await client.smartAddToCart({ productId: item.id, quantity: 1 });
122
+ }
123
+ await refreshCart();
124
+ } catch (err) {
125
+ console.error('Failed to add items to cart:', err);
126
+ } finally {
127
+ setAdding(false);
128
+ }
129
+ }
130
+
131
+ return (
132
+ <section className={cn(className)}>
133
+ <h2>{t('frequentlyBoughtTogether')}</h2>
134
+
135
+ <div className="flex flex-wrap items-center gap-3">
136
+ {/* Current product (always included, no checkbox) */}
137
+ <ProductThumb
138
+ name={currentProduct.name}
139
+ imageUrl={currentImageUrl}
140
+ price={currentPrice}
141
+ currency={currency}
142
+ checked={true}
143
+ disabled
144
+ />
145
+
146
+ {crossSells.map((item) => {
147
+ const img = item.images?.[0];
148
+ const imgUrl = img ? (typeof img === 'string' ? img : img.url) : null;
149
+ return (
150
+ <div key={item.id} className="flex items-center gap-3">
151
+ <span aria-hidden="true">+</span>
152
+ <ProductThumb
153
+ name={item.name}
154
+ imageUrl={imgUrl}
155
+ price={getEffectivePrice(item)}
156
+ currency={currency}
157
+ checked={selected.has(item.id)}
158
+ onToggle={() => toggleItem(item.id)}
159
+ />
160
+ </div>
161
+ );
162
+ })}
163
+ </div>
164
+
165
+ {/* Total + Add button */}
166
+ <div className="flex flex-wrap items-center gap-4">
167
+ <span>{t('totalPrice', { price: formatPrice(totalPrice, { currency }) as string })}</span>
168
+ <button type="button" onClick={handleAddAll} disabled={adding || selected.size === 0}>
169
+ {adding ? t('addingAll') : t('addSelectedToCart')}
170
+ </button>
171
+ </div>
172
+ </section>
173
+ );
174
+ }
@@ -4,7 +4,8 @@ import { useState } from 'react';
4
4
  import { Link, useRouter } from '@/core/lib/navigation';
5
5
  import { CdnImage as Image } from '@/ui/shared/cdn-image';
6
6
  import type { Product } from 'brainerce';
7
- import { getProductPriceInfo, getVariantPrice, formatPrice } from 'brainerce';
7
+ import { getProductPriceInfo, formatPrice } from 'brainerce';
8
+ import { pickDisplayPrice, pickDisplayPriceRange } from '@/core/lib/display-price';
8
9
  import { useTranslations } from '@/core/lib/translations';
9
10
  import { PriceDisplay } from '@/ui/product/price-display';
10
11
  import { StockBadge } from '@/ui/product/stock-badge';
@@ -19,57 +20,14 @@ interface ProductCardProps {
19
20
  className?: string;
20
21
  }
21
22
 
22
- /**
23
- * Pick the price + currency to render for a given product (PRD §23 FX overlay).
24
- *
25
- * When the storefront passed `regionId` to `getProducts({ regionId })` AND the
26
- * region currency differs from the store currency, the backend attaches
27
- * additive `displayPrice` / `displayCurrency` fields. Otherwise we fall back
28
- * to the canonical store-currency `basePrice` / `salePrice`. Either way the
29
- * cart still charges in the store currency — this only affects display.
30
- */
31
- function pickDisplayPrice(
32
- product: Product,
33
- fallbackCurrency: string
34
- ): { price: number | undefined; salePrice: number | null; currency: string } {
35
- if (product.displayPrice != null && product.displayCurrency) {
36
- const sale = product.displaySalePrice != null ? parseFloat(product.displaySalePrice) : null;
37
- return {
38
- // `displayPrice` is the base price converted to the region currency —
39
- // it goes into the PriceDisplay `price` (base) slot, NOT the sale slot.
40
- price: parseFloat(product.displayPrice),
41
- salePrice: sale != null && !Number.isNaN(sale) ? sale : null,
42
- currency: product.displayCurrency,
43
- };
44
- }
45
- // Same-currency fallback. `getProductPriceInfo.price` is the EFFECTIVE
46
- // charged amount (= salePrice when on sale, base otherwise); `originalPrice`
47
- // is the base. Map them to PriceDisplay's (price = base, salePrice = sale).
48
- const { price: effective, originalPrice, isOnSale } = getProductPriceInfo(product);
49
- return {
50
- price: originalPrice,
51
- salePrice: isOnSale ? effective : null,
52
- currency: fallbackCurrency,
53
- };
54
- }
55
-
56
23
  function VariantPriceRange({ product }: { product: Product }) {
57
- const currency = useCurrency();
58
-
59
- let min: number;
60
- let max: number;
61
- if (product.priceMin && product.priceMax) {
62
- min = parseFloat(product.priceMin);
63
- max = parseFloat(product.priceMax);
64
- } else {
65
- const variants = product.variants ?? [];
66
- if (variants.length === 0) return null;
67
- const prices = variants.map((v) => getVariantPrice(v, product.basePrice));
68
- min = Math.min(...prices);
69
- max = Math.max(...prices);
70
- }
71
-
72
- if (isNaN(min) || isNaN(max)) return null;
24
+ const fallbackCurrency = useCurrency();
25
+ // ⛔ Region-aware. A range built from priceMin/priceMax alone stays in the
26
+ // store currency while the single-price card beside it converts, so a
27
+ // variable product would quote a different currency from its neighbours.
28
+ const range = pickDisplayPriceRange(product, fallbackCurrency);
29
+ if (!range) return null;
30
+ const { min, max, currency } = range;
73
31
 
74
32
  return (
75
33
  <span>
@@ -98,6 +98,7 @@ export function ProductClientSection({
98
98
  selectedVariant,
99
99
  setSelectedVariant,
100
100
  priceInfo,
101
+ displayPrice,
101
102
  inventory,
102
103
  canPurchase,
103
104
  description,
@@ -203,8 +204,9 @@ export function ProductClientSection({
203
204
 
204
205
  {/* Price */}
205
206
  <PriceDisplay
206
- price={priceInfo.originalPrice}
207
- salePrice={priceInfo.isOnSale ? priceInfo.price : undefined}
207
+ price={displayPrice.price}
208
+ salePrice={displayPrice.salePrice ?? undefined}
209
+ currency={displayPrice.currency}
208
210
  size="lg"
209
211
  />
210
212
 
@@ -212,7 +214,7 @@ export function ProductClientSection({
212
214
  {product.isDownloadable ? (
213
215
  <p>{t('instantDownload')}</p>
214
216
  ) : (
215
- <StockBadge inventory={inventory} lowStockThreshold={5} />
217
+ <StockBadge inventory={inventory} />
216
218
  )}
217
219
 
218
220
  {/* Downloadable files info */}