@shopbb/helium 0.2.0 → 0.3.0-alpha.2
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 +41 -0
- package/dist/components/AddToCartButton.d.ts.map +1 -0
- package/dist/components/AddToCartButton.js +63 -0
- package/dist/components/AddToCartButton.js.map +1 -0
- package/dist/components/AnalyticsProvider.d.ts +106 -0
- package/dist/components/AnalyticsProvider.d.ts.map +1 -0
- package/dist/components/AnalyticsProvider.js +135 -0
- package/dist/components/AnalyticsProvider.js.map +1 -0
- package/dist/components/CartLineQuantityAdjustButton.d.ts +40 -0
- package/dist/components/CartLineQuantityAdjustButton.d.ts.map +1 -0
- package/dist/components/CartLineQuantityAdjustButton.js +58 -0
- package/dist/components/CartLineQuantityAdjustButton.js.map +1 -0
- package/dist/components/CartProvider.d.ts +111 -0
- package/dist/components/CartProvider.d.ts.map +1 -0
- package/dist/components/CartProvider.js +263 -0
- package/dist/components/CartProvider.js.map +1 -0
- package/dist/components/Image.d.ts +39 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +28 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Money.d.ts +41 -0
- package/dist/components/Money.d.ts.map +1 -0
- package/dist/components/Money.js +53 -0
- package/dist/components/Money.js.map +1 -0
- package/dist/components/ProductOptionsProvider.d.ts +70 -0
- package/dist/components/ProductOptionsProvider.d.ts.map +1 -0
- package/dist/components/ProductOptionsProvider.js +93 -0
- package/dist/components/ProductOptionsProvider.js.map +1 -0
- package/dist/components/ProductPrice.d.ts +28 -0
- package/dist/components/ProductPrice.d.ts.map +1 -0
- package/dist/components/ProductPrice.js +10 -0
- package/dist/components/ProductPrice.js.map +1 -0
- package/dist/components/ShopProvider.d.ts +49 -0
- package/dist/components/ShopProvider.d.ts.map +1 -0
- package/dist/components/ShopProvider.js +62 -0
- package/dist/components/ShopProvider.js.map +1 -0
- package/dist/components/VariantSelector.d.ts +80 -0
- package/dist/components/VariantSelector.d.ts.map +1 -0
- package/dist/components/VariantSelector.js +87 -0
- package/dist/components/VariantSelector.js.map +1 -0
- package/dist/components/index.d.ts +41 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +33 -0
- package/dist/components/index.js.map +1 -0
- package/package.json +12 -4
- package/src/components/AddToCartButton.tsx +101 -0
- package/src/components/AnalyticsProvider.tsx +175 -0
- package/src/components/CartLineQuantityAdjustButton.tsx +119 -0
- package/src/components/CartProvider.tsx +378 -0
- package/src/components/Image.tsx +93 -0
- package/src/components/Money.tsx +112 -0
- package/src/components/ProductOptionsProvider.tsx +149 -0
- package/src/components/ProductPrice.tsx +61 -0
- package/src/components/ShopProvider.tsx +86 -0
- package/src/components/VariantSelector.tsx +148 -0
- package/src/components/index.ts +71 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <AddToCartButton> — 加入购物车按钮
|
|
3
|
+
*
|
|
4
|
+
* 对齐 @shopify/hydrogen <AddToCartButton>:
|
|
5
|
+
* - 内置 loading 态、disabled、error 处理
|
|
6
|
+
* - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
|
|
7
|
+
* - 触发 analytics 事件钩子
|
|
8
|
+
* - 不带样式,商家自己控制
|
|
9
|
+
*
|
|
10
|
+
* 用法(Phase 1,需要传 onAdd):
|
|
11
|
+
* const { addLine } = useCart();
|
|
12
|
+
* <AddToCartButton
|
|
13
|
+
* variantId={selectedVariant.id}
|
|
14
|
+
* quantity={1}
|
|
15
|
+
* disabled={!selectedVariant.availableForSale}
|
|
16
|
+
* onAdd={(vid, qty) => addLine(vid, qty)}
|
|
17
|
+
* >
|
|
18
|
+
* 加入购物车
|
|
19
|
+
* </AddToCartButton>
|
|
20
|
+
*
|
|
21
|
+
* Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
|
|
22
|
+
*/
|
|
23
|
+
import * as React from 'react';
|
|
24
|
+
export interface AddToCartButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'onError'> {
|
|
25
|
+
/** 必传:variant 的 GID(gid://shopbb/ProductVariant/...) */
|
|
26
|
+
variantId: string;
|
|
27
|
+
/** 数量,默认 1 */
|
|
28
|
+
quantity?: number;
|
|
29
|
+
/** 实际加购回调。Phase 1 必传;Phase 2 后可从 Provider 自动获取 */
|
|
30
|
+
onAdd?: (variantId: string, quantity: number) => Promise<void>;
|
|
31
|
+
/** 加购成功回调(如导航到 /cart) */
|
|
32
|
+
onAdded?: () => void;
|
|
33
|
+
/** 加购失败回调 */
|
|
34
|
+
onError?: (err: Error) => void;
|
|
35
|
+
/** 加购中显示的文本,默认 "加入中..." */
|
|
36
|
+
loadingText?: React.ReactNode;
|
|
37
|
+
/** 不可用时显示的文本,默认 "缺货" */
|
|
38
|
+
unavailableText?: React.ReactNode;
|
|
39
|
+
}
|
|
40
|
+
export declare function AddToCartButton(props: AddToCartButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=AddToCartButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddToCartButton.d.ts","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACtH,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa;IACb,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,wBAAwB;IACxB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAwD1D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <AddToCartButton> — 加入购物车按钮
|
|
4
|
+
*
|
|
5
|
+
* 对齐 @shopify/hydrogen <AddToCartButton>:
|
|
6
|
+
* - 内置 loading 态、disabled、error 处理
|
|
7
|
+
* - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
|
|
8
|
+
* - 触发 analytics 事件钩子
|
|
9
|
+
* - 不带样式,商家自己控制
|
|
10
|
+
*
|
|
11
|
+
* 用法(Phase 1,需要传 onAdd):
|
|
12
|
+
* const { addLine } = useCart();
|
|
13
|
+
* <AddToCartButton
|
|
14
|
+
* variantId={selectedVariant.id}
|
|
15
|
+
* quantity={1}
|
|
16
|
+
* disabled={!selectedVariant.availableForSale}
|
|
17
|
+
* onAdd={(vid, qty) => addLine(vid, qty)}
|
|
18
|
+
* >
|
|
19
|
+
* 加入购物车
|
|
20
|
+
* </AddToCartButton>
|
|
21
|
+
*
|
|
22
|
+
* Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
|
|
23
|
+
*/
|
|
24
|
+
import * as React from 'react';
|
|
25
|
+
import { useCartOptional } from './CartProvider';
|
|
26
|
+
import { useAnalytics } from './AnalyticsProvider';
|
|
27
|
+
export function AddToCartButton(props) {
|
|
28
|
+
const { variantId, quantity = 1, onAdd, onAdded, onError, loadingText = '加入中...', unavailableText = '缺货', children = '加入购物车', disabled: disabledProp, ...rest } = props;
|
|
29
|
+
const [adding, setAdding] = React.useState(false);
|
|
30
|
+
const cart = useCartOptional();
|
|
31
|
+
const analytics = useAnalytics();
|
|
32
|
+
const handleClick = async (e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
if (adding || disabledProp)
|
|
35
|
+
return;
|
|
36
|
+
setAdding(true);
|
|
37
|
+
try {
|
|
38
|
+
if (onAdd) {
|
|
39
|
+
// 商家显式传了 onAdd 优先用(Phase 1 兼容路径)
|
|
40
|
+
await onAdd(variantId, quantity);
|
|
41
|
+
}
|
|
42
|
+
else if (cart) {
|
|
43
|
+
// 自动从 <CartProvider> 取
|
|
44
|
+
await cart.linesAdd([{ merchandiseId: variantId, quantity }]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.warn('[AddToCartButton] no onAdd and no <CartProvider> — button does nothing');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
analytics.emit('add_to_cart', { variantId, quantity });
|
|
51
|
+
onAdded?.();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error('[AddToCartButton] add failed:', err);
|
|
55
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
setAdding(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return (_jsx("button", { type: "button", ...rest, "data-add-to-cart": true, "data-loading": adding ? '' : undefined, disabled: disabledProp || adding, onClick: handleClick, children: adding ? loadingText : disabledProp ? unavailableText : children }));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=AddToCartButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddToCartButton.js","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAmBnD,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,EACJ,SAAS,EACT,QAAQ,GAAG,CAAC,EACZ,KAAK,EACL,OAAO,EACP,OAAO,EACP,WAAW,GAAG,QAAQ,EACtB,eAAe,GAAG,IAAI,EACtB,QAAQ,GAAG,OAAO,EAClB,QAAQ,EAAE,YAAY,EACtB,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,MAAM,WAAW,GAAG,KAAK,EAAE,CAAsC,EAAE,EAAE;QACnE,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,IAAI,MAAM,IAAI,YAAY;YAAE,OAAO;QAEnC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,CAAC;gBACV,iCAAiC;gBACjC,MAAM,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACnC,CAAC;iBAAM,IAAI,IAAI,EAAE,CAAC;gBAChB,uBAAuB;gBACvB,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;YACvD,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,KACT,IAAI,4CAEM,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EACrC,QAAQ,EAAE,YAAY,IAAI,MAAM,EAChC,OAAO,EAAE,WAAW,YAEnB,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,GAC1D,CACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <AnalyticsProvider> + useAnalytics() + <Analytics.*> 事件组件
|
|
3
|
+
*
|
|
4
|
+
* 简单事件总线:
|
|
5
|
+
* - 商家注册 reporters(GA / Plausible / 自家 API)
|
|
6
|
+
* - 通过 <Analytics.PageView /> / <Analytics.ProductView /> 等组件声明式触发
|
|
7
|
+
* - 也可以 useAnalytics().emit(name, data)
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* <AnalyticsProvider
|
|
11
|
+
* onEvent={(e) => {
|
|
12
|
+
* fetch('/api/analytics', { method: 'POST', body: JSON.stringify(e) });
|
|
13
|
+
* window.dataLayer?.push(e);
|
|
14
|
+
* }}
|
|
15
|
+
* >
|
|
16
|
+
* <App />
|
|
17
|
+
* </AnalyticsProvider>
|
|
18
|
+
*
|
|
19
|
+
* // 在某页:
|
|
20
|
+
* <Analytics.PageView path="/products" title="Products" />
|
|
21
|
+
* <Analytics.ProductView product={product} />
|
|
22
|
+
* <Analytics.AddToCart cart={cart} addedLineId={lineId} />
|
|
23
|
+
*
|
|
24
|
+
* // 命令式:
|
|
25
|
+
* const { emit } = useAnalytics();
|
|
26
|
+
* emit('search', { query: 'wireless' });
|
|
27
|
+
*/
|
|
28
|
+
import * as React from 'react';
|
|
29
|
+
export interface AnalyticsEvent {
|
|
30
|
+
/** 事件名,例如 'page_view' / 'product_view' / 'add_to_cart' */
|
|
31
|
+
name: string;
|
|
32
|
+
/** 时间戳 (ms) */
|
|
33
|
+
timestamp: number;
|
|
34
|
+
/** 当前 shop(如果在 ShopProvider 内) */
|
|
35
|
+
shop?: {
|
|
36
|
+
storeId: string;
|
|
37
|
+
shopName: string;
|
|
38
|
+
} | null;
|
|
39
|
+
/** 自定义 payload */
|
|
40
|
+
payload: Record<string, any>;
|
|
41
|
+
}
|
|
42
|
+
export interface AnalyticsContextValue {
|
|
43
|
+
emit: (name: string, payload?: Record<string, any>) => void;
|
|
44
|
+
}
|
|
45
|
+
export interface AnalyticsProviderProps {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
/** 接收所有事件的回调 */
|
|
48
|
+
onEvent?: (event: AnalyticsEvent) => void;
|
|
49
|
+
/** 是否自动监听 cart 变化触发 cart_updated 事件 */
|
|
50
|
+
trackCart?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function AnalyticsProvider(props: AnalyticsProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export declare function useAnalytics(): AnalyticsContextValue;
|
|
54
|
+
declare function PageView(props: {
|
|
55
|
+
path?: string;
|
|
56
|
+
title?: string;
|
|
57
|
+
[key: string]: any;
|
|
58
|
+
}): null;
|
|
59
|
+
declare function ProductView(props: {
|
|
60
|
+
product: {
|
|
61
|
+
id: string;
|
|
62
|
+
handle?: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
};
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
}): null;
|
|
67
|
+
declare function CollectionView(props: {
|
|
68
|
+
collection: {
|
|
69
|
+
id: string;
|
|
70
|
+
handle?: string;
|
|
71
|
+
};
|
|
72
|
+
[key: string]: any;
|
|
73
|
+
}): null;
|
|
74
|
+
declare function SearchView(props: {
|
|
75
|
+
query: string;
|
|
76
|
+
results?: number;
|
|
77
|
+
}): null;
|
|
78
|
+
declare function AddToCart(props: {
|
|
79
|
+
variantId: string;
|
|
80
|
+
quantity?: number;
|
|
81
|
+
[key: string]: any;
|
|
82
|
+
}): null;
|
|
83
|
+
declare function CheckoutStart(props: {
|
|
84
|
+
cart?: {
|
|
85
|
+
id: string;
|
|
86
|
+
totalQuantity?: number;
|
|
87
|
+
};
|
|
88
|
+
}): null;
|
|
89
|
+
declare function Purchase(props: {
|
|
90
|
+
orderId: string;
|
|
91
|
+
total: number;
|
|
92
|
+
currencyCode: string;
|
|
93
|
+
[key: string]: any;
|
|
94
|
+
}): null;
|
|
95
|
+
/** 命名空间导出,让用户写 <Analytics.PageView /> */
|
|
96
|
+
export declare const Analytics: {
|
|
97
|
+
PageView: typeof PageView;
|
|
98
|
+
ProductView: typeof ProductView;
|
|
99
|
+
CollectionView: typeof CollectionView;
|
|
100
|
+
SearchView: typeof SearchView;
|
|
101
|
+
AddToCart: typeof AddToCart;
|
|
102
|
+
CheckoutStart: typeof CheckoutStart;
|
|
103
|
+
Purchase: typeof Purchase;
|
|
104
|
+
};
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=AnalyticsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsProvider.d.ts","sourceRoot":"","sources":["../../src/components/AnalyticsProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,eAAe;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;CAC7D;AAID,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,gBAAgB;IAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC1C,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,2CAkC9D;AAED,wBAAgB,YAAY,IAAI,qBAAqB,CAOpD;AAkBD,iBAAS,QAAQ,CAAC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QAE7E;AAED,iBAAS,WAAW,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QAQ3G;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE;IAAE,UAAU,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QAOjG;AAED,iBAAS,UAAU,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,QAE7D;AAED,iBAAS,SAAS,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QAOrF;AAED,iBAAS,aAAa,CAAC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,QAK9E;AAED,iBAAS,QAAQ,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,QAEpG;AAED,yCAAyC;AACzC,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <AnalyticsProvider> + useAnalytics() + <Analytics.*> 事件组件
|
|
4
|
+
*
|
|
5
|
+
* 简单事件总线:
|
|
6
|
+
* - 商家注册 reporters(GA / Plausible / 自家 API)
|
|
7
|
+
* - 通过 <Analytics.PageView /> / <Analytics.ProductView /> 等组件声明式触发
|
|
8
|
+
* - 也可以 useAnalytics().emit(name, data)
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* <AnalyticsProvider
|
|
12
|
+
* onEvent={(e) => {
|
|
13
|
+
* fetch('/api/analytics', { method: 'POST', body: JSON.stringify(e) });
|
|
14
|
+
* window.dataLayer?.push(e);
|
|
15
|
+
* }}
|
|
16
|
+
* >
|
|
17
|
+
* <App />
|
|
18
|
+
* </AnalyticsProvider>
|
|
19
|
+
*
|
|
20
|
+
* // 在某页:
|
|
21
|
+
* <Analytics.PageView path="/products" title="Products" />
|
|
22
|
+
* <Analytics.ProductView product={product} />
|
|
23
|
+
* <Analytics.AddToCart cart={cart} addedLineId={lineId} />
|
|
24
|
+
*
|
|
25
|
+
* // 命令式:
|
|
26
|
+
* const { emit } = useAnalytics();
|
|
27
|
+
* emit('search', { query: 'wireless' });
|
|
28
|
+
*/
|
|
29
|
+
import * as React from 'react';
|
|
30
|
+
import { useCartOptional } from './CartProvider';
|
|
31
|
+
import { useShopOptional } from './ShopProvider';
|
|
32
|
+
const Ctx = React.createContext(null);
|
|
33
|
+
export function AnalyticsProvider(props) {
|
|
34
|
+
const { children, onEvent, trackCart = true } = props;
|
|
35
|
+
const shop = useShopOptional();
|
|
36
|
+
const cartCtx = useCartOptional();
|
|
37
|
+
const onEventRef = React.useRef(onEvent);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
onEventRef.current = onEvent;
|
|
40
|
+
}, [onEvent]);
|
|
41
|
+
const emit = React.useCallback((name, payload = {}) => {
|
|
42
|
+
onEventRef.current?.({
|
|
43
|
+
name,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
shop: shop ? { storeId: shop.storeId, shopName: shop.shopName } : null,
|
|
46
|
+
payload,
|
|
47
|
+
});
|
|
48
|
+
}, [shop]);
|
|
49
|
+
// cart 自动追踪
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (!trackCart || !cartCtx)
|
|
52
|
+
return;
|
|
53
|
+
return cartCtx.subscribe((c) => {
|
|
54
|
+
emit('cart_updated', {
|
|
55
|
+
cartId: c?.id,
|
|
56
|
+
totalQuantity: c?.totalQuantity,
|
|
57
|
+
totalAmount: c?.cost.totalAmount,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}, [trackCart, cartCtx, emit]);
|
|
61
|
+
return _jsx(Ctx.Provider, { value: { emit }, children: children });
|
|
62
|
+
}
|
|
63
|
+
export function useAnalytics() {
|
|
64
|
+
const v = React.useContext(Ctx);
|
|
65
|
+
if (!v) {
|
|
66
|
+
// 没 Provider 时返回 noop(不 throw,便于组件可选嵌入)
|
|
67
|
+
return { emit: () => { } };
|
|
68
|
+
}
|
|
69
|
+
return v;
|
|
70
|
+
}
|
|
71
|
+
// ============================================================
|
|
72
|
+
// 声明式事件组件
|
|
73
|
+
// ============================================================
|
|
74
|
+
function useEmitOnMount(name, payload) {
|
|
75
|
+
const { emit } = useAnalytics();
|
|
76
|
+
const payloadRef = React.useRef(payload);
|
|
77
|
+
payloadRef.current = payload;
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
emit(name, payloadRef.current);
|
|
80
|
+
// 只 mount 时触发一次(路由切换会重新 mount)
|
|
81
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
82
|
+
}, []);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function PageView(props) {
|
|
86
|
+
return useEmitOnMount('page_view', props);
|
|
87
|
+
}
|
|
88
|
+
function ProductView(props) {
|
|
89
|
+
const { product, ...rest } = props;
|
|
90
|
+
return useEmitOnMount('product_view', {
|
|
91
|
+
...rest,
|
|
92
|
+
productId: product.id,
|
|
93
|
+
productHandle: product.handle,
|
|
94
|
+
productTitle: product.title,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function CollectionView(props) {
|
|
98
|
+
const { collection, ...rest } = props;
|
|
99
|
+
return useEmitOnMount('collection_view', {
|
|
100
|
+
...rest,
|
|
101
|
+
collectionId: collection.id,
|
|
102
|
+
collectionHandle: collection.handle,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function SearchView(props) {
|
|
106
|
+
return useEmitOnMount('search', { query: props.query, results: props.results });
|
|
107
|
+
}
|
|
108
|
+
function AddToCart(props) {
|
|
109
|
+
const { variantId, quantity, ...rest } = props;
|
|
110
|
+
return useEmitOnMount('add_to_cart', {
|
|
111
|
+
...rest,
|
|
112
|
+
variantId,
|
|
113
|
+
quantity: quantity ?? 1,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function CheckoutStart(props) {
|
|
117
|
+
return useEmitOnMount('checkout_start', {
|
|
118
|
+
cartId: props.cart?.id,
|
|
119
|
+
totalQuantity: props.cart?.totalQuantity,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function Purchase(props) {
|
|
123
|
+
return useEmitOnMount('purchase', props);
|
|
124
|
+
}
|
|
125
|
+
/** 命名空间导出,让用户写 <Analytics.PageView /> */
|
|
126
|
+
export const Analytics = {
|
|
127
|
+
PageView,
|
|
128
|
+
ProductView,
|
|
129
|
+
CollectionView,
|
|
130
|
+
SearchView,
|
|
131
|
+
AddToCart,
|
|
132
|
+
CheckoutStart,
|
|
133
|
+
Purchase,
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=AnalyticsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsProvider.js","sourceRoot":"","sources":["../../src/components/AnalyticsProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CartLineQuantityAdjustButton> — cart 行数量加减按钮
|
|
3
|
+
*
|
|
4
|
+
* 对齐 @shopify/hydrogen <CartLineQuantityAdjustButton>:
|
|
5
|
+
* - +/- 按钮调用 onChange
|
|
6
|
+
* - 防抖(300ms)避免点快导致多次请求
|
|
7
|
+
* - min/max 校验
|
|
8
|
+
* - 移除时弹确认(可选)
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* const { updateLine, removeLine } = useCart();
|
|
12
|
+
* <CartLineQuantityAdjustButton
|
|
13
|
+
* line={line}
|
|
14
|
+
* onUpdate={(newQty) => updateLine(line.id, newQty)}
|
|
15
|
+
* onRemove={() => removeLine(line.id)}
|
|
16
|
+
* />
|
|
17
|
+
*/
|
|
18
|
+
import * as React from 'react';
|
|
19
|
+
export interface CartLineQuantityAdjustButtonProps {
|
|
20
|
+
/** line 的当前数量 */
|
|
21
|
+
quantity: number;
|
|
22
|
+
/** 最小值,默认 1(小于此值改成 remove) */
|
|
23
|
+
min?: number;
|
|
24
|
+
/** 最大值,默认 99 */
|
|
25
|
+
max?: number;
|
|
26
|
+
/** 数量变化回调 */
|
|
27
|
+
onUpdate?: (newQuantity: number) => Promise<void> | void;
|
|
28
|
+
/** 数量降到 0 时触发 remove */
|
|
29
|
+
onRemove?: () => Promise<void> | void;
|
|
30
|
+
/** 防抖延迟(ms),默认 300 */
|
|
31
|
+
debounceMs?: number;
|
|
32
|
+
/** 自定义包装容器 className */
|
|
33
|
+
className?: string;
|
|
34
|
+
/** 减号按钮 props 透传 */
|
|
35
|
+
decreaseProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
36
|
+
/** 加号按钮 props 透传 */
|
|
37
|
+
increaseProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
38
|
+
}
|
|
39
|
+
export declare function CartLineQuantityAdjustButton(props: CartLineQuantityAdjustButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=CartLineQuantityAdjustButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineQuantityAdjustButton.d.ts","sourceRoot":"","sources":["../../src/components/CartLineQuantityAdjustButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,iCAAiC;IAChD,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa;IACb,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAC9D,oBAAoB;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iCAAiC,2CA6EpF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* <CartLineQuantityAdjustButton> — cart 行数量加减按钮
|
|
4
|
+
*
|
|
5
|
+
* 对齐 @shopify/hydrogen <CartLineQuantityAdjustButton>:
|
|
6
|
+
* - +/- 按钮调用 onChange
|
|
7
|
+
* - 防抖(300ms)避免点快导致多次请求
|
|
8
|
+
* - min/max 校验
|
|
9
|
+
* - 移除时弹确认(可选)
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* const { updateLine, removeLine } = useCart();
|
|
13
|
+
* <CartLineQuantityAdjustButton
|
|
14
|
+
* line={line}
|
|
15
|
+
* onUpdate={(newQty) => updateLine(line.id, newQty)}
|
|
16
|
+
* onRemove={() => removeLine(line.id)}
|
|
17
|
+
* />
|
|
18
|
+
*/
|
|
19
|
+
import * as React from 'react';
|
|
20
|
+
export function CartLineQuantityAdjustButton(props) {
|
|
21
|
+
const { quantity, min = 1, max = 99, onUpdate, onRemove, debounceMs = 300, className, decreaseProps, increaseProps, } = props;
|
|
22
|
+
// optimistic 显示的 quantity
|
|
23
|
+
const [optimistic, setOptimistic] = React.useState(quantity);
|
|
24
|
+
React.useEffect(() => setOptimistic(quantity), [quantity]);
|
|
25
|
+
const [pending, setPending] = React.useState(false);
|
|
26
|
+
const timerRef = React.useRef(null);
|
|
27
|
+
const flushUpdate = React.useCallback((target) => {
|
|
28
|
+
if (timerRef.current)
|
|
29
|
+
clearTimeout(timerRef.current);
|
|
30
|
+
timerRef.current = setTimeout(async () => {
|
|
31
|
+
setPending(true);
|
|
32
|
+
try {
|
|
33
|
+
if (target < min) {
|
|
34
|
+
await onRemove?.();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await onUpdate?.(target);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setPending(false);
|
|
42
|
+
}
|
|
43
|
+
}, debounceMs);
|
|
44
|
+
}, [min, onRemove, onUpdate, debounceMs]);
|
|
45
|
+
const handleAdjust = (delta) => {
|
|
46
|
+
const next = optimistic + delta;
|
|
47
|
+
if (next > max)
|
|
48
|
+
return;
|
|
49
|
+
setOptimistic(next);
|
|
50
|
+
flushUpdate(next);
|
|
51
|
+
};
|
|
52
|
+
React.useEffect(() => () => {
|
|
53
|
+
if (timerRef.current)
|
|
54
|
+
clearTimeout(timerRef.current);
|
|
55
|
+
}, []);
|
|
56
|
+
return (_jsxs("div", { className: className, "data-quantity-adjust": true, style: { display: 'inline-flex', alignItems: 'center' }, children: [_jsx("button", { type: "button", ...decreaseProps, "aria-label": "\u51CF\u5C11", "data-quantity-decrease": true, onClick: () => handleAdjust(-1), disabled: pending && optimistic <= min, children: decreaseProps?.children ?? '−' }), _jsx("span", { "data-quantity-value": true, "aria-live": "polite", style: { margin: '0 0.5em', minWidth: '1.5em', textAlign: 'center' }, children: optimistic }), _jsx("button", { type: "button", ...increaseProps, "aria-label": "\u589E\u52A0", "data-quantity-increase": true, onClick: () => handleAdjust(1), disabled: pending && optimistic >= max, children: increaseProps?.children ?? '+' })] }));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=CartLineQuantityAdjustButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartLineQuantityAdjustButton.js","sourceRoot":"","sources":["../../src/components/CartLineQuantityAdjustButton.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuB/B,MAAM,UAAU,4BAA4B,CAAC,KAAwC;IACnF,MAAM,EACJ,QAAQ,EACR,GAAG,GAAG,CAAC,EACP,GAAG,GAAG,EAAE,EACR,QAAQ,EACR,QAAQ,EACR,UAAU,GAAG,GAAG,EAChB,SAAS,EACT,aAAa,EACb,aAAa,GACd,GAAG,KAAK,CAAC;IAEV,0BAA0B;IAC1B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC7D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE3D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAuC,IAAI,CAAC,CAAC;IAE1E,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CACnC,CAAC,MAAc,EAAE,EAAE;QACjB,IAAI,QAAQ,CAAC,OAAO;YAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrD,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACvC,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,CAAC;gBACH,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;oBACjB,MAAM,QAAQ,EAAE,EAAE,CAAC;gBACrB,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,UAAU,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;QACH,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC,EACD,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,CACtC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,UAAU,GAAG,KAAK,CAAC;QAChC,IAAI,IAAI,GAAG,GAAG;YAAE,OAAO;QACvB,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACzB,IAAI,QAAQ,CAAC,OAAO;YAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,eAAK,SAAS,EAAE,SAAS,gCAAuB,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,aACrG,iBACE,IAAI,EAAC,QAAQ,KACT,aAAa,gBACN,cAAI,kCAEf,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAC/B,QAAQ,EAAE,OAAO,IAAI,UAAU,IAAI,GAAG,YAErC,aAAa,EAAE,QAAQ,IAAI,GAAG,GACxB,EACT,yDAAoC,QAAQ,EAAC,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAC9G,UAAU,GACN,EACP,iBACE,IAAI,EAAC,QAAQ,KACT,aAAa,gBACN,cAAI,kCAEf,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAC9B,QAAQ,EAAE,OAAO,IAAI,UAAU,IAAI,GAAG,YAErC,aAAa,EAAE,QAAQ,IAAI,GAAG,GACxB,IACL,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <CartProvider> + useCart()
|
|
3
|
+
*
|
|
4
|
+
* 接管 cart 全生命周期:
|
|
5
|
+
* - 自动从 cookie 读取 cart_id 并 fetch
|
|
6
|
+
* - 不存在则懒创建
|
|
7
|
+
* - linesAdd / linesUpdate / linesRemove 全部带 optimistic update + 失败回滚
|
|
8
|
+
* - subscribe(callback) 给外部(如 Analytics)订阅 cart 变化
|
|
9
|
+
*
|
|
10
|
+
* 用法:
|
|
11
|
+
* <ShopProvider {...}>
|
|
12
|
+
* <CartProvider>
|
|
13
|
+
* <App />
|
|
14
|
+
* </CartProvider>
|
|
15
|
+
* </ShopProvider>
|
|
16
|
+
*
|
|
17
|
+
* const { cart, status, linesAdd, linesUpdate, linesRemove } = useCart();
|
|
18
|
+
*
|
|
19
|
+
* 注意:必须套在 <ShopProvider> 内(依赖它的 storefrontAccessToken + apiUrl)。
|
|
20
|
+
*/
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
export interface CartLineMerchandise {
|
|
23
|
+
id: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
image?: {
|
|
26
|
+
url: string;
|
|
27
|
+
altText?: string | null;
|
|
28
|
+
width?: number | null;
|
|
29
|
+
height?: number | null;
|
|
30
|
+
};
|
|
31
|
+
price?: {
|
|
32
|
+
amount: string;
|
|
33
|
+
currencyCode: string;
|
|
34
|
+
};
|
|
35
|
+
product?: {
|
|
36
|
+
title: string;
|
|
37
|
+
handle: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface CartLine {
|
|
41
|
+
id: string;
|
|
42
|
+
quantity: number;
|
|
43
|
+
merchandise: CartLineMerchandise;
|
|
44
|
+
cost: {
|
|
45
|
+
totalAmount: {
|
|
46
|
+
amount: string;
|
|
47
|
+
currencyCode: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface Cart {
|
|
52
|
+
id: string;
|
|
53
|
+
totalQuantity: number;
|
|
54
|
+
checkoutUrl: string;
|
|
55
|
+
lines: {
|
|
56
|
+
nodes: CartLine[];
|
|
57
|
+
};
|
|
58
|
+
cost: {
|
|
59
|
+
subtotalAmount: {
|
|
60
|
+
amount: string;
|
|
61
|
+
currencyCode: string;
|
|
62
|
+
};
|
|
63
|
+
totalAmount: {
|
|
64
|
+
amount: string;
|
|
65
|
+
currencyCode: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export type CartStatus = 'uninitialized' | 'loading' | 'idle' | 'updating' | 'error';
|
|
70
|
+
export interface CartUserError {
|
|
71
|
+
field?: string[] | null;
|
|
72
|
+
message: string;
|
|
73
|
+
code?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface CartContextValue {
|
|
76
|
+
cart: Cart | null;
|
|
77
|
+
status: CartStatus;
|
|
78
|
+
error: string | null;
|
|
79
|
+
/** 加 line */
|
|
80
|
+
linesAdd: (lines: Array<{
|
|
81
|
+
merchandiseId: string;
|
|
82
|
+
quantity?: number;
|
|
83
|
+
}>) => Promise<void>;
|
|
84
|
+
/** 更新数量(lineId, quantity);quantity<=0 等价 remove */
|
|
85
|
+
linesUpdate: (lines: Array<{
|
|
86
|
+
id: string;
|
|
87
|
+
quantity: number;
|
|
88
|
+
}>) => Promise<void>;
|
|
89
|
+
/** 删 line */
|
|
90
|
+
linesRemove: (lineIds: string[]) => Promise<void>;
|
|
91
|
+
/** 重新从服务端拉 cart */
|
|
92
|
+
refetch: () => Promise<void>;
|
|
93
|
+
/** 订阅 cart 变化(Analytics 用) */
|
|
94
|
+
subscribe: (listener: (cart: Cart | null) => void) => () => void;
|
|
95
|
+
}
|
|
96
|
+
export interface CartProviderProps {
|
|
97
|
+
children: React.ReactNode;
|
|
98
|
+
/** 加载完成后是否自动拉 cart。默认 true */
|
|
99
|
+
fetchOnMount?: boolean;
|
|
100
|
+
}
|
|
101
|
+
export declare function CartProvider({ children, fetchOnMount }: CartProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
102
|
+
/**
|
|
103
|
+
* 拿 cart context。Provider 外 throw。
|
|
104
|
+
*/
|
|
105
|
+
export declare function useCart(): CartContextValue;
|
|
106
|
+
/**
|
|
107
|
+
* 非 throw 版本:Provider 外返回 null。
|
|
108
|
+
* 给可选 fallback 用,比如 <AddToCartButton> 在没 Provider 时退化到 props.onAdd。
|
|
109
|
+
*/
|
|
110
|
+
export declare function useCartOptional(): CartContextValue | null;
|
|
111
|
+
//# sourceMappingURL=CartProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartProvider.d.ts","sourceRoot":"","sources":["../../src/components/CartProvider.tsx"],"names":[],"mappings":"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"}
|