@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.
- package/package.json +226 -0
- package/src/api/auth.ts +108 -0
- package/src/api/carts.ts +506 -0
- package/src/api/categories.ts +184 -0
- package/src/api/checkout.ts +440 -0
- package/src/api/collections.ts +130 -0
- package/src/api/consent.ts +75 -0
- package/src/api/content.ts +125 -0
- package/src/api/customers.ts +307 -0
- package/src/api/gift-cards.ts +112 -0
- package/src/api/http.ts +122 -0
- package/src/api/index.ts +29 -0
- package/src/api/integrations.ts +130 -0
- package/src/api/menus.ts +77 -0
- package/src/api/metaobjects.ts +136 -0
- package/src/api/orders.ts +290 -0
- package/src/api/products.ts +303 -0
- package/src/api/redirects.ts +37 -0
- package/src/api/regions.ts +141 -0
- package/src/api/reviews.ts +259 -0
- package/src/api/search.ts +133 -0
- package/src/api/types.ts +91 -0
- package/src/cart-drawer/cart-drawer.tsx +86 -0
- package/src/cart-drawer/context.tsx +569 -0
- package/src/cart-drawer/continue-shopping.tsx +28 -0
- package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
- package/src/cart-drawer/empty.tsx +52 -0
- package/src/cart-drawer/free-gift.tsx +71 -0
- package/src/cart-drawer/gift-wrap.tsx +83 -0
- package/src/cart-drawer/header.tsx +52 -0
- package/src/cart-drawer/index.ts +69 -0
- package/src/cart-drawer/item/index.tsx +164 -0
- package/src/cart-drawer/item/quantity.tsx +100 -0
- package/src/cart-drawer/item/upsell.tsx +110 -0
- package/src/cart-drawer/item/variant.tsx +46 -0
- package/src/cart-drawer/labels-bg.ts +72 -0
- package/src/cart-drawer/labels.ts +119 -0
- package/src/cart-drawer/notes.tsx +131 -0
- package/src/cart-drawer/payment-badges.tsx +96 -0
- package/src/cart-drawer/promo-banner.tsx +43 -0
- package/src/cart-drawer/rewards-points.tsx +78 -0
- package/src/cart-drawer/sticky-footer.tsx +73 -0
- package/src/cart-drawer/summary-breakdown.tsx +196 -0
- package/src/cart-drawer/template.tsx +225 -0
- package/src/cart-drawer/tiered-progress.tsx +168 -0
- package/src/checkout/address-error-copy.ts +119 -0
- package/src/checkout/address-form.tsx +224 -0
- package/src/checkout/address-select.tsx +79 -0
- package/src/checkout/boxnow-locker-selector.tsx +410 -0
- package/src/checkout/checkout-client.tsx +222 -0
- package/src/checkout/company-details.tsx +94 -0
- package/src/checkout/compare-addresses.ts +40 -0
- package/src/checkout/context.tsx +76 -0
- package/src/checkout/discount-section.tsx +218 -0
- package/src/checkout/econt-office-selector.tsx +332 -0
- package/src/checkout/error-message.tsx +25 -0
- package/src/checkout/geocode.ts +154 -0
- package/src/checkout/gift-card-section.tsx +224 -0
- package/src/checkout/index.ts +74 -0
- package/src/checkout/labels-bg.ts +128 -0
- package/src/checkout/labels.ts +263 -0
- package/src/checkout/line-item-card.tsx +152 -0
- package/src/checkout/order-summary.tsx +524 -0
- package/src/checkout/payment-button.tsx +373 -0
- package/src/checkout/payment-error-copy.ts +269 -0
- package/src/checkout/payment-method-list.tsx +365 -0
- package/src/checkout/payment-wrapper.tsx +102 -0
- package/src/checkout/promotion-error-copy.ts +124 -0
- package/src/checkout/shipping-method-list.tsx +335 -0
- package/src/checkout/stripe-wrapper.tsx +165 -0
- package/src/checkout/use-checkout-orchestration.ts +1504 -0
- package/src/common/cart-button-client.tsx +39 -0
- package/src/common/cart-button.tsx +28 -0
- package/src/common/country-select.tsx +65 -0
- package/src/common/delete-button.tsx +66 -0
- package/src/common/index.ts +17 -0
- package/src/common/language-select.tsx +78 -0
- package/src/common/localized-link.tsx +45 -0
- package/src/common/skeleton.tsx +29 -0
- package/src/index.ts +12 -0
- package/src/lib/cart-helpers.ts +113 -0
- package/src/lib/dual-price.tsx +73 -0
- package/src/lib/get-percentage-diff.ts +5 -0
- package/src/lib/get-product-price.ts +133 -0
- package/src/lib/hooks/use-intersection.ts +30 -0
- package/src/lib/hooks/use-toggle-state.ts +25 -0
- package/src/lib/money.ts +73 -0
- package/src/lib/payment-constants.ts +66 -0
- package/src/lib/product.ts +22 -0
- package/src/lib/sort-products.ts +63 -0
- package/src/lib/store-api-error.ts +36 -0
- package/src/lib/utils.ts +16 -0
- package/src/order/context.tsx +32 -0
- package/src/order/index.ts +63 -0
- package/src/order/labels-bg.ts +39 -0
- package/src/order/labels.ts +79 -0
- package/src/order/order-address-card.tsx +47 -0
- package/src/order/order-completed-template.tsx +165 -0
- package/src/order/order-confirmation-header.tsx +65 -0
- package/src/order/order-delivery-card.tsx +258 -0
- package/src/order/order-help-section.tsx +47 -0
- package/src/order/order-item.tsx +201 -0
- package/src/order/order-items-list.tsx +52 -0
- package/src/order/order-payment-card.tsx +95 -0
- package/src/order/order-timeline.tsx +141 -0
- package/src/order/order-totals.tsx +245 -0
- package/src/primitives/field.tsx +125 -0
- package/src/primitives/select-field.tsx +77 -0
- package/src/primitives/ui/accordion.tsx +61 -0
- package/src/primitives/ui/button.tsx +68 -0
- package/src/primitives/ui/collapsible.tsx +16 -0
- package/src/primitives/ui/dialog.tsx +112 -0
- package/src/primitives/ui/input.tsx +30 -0
- package/src/primitives/ui/label.tsx +31 -0
- package/src/primitives/ui/popover.tsx +38 -0
- package/src/primitives/ui/select.tsx +163 -0
- package/src/primitives/ui/sheet.tsx +131 -0
- package/src/primitives/ui/tabs.tsx +62 -0
- package/src/products/context.tsx +34 -0
- package/src/products/image-gallery.tsx +43 -0
- package/src/products/index.ts +44 -0
- package/src/products/labels-bg.ts +35 -0
- package/src/products/labels.ts +57 -0
- package/src/products/mobile-actions.tsx +180 -0
- package/src/products/option-select.tsx +67 -0
- package/src/products/preview-price.tsx +36 -0
- package/src/products/product-actions-wrapper.tsx +58 -0
- package/src/products/product-actions.tsx +217 -0
- package/src/products/product-info.tsx +43 -0
- package/src/products/product-preview.tsx +49 -0
- package/src/products/product-price.tsx +69 -0
- package/src/products/product-tabs.tsx +169 -0
- package/src/products/product-template.tsx +114 -0
- package/src/products/purchase-options.tsx +130 -0
- package/src/products/related-products.tsx +86 -0
- package/src/products/thumbnail.tsx +71 -0
- package/src/products/variant-matching.ts +71 -0
- package/src/reviews-ui/helpers.ts +174 -0
- package/src/reviews-ui/index.ts +74 -0
- package/src/reviews-ui/labels-bg.ts +91 -0
- package/src/reviews-ui/labels.ts +199 -0
- package/src/reviews-ui/photo-upload.tsx +345 -0
- package/src/reviews-ui/review-list.tsx +249 -0
- package/src/reviews-ui/review-widget.tsx +224 -0
- package/src/reviews-ui/review-wizard.tsx +560 -0
- package/src/reviews-ui/star-badge.tsx +104 -0
- package/src/reviews-ui/wizard-state.ts +81 -0
- package/src/store/category-template.tsx +129 -0
- package/src/store/collection-template.tsx +139 -0
- package/src/store/index.ts +41 -0
- package/src/store/labels-bg.ts +22 -0
- package/src/store/labels.ts +52 -0
- package/src/store/paginated-products.tsx +116 -0
- package/src/store/pagination.tsx +103 -0
- package/src/store/search-params.ts +256 -0
- package/src/store/search-template.tsx +249 -0
- package/src/store/skeleton-product-grid.tsx +26 -0
- package/src/store/sort-select.tsx +81 -0
- package/src/store/store-template.tsx +65 -0
- package/src/tracking/attribution.ts +418 -0
- package/src/tracking/consent-banner.tsx +355 -0
- package/src/tracking/consent-init.tsx +44 -0
- package/src/tracking/consent.ts +243 -0
- package/src/tracking/fbq.ts +168 -0
- package/src/tracking/ga4.tsx +49 -0
- package/src/tracking/get-tracking-attribution.ts +224 -0
- package/src/tracking/get-tracking-config.ts +50 -0
- package/src/tracking/gtag.ts +200 -0
- package/src/tracking/index.ts +133 -0
- package/src/tracking/meta-pixel.tsx +166 -0
- package/src/tracking/rybbit-events.ts +242 -0
- package/src/tracking/rybbit.tsx +40 -0
- package/src/tracking/types.ts +185 -0
- package/src/tracking/use-engagement-time.ts +58 -0
- 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
|
package/src/lib/money.ts
ADDED
|
@@ -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
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -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
|
+
}
|