@cartbase/storefront 0.1.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 (176) hide show
  1. package/package.json +226 -0
  2. package/src/api/auth.ts +108 -0
  3. package/src/api/carts.ts +506 -0
  4. package/src/api/categories.ts +184 -0
  5. package/src/api/checkout.ts +440 -0
  6. package/src/api/collections.ts +130 -0
  7. package/src/api/consent.ts +75 -0
  8. package/src/api/content.ts +125 -0
  9. package/src/api/customers.ts +307 -0
  10. package/src/api/gift-cards.ts +112 -0
  11. package/src/api/http.ts +122 -0
  12. package/src/api/index.ts +29 -0
  13. package/src/api/integrations.ts +130 -0
  14. package/src/api/menus.ts +77 -0
  15. package/src/api/metaobjects.ts +136 -0
  16. package/src/api/orders.ts +290 -0
  17. package/src/api/products.ts +303 -0
  18. package/src/api/redirects.ts +37 -0
  19. package/src/api/regions.ts +141 -0
  20. package/src/api/reviews.ts +259 -0
  21. package/src/api/search.ts +133 -0
  22. package/src/api/types.ts +91 -0
  23. package/src/cart-drawer/cart-drawer.tsx +86 -0
  24. package/src/cart-drawer/context.tsx +569 -0
  25. package/src/cart-drawer/continue-shopping.tsx +28 -0
  26. package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
  27. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
  28. package/src/cart-drawer/empty.tsx +52 -0
  29. package/src/cart-drawer/free-gift.tsx +71 -0
  30. package/src/cart-drawer/gift-wrap.tsx +83 -0
  31. package/src/cart-drawer/header.tsx +52 -0
  32. package/src/cart-drawer/index.ts +69 -0
  33. package/src/cart-drawer/item/index.tsx +164 -0
  34. package/src/cart-drawer/item/quantity.tsx +100 -0
  35. package/src/cart-drawer/item/upsell.tsx +110 -0
  36. package/src/cart-drawer/item/variant.tsx +46 -0
  37. package/src/cart-drawer/labels-bg.ts +72 -0
  38. package/src/cart-drawer/labels.ts +119 -0
  39. package/src/cart-drawer/notes.tsx +131 -0
  40. package/src/cart-drawer/payment-badges.tsx +96 -0
  41. package/src/cart-drawer/promo-banner.tsx +43 -0
  42. package/src/cart-drawer/rewards-points.tsx +78 -0
  43. package/src/cart-drawer/sticky-footer.tsx +73 -0
  44. package/src/cart-drawer/summary-breakdown.tsx +196 -0
  45. package/src/cart-drawer/template.tsx +225 -0
  46. package/src/cart-drawer/tiered-progress.tsx +168 -0
  47. package/src/checkout/address-error-copy.ts +119 -0
  48. package/src/checkout/address-form.tsx +224 -0
  49. package/src/checkout/address-select.tsx +79 -0
  50. package/src/checkout/boxnow-locker-selector.tsx +410 -0
  51. package/src/checkout/checkout-client.tsx +222 -0
  52. package/src/checkout/company-details.tsx +94 -0
  53. package/src/checkout/compare-addresses.ts +40 -0
  54. package/src/checkout/context.tsx +76 -0
  55. package/src/checkout/discount-section.tsx +218 -0
  56. package/src/checkout/econt-office-selector.tsx +332 -0
  57. package/src/checkout/error-message.tsx +25 -0
  58. package/src/checkout/geocode.ts +154 -0
  59. package/src/checkout/gift-card-section.tsx +224 -0
  60. package/src/checkout/index.ts +74 -0
  61. package/src/checkout/labels-bg.ts +128 -0
  62. package/src/checkout/labels.ts +263 -0
  63. package/src/checkout/line-item-card.tsx +152 -0
  64. package/src/checkout/order-summary.tsx +524 -0
  65. package/src/checkout/payment-button.tsx +373 -0
  66. package/src/checkout/payment-error-copy.ts +269 -0
  67. package/src/checkout/payment-method-list.tsx +365 -0
  68. package/src/checkout/payment-wrapper.tsx +102 -0
  69. package/src/checkout/promotion-error-copy.ts +124 -0
  70. package/src/checkout/shipping-method-list.tsx +335 -0
  71. package/src/checkout/stripe-wrapper.tsx +165 -0
  72. package/src/checkout/use-checkout-orchestration.ts +1504 -0
  73. package/src/common/cart-button-client.tsx +39 -0
  74. package/src/common/cart-button.tsx +28 -0
  75. package/src/common/country-select.tsx +65 -0
  76. package/src/common/delete-button.tsx +66 -0
  77. package/src/common/index.ts +17 -0
  78. package/src/common/language-select.tsx +78 -0
  79. package/src/common/localized-link.tsx +45 -0
  80. package/src/common/skeleton.tsx +29 -0
  81. package/src/index.ts +12 -0
  82. package/src/lib/cart-helpers.ts +113 -0
  83. package/src/lib/dual-price.tsx +73 -0
  84. package/src/lib/get-percentage-diff.ts +5 -0
  85. package/src/lib/get-product-price.ts +133 -0
  86. package/src/lib/hooks/use-intersection.ts +30 -0
  87. package/src/lib/hooks/use-toggle-state.ts +25 -0
  88. package/src/lib/money.ts +73 -0
  89. package/src/lib/payment-constants.ts +66 -0
  90. package/src/lib/product.ts +22 -0
  91. package/src/lib/sort-products.ts +63 -0
  92. package/src/lib/store-api-error.ts +36 -0
  93. package/src/lib/utils.ts +16 -0
  94. package/src/order/context.tsx +32 -0
  95. package/src/order/index.ts +63 -0
  96. package/src/order/labels-bg.ts +39 -0
  97. package/src/order/labels.ts +79 -0
  98. package/src/order/order-address-card.tsx +47 -0
  99. package/src/order/order-completed-template.tsx +165 -0
  100. package/src/order/order-confirmation-header.tsx +65 -0
  101. package/src/order/order-delivery-card.tsx +258 -0
  102. package/src/order/order-help-section.tsx +47 -0
  103. package/src/order/order-item.tsx +201 -0
  104. package/src/order/order-items-list.tsx +52 -0
  105. package/src/order/order-payment-card.tsx +95 -0
  106. package/src/order/order-timeline.tsx +141 -0
  107. package/src/order/order-totals.tsx +245 -0
  108. package/src/primitives/field.tsx +125 -0
  109. package/src/primitives/select-field.tsx +77 -0
  110. package/src/primitives/ui/accordion.tsx +61 -0
  111. package/src/primitives/ui/button.tsx +68 -0
  112. package/src/primitives/ui/collapsible.tsx +16 -0
  113. package/src/primitives/ui/dialog.tsx +112 -0
  114. package/src/primitives/ui/input.tsx +30 -0
  115. package/src/primitives/ui/label.tsx +31 -0
  116. package/src/primitives/ui/popover.tsx +38 -0
  117. package/src/primitives/ui/select.tsx +163 -0
  118. package/src/primitives/ui/sheet.tsx +131 -0
  119. package/src/primitives/ui/tabs.tsx +62 -0
  120. package/src/products/context.tsx +34 -0
  121. package/src/products/image-gallery.tsx +43 -0
  122. package/src/products/index.ts +44 -0
  123. package/src/products/labels-bg.ts +35 -0
  124. package/src/products/labels.ts +57 -0
  125. package/src/products/mobile-actions.tsx +180 -0
  126. package/src/products/option-select.tsx +67 -0
  127. package/src/products/preview-price.tsx +36 -0
  128. package/src/products/product-actions-wrapper.tsx +58 -0
  129. package/src/products/product-actions.tsx +217 -0
  130. package/src/products/product-info.tsx +43 -0
  131. package/src/products/product-preview.tsx +49 -0
  132. package/src/products/product-price.tsx +69 -0
  133. package/src/products/product-tabs.tsx +169 -0
  134. package/src/products/product-template.tsx +114 -0
  135. package/src/products/purchase-options.tsx +130 -0
  136. package/src/products/related-products.tsx +86 -0
  137. package/src/products/thumbnail.tsx +71 -0
  138. package/src/products/variant-matching.ts +71 -0
  139. package/src/reviews-ui/helpers.ts +174 -0
  140. package/src/reviews-ui/index.ts +74 -0
  141. package/src/reviews-ui/labels-bg.ts +91 -0
  142. package/src/reviews-ui/labels.ts +199 -0
  143. package/src/reviews-ui/photo-upload.tsx +345 -0
  144. package/src/reviews-ui/review-list.tsx +249 -0
  145. package/src/reviews-ui/review-widget.tsx +224 -0
  146. package/src/reviews-ui/review-wizard.tsx +560 -0
  147. package/src/reviews-ui/star-badge.tsx +104 -0
  148. package/src/reviews-ui/wizard-state.ts +81 -0
  149. package/src/store/category-template.tsx +129 -0
  150. package/src/store/collection-template.tsx +139 -0
  151. package/src/store/index.ts +41 -0
  152. package/src/store/labels-bg.ts +22 -0
  153. package/src/store/labels.ts +52 -0
  154. package/src/store/paginated-products.tsx +116 -0
  155. package/src/store/pagination.tsx +103 -0
  156. package/src/store/search-params.ts +256 -0
  157. package/src/store/search-template.tsx +249 -0
  158. package/src/store/skeleton-product-grid.tsx +26 -0
  159. package/src/store/sort-select.tsx +81 -0
  160. package/src/store/store-template.tsx +65 -0
  161. package/src/tracking/attribution.ts +418 -0
  162. package/src/tracking/consent-banner.tsx +355 -0
  163. package/src/tracking/consent-init.tsx +44 -0
  164. package/src/tracking/consent.ts +243 -0
  165. package/src/tracking/fbq.ts +168 -0
  166. package/src/tracking/ga4.tsx +49 -0
  167. package/src/tracking/get-tracking-attribution.ts +224 -0
  168. package/src/tracking/get-tracking-config.ts +50 -0
  169. package/src/tracking/gtag.ts +200 -0
  170. package/src/tracking/index.ts +133 -0
  171. package/src/tracking/meta-pixel.tsx +166 -0
  172. package/src/tracking/rybbit-events.ts +242 -0
  173. package/src/tracking/rybbit.tsx +40 -0
  174. package/src/tracking/types.ts +185 -0
  175. package/src/tracking/use-engagement-time.ts +58 -0
  176. package/tailwind-preset.cjs +72 -0
