@tiledev/sdk-shopify 0.2.5
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/LICENSE +21 -0
- package/README.md +269 -0
- package/dist/blogs.d.ts +3 -0
- package/dist/blogs.d.ts.map +1 -0
- package/dist/blogs.js +72 -0
- package/dist/blogs.js.map +1 -0
- package/dist/cart.d.ts +3 -0
- package/dist/cart.d.ts.map +1 -0
- package/dist/cart.js +96 -0
- package/dist/cart.js.map +1 -0
- package/dist/cartPolicy.d.ts +15 -0
- package/dist/cartPolicy.d.ts.map +1 -0
- package/dist/cartPolicy.js +74 -0
- package/dist/cartPolicy.js.map +1 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +130 -0
- package/dist/client.js.map +1 -0
- package/dist/collections.d.ts +3 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +44 -0
- package/dist/collections.js.map +1 -0
- package/dist/customer.d.ts +3 -0
- package/dist/customer.d.ts.map +1 -0
- package/dist/customer.js +124 -0
- package/dist/customer.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +24 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +81 -0
- package/dist/messages.js.map +1 -0
- package/dist/money.d.ts +18 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +93 -0
- package/dist/money.js.map +1 -0
- package/dist/products.d.ts +6 -0
- package/dist/products.d.ts.map +1 -0
- package/dist/products.js +193 -0
- package/dist/products.js.map +1 -0
- package/dist/queries.d.ts +55 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +582 -0
- package/dist/queries.js.map +1 -0
- package/dist/react/ShopifyProvider.d.ts +181 -0
- package/dist/react/ShopifyProvider.d.ts.map +1 -0
- package/dist/react/ShopifyProvider.js +785 -0
- package/dist/react/ShopifyProvider.js.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +14 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useTileCredit.d.ts +34 -0
- package/dist/react/useTileCredit.d.ts.map +1 -0
- package/dist/react/useTileCredit.js +96 -0
- package/dist/react/useTileCredit.js.map +1 -0
- package/dist/shopify.d.ts +3 -0
- package/dist/shopify.d.ts.map +1 -0
- package/dist/shopify.js +54 -0
- package/dist/shopify.js.map +1 -0
- package/dist/tileCredit.d.ts +64 -0
- package/dist/tileCredit.d.ts.map +1 -0
- package/dist/tileCredit.js +274 -0
- package/dist/tileCredit.js.map +1 -0
- package/dist/types.d.ts +785 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -0
- package/dist/variants.d.ts +3 -0
- package/dist/variants.d.ts.map +1 -0
- package/dist/variants.js +67 -0
- package/dist/variants.js.map +1 -0
- package/dist/wishlist.d.ts +4 -0
- package/dist/wishlist.d.ts.map +1 -0
- package/dist/wishlist.js +232 -0
- package/dist/wishlist.js.map +1 -0
- package/package.json +63 -0
- package/react/package.json +6 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShopifyProvider = ShopifyProvider;
|
|
4
|
+
exports.useShopify = useShopify;
|
|
5
|
+
exports.useCart = useCart;
|
|
6
|
+
exports.useWishlist = useWishlist;
|
|
7
|
+
exports.useCustomer = useCustomer;
|
|
8
|
+
exports.useCheckout = useCheckout;
|
|
9
|
+
exports.useShopifyMessage = useShopifyMessage;
|
|
10
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
11
|
+
const react_1 = require("react");
|
|
12
|
+
const shopify_1 = require("../shopify");
|
|
13
|
+
const cartPolicy_1 = require("../cartPolicy");
|
|
14
|
+
const errors_1 = require("../errors");
|
|
15
|
+
const messages_1 = require("../messages");
|
|
16
|
+
/**
|
|
17
|
+
* Shopify rejecting the *contents* of a mutation rather than failing to answer it: only
|
|
18
|
+
* `userErrors` populate `ShopifyError.errors`, so transport and GraphQL failures arrive empty.
|
|
19
|
+
*/
|
|
20
|
+
function isLineRejection(error) {
|
|
21
|
+
return (0, errors_1.isUserErrorRejection)(error);
|
|
22
|
+
}
|
|
23
|
+
function sameAttributes(a, b) {
|
|
24
|
+
return (a.length === b.length &&
|
|
25
|
+
a.every((one) => b.some((other) => other.key === one.key && other.value === one.value)));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The line a just-sent input became. Shopify merges an add into an existing line only when the
|
|
29
|
+
* attributes match too, so a per-add attribute yields a *new* line for the same variant.
|
|
30
|
+
*/
|
|
31
|
+
function lineFor(cart, input) {
|
|
32
|
+
const candidates = cart.lines.filter((line) => { var _a; return ((_a = line.merchandise) === null || _a === void 0 ? void 0 : _a.id) === input.merchandiseId; });
|
|
33
|
+
if (candidates.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
const exact = candidates.find((line) => { var _a; return sameAttributes((_a = input.attributes) !== null && _a !== void 0 ? _a : [], line.attributes); });
|
|
36
|
+
return exact !== null && exact !== void 0 ? exact : candidates[candidates.length - 1];
|
|
37
|
+
}
|
|
38
|
+
const ShopifyContext = (0, react_1.createContext)(undefined);
|
|
39
|
+
const CART_STORAGE_KEY = "shopify:cart-id:v1";
|
|
40
|
+
const CUSTOMER_TOKEN_KEY = "shopify:customer-token:v1";
|
|
41
|
+
/** Alert-carrying events and the message key each resolves. */
|
|
42
|
+
const EVENT_MESSAGE = {
|
|
43
|
+
"cart:add": "cart.added",
|
|
44
|
+
"cart:remove": "cart.removed",
|
|
45
|
+
"cart:limitExceeded": "cart.limitExceeded",
|
|
46
|
+
"cart:outOfStock": "cart.outOfStock",
|
|
47
|
+
"wishlist:add": "wishlist.added",
|
|
48
|
+
"wishlist:remove": "wishlist.removed",
|
|
49
|
+
"auth:loginSuccess": "auth.loginSuccess",
|
|
50
|
+
"auth:loginFailed": "auth.loginFailed",
|
|
51
|
+
"auth:logout": "auth.loggedOut",
|
|
52
|
+
"auth:recoverSent": "auth.resetLinkSent",
|
|
53
|
+
"checkout:orderPlaced": "checkout.orderPlaced",
|
|
54
|
+
"checkout:paymentFailed": "checkout.paymentFailed",
|
|
55
|
+
};
|
|
56
|
+
const ERROR_EVENTS = new Set([
|
|
57
|
+
"cart:limitExceeded",
|
|
58
|
+
"cart:outOfStock",
|
|
59
|
+
"auth:loginFailed",
|
|
60
|
+
"checkout:paymentFailed",
|
|
61
|
+
]);
|
|
62
|
+
function defaultStorage() {
|
|
63
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.localStorage !== "undefined") {
|
|
64
|
+
return globalThis.localStorage;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function ShopifyProvider({ children, config, storage, onEvent, cartGuard, wishlistKeepDeleted, messages, translate, cartPolicy, }) {
|
|
69
|
+
const [ready, setReady] = (0, react_1.useState)(false);
|
|
70
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
71
|
+
const [cart, setCart] = (0, react_1.useState)(null);
|
|
72
|
+
const [cartLoading, setCartLoad] = (0, react_1.useState)(false);
|
|
73
|
+
const [wlItems, setWlItems] = (0, react_1.useState)([]);
|
|
74
|
+
const [customer, setCustomer] = (0, react_1.useState)(null);
|
|
75
|
+
const [token, setToken] = (0, react_1.useState)(null);
|
|
76
|
+
const [authLoading, setAuthLoad] = (0, react_1.useState)(false);
|
|
77
|
+
const [restoring, setRestoring] = (0, react_1.useState)(true);
|
|
78
|
+
const wlUnsubRef = (0, react_1.useRef)(null);
|
|
79
|
+
const storageRef = (0, react_1.useRef)(null);
|
|
80
|
+
storageRef.current = storage !== null && storage !== void 0 ? storage : defaultStorage();
|
|
81
|
+
// In a ref so the cart callbacks keep their identity when the host passes a new guard object.
|
|
82
|
+
const guardRef = (0, react_1.useRef)(cartGuard);
|
|
83
|
+
guardRef.current = cartGuard;
|
|
84
|
+
// Same for onEvent: a host that passes an inline arrow would otherwise rebuild
|
|
85
|
+
// every cart callback on each render.
|
|
86
|
+
const onEventRef = (0, react_1.useRef)(onEvent);
|
|
87
|
+
onEventRef.current = onEvent;
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the copy for `type` and hands the event to the host. A throwing
|
|
90
|
+
* listener must not fail the write that triggered it — the mutation already
|
|
91
|
+
* landed, so swallowing here is the only honest option.
|
|
92
|
+
*/
|
|
93
|
+
const emit = (0, react_1.useCallback)((type, error) => {
|
|
94
|
+
const listener = onEventRef.current;
|
|
95
|
+
if (!listener)
|
|
96
|
+
return;
|
|
97
|
+
const key = EVENT_MESSAGE[type];
|
|
98
|
+
const max = (0, cartPolicy_1.maxLineItems)();
|
|
99
|
+
const event = {
|
|
100
|
+
type,
|
|
101
|
+
severity: ERROR_EVENTS.has(type) ? "error" : key ? "success" : "info",
|
|
102
|
+
...(key
|
|
103
|
+
? {
|
|
104
|
+
messageKey: key,
|
|
105
|
+
message: type === "cart:limitExceeded" && max !== null
|
|
106
|
+
? (0, messages_1.limitExceededMessage)(max)
|
|
107
|
+
: (0, messages_1.message)(key),
|
|
108
|
+
}
|
|
109
|
+
: {}),
|
|
110
|
+
...(error === undefined ? {} : { error }),
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
listener(event);
|
|
114
|
+
}
|
|
115
|
+
catch (listenerError) {
|
|
116
|
+
console.warn("[ShopifyProvider] onEvent threw", listenerError);
|
|
117
|
+
}
|
|
118
|
+
}, []);
|
|
119
|
+
/**
|
|
120
|
+
* Alert copy and cart rules, re-applied whenever the host passes new ones so a
|
|
121
|
+
* Live Layer publish lands without a re-init. `config` values are the base;
|
|
122
|
+
* the props win, which is what makes the panel's value authoritative.
|
|
123
|
+
*/
|
|
124
|
+
const resolvedMessages = (0, react_1.useMemo)(() => { var _a; return ({ ...((_a = config.messages) !== null && _a !== void 0 ? _a : {}), ...(messages !== null && messages !== void 0 ? messages : {}) }); }, [config.messages, messages]);
|
|
125
|
+
const resolvedPolicy = cartPolicy !== null && cartPolicy !== void 0 ? cartPolicy : config.cart;
|
|
126
|
+
const resolvedTranslate = translate !== null && translate !== void 0 ? translate : config.translate;
|
|
127
|
+
/**
|
|
128
|
+
* Applied DURING render, not in an effect. The context value below reports the
|
|
129
|
+
* resolved limit in this same pass, and an effect-queued write would leave
|
|
130
|
+
* `cart.maxLineItems` at null until some unrelated re-render happened to
|
|
131
|
+
* recompute it. These are idempotent writes to module singletons, so a
|
|
132
|
+
* StrictMode double render costs nothing.
|
|
133
|
+
*/
|
|
134
|
+
const appliedRef = (0, react_1.useRef)(null);
|
|
135
|
+
if (!appliedRef.current || appliedRef.current.messages !== resolvedMessages) {
|
|
136
|
+
shopify_1.shopify.alerts.setMessages(resolvedMessages);
|
|
137
|
+
}
|
|
138
|
+
if (!appliedRef.current || appliedRef.current.policy !== resolvedPolicy) {
|
|
139
|
+
shopify_1.shopify.alerts.setPolicy(resolvedPolicy);
|
|
140
|
+
}
|
|
141
|
+
if (!appliedRef.current || appliedRef.current.translate !== resolvedTranslate) {
|
|
142
|
+
(0, messages_1.setMessageResolver)(resolvedTranslate);
|
|
143
|
+
}
|
|
144
|
+
appliedRef.current = {
|
|
145
|
+
messages: resolvedMessages,
|
|
146
|
+
policy: resolvedPolicy,
|
|
147
|
+
translate: resolvedTranslate,
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Re-applies what the render above already applied. `shopify.init()` sets both
|
|
151
|
+
* from `config` for the benefit of non-React consumers, so it runs AFTER this
|
|
152
|
+
* render and would otherwise clobber the props with the config's (often
|
|
153
|
+
* absent) values — the props are the authority.
|
|
154
|
+
*/
|
|
155
|
+
const reapplyAlertConfig = (0, react_1.useCallback)(() => {
|
|
156
|
+
const applied = appliedRef.current;
|
|
157
|
+
if (!applied)
|
|
158
|
+
return;
|
|
159
|
+
shopify_1.shopify.alerts.setMessages(applied.messages);
|
|
160
|
+
shopify_1.shopify.alerts.setPolicy(applied.policy);
|
|
161
|
+
(0, messages_1.setMessageResolver)(applied.translate);
|
|
162
|
+
}, []);
|
|
163
|
+
/**
|
|
164
|
+
* Shopify rejects concurrent writes to one cart ("The cart conflicted with another request"), so
|
|
165
|
+
* every mutation waits for the one before it. The chain is never poisoned by a failure: the next
|
|
166
|
+
* write runs either way, while the caller still sees its own error.
|
|
167
|
+
*/
|
|
168
|
+
const writeQueue = (0, react_1.useRef)(Promise.resolve());
|
|
169
|
+
const serialize = (0, react_1.useCallback)((write) => {
|
|
170
|
+
const run = writeQueue.current.then(write, write);
|
|
171
|
+
writeQueue.current = run.catch(() => undefined);
|
|
172
|
+
return run;
|
|
173
|
+
}, []);
|
|
174
|
+
/**
|
|
175
|
+
* A guard is advisory: a hook that throws must not take the cart down with it. `fallback` is what
|
|
176
|
+
* that failure means — approval for the `before*` hooks, nothing for the observers.
|
|
177
|
+
*/
|
|
178
|
+
const runGuard = (0, react_1.useCallback)(async (work, fallback) => {
|
|
179
|
+
try {
|
|
180
|
+
return await work();
|
|
181
|
+
}
|
|
182
|
+
catch (guardError) {
|
|
183
|
+
console.warn("[ShopifyProvider] cartGuard failed; continuing", guardError);
|
|
184
|
+
return fallback;
|
|
185
|
+
}
|
|
186
|
+
}, []);
|
|
187
|
+
const approveAdd = (0, react_1.useCallback)(async (input) => {
|
|
188
|
+
const guard = guardRef.current;
|
|
189
|
+
if (!(guard === null || guard === void 0 ? void 0 : guard.beforeAdd))
|
|
190
|
+
return input;
|
|
191
|
+
return runGuard(() => guard.beforeAdd(input), input);
|
|
192
|
+
}, [runGuard]);
|
|
193
|
+
/** Hands back units the guard approved for a write that then failed. */
|
|
194
|
+
const releaseRejected = (0, react_1.useCallback)((input) => {
|
|
195
|
+
const guard = guardRef.current;
|
|
196
|
+
if (!(guard === null || guard === void 0 ? void 0 : guard.onReleased))
|
|
197
|
+
return;
|
|
198
|
+
void runGuard(() => guard.onReleased({
|
|
199
|
+
variantId: input.merchandiseId,
|
|
200
|
+
quantity: input.quantity,
|
|
201
|
+
reason: "rejected",
|
|
202
|
+
line: null,
|
|
203
|
+
cart: null,
|
|
204
|
+
}), undefined);
|
|
205
|
+
}, [runGuard]);
|
|
206
|
+
const announceLanded = (0, react_1.useCallback)((input, next) => {
|
|
207
|
+
const guard = guardRef.current;
|
|
208
|
+
if (!(guard === null || guard === void 0 ? void 0 : guard.onLanded))
|
|
209
|
+
return;
|
|
210
|
+
void runGuard(() => guard.onLanded({
|
|
211
|
+
variantId: input.merchandiseId,
|
|
212
|
+
quantity: input.quantity,
|
|
213
|
+
line: lineFor(next, input),
|
|
214
|
+
cart: next,
|
|
215
|
+
}), undefined);
|
|
216
|
+
}, [runGuard]);
|
|
217
|
+
(0, react_1.useEffect)(() => {
|
|
218
|
+
let mounted = true;
|
|
219
|
+
(async () => {
|
|
220
|
+
var _a;
|
|
221
|
+
try {
|
|
222
|
+
await shopify_1.shopify.init(config);
|
|
223
|
+
reapplyAlertConfig();
|
|
224
|
+
setCartLoad(true);
|
|
225
|
+
try {
|
|
226
|
+
const s = storageRef.current;
|
|
227
|
+
const savedId = s ? await Promise.resolve(s.getItem(CART_STORAGE_KEY)) : null;
|
|
228
|
+
let next = null;
|
|
229
|
+
if (savedId)
|
|
230
|
+
next = await shopify_1.shopify.cart.get(savedId);
|
|
231
|
+
if (!next) {
|
|
232
|
+
next = await shopify_1.shopify.cart.create();
|
|
233
|
+
if (s)
|
|
234
|
+
await Promise.resolve(s.setItem(CART_STORAGE_KEY, next.id));
|
|
235
|
+
}
|
|
236
|
+
if (mounted)
|
|
237
|
+
setCart(next);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
if (mounted)
|
|
241
|
+
setCartLoad(false);
|
|
242
|
+
}
|
|
243
|
+
const initialItems = await shopify_1.shopify.wishlist.init({
|
|
244
|
+
storage: (_a = storageRef.current) !== null && _a !== void 0 ? _a : undefined,
|
|
245
|
+
keepDeleted: wishlistKeepDeleted,
|
|
246
|
+
});
|
|
247
|
+
if (mounted)
|
|
248
|
+
setWlItems(initialItems);
|
|
249
|
+
wlUnsubRef.current = shopify_1.shopify.wishlist.onChange((items) => {
|
|
250
|
+
if (mounted)
|
|
251
|
+
setWlItems(items);
|
|
252
|
+
});
|
|
253
|
+
// Restore a stored session. A token that no longer resolves is dropped
|
|
254
|
+
// silently — an expired login is not a failed one, and firing
|
|
255
|
+
// `auth:loginFailed` on app open would toast at a shopper who did nothing.
|
|
256
|
+
try {
|
|
257
|
+
const s = storageRef.current;
|
|
258
|
+
const savedToken = s ? await Promise.resolve(s.getItem(CUSTOMER_TOKEN_KEY)) : null;
|
|
259
|
+
if (savedToken) {
|
|
260
|
+
const profile = await shopify_1.shopify.customer.profile(savedToken);
|
|
261
|
+
if (mounted && profile) {
|
|
262
|
+
setCustomer(profile);
|
|
263
|
+
setToken(savedToken);
|
|
264
|
+
}
|
|
265
|
+
else if (s && !profile) {
|
|
266
|
+
await Promise.resolve(s.removeItem(CUSTOMER_TOKEN_KEY));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (sessionError) {
|
|
271
|
+
console.warn("[ShopifyProvider] session restore failed", sessionError);
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
if (mounted)
|
|
275
|
+
setRestoring(false);
|
|
276
|
+
}
|
|
277
|
+
if (mounted)
|
|
278
|
+
setReady(true);
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.error("[ShopifyProvider] init failed", e);
|
|
282
|
+
if (mounted) {
|
|
283
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
284
|
+
setRestoring(false);
|
|
285
|
+
// Ready so downstream UIs render an error state rather than an indefinite spinner.
|
|
286
|
+
setReady(true);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
})();
|
|
290
|
+
return () => {
|
|
291
|
+
var _a;
|
|
292
|
+
mounted = false;
|
|
293
|
+
(_a = wlUnsubRef.current) === null || _a === void 0 ? void 0 : _a.call(wlUnsubRef);
|
|
294
|
+
};
|
|
295
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
296
|
+
}, []);
|
|
297
|
+
const persistCart = (0, react_1.useCallback)(async (next) => {
|
|
298
|
+
setCart(next);
|
|
299
|
+
const s = storageRef.current;
|
|
300
|
+
if (!s)
|
|
301
|
+
return;
|
|
302
|
+
if (next)
|
|
303
|
+
await Promise.resolve(s.setItem(CART_STORAGE_KEY, next.id));
|
|
304
|
+
else
|
|
305
|
+
await Promise.resolve(s.removeItem(CART_STORAGE_KEY));
|
|
306
|
+
}, []);
|
|
307
|
+
const ensureCartId = (0, react_1.useCallback)(async () => {
|
|
308
|
+
if (cart === null || cart === void 0 ? void 0 : cart.id)
|
|
309
|
+
return cart.id;
|
|
310
|
+
const created = await shopify_1.shopify.cart.create();
|
|
311
|
+
await persistCart(created);
|
|
312
|
+
return created.id;
|
|
313
|
+
}, [cart, persistCart]);
|
|
314
|
+
/**
|
|
315
|
+
* Classifies a failed cart write and emits the matching alert before the error
|
|
316
|
+
* carries on to the caller. Returns true when it was an out-of-stock refusal,
|
|
317
|
+
* which the batch retry treats differently from a dead request.
|
|
318
|
+
*/
|
|
319
|
+
const reportCartFailure = (0, react_1.useCallback)((failure) => {
|
|
320
|
+
if ((0, errors_1.isOutOfStockError)(failure)) {
|
|
321
|
+
emit("cart:outOfStock", failure);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}, [emit]);
|
|
326
|
+
const cartAddLine = (0, react_1.useCallback)(async (input) => {
|
|
327
|
+
const approved = await approveAdd(input);
|
|
328
|
+
if (!approved)
|
|
329
|
+
return { ok: false, reason: "guard", cart };
|
|
330
|
+
// Checked before the write so a refusal costs no round trip, and so the
|
|
331
|
+
// shopper reads the configured message rather than a Shopify error.
|
|
332
|
+
if ((0, cartPolicy_1.wouldExceedLineLimit)(cart, [approved])) {
|
|
333
|
+
emit("cart:limitExceeded");
|
|
334
|
+
const max = (0, cartPolicy_1.maxLineItems)();
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
reason: "limit",
|
|
338
|
+
message: max !== null ? (0, messages_1.limitExceededMessage)(max) : undefined,
|
|
339
|
+
cart,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
setCartLoad(true);
|
|
343
|
+
try {
|
|
344
|
+
const id = await ensureCartId();
|
|
345
|
+
let next;
|
|
346
|
+
try {
|
|
347
|
+
next = await serialize(() => shopify_1.shopify.cart.addLines(id, [approved]));
|
|
348
|
+
}
|
|
349
|
+
catch (addError) {
|
|
350
|
+
releaseRejected(approved);
|
|
351
|
+
reportCartFailure(addError);
|
|
352
|
+
throw addError;
|
|
353
|
+
}
|
|
354
|
+
await persistCart(next);
|
|
355
|
+
emit("cart:add");
|
|
356
|
+
announceLanded(approved, next);
|
|
357
|
+
return { ok: true, cart: next };
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
setCartLoad(false);
|
|
361
|
+
}
|
|
362
|
+
}, [cart, approveAdd, releaseRejected, announceLanded, ensureCartId, persistCart, emit, reportCartFailure, serialize]);
|
|
363
|
+
const cartAddLines = (0, react_1.useCallback)(async (requested) => {
|
|
364
|
+
if (requested.length === 0)
|
|
365
|
+
return cart;
|
|
366
|
+
// Vetted up front so a guard's veto does not cost the other lines their fast path.
|
|
367
|
+
const inputs = (await Promise.all(requested.map(approveAdd))).filter((input) => input !== null);
|
|
368
|
+
if (inputs.length === 0)
|
|
369
|
+
return null;
|
|
370
|
+
// The whole batch is judged against the limit, not line by line — landing
|
|
371
|
+
// half a "add these 5" is worse than refusing it with the message.
|
|
372
|
+
if ((0, cartPolicy_1.wouldExceedLineLimit)(cart, inputs)) {
|
|
373
|
+
emit("cart:limitExceeded");
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
setCartLoad(true);
|
|
377
|
+
try {
|
|
378
|
+
const id = await ensureCartId();
|
|
379
|
+
let next = null;
|
|
380
|
+
// Which inputs landed, so the guard hears about exactly those.
|
|
381
|
+
let landed = [];
|
|
382
|
+
try {
|
|
383
|
+
next = await serialize(() => shopify_1.shopify.cart.addLines(id, inputs));
|
|
384
|
+
landed = inputs;
|
|
385
|
+
}
|
|
386
|
+
catch (batchError) {
|
|
387
|
+
// A transport or GraphQL failure would fail all N the same way, so retrying it would turn
|
|
388
|
+
// one dead request into N and still end at null. Only line rejections are worth retrying.
|
|
389
|
+
if (!isLineRejection(batchError)) {
|
|
390
|
+
inputs.forEach(releaseRejected);
|
|
391
|
+
reportCartFailure(batchError);
|
|
392
|
+
throw batchError;
|
|
393
|
+
}
|
|
394
|
+
// The batch is all-or-nothing, so retry one line at a time and keep the successes. They
|
|
395
|
+
// accumulate server-side, so `next` ends up holding the whole accepted set.
|
|
396
|
+
let anyOutOfStock = false;
|
|
397
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
398
|
+
const input = inputs[i];
|
|
399
|
+
try {
|
|
400
|
+
next = await serialize(() => shopify_1.shopify.cart.addLines(id, [input]));
|
|
401
|
+
landed.push(input);
|
|
402
|
+
}
|
|
403
|
+
catch (lineError) {
|
|
404
|
+
releaseRejected(input);
|
|
405
|
+
if ((0, errors_1.isOutOfStockError)(lineError))
|
|
406
|
+
anyOutOfStock = true;
|
|
407
|
+
// Anything but a rejection means the retry itself is failing, so stop.
|
|
408
|
+
if (!isLineRejection(lineError)) {
|
|
409
|
+
inputs.slice(i + 1).forEach(releaseRejected);
|
|
410
|
+
reportCartFailure(lineError);
|
|
411
|
+
throw lineError;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// One alert for the batch: N unsellable lines is one thing that went
|
|
416
|
+
// wrong from the shopper's side, not N toasts.
|
|
417
|
+
if (anyOutOfStock)
|
|
418
|
+
emit("cart:outOfStock", batchError);
|
|
419
|
+
// Every line individually rejected. Surface the original rather than an empty success,
|
|
420
|
+
// which reads as "nothing to add" instead of "none of this is sellable".
|
|
421
|
+
if (landed.length === 0)
|
|
422
|
+
throw batchError;
|
|
423
|
+
}
|
|
424
|
+
if (next) {
|
|
425
|
+
await persistCart(next);
|
|
426
|
+
emit("cart:add");
|
|
427
|
+
const settled = next;
|
|
428
|
+
landed.forEach((input) => announceLanded(input, settled));
|
|
429
|
+
}
|
|
430
|
+
return next;
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
setCartLoad(false);
|
|
434
|
+
}
|
|
435
|
+
}, [cart, approveAdd, releaseRejected, announceLanded, ensureCartId, persistCart, emit, reportCartFailure, serialize]);
|
|
436
|
+
const cartSetBuyerIdentity = (0, react_1.useCallback)(async (identity) => {
|
|
437
|
+
// Deliberately does NOT create a cart — that would mint an empty one checkout never sees.
|
|
438
|
+
if (!(cart === null || cart === void 0 ? void 0 : cart.id))
|
|
439
|
+
return false;
|
|
440
|
+
setCartLoad(true);
|
|
441
|
+
try {
|
|
442
|
+
const next = await serialize(() => shopify_1.shopify.cart.setBuyerIdentity(cart.id, identity));
|
|
443
|
+
await persistCart(next);
|
|
444
|
+
emit("cart:buyerIdentity");
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
setCartLoad(false);
|
|
449
|
+
}
|
|
450
|
+
}, [cart, persistCart, emit, serialize]);
|
|
451
|
+
const cartUpdateLine = (0, react_1.useCallback)(async (lineId, quantity, attributes) => {
|
|
452
|
+
var _a;
|
|
453
|
+
if (!(cart === null || cart === void 0 ? void 0 : cart.id))
|
|
454
|
+
return false;
|
|
455
|
+
const guard = guardRef.current;
|
|
456
|
+
const previous = (_a = cart.lines.find((line) => line.id === lineId)) !== null && _a !== void 0 ? _a : null;
|
|
457
|
+
const delta = previous ? quantity - previous.quantity : 0;
|
|
458
|
+
// Only an increase can be vetted; a decrease is reported after the fact, once Shopify has
|
|
459
|
+
// actually taken the units back.
|
|
460
|
+
let update = { id: lineId, quantity, ...(attributes ? { attributes } : {}) };
|
|
461
|
+
if (previous && delta > 0 && (guard === null || guard === void 0 ? void 0 : guard.beforeIncrease)) {
|
|
462
|
+
const approved = await runGuard(() => guard.beforeIncrease(previous, quantity), update);
|
|
463
|
+
if (!approved)
|
|
464
|
+
return false;
|
|
465
|
+
update = approved;
|
|
466
|
+
}
|
|
467
|
+
setCartLoad(true);
|
|
468
|
+
try {
|
|
469
|
+
let next;
|
|
470
|
+
try {
|
|
471
|
+
next = await serialize(() => shopify_1.shopify.cart.updateLines(cart.id, [update]));
|
|
472
|
+
}
|
|
473
|
+
catch (updateError) {
|
|
474
|
+
if (previous && delta > 0) {
|
|
475
|
+
releaseRejected({ merchandiseId: previous.merchandise.id, quantity: delta });
|
|
476
|
+
}
|
|
477
|
+
reportCartFailure(updateError);
|
|
478
|
+
throw updateError;
|
|
479
|
+
}
|
|
480
|
+
await persistCart(next);
|
|
481
|
+
// A quantity change is not one of the panel's alerts — emitted as a state
|
|
482
|
+
// signal so a host can refresh a badge, with no copy attached.
|
|
483
|
+
emit("cart:update");
|
|
484
|
+
if (previous && delta < 0 && (guard === null || guard === void 0 ? void 0 : guard.onReleased)) {
|
|
485
|
+
void runGuard(() => guard.onReleased({
|
|
486
|
+
variantId: previous.merchandise.id,
|
|
487
|
+
quantity: -delta,
|
|
488
|
+
reason: "decreased",
|
|
489
|
+
line: previous,
|
|
490
|
+
cart: next,
|
|
491
|
+
}), undefined);
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
finally {
|
|
496
|
+
setCartLoad(false);
|
|
497
|
+
}
|
|
498
|
+
}, [cart, persistCart, runGuard, releaseRejected, emit, reportCartFailure, serialize]);
|
|
499
|
+
const cartRemoveLine = (0, react_1.useCallback)(async (lineId) => {
|
|
500
|
+
var _a;
|
|
501
|
+
if (!(cart === null || cart === void 0 ? void 0 : cart.id))
|
|
502
|
+
return;
|
|
503
|
+
const guard = guardRef.current;
|
|
504
|
+
const previous = (_a = cart.lines.find((line) => line.id === lineId)) !== null && _a !== void 0 ? _a : null;
|
|
505
|
+
setCartLoad(true);
|
|
506
|
+
try {
|
|
507
|
+
const next = await serialize(() => shopify_1.shopify.cart.removeLines(cart.id, [lineId]));
|
|
508
|
+
await persistCart(next);
|
|
509
|
+
// The panel's "Removed From Cart" alert. Previously nothing fired here, so
|
|
510
|
+
// the configured copy had no trigger at all.
|
|
511
|
+
emit("cart:remove");
|
|
512
|
+
if (previous && (guard === null || guard === void 0 ? void 0 : guard.onReleased)) {
|
|
513
|
+
void runGuard(() => guard.onReleased({
|
|
514
|
+
variantId: previous.merchandise.id,
|
|
515
|
+
quantity: previous.quantity,
|
|
516
|
+
reason: "removed",
|
|
517
|
+
line: previous,
|
|
518
|
+
cart: next,
|
|
519
|
+
}), undefined);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
setCartLoad(false);
|
|
524
|
+
}
|
|
525
|
+
}, [cart, persistCart, runGuard, emit, serialize]);
|
|
526
|
+
const cartApplyDiscounts = (0, react_1.useCallback)(async (codes) => {
|
|
527
|
+
if (!(cart === null || cart === void 0 ? void 0 : cart.id))
|
|
528
|
+
return;
|
|
529
|
+
setCartLoad(true);
|
|
530
|
+
try {
|
|
531
|
+
const next = await serialize(() => shopify_1.shopify.cart.applyDiscountCodes(cart.id, codes));
|
|
532
|
+
await persistCart(next);
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
setCartLoad(false);
|
|
536
|
+
}
|
|
537
|
+
}, [cart, persistCart, serialize]);
|
|
538
|
+
const cartRefresh = (0, react_1.useCallback)(async () => {
|
|
539
|
+
if (!(cart === null || cart === void 0 ? void 0 : cart.id))
|
|
540
|
+
return null;
|
|
541
|
+
const next = await shopify_1.shopify.cart.get(cart.id);
|
|
542
|
+
if (next)
|
|
543
|
+
await persistCart(next);
|
|
544
|
+
return next;
|
|
545
|
+
}, [cart, persistCart]);
|
|
546
|
+
const cartAdopt = (0, react_1.useCallback)(async (cartId) => {
|
|
547
|
+
const next = await shopify_1.shopify.cart.get(cartId);
|
|
548
|
+
if (next)
|
|
549
|
+
await persistCart(next);
|
|
550
|
+
return next;
|
|
551
|
+
}, [persistCart]);
|
|
552
|
+
const cartReset = (0, react_1.useCallback)(async () => {
|
|
553
|
+
const created = await shopify_1.shopify.cart.create();
|
|
554
|
+
await persistCart(created);
|
|
555
|
+
}, [persistCart]);
|
|
556
|
+
// The wishlist module owns storage and hydration; these just wrap the mutating methods and
|
|
557
|
+
// rely on onChange to keep React in sync.
|
|
558
|
+
const wlAdd = (0, react_1.useCallback)(async (p) => { await shopify_1.shopify.wishlist.add(p); emit("wishlist:add"); }, [emit]);
|
|
559
|
+
// `remove` emits too. It used not to, so "Removed From Wishlist" only ever
|
|
560
|
+
// fired via `toggle` — a dedicated remove button showed nothing.
|
|
561
|
+
const wlRemove = (0, react_1.useCallback)(async (id) => {
|
|
562
|
+
const removed = await shopify_1.shopify.wishlist.remove(id);
|
|
563
|
+
if (removed)
|
|
564
|
+
emit("wishlist:remove");
|
|
565
|
+
}, [emit]);
|
|
566
|
+
const wlToggle = (0, react_1.useCallback)(async (p) => {
|
|
567
|
+
const nowSaved = await shopify_1.shopify.wishlist.toggle(p);
|
|
568
|
+
emit(nowSaved ? "wishlist:add" : "wishlist:remove");
|
|
569
|
+
return nowSaved;
|
|
570
|
+
}, [emit]);
|
|
571
|
+
const wlClear = (0, react_1.useCallback)(async () => { await shopify_1.shopify.wishlist.clear(); }, []);
|
|
572
|
+
const wlRefresh = (0, react_1.useCallback)(async () => { await shopify_1.shopify.wishlist.refresh({ keepDeleted: wishlistKeepDeleted }); }, [wishlistKeepDeleted]);
|
|
573
|
+
const wlHas = (0, react_1.useCallback)((id) => shopify_1.shopify.wishlist.has(id), []);
|
|
574
|
+
// ── Customer session ────────────────────────────────────────────────────────
|
|
575
|
+
// Wraps `shopify.customer` with token persistence and the four Login alerts.
|
|
576
|
+
// Bad credentials resolve `false` rather than throwing: it is an expected
|
|
577
|
+
// answer to a login form, and every caller would otherwise need a try/catch to
|
|
578
|
+
// tell it apart from the store being unreachable (which still throws).
|
|
579
|
+
const persistToken = (0, react_1.useCallback)(async (next) => {
|
|
580
|
+
setToken(next);
|
|
581
|
+
const s = storageRef.current;
|
|
582
|
+
if (!s)
|
|
583
|
+
return;
|
|
584
|
+
if (next)
|
|
585
|
+
await Promise.resolve(s.setItem(CUSTOMER_TOKEN_KEY, next));
|
|
586
|
+
else
|
|
587
|
+
await Promise.resolve(s.removeItem(CUSTOMER_TOKEN_KEY));
|
|
588
|
+
}, []);
|
|
589
|
+
const authLogin = (0, react_1.useCallback)(async (email, password) => {
|
|
590
|
+
setAuthLoad(true);
|
|
591
|
+
try {
|
|
592
|
+
let accessToken;
|
|
593
|
+
try {
|
|
594
|
+
const minted = await shopify_1.shopify.customer.login({ email, password });
|
|
595
|
+
accessToken = minted.accessToken;
|
|
596
|
+
}
|
|
597
|
+
catch (loginError) {
|
|
598
|
+
// Only a credential problem is a "Login Failed" toast; a network failure
|
|
599
|
+
// is not the shopper's mistake, so it propagates instead.
|
|
600
|
+
if ((0, errors_1.classifyAuthFailure)(loginError) === "unknown")
|
|
601
|
+
throw loginError;
|
|
602
|
+
emit("auth:loginFailed", loginError);
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
const profile = await shopify_1.shopify.customer.profile(accessToken);
|
|
606
|
+
await persistToken(accessToken);
|
|
607
|
+
setCustomer(profile);
|
|
608
|
+
emit("auth:loginSuccess");
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
finally {
|
|
612
|
+
setAuthLoad(false);
|
|
613
|
+
}
|
|
614
|
+
}, [emit, persistToken]);
|
|
615
|
+
const authSignup = (0, react_1.useCallback)(async (input) => {
|
|
616
|
+
setAuthLoad(true);
|
|
617
|
+
try {
|
|
618
|
+
let created;
|
|
619
|
+
try {
|
|
620
|
+
created = await shopify_1.shopify.customer.signup(input);
|
|
621
|
+
}
|
|
622
|
+
catch (signupError) {
|
|
623
|
+
if ((0, errors_1.classifyAuthFailure)(signupError) === "unknown")
|
|
624
|
+
throw signupError;
|
|
625
|
+
emit("auth:loginFailed", signupError);
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
await persistToken(created.accessToken.accessToken);
|
|
629
|
+
setCustomer(created.customer);
|
|
630
|
+
// Signup mints a token, so the shopper IS logged in — both events fire, and
|
|
631
|
+
// a host that only toasts on `auth:loginSuccess` still says the right thing.
|
|
632
|
+
emit("auth:signup");
|
|
633
|
+
emit("auth:loginSuccess");
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
finally {
|
|
637
|
+
setAuthLoad(false);
|
|
638
|
+
}
|
|
639
|
+
}, [emit, persistToken]);
|
|
640
|
+
const authLogout = (0, react_1.useCallback)(async () => {
|
|
641
|
+
const current = token;
|
|
642
|
+
setAuthLoad(true);
|
|
643
|
+
try {
|
|
644
|
+
if (current) {
|
|
645
|
+
try {
|
|
646
|
+
await shopify_1.shopify.customer.logout(current);
|
|
647
|
+
}
|
|
648
|
+
catch (logoutError) {
|
|
649
|
+
// The local session ends regardless — leaving a shopper "logged in"
|
|
650
|
+
// because Shopify was unreachable is the worse failure.
|
|
651
|
+
console.warn("[ShopifyProvider] token delete failed; clearing locally", logoutError);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
await persistToken(null);
|
|
655
|
+
setCustomer(null);
|
|
656
|
+
emit("auth:logout");
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
setAuthLoad(false);
|
|
660
|
+
}
|
|
661
|
+
}, [token, emit, persistToken]);
|
|
662
|
+
const authRecover = (0, react_1.useCallback)(async (email) => {
|
|
663
|
+
setAuthLoad(true);
|
|
664
|
+
try {
|
|
665
|
+
await shopify_1.shopify.customer.recoverPassword(email);
|
|
666
|
+
emit("auth:recoverSent");
|
|
667
|
+
}
|
|
668
|
+
finally {
|
|
669
|
+
setAuthLoad(false);
|
|
670
|
+
}
|
|
671
|
+
}, [emit]);
|
|
672
|
+
const authRefresh = (0, react_1.useCallback)(async () => {
|
|
673
|
+
if (!token)
|
|
674
|
+
return null;
|
|
675
|
+
const profile = await shopify_1.shopify.customer.profile(token);
|
|
676
|
+
if (!profile) {
|
|
677
|
+
// Token expired or revoked. Clear it silently — see the mount restore.
|
|
678
|
+
await persistToken(null);
|
|
679
|
+
setCustomer(null);
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
setCustomer(profile);
|
|
683
|
+
return profile;
|
|
684
|
+
}, [token, persistToken]);
|
|
685
|
+
// ── Checkout outcome ────────────────────────────────────────────────────────
|
|
686
|
+
const checkoutOrderPlaced = (0, react_1.useCallback)(async (details) => {
|
|
687
|
+
emit("checkout:orderPlaced", details);
|
|
688
|
+
// The old cart is spent once an order exists; keeping it would show the
|
|
689
|
+
// shopper their purchased items still sitting in the bag.
|
|
690
|
+
try {
|
|
691
|
+
await cartReset();
|
|
692
|
+
}
|
|
693
|
+
catch (resetError) {
|
|
694
|
+
console.warn("[ShopifyProvider] cart reset after order failed", resetError);
|
|
695
|
+
}
|
|
696
|
+
}, [emit, cartReset]);
|
|
697
|
+
const checkoutPaymentFailed = (0, react_1.useCallback)((paymentError) => {
|
|
698
|
+
emit("checkout:paymentFailed", paymentError);
|
|
699
|
+
}, [emit]);
|
|
700
|
+
const value = (0, react_1.useMemo)(() => {
|
|
701
|
+
var _a, _b;
|
|
702
|
+
return ({
|
|
703
|
+
ready, error,
|
|
704
|
+
message: messages_1.message,
|
|
705
|
+
cart: {
|
|
706
|
+
cart, loading: cartLoading, itemCount: (_a = cart === null || cart === void 0 ? void 0 : cart.totalQuantity) !== null && _a !== void 0 ? _a : 0,
|
|
707
|
+
lineCount: (_b = cart === null || cart === void 0 ? void 0 : cart.lines.length) !== null && _b !== void 0 ? _b : 0,
|
|
708
|
+
maxLineItems: (0, cartPolicy_1.maxLineItems)(),
|
|
709
|
+
addLine: cartAddLine,
|
|
710
|
+
addLines: cartAddLines,
|
|
711
|
+
updateLine: cartUpdateLine,
|
|
712
|
+
removeLine: cartRemoveLine,
|
|
713
|
+
applyDiscountCodes: cartApplyDiscounts,
|
|
714
|
+
setBuyerIdentity: cartSetBuyerIdentity,
|
|
715
|
+
refresh: cartRefresh,
|
|
716
|
+
adopt: cartAdopt,
|
|
717
|
+
reset: cartReset,
|
|
718
|
+
},
|
|
719
|
+
wishlist: {
|
|
720
|
+
items: wlItems,
|
|
721
|
+
count: wlItems.length,
|
|
722
|
+
has: wlHas,
|
|
723
|
+
isWishlisted: wlHas,
|
|
724
|
+
add: wlAdd,
|
|
725
|
+
remove: wlRemove,
|
|
726
|
+
toggle: wlToggle,
|
|
727
|
+
clear: wlClear,
|
|
728
|
+
refresh: wlRefresh,
|
|
729
|
+
},
|
|
730
|
+
customer: {
|
|
731
|
+
customer,
|
|
732
|
+
loggedIn: !!customer,
|
|
733
|
+
accessToken: token,
|
|
734
|
+
loading: authLoading,
|
|
735
|
+
restoring,
|
|
736
|
+
login: authLogin,
|
|
737
|
+
signup: authSignup,
|
|
738
|
+
logout: authLogout,
|
|
739
|
+
recoverPassword: authRecover,
|
|
740
|
+
refresh: authRefresh,
|
|
741
|
+
},
|
|
742
|
+
checkout: {
|
|
743
|
+
reportOrderPlaced: checkoutOrderPlaced,
|
|
744
|
+
reportPaymentFailed: checkoutPaymentFailed,
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
}, [
|
|
748
|
+
ready, error,
|
|
749
|
+
// A new policy changes `cart.maxLineItems`, so the memo must see it.
|
|
750
|
+
resolvedPolicy,
|
|
751
|
+
cart, cartLoading, cartAddLine, cartAddLines, cartUpdateLine, cartRemoveLine, cartApplyDiscounts, cartSetBuyerIdentity, cartRefresh, cartAdopt, cartReset,
|
|
752
|
+
wlItems, wlHas, wlAdd, wlRemove, wlToggle, wlClear, wlRefresh,
|
|
753
|
+
customer, token, authLoading, restoring, authLogin, authSignup, authLogout, authRecover, authRefresh,
|
|
754
|
+
checkoutOrderPlaced, checkoutPaymentFailed,
|
|
755
|
+
]);
|
|
756
|
+
return (0, jsx_runtime_1.jsx)(ShopifyContext.Provider, { value: value, children: children });
|
|
757
|
+
}
|
|
758
|
+
function useShopify() {
|
|
759
|
+
const ctx = (0, react_1.useContext)(ShopifyContext);
|
|
760
|
+
if (!ctx)
|
|
761
|
+
throw new Error("useShopify must be used within a ShopifyProvider");
|
|
762
|
+
return ctx;
|
|
763
|
+
}
|
|
764
|
+
function useCart() {
|
|
765
|
+
return useShopify().cart;
|
|
766
|
+
}
|
|
767
|
+
function useWishlist() {
|
|
768
|
+
return useShopify().wishlist;
|
|
769
|
+
}
|
|
770
|
+
/** Customer session — the source of the four Login alerts. */
|
|
771
|
+
function useCustomer() {
|
|
772
|
+
return useShopify().customer;
|
|
773
|
+
}
|
|
774
|
+
/** Reports a hosted-checkout outcome so the two Checkout alerts can fire. */
|
|
775
|
+
function useCheckout() {
|
|
776
|
+
return useShopify().checkout;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Resolved alert copy, for UI that isn't a toast — the empty-wishlist
|
|
780
|
+
* placeholder (`wishlist.empty`) is configurable but has no event.
|
|
781
|
+
*/
|
|
782
|
+
function useShopifyMessage() {
|
|
783
|
+
return useShopify().message;
|
|
784
|
+
}
|
|
785
|
+
//# sourceMappingURL=ShopifyProvider.js.map
|