@shopbb/helium 0.6.3 → 0.7.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/dist/components/AddToCartButton.d.ts +17 -22
- package/dist/components/AddToCartButton.d.ts.map +1 -1
- package/dist/components/AddToCartButton.js +8 -45
- package/dist/components/AddToCartButton.js.map +1 -1
- package/dist/components/AddressForm.d.ts +42 -18
- package/dist/components/AddressForm.d.ts.map +1 -1
- package/dist/components/AddressForm.js +23 -20
- package/dist/components/AddressForm.js.map +1 -1
- package/dist/components/AddressList.d.ts +34 -17
- package/dist/components/AddressList.d.ts.map +1 -1
- package/dist/components/AddressList.js +7 -21
- package/dist/components/AddressList.js.map +1 -1
- package/dist/components/AddressPicker.d.ts +14 -16
- package/dist/components/AddressPicker.d.ts.map +1 -1
- package/dist/components/AddressPicker.js +10 -26
- package/dist/components/AddressPicker.js.map +1 -1
- package/dist/components/AnalyticsProvider.d.ts +5 -2
- package/dist/components/AnalyticsProvider.d.ts.map +1 -1
- package/dist/components/AnalyticsProvider.js +13 -11
- package/dist/components/AnalyticsProvider.js.map +1 -1
- package/dist/components/BuyNowButton.d.ts +7 -24
- package/dist/components/BuyNowButton.d.ts.map +1 -1
- package/dist/components/BuyNowButton.js +9 -43
- package/dist/components/BuyNowButton.js.map +1 -1
- package/dist/components/CartCheckoutButton.d.ts +10 -21
- package/dist/components/CartCheckoutButton.d.ts.map +1 -1
- package/dist/components/CartCheckoutButton.js +6 -11
- package/dist/components/CartCheckoutButton.js.map +1 -1
- package/dist/components/CartCost.d.ts +15 -23
- package/dist/components/CartCost.d.ts.map +1 -1
- package/dist/components/CartCost.js +1 -3
- package/dist/components/CartCost.js.map +1 -1
- package/dist/components/CartForm.d.ts +30 -102
- package/dist/components/CartForm.d.ts.map +1 -1
- package/dist/components/CartForm.js +32 -172
- package/dist/components/CartForm.js.map +1 -1
- package/dist/components/DiscountComponents.d.ts +67 -17
- package/dist/components/DiscountComponents.d.ts.map +1 -1
- package/dist/components/DiscountComponents.js +28 -74
- package/dist/components/DiscountComponents.js.map +1 -1
- package/dist/components/DiscountSelector.d.ts +50 -15
- package/dist/components/DiscountSelector.d.ts.map +1 -1
- package/dist/components/DiscountSelector.js +16 -44
- package/dist/components/DiscountSelector.js.map +1 -1
- package/dist/components/hooks/useOptimisticCart.d.ts +36 -37
- package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -1
- package/dist/components/hooks/useOptimisticCart.js +95 -127
- package/dist/components/hooks/useOptimisticCart.js.map +1 -1
- package/dist/components/index.d.ts +24 -45
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +21 -37
- package/dist/components/index.js.map +1 -1
- package/dist/createCartHandler.d.ts.map +1 -1
- package/dist/createCartHandler.js +8 -1
- package/dist/createCartHandler.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -10
- package/src/components/AddToCartButton.tsx +34 -92
- package/src/components/AddressForm.tsx +56 -26
- package/src/components/AddressList.tsx +42 -33
- package/src/components/AddressPicker.tsx +19 -29
- package/src/components/AnalyticsProvider.tsx +18 -13
- package/src/components/BuyNowButton.tsx +28 -93
- package/src/components/CartCheckoutButton.tsx +16 -33
- package/src/components/CartCost.tsx +16 -28
- package/src/components/CartForm.tsx +87 -231
- package/src/components/DiscountComponents.tsx +94 -100
- package/src/components/DiscountSelector.tsx +68 -49
- package/src/components/hooks/useOptimisticCart.ts +122 -156
- package/src/components/index.ts +51 -99
- package/src/createCartHandler.ts +10 -1
- package/src/index.ts +0 -2
- /package/src/components/{AddressBookProvider.tsx → AddressBookProvider.tsx.deleted-0.7} +0 -0
- /package/src/components/{CartLineQuantityAdjustButton.tsx → CartLineQuantityAdjustButton.tsx.deleted-0.7} +0 -0
- /package/src/components/{CartProvider.tsx → CartProvider.tsx.deleted-0.7} +0 -0
- /package/src/components/{DiscountProvider.tsx → DiscountProvider.tsx.deleted-0.7} +0 -0
- /package/src/components/hooks/{useMounted.ts → useMounted.ts.deleted-0.7} +0 -0
- /package/src/{handleCartFormAction.ts → handleCartFormAction.ts.deleted-0.7} +0 -0
|
@@ -1,46 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <BuyNowButton> —
|
|
2
|
+
* <BuyNowButton> — "立即购买"按钮
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 实现:
|
|
7
|
-
* 1. 内部包 <CartForm action="LinesAdd" inputs={{lines, navigateOnSuccess: '/checkout'}}>
|
|
8
|
-
* 2. submit 成功后 client 自动 navigate 到 checkoutUrl(或 fallback /checkout)
|
|
9
|
-
* 3. 无 JS 时:浏览器走 form POST,服务端 303 跳 /checkout
|
|
4
|
+
* helium 0.7:本质是 <CartForm action=LinesAdd> + 隐藏 redirectTo 字段。
|
|
5
|
+
* shopflare cart.tsx action 收到 redirectTo 后会 303 跳转 checkout。
|
|
10
6
|
*
|
|
11
7
|
* 用法:
|
|
12
|
-
* <BuyNowButton variantId=
|
|
8
|
+
* <BuyNowButton variantId={variantId} quantity={1}>立即购买</BuyNowButton>
|
|
13
9
|
*/
|
|
14
10
|
|
|
15
11
|
import * as React from 'react';
|
|
16
|
-
import { CartForm,
|
|
17
|
-
import { useAnalytics } from './AnalyticsProvider';
|
|
12
|
+
import { CartForm, type CartLineInput } from './CartForm';
|
|
18
13
|
|
|
19
14
|
export interface BuyNowButtonProps {
|
|
20
|
-
/** 必传 variant GID */
|
|
21
15
|
variantId: string;
|
|
22
|
-
/** 数量,默认 1 */
|
|
23
16
|
quantity?: number;
|
|
24
|
-
/** 行级 attributes */
|
|
25
17
|
attributes?: Array<{ key: string; value: string }>;
|
|
26
|
-
|
|
18
|
+
selectedVariant?: any;
|
|
19
|
+
/** 加购成功后跳转的 URL;商家 action 内部需根据 redirectTo 触发 redirect */
|
|
27
20
|
redirectTo?: string;
|
|
28
|
-
/** 失败回调 */
|
|
29
|
-
onError?: (err: Error) => void;
|
|
30
|
-
/** Loading 文案 */
|
|
31
|
-
loadingText?: React.ReactNode;
|
|
32
|
-
/** Disabled 文案 */
|
|
33
21
|
unavailableText?: React.ReactNode;
|
|
34
|
-
/** disabled */
|
|
35
22
|
disabled?: boolean;
|
|
36
|
-
/** Form className */
|
|
37
23
|
className?: string;
|
|
38
|
-
/** 按钮 className */
|
|
39
|
-
buttonClassName?: string;
|
|
40
|
-
/** 按钮 content */
|
|
41
|
-
children?: React.ReactNode;
|
|
42
|
-
/** 提交 route,默认 /cart */
|
|
43
24
|
route?: string;
|
|
25
|
+
children?: React.ReactNode;
|
|
44
26
|
}
|
|
45
27
|
|
|
46
28
|
export function BuyNowButton(props: BuyNowButtonProps) {
|
|
@@ -48,88 +30,41 @@ export function BuyNowButton(props: BuyNowButtonProps) {
|
|
|
48
30
|
variantId,
|
|
49
31
|
quantity = 1,
|
|
50
32
|
attributes,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
loadingText = '处理中...',
|
|
33
|
+
selectedVariant,
|
|
34
|
+
redirectTo = '/checkout',
|
|
54
35
|
unavailableText = '暂不可购',
|
|
55
36
|
disabled = false,
|
|
56
37
|
className,
|
|
57
|
-
buttonClassName,
|
|
58
|
-
children = '立即购买',
|
|
59
38
|
route = '/cart',
|
|
39
|
+
children = '立即购买',
|
|
60
40
|
} = props;
|
|
61
41
|
|
|
62
42
|
const line: CartLineInput = { merchandiseId: variantId, quantity };
|
|
63
43
|
if (attributes && attributes.length > 0) line.attributes = attributes;
|
|
44
|
+
if (selectedVariant) line.selectedVariant = selectedVariant;
|
|
64
45
|
|
|
65
46
|
return (
|
|
66
47
|
<CartForm
|
|
67
48
|
route={route}
|
|
68
49
|
action={CartForm.ACTIONS.LinesAdd}
|
|
69
50
|
inputs={{ lines: [line] }}
|
|
70
|
-
className={className}
|
|
71
51
|
>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
52
|
+
{(fetcher) => {
|
|
53
|
+
const submitting = fetcher.state !== 'idle';
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<input type="hidden" name="redirectTo" value={redirectTo} />
|
|
57
|
+
<button
|
|
58
|
+
type="submit"
|
|
59
|
+
className={className}
|
|
60
|
+
disabled={disabled || submitting}
|
|
61
|
+
data-buy-now
|
|
62
|
+
>
|
|
63
|
+
{submitting ? '处理中...' : disabled ? unavailableText : children}
|
|
64
|
+
</button>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
}}
|
|
84
68
|
</CartForm>
|
|
85
69
|
);
|
|
86
70
|
}
|
|
87
|
-
|
|
88
|
-
function BuyNowButtonInner({
|
|
89
|
-
disabled, buttonClassName, loadingText, unavailableText, children,
|
|
90
|
-
variantId, quantity, redirectTo, onError,
|
|
91
|
-
}: {
|
|
92
|
-
disabled: boolean;
|
|
93
|
-
buttonClassName?: string;
|
|
94
|
-
loadingText: React.ReactNode;
|
|
95
|
-
unavailableText: React.ReactNode;
|
|
96
|
-
children: React.ReactNode;
|
|
97
|
-
variantId: string;
|
|
98
|
-
quantity: number;
|
|
99
|
-
redirectTo?: string;
|
|
100
|
-
onError?: (err: Error) => void;
|
|
101
|
-
}) {
|
|
102
|
-
const fetcher = useFetcher();
|
|
103
|
-
const analytics = useAnalytics();
|
|
104
|
-
const prevState = React.useRef(fetcher.state);
|
|
105
|
-
|
|
106
|
-
React.useEffect(() => {
|
|
107
|
-
const wasNonIdle = prevState.current !== 'idle';
|
|
108
|
-
const isIdle = fetcher.state === 'idle';
|
|
109
|
-
if (wasNonIdle && isIdle) {
|
|
110
|
-
if (fetcher.error) {
|
|
111
|
-
onError?.(new Error(fetcher.error));
|
|
112
|
-
} else if (fetcher.data?.cart) {
|
|
113
|
-
analytics.emit('buy_now', { variantId, quantity });
|
|
114
|
-
const target = redirectTo || (fetcher.data.cart as any).checkoutUrl || '/checkout';
|
|
115
|
-
if (typeof window !== 'undefined') {
|
|
116
|
-
window.location.href = target;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
prevState.current = fetcher.state;
|
|
121
|
-
}, [fetcher.state, fetcher.error, fetcher.data, redirectTo, onError, analytics, variantId, quantity]);
|
|
122
|
-
|
|
123
|
-
const pending = fetcher.state !== 'idle';
|
|
124
|
-
return (
|
|
125
|
-
<button
|
|
126
|
-
type="submit"
|
|
127
|
-
className={buttonClassName}
|
|
128
|
-
disabled={disabled || pending}
|
|
129
|
-
data-buy-now
|
|
130
|
-
data-loading={pending ? '' : undefined}
|
|
131
|
-
>
|
|
132
|
-
{pending ? loadingText : disabled ? unavailableText : children}
|
|
133
|
-
</button>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
@@ -1,54 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <CartCheckoutButton> — 对齐 Hydrogen React
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 渲染跳转到 cart.checkoutUrl 的按钮 / 链接。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* - 取 cart.checkoutUrl,无 cart / 空 cart 时按钮 disabled
|
|
8
|
-
* - 默认 <a> 元素(无 JS 也能用),可改成 <button> + onClick
|
|
9
|
-
* - 渲染纯样式,行为非业务逻辑
|
|
6
|
+
* helium 0.7:从 props 接 checkoutUrl,不再 useCart()。
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* // 自定义渲染
|
|
15
|
-
* <CartCheckoutButton>
|
|
16
|
-
* {(href, disabled) => (
|
|
17
|
-
* <a href={href} className="my-cta" aria-disabled={disabled}>
|
|
18
|
-
* 结算 {cart.totalQuantity} 件
|
|
19
|
-
* </a>
|
|
20
|
-
* )}
|
|
8
|
+
* <CartCheckoutButton checkoutUrl={cart?.checkoutUrl} disabled={!cart?.totalQuantity}>
|
|
9
|
+
* 去结算
|
|
21
10
|
* </CartCheckoutButton>
|
|
22
11
|
*/
|
|
23
12
|
|
|
24
13
|
import * as React from 'react';
|
|
25
|
-
import { useCart } from './CartProvider';
|
|
26
14
|
|
|
27
15
|
export interface CartCheckoutButtonProps {
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/** 强制 disabled
|
|
16
|
+
/** cart.checkoutUrl — 没有时按钮 disabled */
|
|
17
|
+
checkoutUrl?: string | null;
|
|
18
|
+
/** 强制 disabled */
|
|
31
19
|
disabled?: boolean;
|
|
32
20
|
/** 按钮内容;不传默认 "去结算" */
|
|
33
21
|
children?: React.ReactNode | ((href: string, disabled: boolean) => React.ReactNode);
|
|
34
|
-
/**
|
|
22
|
+
/** className */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** 渲染元素:默认 a,可改 button */
|
|
35
25
|
as?: 'a' | 'button';
|
|
36
|
-
/** 点击回调(事件触发) */
|
|
37
26
|
onClick?: React.MouseEventHandler;
|
|
38
|
-
/** 透传其它 props 到根元素 */
|
|
39
27
|
[key: string]: any;
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
export function CartCheckoutButton(props: CartCheckoutButtonProps) {
|
|
43
|
-
const {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const href = (cart as any)?.checkoutUrl as string | undefined;
|
|
47
|
-
const noCart = !cart || cart.totalQuantity === 0 || !href;
|
|
48
|
-
const disabled = !!disabledProp || noCart;
|
|
31
|
+
const { checkoutUrl, disabled: disabledProp, children, className, as = 'a', onClick, ...rest } = props;
|
|
32
|
+
const disabled = !!disabledProp || !checkoutUrl;
|
|
49
33
|
|
|
50
34
|
if (typeof children === 'function') {
|
|
51
|
-
return <>{children(
|
|
35
|
+
return <>{children(checkoutUrl || '#', disabled)}</>;
|
|
52
36
|
}
|
|
53
37
|
|
|
54
38
|
const label = children ?? '去结算';
|
|
@@ -63,8 +47,8 @@ export function CartCheckoutButton(props: CartCheckoutButtonProps) {
|
|
|
63
47
|
onClick={(e) => {
|
|
64
48
|
if (disabled) return;
|
|
65
49
|
onClick?.(e);
|
|
66
|
-
if (!e.defaultPrevented &&
|
|
67
|
-
window.location.href =
|
|
50
|
+
if (!e.defaultPrevented && checkoutUrl) {
|
|
51
|
+
window.location.href = checkoutUrl;
|
|
68
52
|
}
|
|
69
53
|
}}
|
|
70
54
|
{...rest}
|
|
@@ -74,10 +58,9 @@ export function CartCheckoutButton(props: CartCheckoutButtonProps) {
|
|
|
74
58
|
);
|
|
75
59
|
}
|
|
76
60
|
|
|
77
|
-
// <a> 形式:无 JS 也能跳
|
|
78
61
|
return (
|
|
79
62
|
<a
|
|
80
|
-
href={disabled ? undefined :
|
|
63
|
+
href={disabled ? undefined : (checkoutUrl || undefined)}
|
|
81
64
|
className={className}
|
|
82
65
|
aria-disabled={disabled || undefined}
|
|
83
66
|
data-cart-checkout-button
|
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <CartCost> — 对齐 Hydrogen React
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* "total" → cost.totalAmount (默认)
|
|
6
|
-
* "subtotal" → cost.subtotalAmount
|
|
7
|
-
* "tax" → cost.totalTaxAmount
|
|
8
|
-
* "duty" → cost.totalDutyAmount
|
|
9
|
-
* "discount" → cost.totalDiscountAmount(shopbb-extension)
|
|
4
|
+
* 渲染 cart 上不同维度的金额。
|
|
10
5
|
*
|
|
11
|
-
*
|
|
6
|
+
* Hydrogen 0.7 改造:cart 通过 prop 接入(不再 useCart())。
|
|
7
|
+
* 商家从 loader 拿 cart 后传入。
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
* <CartCost amountType="
|
|
15
|
-
* <CartCost amountType="discount"
|
|
16
|
-
* <CartCost amountType="total" withoutTrailingZeros />
|
|
17
|
-
*
|
|
18
|
-
* 自定义渲染:
|
|
19
|
-
* <CartCost amountType="total">
|
|
20
|
-
* {(money) => <strong>{money.amount} {money.currencyCode}</strong>}
|
|
21
|
-
* </CartCost>
|
|
9
|
+
* <CartCost cart={cart} amountType="subtotal" />
|
|
10
|
+
* <CartCost cart={cart} amountType="total" as="strong" />
|
|
11
|
+
* <CartCost cart={cart} amountType="discount">{(money) => <strong>{money.amount}</strong>}</CartCost>
|
|
22
12
|
*/
|
|
23
13
|
|
|
24
14
|
import * as React from 'react';
|
|
25
|
-
import { useCart } from './CartProvider';
|
|
26
15
|
import { Money, type MoneyProps } from './Money';
|
|
27
16
|
|
|
28
17
|
export interface MoneyValue {
|
|
@@ -31,24 +20,24 @@ export interface MoneyValue {
|
|
|
31
20
|
}
|
|
32
21
|
|
|
33
22
|
export interface CartCostProps extends Omit<MoneyProps, 'data' | 'children'> {
|
|
23
|
+
/** cart 对象 — 必须包含 .cost 字段 */
|
|
24
|
+
cart: any;
|
|
34
25
|
/**
|
|
35
|
-
* 渲染哪一项 cost
|
|
36
|
-
* - "total" :
|
|
37
|
-
* - "subtotal" :
|
|
38
|
-
* - "tax" :
|
|
39
|
-
* - "duty" :
|
|
40
|
-
* - "discount" :
|
|
26
|
+
* 渲染哪一项 cost:
|
|
27
|
+
* - "total" : cart.cost.totalAmount(默认)
|
|
28
|
+
* - "subtotal" : cart.cost.subtotalAmount
|
|
29
|
+
* - "tax" : cart.cost.totalTaxAmount
|
|
30
|
+
* - "duty" : cart.cost.totalDutyAmount
|
|
31
|
+
* - "discount" : cart.cost.totalDiscountAmount(shopbb extension)
|
|
41
32
|
*/
|
|
42
33
|
amountType?: 'total' | 'subtotal' | 'tax' | 'duty' | 'discount';
|
|
43
|
-
/**
|
|
34
|
+
/** 自定义渲染 */
|
|
44
35
|
children?: (money: MoneyValue) => React.ReactNode;
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
export function CartCost(props: CartCostProps) {
|
|
48
|
-
const { amountType = 'total', children, ...moneyProps } = props;
|
|
49
|
-
const { cart } = useCart();
|
|
39
|
+
const { cart, amountType = 'total', children, ...moneyProps } = props;
|
|
50
40
|
if (!cart?.cost) return null;
|
|
51
|
-
|
|
52
41
|
const cost = cart.cost as any;
|
|
53
42
|
let money: MoneyValue | null = null;
|
|
54
43
|
switch (amountType) {
|
|
@@ -59,7 +48,6 @@ export function CartCost(props: CartCostProps) {
|
|
|
59
48
|
case 'discount': money = cost.totalDiscountAmount ?? null; break;
|
|
60
49
|
}
|
|
61
50
|
if (!money) return null;
|
|
62
|
-
|
|
63
51
|
if (children) return <>{children(money)}</>;
|
|
64
52
|
return <Money data={money} {...moneyProps} />;
|
|
65
53
|
}
|