@@ -0,0 +1,133 @@
1
+ import { getPercentageDiff } from "./get-percentage-diff"
2
+ import { convertToLocale } from "./money"
3
+
4
+ /**
5
+ * Product price extraction — ported from
6
+ * `@1click/ui/src/lib/get-product-price.ts` (v2.3.1). The
7
+ * `@medusajs/types` `HttpTypes.StoreProduct` parameter is replaced by the
8
+ * structural shapes below so this module stays decoupled from the SDK
9
+ * domain modules (barter's `variant.calculated_price` is the b2b-v1
10
+ * pricing context documented in `@cartbase/storefront/api/types`
11
+ * `CalculatedPrice`, decimal EUR major units).
12
+ *
13
+ * Wire-shape note (code truth, `src/lib/products/pricing.ts`): barter serves
14
+ * `calculated_price.price_list_type` FLAT on the calculated_price object.
15
+ * The Medusa wire shape ALSO nests a `calculated_price.calculated_price`
16
+ * detail object carrying `price_list_type` — the original library read the
17
+ * nested path. `getPricesForVariant` reads the flat key first and falls
18
+ * back to the nested path so both shapes produce the same result.
19
+ */
20
+
21
+ /** Structural `variant.calculated_price` shape (see api/types CalculatedPrice). */
22
+ export type CalculatedPriceLike = {
23
+ calculated_amount: number
24
+ original_amount: number
25
+ currency_code: string
26
+ price_list_type?: string | null
27
+ /** Medusa-wire nested detail object (fallback path). */
28
+ calculated_price?: { price_list_type?: string | null } | null
29
+ }
30
+
31
+ /** Structural variant shape — any richer product-variant DTO satisfies it. */
32
+ export type VariantWithPrice = {
33
+ id?: string
34
+ sku?: string | null
35
+ calculated_price?: CalculatedPriceLike | null
36
+ }
37
+
38
+ /** Structural product shape — any richer product DTO satisfies it. */
39
+ export type ProductWithVariants = {
40
+ id?: string
41
+ variants?: VariantWithPrice[] | null
42
+ }
43
+
44
+ export type VariantPrice = {
45
+ calculated_price_number: number
46
+ calculated_price: string
47
+ original_price_number: number
48
+ original_price: string
49
+ currency_code: string
50
+ price_type: string | null | undefined
51
+ percentage_diff: string
52
+ }
53
+
54
+ export const getPricesForVariant = (
55
+ variant: VariantWithPrice | null | undefined
56
+ ): VariantPrice | null => {
57
+ if (!variant?.calculated_price?.calculated_amount) {
58
+ return null
59
+ }
60
+
61
+ const cp = variant.calculated_price
62
+ return {
63
+ calculated_price_number: cp.calculated_amount,
64
+ calculated_price: convertToLocale({
65
+ amount: cp.calculated_amount,
66
+ currency_code: cp.currency_code,
67
+ }),
68
+ original_price_number: cp.original_amount,
69
+ original_price: convertToLocale({
70
+ amount: cp.original_amount,
71
+ currency_code: cp.currency_code,
72
+ }),
73
+ currency_code: cp.currency_code,
74
+ // Flat key (barter wire shape) first — an explicit null stays null;
75
+ // the nested Medusa detail is the fallback only when the flat key is
76
+ // absent entirely (the original @1click read path).
77
+ price_type:
78
+ cp.price_list_type !== undefined
79
+ ? cp.price_list_type
80
+ : cp.calculated_price?.price_list_type,
81
+ percentage_diff: getPercentageDiff(cp.original_amount, cp.calculated_amount),
82
+ }
83
+ }
84
+
85
+ export function getProductPrice({
86
+ product,
87
+ variantId,
88
+ }: {
89
+ product: ProductWithVariants
90
+ variantId?: string
91
+ }) {
92
+ if (!product || !product.id) {
93
+ throw new Error("No product provided")
94
+ }
95
+
96
+ const cheapestPrice = (): VariantPrice | null => {
97
+ if (!product || !product.variants?.length) {
98
+ return null
99
+ }
100
+
101
+ const cheapestVariant = product.variants
102
+ .filter((v) => !!v.calculated_price)
103
+ .sort(
104
+ (a, b) =>
105
+ (a.calculated_price?.calculated_amount ?? 0) -
106
+ (b.calculated_price?.calculated_amount ?? 0)
107
+ )[0]
108
+
109
+ return getPricesForVariant(cheapestVariant)
110
+ }
111
+
112
+ const variantPrice = (): VariantPrice | null => {
113
+ if (!product || !variantId) {
114
+ return null
115
+ }
116
+
117
+ const variant = product.variants?.find(
118
+ (v) => v.id === variantId || v.sku === variantId
119
+ )
120
+
121
+ if (!variant) {
122
+ return null
123
+ }
124
+
125
+ return getPricesForVariant(variant)
126
+ }
127
+
128
+ return {
129
+ product,
130
+ cheapestPrice: cheapestPrice(),
131
+ variantPrice: variantPrice(),
132
+ }
133
+ }
@@ -0,0 +1,30 @@
1
+ import { type RefObject, useEffect, useState } from "react"
2
+
3
+ export const useIntersection = (
4
+ element: RefObject<HTMLDivElement | null>,
5
+ rootMargin: string
6
+ ) => {
7
+ const [isVisible, setState] = useState(false)
8
+
9
+ useEffect(() => {
10
+ if (!element.current) {
11
+ return
12
+ }
13
+
14
+ const el = element.current
15
+
16
+ const observer = new IntersectionObserver(
17
+ (entries) => {
18
+ const entry = entries[0]
19
+ if (entry) setState(entry.isIntersecting)
20
+ },
21
+ { rootMargin }
22
+ )
23
+
24
+ observer.observe(el)
25
+
26
+ return () => observer.unobserve(el)
27
+ }, [element, rootMargin])
28
+
29
+ return isVisible
30
+ }
@@ -0,0 +1,25 @@
1
+ import { useState } from "react"
2
+
3
+ export type StateType = [boolean, () => void, () => void, () => void] & {
4
+ state: boolean
5
+ open: () => void
6
+ close: () => void
7
+ toggle: () => void
8
+ }
9
+
10
+ const useToggleState = (initialState = false) => {
11
+ const [state, setState] = useState<boolean>(initialState)
12
+
13
+ const close = () => setState(false)
14
+ const open = () => setState(true)
15
+ const toggle = () => setState((s) => !s)
16
+
17
+ const hookData = [state, open, close, toggle] as StateType
18
+ hookData.state = state
19
+ hookData.open = open
20
+ hookData.close = close
21
+ hookData.toggle = toggle
22
+ return hookData
23
+ }
24
+
25
+ export default useToggleState
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Money utilities — pure, side-effect-free currency formatting helpers.
3
+ *
4
+ * Extracted from mindpages-storefront src/lib/util/money.ts + isEmpty.ts.
5
+ * Used by DualPrice and any other price rendering in the library.
6
+ */
7
+
8
+ function isEmpty(value: unknown): boolean {
9
+ if (value == null) return true
10
+ if (typeof value === "string") return value.length === 0
11
+ if (Array.isArray(value)) return value.length === 0
12
+ if (typeof value === "object") return Object.keys(value as object).length === 0
13
+ return false
14
+ }
15
+
16
+ export type ConvertToLocaleParams = {
17
+ amount: number
18
+ currency_code: string
19
+ minimumFractionDigits?: number
20
+ maximumFractionDigits?: number
21
+ locale?: string
22
+ }
23
+
24
+ /**
25
+ * Format a numeric amount as a localized currency string.
26
+ *
27
+ * @example
28
+ * convertToLocale({ amount: 19.99, currency_code: "eur" })
29
+ * // → "€19.99"
30
+ */
31
+ export function convertToLocale({
32
+ amount,
33
+ currency_code,
34
+ minimumFractionDigits,
35
+ maximumFractionDigits,
36
+ locale = "en-US",
37
+ }: ConvertToLocaleParams): string {
38
+ return currency_code && !isEmpty(currency_code)
39
+ ? new Intl.NumberFormat(locale, {
40
+ style: "currency",
41
+ currency: currency_code,
42
+ minimumFractionDigits,
43
+ maximumFractionDigits,
44
+ }).format(amount)
45
+ : amount.toString()
46
+ }
47
+
48
+ /**
49
+ * Currencies that Medusa stores WITHOUT minor units (no cent division).
50
+ * Used when converting between minor-unit (Medusa's internal representation)
51
+ * and major-unit (display) amounts.
52
+ */
53
+ export const noDivisionCurrencies = [
54
+ "krw",
55
+ "jpy",
56
+ "vnd",
57
+ "clp",
58
+ "pyg",
59
+ "xaf",
60
+ "xof",
61
+ "bif",
62
+ "djf",
63
+ "gnf",
64
+ "kmf",
65
+ "mga",
66
+ "rwf",
67
+ "xpf",
68
+ "htg",
69
+ "vuv",
70
+ "xag",
71
+ "xdr",
72
+ "xau",
73
+ ] as const
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Payment provider helpers — identifier sniffing for store payment sessions.
3
+ *
4
+ * Ported from `@1click/ui/src/lib/payment-constants.ts` (v2.3.1) and
5
+ * translated to the barter provider-id seam (code truth:
6
+ * `src/lib/stripe/providers.ts isStripeProviderId` matches exactly
7
+ * `"pp_stripe" | "stripe"`; the accepted checkout ids are
8
+ * `pp_stripe | pp_cod | pp_manual` — docs/contracts/store-api.md).
9
+ * `pp_system_default` is the RETIRED legacy manual twin (payments-registry
10
+ * card): no longer provisioned, hidden from merchants, kept here only so
11
+ * historic orders keep rendering. The Medusa-era prefixes (`pp_stripe_*`,
12
+ * `pp_medusa-*`) are kept as fallbacks so components ported later keep
13
+ * working against either wire.
14
+ *
15
+ * Intentionally ships NO icons — stores render their own icons via their
16
+ * own icon set. The library stays free of icon-kit imports at this layer.
17
+ */
18
+
19
+ /**
20
+ * True if the provider id is a Stripe-backed card payment provider.
21
+ * Barter's canonical ids first (`pp_stripe` / `stripe`), then the
22
+ * Medusa-era prefixes (`pp_stripe_*`, `pp_medusa-*`).
23
+ */
24
+ export const isStripeLike = (providerId?: string): boolean => {
25
+ return Boolean(
26
+ providerId &&
27
+ (providerId === "pp_stripe" ||
28
+ providerId === "stripe" ||
29
+ providerId.startsWith("pp_stripe_") ||
30
+ providerId.startsWith("pp_medusa-"))
31
+ )
32
+ }
33
+
34
+ /** True if the provider id is PayPal. */
35
+ export const isPaypal = (providerId?: string): boolean => {
36
+ return Boolean(providerId?.startsWith("pp_paypal"))
37
+ }
38
+
39
+ /**
40
+ * True if the provider id is an offline/manual provider: `pp_manual` (the
41
+ * id checkout actually charges — payments-registry card) with the retired
42
+ * `pp_system_default*` legacy twin as fallback for historic orders. COD is
43
+ * NOT manual — `pp_cod` has its own id and its own fee flow.
44
+ */
45
+ export const isManual = (providerId?: string): boolean => {
46
+ return Boolean(
47
+ providerId &&
48
+ (providerId === "pp_manual" || providerId.startsWith("pp_system_default"))
49
+ )
50
+ }
51
+
52
+ export const paymentInfoMap: Record<
53
+ string,
54
+ { title: string; icon: React.JSX.Element | null }
55
+ > = {
56
+ pp_stripe: { title: "Credit / debit card", icon: null },
57
+ pp_stripe_stripe: { title: "Credit / debit card", icon: null },
58
+ "pp_medusa-payments_default": { title: "Credit / debit card", icon: null },
59
+ "pp_stripe-ideal_stripe": { title: "iDeal", icon: null },
60
+ "pp_stripe-bancontact_stripe": { title: "Bancontact", icon: null },
61
+ pp_paypal_paypal: { title: "PayPal", icon: null },
62
+ pp_cod: { title: "Cash on delivery", icon: null },
63
+ pp_manual: { title: "Manual payment", icon: null },
64
+ // Retired legacy manual twin — historic orders only (payments-registry).
65
+ pp_system_default: { title: "Manual payment", icon: null },
66
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Product predicates — ported from `@1click/ui/src/lib/product.ts` (v2.3.1);
3
+ * the `@medusajs/types` parameter is replaced by a structural shape so the
4
+ * module stays decoupled from the SDK domain modules.
5
+ */
6
+
7
+ /** Structural product-options shape — any richer product DTO satisfies it. */
8
+ export type ProductWithOptions = {
9
+ options?: Array<{ values?: Array<unknown> | null } | null> | null
10
+ }
11
+
12
+ /**
13
+ * A "simple" product has exactly one option with exactly one value —
14
+ * i.e. no real variant choice. Storefronts use this to skip the option
15
+ * selector and add the single variant directly.
16
+ */
17
+ export const isSimpleProduct = (product: ProductWithOptions): boolean => {
18
+ return (
19
+ product.options?.length === 1 &&
20
+ (product.options[0]?.values?.length ?? 0) === 1
21
+ )
22
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Client-side product sorting — ported from
3
+ * `@1click/ui/src/lib/sort-products.ts` (v2.3.1); the `@medusajs/types`
4
+ * parameter is replaced by a structural shape so the module stays decoupled
5
+ * from the SDK domain modules.
6
+ *
7
+ * Server truth caveat (card trap): the store API's `order` query param is
8
+ * the authoritative sort for paginated listings — this helper is for
9
+ * client-side re-sorting of an already-fetched page (e.g. a small
10
+ * collection rendered fully client-side). It never invents pricing: it
11
+ * reads the server-computed `variant.calculated_price`.
12
+ */
13
+
14
+ export type SortOptions = "price_asc" | "price_desc" | "created_at"
15
+
16
+ /** Structural sortable-product shape — any richer product DTO satisfies it. */
17
+ export type SortableProduct = {
18
+ created_at?: string | null
19
+ variants?: Array<{
20
+ calculated_price?: { calculated_amount?: number | null } | null
21
+ } | null> | null
22
+ }
23
+
24
+ interface MinPriced {
25
+ _minPrice?: number
26
+ }
27
+
28
+ export function sortProducts<T extends SortableProduct>(
29
+ products: T[],
30
+ sortBy: SortOptions
31
+ ): T[] {
32
+ const sortedProducts = [...products] as Array<T & MinPriced>
33
+
34
+ if (["price_asc", "price_desc"].includes(sortBy)) {
35
+ sortedProducts.forEach((product) => {
36
+ if (product.variants && product.variants.length > 0) {
37
+ product._minPrice = Math.min(
38
+ ...product.variants.map(
39
+ (variant) => variant?.calculated_price?.calculated_amount || 0
40
+ )
41
+ )
42
+ } else {
43
+ product._minPrice = Infinity
44
+ }
45
+ })
46
+
47
+ sortedProducts.sort((a, b) => {
48
+ const diff = (a._minPrice ?? 0) - (b._minPrice ?? 0)
49
+ return sortBy === "price_asc" ? diff : -diff
50
+ })
51
+ }
52
+
53
+ if (sortBy === "created_at") {
54
+ sortedProducts.sort((a, b) => {
55
+ return (
56
+ new Date(b.created_at ?? 0).getTime() -
57
+ new Date(a.created_at ?? 0).getTime()
58
+ )
59
+ })
60
+ }
61
+
62
+ return sortedProducts
63
+ }
@@ -0,0 +1,36 @@
1
+ import { StoreApiError } from "../api/types"
2
+
3
+ /**
4
+ * storeApiError — the successor of `@1click/ui`'s `medusaError`.
5
+ *
6
+ * Normalizes any error thrown by the `@cartbase/storefront` SDK layer into a
7
+ * clean, user-presentable `Error` whose message starts with a capital and
8
+ * ends with a period — the same normalization contract the Medusa-era
9
+ * helper enforced, adapted to the barter error contract (`StoreApiError`
10
+ * with `status` + `code` + raw `body`, see `../api/types`).
11
+ *
12
+ * Always throws — never returns. Use in `.catch()` chains:
13
+ *
14
+ * addLineItem(client, cartId, input).catch(storeApiError)
15
+ *
16
+ * Callers that need the PROGRAMMATIC surface (branching on `status` /
17
+ * `code`, e.g. `checkout_method_hidden`, `invalid_gift_card`) should catch
18
+ * `StoreApiError` directly instead — this adapter is the display-boundary
19
+ * normalizer, deliberately lossy about everything but the message.
20
+ */
21
+ export default function storeApiError(error: unknown): never {
22
+ if (error instanceof StoreApiError) {
23
+ const message = error.message || `Store API error (${error.status})`
24
+ throw new Error(sentence(message))
25
+ }
26
+ if (error instanceof Error) {
27
+ throw new Error(sentence(error.message || "Unknown error"))
28
+ }
29
+ throw new Error("Error setting up the request: " + String(error))
30
+ }
31
+
32
+ /** Capitalize + terminal period — the medusaError display normalization. */
33
+ function sentence(message: string): string {
34
+ const capitalized = message.charAt(0).toUpperCase() + message.slice(1)
35
+ return capitalized.endsWith(".") ? capitalized : capitalized + "."
36
+ }
@@ -0,0 +1,16 @@
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ /**
5
+ * cn — combine and de-duplicate Tailwind class names.
6
+ *
7
+ * Standard shadcn helper used by every component in the library. Accepts
8
+ * strings, arrays, and conditional objects via clsx, then resolves conflicts
9
+ * via tailwind-merge (e.g. `px-2 px-4` → `px-4`).
10
+ *
11
+ * @example
12
+ * cn("px-2 py-1", isActive && "bg-primary")
13
+ */
14
+ export function cn(...inputs: ClassValue[]): string {
15
+ return twMerge(clsx(inputs))
16
+ }
@@ -0,0 +1,32 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Order labels context — verbatim port of `@1click/ui/src/order/context.tsx`
5
+ * (v2.3.1). Wrap order pages once, override per-store copy via `labels`.
6
+ */
7
+ import { createContext, useContext, type ReactNode } from "react"
8
+ import { defaultOrderLabels, type OrderLabels } from "./labels"
9
+
10
+ const OrderLabelsContext = createContext<OrderLabels>(defaultOrderLabels)
11
+
12
+ export function OrderLabelsProvider({
13
+ labels,
14
+ children,
15
+ }: {
16
+ labels?: Partial<OrderLabels>
17
+ children: ReactNode
18
+ }) {
19
+ const merged = labels
20
+ ? { ...defaultOrderLabels, ...labels }
21
+ : defaultOrderLabels
22
+
23
+ return (
24
+ <OrderLabelsContext.Provider value={merged}>
25
+ {children}
26
+ </OrderLabelsContext.Provider>
27
+ )
28
+ }
29
+
30
+ export function useOrderLabels() {
31
+ return useContext(OrderLabelsContext)
32
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @cartbase/storefront/order — order confirmation + account order views.
3
+ * Ported from `@1click/ui/src/order` (v2.3.1) with barter data seams; see
4
+ * per-module JSDoc and docs/storefront/components.md for the contracts.
5
+ */
6
+ export { OrderLabelsProvider, useOrderLabels } from "./context"
7
+ export { defaultOrderLabels, type OrderLabels } from "./labels"
8
+ export { bulgarianOrderLabels } from "./labels-bg"
9
+
10
+ export {
11
+ OrderConfirmationHeader,
12
+ type OrderConfirmationHeaderProps,
13
+ type OrderHeaderData,
14
+ } from "./order-confirmation-header"
15
+ export {
16
+ OrderItem,
17
+ displayItemFromOrderItem,
18
+ displayItemFromCartLine,
19
+ variantLabelFrom,
20
+ type OrderItemProps,
21
+ type OrderDisplayItem,
22
+ type CartLineLike,
23
+ } from "./order-item"
24
+ export { OrderItemsList, type OrderItemsListProps } from "./order-items-list"
25
+ export {
26
+ OrderTotals,
27
+ selectOrderTotalsRows,
28
+ orderTotalsFromSummary,
29
+ type OrderTotalsProps,
30
+ type OrderTotalsSource,
31
+ type OrderTotalsRow,
32
+ } from "./order-totals"
33
+ export {
34
+ OrderAddressCard,
35
+ type OrderAddressCardProps,
36
+ type OrderContactData,
37
+ } from "./order-address-card"
38
+ export {
39
+ OrderDeliveryCard,
40
+ pickTrackingLabels,
41
+ type OrderDeliveryCardProps,
42
+ type OrderDeliveryData,
43
+ type ShippingMethodDisplay,
44
+ type TrackingLabel,
45
+ } from "./order-delivery-card"
46
+ export {
47
+ OrderPaymentCard,
48
+ resolvePaymentTitle,
49
+ type OrderPaymentCardProps,
50
+ } from "./order-payment-card"
51
+ export {
52
+ OrderTimeline,
53
+ deriveFulfillmentStatus,
54
+ type OrderTimelineProps,
55
+ } from "./order-timeline"
56
+ export {
57
+ OrderHelpSection,
58
+ type OrderHelpSectionProps,
59
+ } from "./order-help-section"
60
+ export {
61
+ OrderCompletedTemplate,
62
+ type OrderCompletedTemplateProps,
63
+ } from "./order-completed-template"
@@ -0,0 +1,39 @@
1
+ import type { OrderLabels } from "./labels"
2
+
3
+ /**
4
+ * Bulgarian order labels — the Alenika/mindpages production copy, ported
5
+ * from `@1click/ui/src/order/labels-bg.ts` (v2.3.1) + `tracking`.
6
+ */
7
+ export const bulgarianOrderLabels: OrderLabels = {
8
+ orderConfirmed: "Поръчката е потвърдена",
9
+ confirmationSent: "Изпратихме потвърждение на",
10
+ orderNumber: "Поръчка",
11
+ orderDate: "Дата",
12
+ summary: "Обобщение на поръчката",
13
+ subtotal: "Междинна сума",
14
+ shipping: "Доставка",
15
+ discount: "Отстъпка",
16
+ tax: "ДДС",
17
+ total: "Общо",
18
+ codFee: "Такса наложен платеж",
19
+ contactInfo: "Данни за контакт",
20
+ delivery: "Доставка",
21
+ paymentMethod: "Метод на плащане",
22
+ paymentMethodTitles: {
23
+ card: "Кредитна / дебитна карта",
24
+ cod: "Наложен платеж",
25
+ manual: "Друг начин на плащане",
26
+ paypal: "PayPal",
27
+ },
28
+ needHelp: "Имате нужда от помощ?",
29
+ contactUs: "Свържете се с нас",
30
+ returnsExchanges: "Връщания и замени",
31
+ orderPlaced: "Приета",
32
+ processing: "Обработка",
33
+ shipped: "Изпратена",
34
+ delivered: "Доставена",
35
+ free: "БЕЗПЛАТНА",
36
+ continueShopping: "Продължи пазаруването",
37
+ qty: "бр.",
38
+ tracking: "Проследяване",
39
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Order family label pack. Ported from `@1click/ui/src/order/labels.ts`
3
+ * (v2.3.1) unchanged, plus `tracking` — new in the barter port because the
4
+ * barter order surface exposes fulfillment tracking labels
5
+ * (`StoreOrderDetail.fulfillments[].fulfillment.labels`) that
6
+ * `OrderDeliveryCard` now renders.
7
+ */
8
+ export type OrderLabels = {
9
+ orderConfirmed: string
10
+ confirmationSent: string
11
+ orderNumber: string
12
+ orderDate: string
13
+ summary: string
14
+ subtotal: string
15
+ shipping: string
16
+ discount: string
17
+ tax: string
18
+ total: string
19
+ /** Default label for the cash-on-delivery fee row. Used as the third-
20
+ * level fallback when neither a per-store override nor the server's
21
+ * `cod_fee_label` / fee line title is available. */
22
+ codFee: string
23
+ contactInfo: string
24
+ delivery: string
25
+ paymentMethod: string
26
+ paymentMethodTitles: {
27
+ card: string
28
+ cod: string
29
+ /** Offline manual payments (pp_manual) — NOT cash on delivery. */
30
+ manual: string
31
+ paypal: string
32
+ }
33
+ needHelp: string
34
+ contactUs: string
35
+ returnsExchanges: string
36
+ orderPlaced: string
37
+ processing: string
38
+ shipped: string
39
+ delivered: string
40
+ free: string
41
+ continueShopping: string
42
+ qty: string
43
+ /** Heading for the fulfillment tracking-numbers block (barter addition). */
44
+ tracking: string
45
+ }
46
+
47
+ export const defaultOrderLabels: OrderLabels = {
48
+ orderConfirmed: "Order confirmed",
49
+ confirmationSent: "We sent a confirmation to",
50
+ orderNumber: "Order",
51
+ orderDate: "Date",
52
+ summary: "Order summary",
53
+ subtotal: "Subtotal",
54
+ shipping: "Shipping",
55
+ discount: "Discount",
56
+ tax: "Tax",
57
+ total: "Total",
58
+ codFee: "Cash on delivery fee",
59
+ contactInfo: "Contact info",
60
+ delivery: "Delivery",
61
+ paymentMethod: "Payment method",
62
+ paymentMethodTitles: {
63
+ card: "Credit / debit card",
64
+ cod: "Cash on delivery",
65
+ manual: "Manual payment",
66
+ paypal: "PayPal",
67
+ },
68
+ needHelp: "Need help?",
69
+ contactUs: "Contact us",
70
+ returnsExchanges: "Returns & exchanges",
71
+ orderPlaced: "Placed",
72
+ processing: "Processing",
73
+ shipped: "Shipped",
74
+ delivered: "Delivered",
75
+ free: "FREE",
76
+ continueShopping: "Continue shopping",
77
+ qty: "qty.",
78
+ tracking: "Tracking",
79
+ }