@cartbase/storefront 0.7.0 → 0.9.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.
package/src/api/http.ts CHANGED
@@ -4,9 +4,11 @@
4
4
  * Every SDK function in this package is a thin typed wrapper over
5
5
  * `storeFetch()` from a `StorefrontClient`. Auth model (store-api.md):
6
6
  *
7
- * - `x-client-id` required for anon reads (RLS scope)
8
- * - `x-publishable-api-key` optional; scopes catalog/carts to the key's
9
- * sales channels when set
7
+ * - `x-publishable-api-key` THE store's key: names the store on its own
8
+ * (one key, 2026-09-07) and scopes catalog and
9
+ * carts to the key's sales channels
10
+ * - `x-client-id` — the platform's own door (hosted builds carry it);
11
+ * optional, wins over the key when both are sent
10
12
  * - `authorization: Bearer <jwt>` — customer session (passwordless code
11
13
  * flow or supabase password login)
12
14
  * - `x-locale` — optional storefront locale hint
@@ -19,12 +21,16 @@
19
21
  import { StoreApiError } from "./types"
20
22
 
21
23
  export interface StorefrontClientConfig {
22
- /** Deployment origin, e.g. `https://admin.mindpages.bg` */
24
+ /** Deployment origin, e.g. `https://admin.cartbase.ai` */
23
25
  baseUrl: string
24
- /** Tenant id — required for anon reads. */
25
- clientId: string
26
- /** Publishable API key (channel scope). Optional for single-channel stores. */
26
+ /**
27
+ * The store's publishable key: names the store and scopes the catalog to
28
+ * the key's sales channels. The one input a merchant hands out; at least
29
+ * one of `publishableKey` and `clientId` is required.
30
+ */
27
31
  publishableKey?: string
32
+ /** Tenant id — the platform's own door. Optional when a key is given. */
33
+ clientId?: string
28
34
  /** Called per request; return the customer JWT or null for guests. */
29
35
  getAuthToken?: () => string | null | Promise<string | null>
30
36
  /** Called per request; return the active locale code or null. */
@@ -50,7 +56,9 @@ export interface RequestOptions {
50
56
  export class StorefrontClient {
51
57
  constructor(private readonly config: StorefrontClientConfig) {
52
58
  if (!config.baseUrl) throw new Error("StorefrontClient: baseUrl is required")
53
- if (!config.clientId) throw new Error("StorefrontClient: clientId is required")
59
+ if (!config.publishableKey && !config.clientId) {
60
+ throw new Error("StorefrontClient: publishableKey (or clientId) is required")
61
+ }
54
62
  }
55
63
 
56
64
  /** The low-level typed request. Domain modules call this — apps rarely should. */
@@ -72,7 +80,7 @@ export class StorefrontClient {
72
80
  }
73
81
 
74
82
  const headers: Record<string, string> = {
75
- "x-client-id": this.config.clientId,
83
+ ...(this.config.clientId ? { "x-client-id": this.config.clientId } : {}),
76
84
  ...(this.config.publishableKey
77
85
  ? { "x-publishable-api-key": this.config.publishableKey }
78
86
  : {}),
package/src/api/index.ts CHANGED
@@ -13,6 +13,7 @@ export * as products from "./products"
13
13
  export * as collections from "./collections"
14
14
  export * as categories from "./categories"
15
15
  export * as regions from "./regions"
16
+ export * as store from "./store"
16
17
  export * as carts from "./carts"
17
18
  export * as giftCards from "./gift-cards"
18
19
  export * as checkout from "./checkout"
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @cartbase/storefront/api/store — the store's identity.
3
+ *
4
+ * Name, slug and brand (the merchant's Settings → Brand): what a
5
+ * storefront needs to title, head and foot itself without hardcoding a
6
+ * name. One anonymous read at layout level. Docs: docs/storefront/store.md.
7
+ */
8
+ import type { StorefrontClient } from "./http"
9
+
10
+ export interface StoreBrand {
11
+ /** Primary logo, public URL, or null. */
12
+ logo_url: string | null
13
+ /** Square mark, public URL, or null. */
14
+ logo_square_url: string | null
15
+ /** `#rrggbb` or null. */
16
+ color_primary: string | null
17
+ /** `#rrggbb` or null. */
18
+ color_secondary: string | null
19
+ slogan: string | null
20
+ }
21
+
22
+ export interface StoreIdentity {
23
+ /** The store's public id: what `<PlatformInit storeId>` mounts. */
24
+ id: string
25
+ /** The store's display name. */
26
+ name: string
27
+ /** The store's slug; its address is `{slug}.cartbase.net` until a custom domain. */
28
+ slug: string
29
+ brand: StoreBrand
30
+ }
31
+
32
+ /** GET /api/store/store — anon. */
33
+ export function getStore(client: StorefrontClient): Promise<{ store: StoreIdentity }> {
34
+ return client.request<{ store: StoreIdentity }>("/api/store/store")
35
+ }
@@ -4,7 +4,7 @@ import Image from "next/image"
4
4
  import Link from "next/link"
5
5
  import { useRef, useState } from "react"
6
6
 
7
- import { DualPrice } from "../lib/dual-price"
7
+ import { Price } from "../lib/price"
8
8
  import { cn } from "../lib/utils"
9
9
  import { useCartDrawer } from "./context"
10
10
 
@@ -178,11 +178,10 @@ export function CartCrossSellCarousel({
178
178
  >
179
179
  {product.title}
180
180
  </Link>
181
- <DualPrice
181
+ <Price
182
182
  amount={product.price}
183
183
  currencyCode={product.currencyCode}
184
184
  className="text-[13px] font-bold text-foreground block mt-0.5"
185
- bgnClassName="text-muted-foreground text-[10px] ml-1"
186
185
  />
187
186
  </div>
188
187
 
@@ -1,83 +1,82 @@
1
- "use client"
2
-
3
- import { useState } from "react"
4
-
5
- import { DualPrice } from "../lib/dual-price"
6
- import { cn } from "../lib/utils"
7
- import { useCartDrawer } from "./context"
8
-
9
- /**
10
- * CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
11
- *
12
- * Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
13
- * originally extracted from mindpages-storefront
14
- * src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
15
- * The store owns the effect: wire `onToggle` to add/remove the store's
16
- * gift-wrap variant via the provider's `addItem` / `removeItem`.
17
- */
18
-
19
- type CartGiftWrapProps = {
20
- price: number
21
- currencyCode: string
22
- enabled?: boolean
23
- onToggle?: (enabled: boolean) => void
24
- }
25
-
26
- export function CartGiftWrap({
27
- price,
28
- currencyCode,
29
- enabled = false,
30
- onToggle,
31
- }: CartGiftWrapProps) {
32
- const { labels } = useCartDrawer()
33
- const [checked, setChecked] = useState(enabled)
34
-
35
- const handleToggle = () => {
36
- const next = !checked
37
- setChecked(next)
38
- onToggle?.(next)
39
- }
40
-
41
- return (
42
- <div className="px-5 sm:px-6 py-2">
43
- <button
44
- type="button"
45
- onClick={handleToggle}
46
- className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
47
- >
48
- <div
49
- className={cn(
50
- "w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
51
- checked
52
- ? "bg-foreground border-text-base"
53
- : "border-border group-hover:border-text-muted group-active:border-text-muted"
54
- )}
55
- >
56
- {checked && (
57
- <svg
58
- width="10"
59
- height="10"
60
- viewBox="0 0 10 10"
61
- fill="none"
62
- stroke="white"
63
- strokeWidth="2"
64
- strokeLinecap="round"
65
- strokeLinejoin="round"
66
- >
67
- <path d="M2 5l2 2 4-4" />
68
- </svg>
69
- )}
70
- </div>
71
- <span className="text-[13px] text-foreground flex-1">
72
- {labels.addGiftWrap}
73
- </span>
74
- <DualPrice
75
- amount={price}
76
- currencyCode={currencyCode}
77
- className="text-[13px] text-muted-foreground font-medium"
78
- bgnClassName="text-muted-foreground text-[10px] ml-1"
79
- />
80
- </button>
81
- </div>
82
- )
83
- }
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+
5
+ import { Price } from "../lib/price"
6
+ import { cn } from "../lib/utils"
7
+ import { useCartDrawer } from "./context"
8
+
9
+ /**
10
+ * CartGiftWrap — inline toggle row for adding gift wrapping to the cart.
11
+ *
12
+ * Ported from `@1click/ui/src/cart-drawer/gift-wrap.tsx` (v2.3.1);
13
+ * originally extracted from mindpages-storefront
14
+ * src/modules/cart-drawer/cart-gift-wrap.tsx. `price` is EUR major units.
15
+ * The store owns the effect: wire `onToggle` to add/remove the store's
16
+ * gift-wrap variant via the provider's `addItem` / `removeItem`.
17
+ */
18
+
19
+ type CartGiftWrapProps = {
20
+ price: number
21
+ currencyCode: string
22
+ enabled?: boolean
23
+ onToggle?: (enabled: boolean) => void
24
+ }
25
+
26
+ export function CartGiftWrap({
27
+ price,
28
+ currencyCode,
29
+ enabled = false,
30
+ onToggle,
31
+ }: CartGiftWrapProps) {
32
+ const { labels } = useCartDrawer()
33
+ const [checked, setChecked] = useState(enabled)
34
+
35
+ const handleToggle = () => {
36
+ const next = !checked
37
+ setChecked(next)
38
+ onToggle?.(next)
39
+ }
40
+
41
+ return (
42
+ <div className="px-5 sm:px-6 py-2">
43
+ <button
44
+ type="button"
45
+ onClick={handleToggle}
46
+ className="flex items-center gap-3 w-full text-left py-2 min-h-[44px] group"
47
+ >
48
+ <div
49
+ className={cn(
50
+ "w-5 h-5 rounded border-[1.5px] flex items-center justify-center transition-all flex-shrink-0",
51
+ checked
52
+ ? "bg-foreground border-text-base"
53
+ : "border-border group-hover:border-text-muted group-active:border-text-muted"
54
+ )}
55
+ >
56
+ {checked && (
57
+ <svg
58
+ width="10"
59
+ height="10"
60
+ viewBox="0 0 10 10"
61
+ fill="none"
62
+ stroke="white"
63
+ strokeWidth="2"
64
+ strokeLinecap="round"
65
+ strokeLinejoin="round"
66
+ >
67
+ <path d="M2 5l2 2 4-4" />
68
+ </svg>
69
+ )}
70
+ </div>
71
+ <span className="text-[13px] text-foreground flex-1">
72
+ {labels.addGiftWrap}
73
+ </span>
74
+ <Price
75
+ amount={price}
76
+ currencyCode={currencyCode}
77
+ className="text-[13px] text-muted-foreground font-medium"
78
+ />
79
+ </button>
80
+ </div>
81
+ )
82
+ }
@@ -5,7 +5,7 @@ import Link from "next/link"
5
5
  import { type ReactNode } from "react"
6
6
 
7
7
  import type { CartLineItem } from "../../api/carts"
8
- import { DualPrice } from "../../lib/dual-price"
8
+ import { Price } from "../../lib/price"
9
9
  import { cn } from "../../lib/utils"
10
10
  import { useCartDrawer } from "../context"
11
11
  import { CartItemQuantity } from "./quantity"
@@ -13,7 +13,7 @@ import { CartItemVariant } from "./variant"
13
13
 
14
14
  /**
15
15
  * CartItem — a single cart line row: thumbnail, title, variant, quantity
16
- * stepper, price (with dual-currency + optional strikethrough), remove button.
16
+ * stepper, price (with optional strikethrough), remove button.
17
17
  *
18
18
  * Ported from `@1click/ui/src/cart-drawer/item/index.tsx` (v2.3.1). Cartbase
19
19
  * data seam: `item` is the SDK's decorated `CartLineItem`
@@ -111,22 +111,20 @@ export function CartItem({ item, currencyCode, children }: CartItemProps) {
111
111
  <div className="text-right flex-shrink-0">
112
112
  {hasDiscount && (
113
113
  <span className="text-xs text-muted-foreground line-through block leading-none mb-0.5">
114
- <DualPrice
114
+ <Price
115
115
  amount={originalTotal}
116
116
  currencyCode={currencyCode}
117
117
  className="text-xs text-muted-foreground"
118
- bgnClassName="hidden"
119
118
  />
120
119
  </span>
121
120
  )}
122
- <DualPrice
121
+ <Price
123
122
  amount={total}
124
123
  currencyCode={currencyCode}
125
124
  className={cn(
126
125
  "text-sm font-bold",
127
126
  hasDiscount ? "text-success" : "text-foreground"
128
127
  )}
129
- bgnClassName="text-muted-foreground text-[10px] ml-1"
130
128
  />
131
129
  </div>
132
130
  </div>
@@ -1,73 +1,73 @@
1
- "use client"
2
-
3
- import Link from "next/link"
4
-
5
- import { DualPrice } from "../lib/dual-price"
6
- import { productTotal } from "../lib/cart-helpers"
7
- import { useCartDrawer } from "./context"
8
-
9
- /**
10
- * CartStickyFooter — bottom-of-drawer subtotal + checkout CTA with
11
- * iPhone home-indicator safe-area padding.
12
- *
13
- * Ported from `@1click/ui/src/cart-drawer/sticky-footer.tsx` (v2.3.1);
14
- * originally extracted from mindpages-storefront
15
- * src/modules/cart-drawer/cart-sticky-footer.tsx. Amounts EUR major units.
16
- */
17
-
18
- export function CartStickyFooter() {
19
- const { cart, close, labels, hrefs } = useCartDrawer()
20
- if (!cart || !cart.items?.length) return null
21
-
22
- // Drawer is pre-checkout context — never read `cart.total` here.
23
- // `cart.total` includes shipping/tax/COD fee, all of which are
24
- // checkout-context state that must not leak into the shopping
25
- // drawer. Sum of product line totals only.
26
- const total = productTotal(cart.items)
27
- const currencyCode = cart.currency_code
28
-
29
- return (
30
- <div
31
- className="flex-shrink-0 bg-card px-5 sm:px-6 pt-4 sm:pt-5 relative z-10"
32
- style={{
33
- boxShadow: "0 -4px 12px rgba(0,0,0,0.04)",
34
- paddingBottom: "max(1.25rem, env(safe-area-inset-bottom, 1.25rem))",
35
- }}
36
- >
37
- <div className="flex items-baseline justify-between mb-1">
38
- <span className="text-[15px] font-bold text-foreground">
39
- {labels.subtotal}
40
- </span>
41
- <DualPrice
42
- amount={total}
43
- currencyCode={currencyCode}
44
- className="text-xl font-bold text-foreground tracking-tight"
45
- />
46
- </div>
47
- <p className="text-[11px] text-muted-foreground mb-3.5 sm:mb-4">
48
- {labels.taxAndShipping}
49
- </p>
50
- <Link href={hrefs.checkout} onClick={close}>
51
- <button
52
- type="button"
53
- className="w-full h-[52px] bg-foreground hover:bg-foreground/90 active:bg-foreground/95 text-card text-[15px] sm:text-sm font-semibold rounded-[2px] flex items-center justify-center gap-2.5 transition-colors active:scale-[0.98]"
54
- >
55
- <svg
56
- width="16"
57
- height="16"
58
- viewBox="0 0 16 16"
59
- fill="none"
60
- stroke="currentColor"
61
- strokeWidth="1.5"
62
- strokeLinecap="round"
63
- strokeLinejoin="round"
64
- >
65
- <rect x="3" y="6" width="10" height="8" rx="1.5" />
66
- <path d="M5.5 6V4.5a2.5 2.5 0 015 0V6" />
67
- </svg>
68
- {labels.secureCheckout}
69
- </button>
70
- </Link>
71
- </div>
72
- )
73
- }
1
+ "use client"
2
+
3
+ import Link from "next/link"
4
+
5
+ import { Price } from "../lib/price"
6
+ import { productTotal } from "../lib/cart-helpers"
7
+ import { useCartDrawer } from "./context"
8
+
9
+ /**
10
+ * CartStickyFooter — bottom-of-drawer subtotal + checkout CTA with
11
+ * iPhone home-indicator safe-area padding.
12
+ *
13
+ * Ported from `@1click/ui/src/cart-drawer/sticky-footer.tsx` (v2.3.1);
14
+ * originally extracted from mindpages-storefront
15
+ * src/modules/cart-drawer/cart-sticky-footer.tsx. Amounts EUR major units.
16
+ */
17
+
18
+ export function CartStickyFooter() {
19
+ const { cart, close, labels, hrefs } = useCartDrawer()
20
+ if (!cart || !cart.items?.length) return null
21
+
22
+ // Drawer is pre-checkout context — never read `cart.total` here.
23
+ // `cart.total` includes shipping/tax/COD fee, all of which are
24
+ // checkout-context state that must not leak into the shopping
25
+ // drawer. Sum of product line totals only.
26
+ const total = productTotal(cart.items)
27
+ const currencyCode = cart.currency_code
28
+
29
+ return (
30
+ <div
31
+ className="flex-shrink-0 bg-card px-5 sm:px-6 pt-4 sm:pt-5 relative z-10"
32
+ style={{
33
+ boxShadow: "0 -4px 12px rgba(0,0,0,0.04)",
34
+ paddingBottom: "max(1.25rem, env(safe-area-inset-bottom, 1.25rem))",
35
+ }}
36
+ >
37
+ <div className="flex items-baseline justify-between mb-1">
38
+ <span className="text-[15px] font-bold text-foreground">
39
+ {labels.subtotal}
40
+ </span>
41
+ <Price
42
+ amount={total}
43
+ currencyCode={currencyCode}
44
+ className="text-xl font-bold text-foreground tracking-tight"
45
+ />
46
+ </div>
47
+ <p className="text-[11px] text-muted-foreground mb-3.5 sm:mb-4">
48
+ {labels.taxAndShipping}
49
+ </p>
50
+ <Link href={hrefs.checkout} onClick={close}>
51
+ <button
52
+ type="button"
53
+ className="w-full h-[52px] bg-foreground hover:bg-foreground/90 active:bg-foreground/95 text-card text-[15px] sm:text-sm font-semibold rounded-[2px] flex items-center justify-center gap-2.5 transition-colors active:scale-[0.98]"
54
+ >
55
+ <svg
56
+ width="16"
57
+ height="16"
58
+ viewBox="0 0 16 16"
59
+ fill="none"
60
+ stroke="currentColor"
61
+ strokeWidth="1.5"
62
+ strokeLinecap="round"
63
+ strokeLinejoin="round"
64
+ >
65
+ <rect x="3" y="6" width="10" height="8" rx="1.5" />
66
+ <path d="M5.5 6V4.5a2.5 2.5 0 015 0V6" />
67
+ </svg>
68
+ {labels.secureCheckout}
69
+ </button>
70
+ </Link>
71
+ </div>
72
+ )
73
+ }
@@ -141,7 +141,7 @@ export async function geocodeAddress(
141
141
  url.searchParams.set("limit", "1")
142
142
 
143
143
  const res = await fetch(url.toString(), {
144
- headers: { "User-Agent": "barter-storefront/1.0" },
144
+ headers: { "User-Agent": "cartbase-storefront/1.0" },
145
145
  })
146
146
  const data = await res.json()
147
147
  if (data?.[0]) {
@@ -5,7 +5,7 @@ import { useState } from "react"
5
5
  import type { StorefrontClient } from "../api/http"
6
6
  import type { Cart } from "../api/carts"
7
7
  import { applyGiftCard, removeGiftCard } from "../api/gift-cards"
8
- import { DualPrice } from "../lib/dual-price"
8
+ import { Price } from "../lib/price"
9
9
  import { cn } from "../lib/utils"
10
10
  import { useCheckoutLabels } from "./context"
11
11
  import { translateGiftCardError } from "./payment-error-copy"
@@ -201,7 +201,7 @@ export function GiftCardSection({
201
201
  remainder didn't shrink. */}
202
202
  <span className="text-sm font-medium text-success">
203
203
  -
204
- <DualPrice
204
+ <Price
205
205
  amount={gc.amount}
206
206
  currencyCode={cart.currency_code}
207
207
  />
@@ -4,14 +4,14 @@ import { useState } from "react"
4
4
 
5
5
  import type { StorefrontClient } from "../api/http"
6
6
  import { updateLineItem, type Cart, type CartLineItem } from "../api/carts"
7
- import { DualPrice } from "../lib/dual-price"
7
+ import { Price } from "../lib/price"
8
8
  import { cn } from "../lib/utils"
9
9
  import { useCheckoutLabels } from "./context"
10
10
 
11
11
  /**
12
12
  * LineItemCard — a single cart line shown inside the checkout order
13
13
  * summary card. Includes thumbnail, title, variant, quantity select,
14
- * and dual-currency price.
14
+ * and price.
15
15
  *
16
16
  * Ported from `@1click/ui/src/checkout/line-item-card.tsx` (v2.3.1) with
17
17
  * the Cartbase data seam: the cookie-bound `updateLineItem` server action →
@@ -141,7 +141,7 @@ export function LineItemCard({
141
141
  </div>
142
142
 
143
143
  <div className="flex flex-col items-end justify-center flex-shrink-0">
144
- <DualPrice
144
+ <Price
145
145
  amount={total}
146
146
  currencyCode={currencyCode}
147
147
  className="text-sm font-bold text-foreground"
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react"
4
4
 
5
5
  import type { StorefrontClient } from "../api/http"
6
6
  import { updateLineItem, type Cart, type CartLineItem } from "../api/carts"
7
- import { DualPrice } from "../lib/dual-price"
7
+ import { Price } from "../lib/price"
8
8
  import { isProductLine } from "../lib/cart-helpers"
9
9
  import { cn } from "../lib/utils"
10
10
  import { useCheckoutLabels } from "./context"
@@ -170,7 +170,7 @@ export function CheckoutLineItem({
170
170
  <div className="hidden sm:flex flex-1 min-w-0 flex-col gap-2.5">
171
171
  <div className="flex items-baseline justify-between gap-3">
172
172
  {titleBlock}
173
- <DualPrice
173
+ <Price
174
174
  amount={item.total ?? 0}
175
175
  currencyCode={currencyCode}
176
176
  className="text-sm font-bold text-foreground text-right flex-shrink-0"
@@ -185,7 +185,7 @@ export function CheckoutLineItem({
185
185
  {titleBlock}
186
186
  <div className="flex items-center justify-between gap-3">
187
187
  {qtyPill}
188
- <DualPrice
188
+ <Price
189
189
  amount={item.total ?? 0}
190
190
  currencyCode={currencyCode}
191
191
  className="text-sm font-bold text-foreground text-right"
@@ -376,7 +376,7 @@ export function OrderSummary({
376
376
  <div className="px-5 sm:px-6 space-y-3">
377
377
  <div className="flex justify-between text-sm">
378
378
  <span className="text-muted-foreground">{labels.subtotal}</span>
379
- <DualPrice
379
+ <Price
380
380
  amount={productSubtotal}
381
381
  currencyCode={cart.currency_code}
382
382
  className="font-medium text-foreground"
@@ -391,7 +391,7 @@ export function OrderSummary({
391
391
  {labels.shippingFree}
392
392
  </span>
393
393
  ) : (
394
- <DualPrice
394
+ <Price
395
395
  amount={shippingCost ?? 0}
396
396
  currencyCode={cart.currency_code}
397
397
  className="font-medium text-foreground"
@@ -409,7 +409,7 @@ export function OrderSummary({
409
409
  <span className="text-muted-foreground">
410
410
  {methodFeeLabel ?? cart.payment_method_fee_label ?? labels.paymentMethodFee}
411
411
  </span>
412
- <DualPrice
412
+ <Price
413
413
  amount={methodFeeAmount}
414
414
  currencyCode={cart.currency_code}
415
415
  className="font-medium text-foreground"
@@ -422,7 +422,7 @@ export function OrderSummary({
422
422
  <span>{labels.discount}</span>
423
423
  <span className="font-medium">
424
424
  -
425
- <DualPrice
425
+ <Price
426
426
  amount={cart.discount_total}
427
427
  currencyCode={cart.currency_code}
428
428
  />
@@ -450,7 +450,7 @@ export function OrderSummary({
450
450
  {labels.taxTooltip}
451
451
  </span>
452
452
  </span>
453
- <DualPrice
453
+ <Price
454
454
  amount={cart.tax_total ?? 0}
455
455
  currencyCode={cart.currency_code}
456
456
  className="font-medium text-foreground"
@@ -464,11 +464,10 @@ export function OrderSummary({
464
464
  {labels.total}
465
465
  </span>
466
466
  <div className="text-right">
467
- <DualPrice
467
+ <Price
468
468
  amount={displayTotal ?? 0}
469
469
  currencyCode={cart.currency_code}
470
470
  className="text-2xl font-bold text-foreground tracking-tight"
471
- bgnClassName="ml-2 text-sm text-muted-foreground/70 font-normal"
472
471
  />
473
472
  </div>
474
473
  </div>
@@ -482,7 +481,7 @@ export function OrderSummary({
482
481
  <span>{labels.giftCardApplied}</span>
483
482
  <span className="font-medium">
484
483
  -
485
- <DualPrice
484
+ <Price
486
485
  amount={giftCardTotal}
487
486
  currencyCode={cart.currency_code}
488
487
  />
@@ -492,7 +491,7 @@ export function OrderSummary({
492
491
  <span className="text-muted-foreground">
493
492
  {labels.giftCardRemaining}
494
493
  </span>
495
- <DualPrice
494
+ <Price
496
495
  amount={giftCardRemainder}
497
496
  currencyCode={cart.currency_code}
498
497
  className="font-semibold text-foreground"
@@ -3,7 +3,7 @@
3
3
  import { useElements, useStripe } from "@stripe/react-stripe-js"
4
4
  import { useContext, useEffect, useRef, useState } from "react"
5
5
 
6
- import { DualPrice } from "../lib/dual-price"
6
+ import { Price } from "../lib/price"
7
7
  import { cn } from "../lib/utils"
8
8
  import { translatePaymentError } from "./payment-error-copy"
9
9
  import { useCheckoutLabels } from "./context"
@@ -153,11 +153,10 @@ function OrderButton({
153
153
  {total !== undefined && currencyCode && (
154
154
  <span className="inline-flex items-center gap-1">
155
155
  <span aria-hidden="true"> · </span>
156
- <DualPrice
156
+ <Price
157
157
  amount={total}
158
158
  currencyCode={currencyCode}
159
159
  className="font-semibold"
160
- bgnClassName="text-card/70 text-[11px] ml-1"
161
160
  />
162
161
  </span>
163
162
  )}