@usethrottle/catalog 1.1.0 → 1.3.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/CHANGELOG.md +28 -0
- package/README.md +135 -24
- package/dist/admin.cjs +16 -16
- package/dist/admin.d.cts +6 -2
- package/dist/admin.d.ts +6 -2
- package/dist/admin.js +1 -1
- package/dist/{catalog-error-s1BINNkt.d.cts → catalog-error-BOaSKgY3.d.cts} +12 -2
- package/dist/{catalog-error-C_zLOtYA.d.ts → catalog-error-CSoPS0-d.d.ts} +12 -2
- package/dist/{chunk-4ORXUW2U.cjs → chunk-2YQNMBDI.cjs} +119 -33
- package/dist/{chunk-JK2UY4CD.js → chunk-3DLH56OX.js} +99 -13
- package/dist/chunk-CADIHOIR.js +496 -0
- package/dist/chunk-GU63WFXI.cjs +496 -0
- package/dist/{client-c2F-eCaL.d.ts → client-6j1Iz-iJ.d.cts} +2 -9
- package/dist/{client-BAoq6jmM.d.cts → client-B3mL2-xJ.d.ts} +2 -9
- package/dist/{contract-types-DhiThMEr.d.cts → contract-types-BRyonOYH.d.cts} +23 -21
- package/dist/{contract-types-DhiThMEr.d.ts → contract-types-BRyonOYH.d.ts} +23 -21
- package/dist/index.cjs +24 -2
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.js +25 -3
- package/dist/react-extras.cjs +42 -0
- package/dist/react-extras.d.cts +23 -0
- package/dist/react-extras.d.ts +23 -0
- package/dist/react-extras.js +42 -0
- package/dist/react.cjs +286 -491
- package/dist/react.d.cts +44 -38
- package/dist/react.d.ts +44 -38
- package/dist/react.js +289 -494
- package/dist/server.cjs +19 -14
- package/dist/server.d.cts +4 -3
- package/dist/server.d.ts +4 -3
- package/dist/server.js +7 -2
- package/dist/{types-DsjoG8ce.d.cts → types-BfveENg1.d.ts} +76 -3
- package/dist/{types-xQLID-4j.d.ts → types-D0PywKnN.d.cts} +76 -3
- package/dist/url.d.cts +2 -1
- package/dist/url.d.ts +2 -1
- package/dist/use-query-CR7r5tam.d.ts +40 -0
- package/dist/use-query-UWf6Pi5g.d.cts +40 -0
- package/dist/version-ev3p03v2.d.cts +8 -0
- package/dist/version-ev3p03v2.d.ts +8 -0
- package/package.json +13 -2
package/dist/react.js
CHANGED
|
@@ -1,454 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
CatalogProvider,
|
|
4
|
+
catalogQueryKey,
|
|
5
|
+
createMemoryCartStorage,
|
|
6
|
+
useCatalog,
|
|
7
|
+
useCatalogContext,
|
|
8
|
+
useCategories,
|
|
9
|
+
useCollection,
|
|
10
|
+
useConfig,
|
|
11
|
+
useCustomerKey,
|
|
12
|
+
useFilterValues,
|
|
13
|
+
useProduct,
|
|
14
|
+
useProducts,
|
|
15
|
+
useRelated,
|
|
16
|
+
useReviews,
|
|
17
|
+
useSuggest
|
|
18
|
+
} from "./chunk-CADIHOIR.js";
|
|
19
|
+
import {
|
|
4
20
|
CatalogError,
|
|
5
|
-
INDEXING_REVALIDATE_MS,
|
|
6
|
-
RATE_LIMIT_DEFAULT_RETRY_S,
|
|
7
21
|
RateLimitedError,
|
|
8
22
|
SDK_VERSION,
|
|
9
|
-
STALE_MS,
|
|
10
|
-
SUGGEST_DEBOUNCE_MS,
|
|
11
|
-
SUGGEST_MIN_CHARS,
|
|
12
23
|
THROTTLE_CHECKOUT_URL,
|
|
24
|
+
formatMoney,
|
|
13
25
|
isCatalogError
|
|
14
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-3DLH56OX.js";
|
|
15
27
|
import "./chunk-PKBMQBKP.js";
|
|
16
28
|
|
|
17
|
-
// src/react/provider.tsx
|
|
18
|
-
import {
|
|
19
|
-
createContext,
|
|
20
|
-
useContext,
|
|
21
|
-
useEffect,
|
|
22
|
-
useMemo,
|
|
23
|
-
useRef
|
|
24
|
-
} from "react";
|
|
25
|
-
|
|
26
|
-
// src/react/store.ts
|
|
27
|
-
var HOOK_CALL = Symbol.for("@usethrottle/catalog.hookCall");
|
|
28
|
-
var EMPTY = {
|
|
29
|
-
data: void 0,
|
|
30
|
-
error: void 0,
|
|
31
|
-
isValidating: false,
|
|
32
|
-
fetchedAt: void 0
|
|
33
|
-
};
|
|
34
|
-
var done = Promise.resolve();
|
|
35
|
-
function createStore(client, options = {}) {
|
|
36
|
-
const now = options.now ?? Date.now;
|
|
37
|
-
const entries = /* @__PURE__ */ new Map();
|
|
38
|
-
let until = 0;
|
|
39
|
-
let cause;
|
|
40
|
-
let pauseTimer;
|
|
41
|
-
const pausedError = () => new RateLimitedError({
|
|
42
|
-
code: cause?.code ?? "RATE_LIMITED",
|
|
43
|
-
message: cause?.message ?? "Too many requests.",
|
|
44
|
-
statusCode: 429,
|
|
45
|
-
source: cause?.source ?? "catalog",
|
|
46
|
-
retryAfterSeconds: Math.ceil((until - now()) / 1e3)
|
|
47
|
-
});
|
|
48
|
-
const update = (entry, patch) => {
|
|
49
|
-
entry.snap = { ...entry.snap, ...patch };
|
|
50
|
-
for (const listener of [...entry.subs]) listener();
|
|
51
|
-
};
|
|
52
|
-
const touch = (key, initial, stale = false) => {
|
|
53
|
-
let entry = entries.get(key);
|
|
54
|
-
if (entry !== void 0) {
|
|
55
|
-
entries.delete(key);
|
|
56
|
-
} else {
|
|
57
|
-
entry = {
|
|
58
|
-
snap: initial === void 0 ? EMPTY : { ...EMPTY, data: initial, fetchedAt: stale ? void 0 : now() },
|
|
59
|
-
subs: /* @__PURE__ */ new Set()
|
|
60
|
-
};
|
|
61
|
-
for (const [oldKey, old] of entries) {
|
|
62
|
-
if (entries.size < CACHE_MAX_ENTRIES) break;
|
|
63
|
-
if (old.subs.size === 0 && old.flight === void 0) {
|
|
64
|
-
clearTimeout(old.timer);
|
|
65
|
-
entries.delete(oldKey);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
entries.set(key, entry);
|
|
70
|
-
return entry;
|
|
71
|
-
};
|
|
72
|
-
const later = (key, entry, ms) => {
|
|
73
|
-
entry.indexing = true;
|
|
74
|
-
clearTimeout(entry.timer);
|
|
75
|
-
entry.timer = setTimeout(() => {
|
|
76
|
-
if (entries.get(key) === entry && entry.subs.size > 0 && entry.run)
|
|
77
|
-
void fetchKey(key, entry.run, { force: true });
|
|
78
|
-
else entry.indexing = false;
|
|
79
|
-
}, ms);
|
|
80
|
-
};
|
|
81
|
-
function fetchKey(key, run, { force = false, staleMs = STALE_MS } = {}) {
|
|
82
|
-
const entry = touch(key);
|
|
83
|
-
entry.run = run;
|
|
84
|
-
if (entry.promise !== void 0 && !entry.flight?.signal.aborted)
|
|
85
|
-
return entry.promise;
|
|
86
|
-
if (now() < until) {
|
|
87
|
-
if (entry.snap.data === void 0 && entry.snap.error === void 0)
|
|
88
|
-
update(entry, { error: pausedError() });
|
|
89
|
-
return done;
|
|
90
|
-
}
|
|
91
|
-
const at = entry.snap.fetchedAt;
|
|
92
|
-
if (!force && at !== void 0 && now() - at < staleMs) return done;
|
|
93
|
-
const controller = new AbortController();
|
|
94
|
-
const box = entry.etag === void 0 ? {} : { etag: entry.etag };
|
|
95
|
-
const current = () => {
|
|
96
|
-
if (entry.flight !== controller) return false;
|
|
97
|
-
entry.flight = entry.promise = void 0;
|
|
98
|
-
if (!controller.signal.aborted) return true;
|
|
99
|
-
update(entry, { isValidating: false });
|
|
100
|
-
return false;
|
|
101
|
-
};
|
|
102
|
-
entry.flight = controller;
|
|
103
|
-
update(entry, { isValidating: true });
|
|
104
|
-
let pending;
|
|
105
|
-
try {
|
|
106
|
-
pending = run(client, {
|
|
107
|
-
signal: controller.signal,
|
|
108
|
-
[HOOK_CALL]: box
|
|
109
|
-
});
|
|
110
|
-
} catch (error) {
|
|
111
|
-
pending = Promise.reject(error);
|
|
112
|
-
}
|
|
113
|
-
const promise = pending.then(
|
|
114
|
-
(value) => {
|
|
115
|
-
if (!current()) return;
|
|
116
|
-
const result = box.result ?? {
|
|
117
|
-
notModified: false,
|
|
118
|
-
data: value,
|
|
119
|
-
etag: null
|
|
120
|
-
};
|
|
121
|
-
entry.etag = result.etag ?? void 0;
|
|
122
|
-
if (result.notModified) {
|
|
123
|
-
update(entry, {
|
|
124
|
-
error: void 0,
|
|
125
|
-
isValidating: false,
|
|
126
|
-
fetchedAt: now()
|
|
127
|
-
});
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (result.data?.indexing === true) {
|
|
131
|
-
if (!entry.indexing) later(key, entry, INDEXING_REVALIDATE_MS);
|
|
132
|
-
} else {
|
|
133
|
-
entry.indexing = false;
|
|
134
|
-
}
|
|
135
|
-
update(entry, {
|
|
136
|
-
data: result.data,
|
|
137
|
-
error: void 0,
|
|
138
|
-
isValidating: false,
|
|
139
|
-
fetchedAt: now()
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
(error) => {
|
|
143
|
-
if (!current()) return;
|
|
144
|
-
const retryMs = (error.retryAfterSeconds ?? RATE_LIMIT_DEFAULT_RETRY_S) * 1e3;
|
|
145
|
-
if (error.statusCode === 429 && error.code === "RATE_LIMITED")
|
|
146
|
-
pause(now() + retryMs, error);
|
|
147
|
-
else if (error.code === "STOREFRONT_INDEXING" && !entry.indexing)
|
|
148
|
-
later(key, entry, retryMs);
|
|
149
|
-
update(entry, { error, isValidating: false });
|
|
150
|
-
}
|
|
151
|
-
);
|
|
152
|
-
entry.promise = promise;
|
|
153
|
-
return promise;
|
|
154
|
-
}
|
|
155
|
-
function pause(untilMs, error) {
|
|
156
|
-
if (untilMs <= Math.max(until, now())) return;
|
|
157
|
-
until = untilMs;
|
|
158
|
-
cause = error;
|
|
159
|
-
clearTimeout(pauseTimer);
|
|
160
|
-
pauseTimer = setTimeout(() => {
|
|
161
|
-
until = 0;
|
|
162
|
-
for (const [key, entry] of [...entries]) {
|
|
163
|
-
if (entry.subs.size > 0 && entry.run)
|
|
164
|
-
void fetchKey(key, entry.run, { force: true });
|
|
165
|
-
}
|
|
166
|
-
}, untilMs - now());
|
|
167
|
-
}
|
|
168
|
-
function clear() {
|
|
169
|
-
for (const [key, entry] of [...entries]) {
|
|
170
|
-
clearTimeout(entry.timer);
|
|
171
|
-
entry.flight?.abort();
|
|
172
|
-
if (entry.subs.size === 0) {
|
|
173
|
-
entries.delete(key);
|
|
174
|
-
} else {
|
|
175
|
-
entry.etag = entry.flight = entry.promise = void 0;
|
|
176
|
-
entry.indexing = false;
|
|
177
|
-
update(entry, EMPTY);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return {
|
|
182
|
-
subscribe(key, listener) {
|
|
183
|
-
const entry = touch(key);
|
|
184
|
-
entry.subs.add(listener);
|
|
185
|
-
return () => {
|
|
186
|
-
entry.subs.delete(listener);
|
|
187
|
-
void done.then(() => {
|
|
188
|
-
if (entry.subs.size === 0) entry.flight?.abort();
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
},
|
|
192
|
-
get: (key, initial, stale) => key === null ? EMPTY : (entries.get(key) ?? (initial === void 0 ? void 0 : touch(key, initial, stale)))?.snap ?? EMPTY,
|
|
193
|
-
getServer(key, initial, stale) {
|
|
194
|
-
if (key === null || initial === void 0) return EMPTY;
|
|
195
|
-
const entry = entries.get(key) ?? touch(key, initial, stale);
|
|
196
|
-
return entry.server ?? (entry.server = { ...EMPTY, data: initial });
|
|
197
|
-
},
|
|
198
|
-
fetch: fetchKey,
|
|
199
|
-
clear,
|
|
200
|
-
pause,
|
|
201
|
-
pausedUntil: () => until > now() ? until : 0,
|
|
202
|
-
dispose() {
|
|
203
|
-
clearTimeout(pauseTimer);
|
|
204
|
-
until = 0;
|
|
205
|
-
clear();
|
|
206
|
-
for (const entry of entries.values()) entry.subs.clear();
|
|
207
|
-
entries.clear();
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// src/react/provider.tsx
|
|
213
|
-
import { jsx } from "react/jsx-runtime";
|
|
214
|
-
function createMemoryCartStorage() {
|
|
215
|
-
let id = null;
|
|
216
|
-
return {
|
|
217
|
-
get: () => id,
|
|
218
|
-
set: (value) => {
|
|
219
|
-
id = value;
|
|
220
|
-
},
|
|
221
|
-
remove: () => {
|
|
222
|
-
id = null;
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
var CatalogContext = createContext(null);
|
|
227
|
-
function useCatalogContext() {
|
|
228
|
-
const value = useContext(CatalogContext);
|
|
229
|
-
if (value === null)
|
|
230
|
-
throw new Error("Catalog hooks must be used inside a <CatalogProvider>.");
|
|
231
|
-
return value;
|
|
232
|
-
}
|
|
233
|
-
function CatalogProvider(props) {
|
|
234
|
-
const { client, children, ...rest } = props;
|
|
235
|
-
const customerKey = props.customerKey ?? null;
|
|
236
|
-
const mounted = useRef(null);
|
|
237
|
-
const store = useMemo(() => {
|
|
238
|
-
const next = createStore(client);
|
|
239
|
-
next.pause(mounted.current?.pausedUntil() ?? 0);
|
|
240
|
-
return next;
|
|
241
|
-
}, [client, customerKey]);
|
|
242
|
-
useEffect(() => {
|
|
243
|
-
mounted.current = store;
|
|
244
|
-
return () => {
|
|
245
|
-
mounted.current = null;
|
|
246
|
-
void Promise.resolve().then(() => {
|
|
247
|
-
if (mounted.current !== store) store.dispose();
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
}, [store]);
|
|
251
|
-
const memory = useMemo(createMemoryCartStorage, []);
|
|
252
|
-
const cartStorage = rest.cartStorage ?? memory;
|
|
253
|
-
const shared = useRef({}).current;
|
|
254
|
-
const cart = useMemo(
|
|
255
|
-
() => ({ shared }),
|
|
256
|
-
[shared, client, rest.throttleBaseUrl]
|
|
257
|
-
);
|
|
258
|
-
const urls = rest.cartUrls;
|
|
259
|
-
const value = useMemo(
|
|
260
|
-
() => ({ ...rest, client, customerKey, store, cartStorage, cart }),
|
|
261
|
-
// `rest` is spread fresh each render; its fields are the dependencies.
|
|
262
|
-
[
|
|
263
|
-
client,
|
|
264
|
-
customerKey,
|
|
265
|
-
store,
|
|
266
|
-
cartStorage,
|
|
267
|
-
cart,
|
|
268
|
-
rest.throttleBaseUrl,
|
|
269
|
-
rest.throttleCheckoutUrl,
|
|
270
|
-
urls?.returnUrl,
|
|
271
|
-
urls?.cancelUrl,
|
|
272
|
-
urls?.storefrontUrl
|
|
273
|
-
]
|
|
274
|
-
);
|
|
275
|
-
cart.context = value;
|
|
276
|
-
shared.home = cart;
|
|
277
|
-
return /* @__PURE__ */ jsx(CatalogContext.Provider, { value, children });
|
|
278
|
-
}
|
|
279
|
-
function useCatalog() {
|
|
280
|
-
return useCatalogContext().client;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// src/react/use-query.ts
|
|
284
|
-
import { useCallback, useEffect as useEffect2, useRef as useRef2, useSyncExternalStore } from "react";
|
|
285
|
-
function canonical(value) {
|
|
286
|
-
if (Array.isArray(value)) return value.map(canonical);
|
|
287
|
-
if (value === null || typeof value !== "object") return value;
|
|
288
|
-
const out = {};
|
|
289
|
-
for (const name of Object.keys(value).sort()) {
|
|
290
|
-
const item = value[name];
|
|
291
|
-
if (item !== void 0) out[name] = canonical(item);
|
|
292
|
-
}
|
|
293
|
-
return out;
|
|
294
|
-
}
|
|
295
|
-
function catalogQueryKey(route, params) {
|
|
296
|
-
return `${route}:${JSON.stringify(canonical(params ?? null))}`;
|
|
297
|
-
}
|
|
298
|
-
var noop = () => {
|
|
299
|
-
};
|
|
300
|
-
var unsubscribe = () => noop;
|
|
301
|
-
function useQuery(route, params, run, options = {}) {
|
|
302
|
-
const { store, customerKey } = useCatalogContext();
|
|
303
|
-
const key = run === null || options.enabled === false ? null : `${customerKey ?? ""}\0${catalogQueryKey(route, params)}`;
|
|
304
|
-
const first = useRef2({ store, key });
|
|
305
|
-
const initial = first.current.store === store && first.current.key === key ? options.initialData : void 0;
|
|
306
|
-
const subscribe = useCallback(
|
|
307
|
-
(listener) => key === null ? unsubscribe() : store.subscribe(key, listener),
|
|
308
|
-
[store, key]
|
|
309
|
-
);
|
|
310
|
-
const stale = customerKey !== null;
|
|
311
|
-
const snap = useSyncExternalStore(
|
|
312
|
-
subscribe,
|
|
313
|
-
() => store.get(key, initial, stale),
|
|
314
|
-
() => store.getServer(key, initial, stale)
|
|
315
|
-
);
|
|
316
|
-
const staleMs = options.staleTimeMs;
|
|
317
|
-
useEffect2(() => {
|
|
318
|
-
if (key !== null && run !== null)
|
|
319
|
-
void store.fetch(key, run, staleMs === void 0 ? {} : { staleMs });
|
|
320
|
-
}, [store, key, staleMs]);
|
|
321
|
-
const refetch = useCallback(
|
|
322
|
-
() => key === null || run === null ? Promise.resolve() : store.fetch(key, run, { force: true }),
|
|
323
|
-
[store, key]
|
|
324
|
-
);
|
|
325
|
-
return {
|
|
326
|
-
data: snap.data,
|
|
327
|
-
error: snap.error,
|
|
328
|
-
isLoading: key !== null && snap.data === void 0 && snap.error === void 0,
|
|
329
|
-
isValidating: snap.isValidating,
|
|
330
|
-
refetch
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// src/react/hooks.ts
|
|
335
|
-
import { useEffect as useEffect3, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
336
|
-
function defined(options) {
|
|
337
|
-
const out = {};
|
|
338
|
-
for (const name in options) {
|
|
339
|
-
if (options[name] !== void 0) out[name] = options[name];
|
|
340
|
-
}
|
|
341
|
-
return out;
|
|
342
|
-
}
|
|
343
|
-
function useProducts(query = {}, options) {
|
|
344
|
-
return useQuery(
|
|
345
|
-
"products",
|
|
346
|
-
query,
|
|
347
|
-
(client, call) => client.products.list(query, call),
|
|
348
|
-
options
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
function useProduct(handle, options = {}) {
|
|
352
|
-
const extra = defined({ include: options.include });
|
|
353
|
-
return useQuery(
|
|
354
|
-
"product",
|
|
355
|
-
{ handle, ...extra },
|
|
356
|
-
handle === null ? null : (client, call) => client.products.get(handle, { ...call, ...extra }),
|
|
357
|
-
options
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
function useRelated(handle, options = {}) {
|
|
361
|
-
const extra = defined({ limit: options.limit });
|
|
362
|
-
return useQuery(
|
|
363
|
-
"related",
|
|
364
|
-
{ handle, ...extra },
|
|
365
|
-
handle === null ? null : (client, call) => client.products.related(handle, { ...call, ...extra }),
|
|
366
|
-
options
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
function useDebounced(q, ms) {
|
|
370
|
-
const box = useMemo2(() => {
|
|
371
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
372
|
-
let value = q;
|
|
373
|
-
return {
|
|
374
|
-
get: () => value,
|
|
375
|
-
set(next) {
|
|
376
|
-
value = next;
|
|
377
|
-
for (const listener of [...listeners]) listener();
|
|
378
|
-
},
|
|
379
|
-
subscribe(listener) {
|
|
380
|
-
listeners.add(listener);
|
|
381
|
-
return () => listeners.delete(listener);
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}, []);
|
|
385
|
-
useEffect3(() => {
|
|
386
|
-
const timer = setTimeout(() => box.set(q), ms);
|
|
387
|
-
return () => clearTimeout(timer);
|
|
388
|
-
}, [box, q, ms]);
|
|
389
|
-
return useSyncExternalStore2(box.subscribe, box.get, box.get);
|
|
390
|
-
}
|
|
391
|
-
function useSuggest(q, options = {}) {
|
|
392
|
-
const settled = useDebounced(q, options.debounceMs ?? SUGGEST_DEBOUNCE_MS);
|
|
393
|
-
const extra = defined({ limit: options.limit });
|
|
394
|
-
const long = (value) => value.trim().length >= SUGGEST_MIN_CHARS;
|
|
395
|
-
return useQuery(
|
|
396
|
-
"suggest",
|
|
397
|
-
{ q: settled, ...extra },
|
|
398
|
-
long(q) && long(settled) ? (client, call) => client.suggest(settled, { ...call, ...extra }) : null,
|
|
399
|
-
options
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
function useCategories(options = {}) {
|
|
403
|
-
const extra = defined({ depth: options.depth });
|
|
404
|
-
return useQuery(
|
|
405
|
-
"categories",
|
|
406
|
-
extra,
|
|
407
|
-
(client, call) => client.categories.tree({ ...call, ...extra }),
|
|
408
|
-
options
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
function useCollection(handle, options) {
|
|
412
|
-
return useQuery(
|
|
413
|
-
"collection",
|
|
414
|
-
{ handle },
|
|
415
|
-
handle === null ? null : (client, call) => client.collections.get(handle, call),
|
|
416
|
-
options
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
function useReviews(handle, options = {}) {
|
|
420
|
-
const extra = defined({
|
|
421
|
-
sort: options.sort,
|
|
422
|
-
rating: options.rating,
|
|
423
|
-
limit: options.limit,
|
|
424
|
-
cursor: options.cursor
|
|
425
|
-
});
|
|
426
|
-
return useQuery(
|
|
427
|
-
"reviews",
|
|
428
|
-
{ handle, ...extra },
|
|
429
|
-
handle === null ? null : (client, call) => client.reviews.list(handle, { ...call, ...extra }),
|
|
430
|
-
options
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
function useConfig(options) {
|
|
434
|
-
return useQuery(
|
|
435
|
-
"config",
|
|
436
|
-
null,
|
|
437
|
-
(client, call) => client.config(call),
|
|
438
|
-
options
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
function useFilterValues(key, query = {}, options) {
|
|
442
|
-
return useQuery(
|
|
443
|
-
"filterValues",
|
|
444
|
-
{ key, query },
|
|
445
|
-
key === null ? null : (client, call) => client.filters.values(key, query, call),
|
|
446
|
-
options
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
29
|
// src/react/use-cart.ts
|
|
451
|
-
import { useEffect
|
|
30
|
+
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
|
452
31
|
|
|
453
32
|
// src/react/mutation.ts
|
|
454
33
|
function newKey() {
|
|
@@ -515,13 +94,14 @@ function createBox(initial) {
|
|
|
515
94
|
}
|
|
516
95
|
|
|
517
96
|
// src/react/use-cart.ts
|
|
518
|
-
var
|
|
97
|
+
var EMPTY = {
|
|
519
98
|
cart: null,
|
|
520
99
|
sessionId: null,
|
|
521
100
|
cartUrl: null,
|
|
522
101
|
status: "idle",
|
|
523
102
|
error: void 0,
|
|
524
|
-
pending: false
|
|
103
|
+
pending: false,
|
|
104
|
+
warnings: []
|
|
525
105
|
};
|
|
526
106
|
var ENDED = /* @__PURE__ */ new Set([
|
|
527
107
|
"cart_session_expired",
|
|
@@ -543,6 +123,8 @@ function itemsOf(cart) {
|
|
|
543
123
|
return items;
|
|
544
124
|
}
|
|
545
125
|
var skuOf = (cart, lineId) => cart?.lineItems?.find((line) => line.id === lineId)?.referenceId ?? null;
|
|
126
|
+
var CART_REREAD_MS = 3e4;
|
|
127
|
+
var TRANSPORT = Symbol.for("@usethrottle/catalog.transport");
|
|
546
128
|
function createController(home) {
|
|
547
129
|
var _a;
|
|
548
130
|
const ctx = () => home.context;
|
|
@@ -551,16 +133,19 @@ function createController(home) {
|
|
|
551
133
|
seen: void 0,
|
|
552
134
|
moving: void 0,
|
|
553
135
|
unremoved: false,
|
|
136
|
+
reads: /* @__PURE__ */ new Map(),
|
|
137
|
+
slots: /* @__PURE__ */ new Set(),
|
|
554
138
|
abort: new AbortController(),
|
|
555
139
|
writing: Promise.resolve(),
|
|
556
140
|
tail: Promise.resolve()
|
|
557
141
|
});
|
|
558
142
|
const retired = () => home.shared.home !== home;
|
|
559
|
-
const box = createBox(
|
|
143
|
+
const box = createBox(EMPTY);
|
|
560
144
|
let queued = 0;
|
|
561
145
|
let loaded = false;
|
|
562
146
|
let currency;
|
|
563
|
-
|
|
147
|
+
let hinted;
|
|
148
|
+
const reset = () => box.set({ ...EMPTY, pending: queued > 0 });
|
|
564
149
|
const session = (id) => {
|
|
565
150
|
const { client, throttleBaseUrl } = ctx();
|
|
566
151
|
return client.carts.session(
|
|
@@ -574,12 +159,106 @@ function createController(home) {
|
|
|
574
159
|
if (signal.aborted) throw signal.reason;
|
|
575
160
|
return out;
|
|
576
161
|
};
|
|
162
|
+
const resuming = () => ctx().customerCart !== void 0 && ctx().customerKey !== null;
|
|
163
|
+
const wanted = (options) => {
|
|
164
|
+
const transport = ctx().client[TRANSPORT];
|
|
165
|
+
return (currency ?? options?.currency ?? hinted ?? transport?.context?.().currency)?.toUpperCase();
|
|
166
|
+
};
|
|
167
|
+
async function soft(signal, step) {
|
|
168
|
+
try {
|
|
169
|
+
await step();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (signal.aborted) throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function find(signal, wantedCurrency, strict = false) {
|
|
175
|
+
const source = ctx().customerCart;
|
|
176
|
+
if (source === void 0 || wantedCurrency === void 0) return null;
|
|
177
|
+
const asked = wantedCurrency.toUpperCase();
|
|
178
|
+
try {
|
|
179
|
+
const found = await live(signal, source.getOpenCart({ currency: asked }));
|
|
180
|
+
return found !== null && found.currency.toUpperCase() === asked ? found : null;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (signal.aborted || strict) throw error;
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function findTarget(signal, code, storage) {
|
|
187
|
+
if (resuming()) {
|
|
188
|
+
const found = await find(signal, code, true);
|
|
189
|
+
return found === null ? null : { id: found.sessionId, cartUrl: fallbackUrl(found.sessionId) };
|
|
190
|
+
}
|
|
191
|
+
if (ctx().customerKey !== null || storage.getFor === void 0) return null;
|
|
192
|
+
const id = await live(signal, storage.getFor(code));
|
|
193
|
+
if (id === null || id === "") return null;
|
|
194
|
+
try {
|
|
195
|
+
const answer = await live(signal, session(id).get({ signal }));
|
|
196
|
+
if (answer.cart.status !== "open" || answer.cart.currency.toUpperCase() !== code)
|
|
197
|
+
return null;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (signal.aborted || !ended(error)) throw error;
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return { id, cartUrl: fallbackUrl(id) };
|
|
203
|
+
}
|
|
204
|
+
async function findOnce(signal, storage) {
|
|
205
|
+
var _a2;
|
|
206
|
+
if (!resuming()) return;
|
|
207
|
+
const looked = (_a2 = home.shared).looked ?? (_a2.looked = /* @__PURE__ */ new Set());
|
|
208
|
+
const asked = wanted();
|
|
209
|
+
if (asked === void 0) return;
|
|
210
|
+
const key = `${ctx().customerKey}\0${asked}`;
|
|
211
|
+
if (looked.has(key)) return;
|
|
212
|
+
looked.add(key);
|
|
213
|
+
let found = null;
|
|
214
|
+
try {
|
|
215
|
+
found = await find(signal, asked);
|
|
216
|
+
} finally {
|
|
217
|
+
if (found === null) looked.delete(key);
|
|
218
|
+
}
|
|
219
|
+
if (found !== null) await soft(signal, () => adopt(signal, found, storage));
|
|
220
|
+
}
|
|
221
|
+
async function forgetSlots(storage, codes) {
|
|
222
|
+
for (const code of codes)
|
|
223
|
+
if (storage.removeFor !== void 0) await storage.removeFor(code);
|
|
224
|
+
else await storage.setFor?.(code, "");
|
|
225
|
+
}
|
|
226
|
+
async function save(signal, storage, id) {
|
|
227
|
+
const write = storage.set(id);
|
|
228
|
+
me.writing = Promise.resolve(write).catch(() => void 0);
|
|
229
|
+
await live(signal, write);
|
|
230
|
+
}
|
|
231
|
+
async function adopt(signal, found, storage) {
|
|
232
|
+
const id = found.sessionId;
|
|
233
|
+
const answer = await live(signal, session(id).get({ signal }));
|
|
234
|
+
me.reads.set(id, Date.now());
|
|
235
|
+
await save(signal, storage, id);
|
|
236
|
+
box.set({
|
|
237
|
+
sessionId: id,
|
|
238
|
+
cartUrl: fallbackUrl(id),
|
|
239
|
+
cart: answer.cart,
|
|
240
|
+
status: "ready",
|
|
241
|
+
error: void 0
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function supersede(signal, answer, storage) {
|
|
245
|
+
if (answer.cart.status !== "abandoned" || !resuming()) return;
|
|
246
|
+
const found = await find(signal, answer.cart.currency);
|
|
247
|
+
if (found !== null && found.cartId !== answer.cart.cartId)
|
|
248
|
+
await adopt(signal, found, storage);
|
|
249
|
+
}
|
|
250
|
+
async function read(signal, id) {
|
|
251
|
+
const answer = await live(signal, session(id).get({ signal }));
|
|
252
|
+
me.reads.set(id, Date.now());
|
|
253
|
+
box.set({ cart: answer.cart, status: "ready", error: void 0 });
|
|
254
|
+
return answer;
|
|
255
|
+
}
|
|
577
256
|
async function endSession(signal, error, storage) {
|
|
578
257
|
await live(signal, storage.remove());
|
|
579
258
|
if (converted(error)) reset();
|
|
580
259
|
else box.set({ sessionId: null, cartUrl: null });
|
|
581
260
|
}
|
|
582
|
-
async function
|
|
261
|
+
async function start(signal, options, items) {
|
|
583
262
|
const context = ctx();
|
|
584
263
|
const body = {
|
|
585
264
|
items: [...items].map(([sku, quantity]) => ({ sku, quantity }))
|
|
@@ -603,33 +282,25 @@ function createController(home) {
|
|
|
603
282
|
throw error;
|
|
604
283
|
}
|
|
605
284
|
const id = answer.sessionId;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
cartUrl,
|
|
615
|
-
cart: snapshot.cart,
|
|
616
|
-
status: "ready",
|
|
617
|
-
error: void 0
|
|
618
|
-
});
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
285
|
+
return {
|
|
286
|
+
id,
|
|
287
|
+
cartUrl: isHttpUrl(answer.cartUrl) ? answer.cartUrl : fallbackUrl(id)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async function create(signal, options, items) {
|
|
291
|
+
const storage = ctx().cartStorage;
|
|
292
|
+
const { id, cartUrl } = await start(signal, options, items);
|
|
621
293
|
box.set({ sessionId: id, cartUrl });
|
|
622
294
|
let unsaved;
|
|
623
295
|
try {
|
|
624
|
-
|
|
625
|
-
me.writing = Promise.resolve(write).catch(() => void 0);
|
|
626
|
-
await live(signal, write);
|
|
296
|
+
await save(signal, storage, id);
|
|
627
297
|
} catch (error) {
|
|
628
298
|
if (signal.aborted) throw error;
|
|
629
299
|
unsaved = error;
|
|
630
300
|
}
|
|
631
301
|
try {
|
|
632
302
|
const snapshot = await live(signal, session(id).get({ signal }));
|
|
303
|
+
me.reads.set(id, Date.now());
|
|
633
304
|
box.set({ cart: snapshot.cart, status: "ready", error: void 0 });
|
|
634
305
|
} catch (error) {
|
|
635
306
|
if (!signal.aborted) box.set({ status: "error" });
|
|
@@ -640,18 +311,52 @@ function createController(home) {
|
|
|
640
311
|
async function mutate(signal, options, call, apply) {
|
|
641
312
|
checkPause(ctx().store);
|
|
642
313
|
const storage = ctx().cartStorage;
|
|
643
|
-
const id = box.get().sessionId;
|
|
644
314
|
let met;
|
|
645
|
-
|
|
315
|
+
const attempt = async (id2) => {
|
|
646
316
|
try {
|
|
647
|
-
const answer = await live(signal, call(session(
|
|
317
|
+
const answer = await live(signal, call(session(id2), signal));
|
|
648
318
|
if (answer !== null)
|
|
649
319
|
box.set({ cart: answer.cart, status: "ready", error: void 0 });
|
|
650
|
-
return
|
|
320
|
+
return true;
|
|
651
321
|
} catch (error) {
|
|
652
322
|
if (signal.aborted || !ended(error)) throw error;
|
|
653
323
|
await endSession(signal, error, storage);
|
|
654
324
|
met = error;
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const stale = box.get().sessionId;
|
|
329
|
+
if (stale !== null && resuming() && Date.now() - (me.reads.get(stale) ?? 0) > CART_REREAD_MS) {
|
|
330
|
+
let answer;
|
|
331
|
+
try {
|
|
332
|
+
answer = await read(signal, stale);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (signal.aborted) throw error;
|
|
335
|
+
if (ended(error)) {
|
|
336
|
+
await endSession(signal, error, storage);
|
|
337
|
+
met = error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (answer !== void 0) {
|
|
341
|
+
const seen = answer;
|
|
342
|
+
await soft(signal, () => supersede(signal, seen, storage));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const id = box.get().sessionId;
|
|
346
|
+
if (id !== null && await attempt(id)) return void 0;
|
|
347
|
+
if (resuming()) {
|
|
348
|
+
const found = await find(signal, wanted(options));
|
|
349
|
+
if (found !== null) {
|
|
350
|
+
let adopted = true;
|
|
351
|
+
await soft(signal, async () => {
|
|
352
|
+
try {
|
|
353
|
+
await adopt(signal, found, storage);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
adopted = false;
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
if (adopted && await attempt(found.sessionId)) return void 0;
|
|
655
360
|
}
|
|
656
361
|
}
|
|
657
362
|
const cart = box.get().cart;
|
|
@@ -687,13 +392,16 @@ function createController(home) {
|
|
|
687
392
|
me.tail = result.catch(() => void 0);
|
|
688
393
|
return result;
|
|
689
394
|
}
|
|
690
|
-
async function restore(signal, storage = ctx().cartStorage) {
|
|
395
|
+
async function restore(signal, storage = ctx().cartStorage, look = true) {
|
|
691
396
|
const id = await live(signal, storage.get());
|
|
692
|
-
if (id === null || id === "")
|
|
397
|
+
if (id === null || id === "") {
|
|
398
|
+
if (look) await findOnce(signal, storage);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
693
401
|
box.set({ sessionId: id, cartUrl: fallbackUrl(id), status: "loading" });
|
|
402
|
+
let answer;
|
|
694
403
|
try {
|
|
695
|
-
|
|
696
|
-
box.set({ cart: answer.cart, status: "ready", error: void 0 });
|
|
404
|
+
answer = await read(signal, id);
|
|
697
405
|
} catch (error) {
|
|
698
406
|
if (signal.aborted) return;
|
|
699
407
|
if (!ended(error)) {
|
|
@@ -707,7 +415,10 @@ function createController(home) {
|
|
|
707
415
|
return;
|
|
708
416
|
}
|
|
709
417
|
reset();
|
|
418
|
+
if (look) await findOnce(signal, storage);
|
|
419
|
+
return;
|
|
710
420
|
}
|
|
421
|
+
if (look) await soft(signal, () => supersede(signal, answer, storage));
|
|
711
422
|
}
|
|
712
423
|
function load() {
|
|
713
424
|
if (loaded) return;
|
|
@@ -716,6 +427,17 @@ function createController(home) {
|
|
|
716
427
|
const signal = me.abort.signal;
|
|
717
428
|
me.tail = me.tail.then(() => me.unremoved ? void 0 : restore(signal)).catch(() => void 0);
|
|
718
429
|
}
|
|
430
|
+
async function claimSettled(signal) {
|
|
431
|
+
const source = ctx().customerCart;
|
|
432
|
+
if (source === void 0) return false;
|
|
433
|
+
try {
|
|
434
|
+
await live(signal, source.whenClaimSettled());
|
|
435
|
+
return true;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (signal.aborted) throw error;
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
719
441
|
function follow(guest, fresh) {
|
|
720
442
|
const signal = me.abort.signal;
|
|
721
443
|
me.moving = guest;
|
|
@@ -727,18 +449,32 @@ function createController(home) {
|
|
|
727
449
|
me.tail = me.tail.then(async () => {
|
|
728
450
|
await me.writing;
|
|
729
451
|
if (signal.aborted) return;
|
|
730
|
-
if (fresh && !me.unremoved) await restore(signal, guest);
|
|
452
|
+
if (fresh && !me.unremoved) await restore(signal, guest, false);
|
|
731
453
|
if (signal.aborted) return;
|
|
454
|
+
const codes = new Set(me.slots);
|
|
455
|
+
const shown = box.get().cart?.currency;
|
|
456
|
+
if (shown !== void 0) codes.add(shown.toUpperCase());
|
|
457
|
+
await live(signal, forgetSlots(guest, codes));
|
|
458
|
+
me.slots.clear();
|
|
732
459
|
const id = box.get().sessionId;
|
|
733
460
|
if (id === null) {
|
|
734
|
-
if (box.get().cart === null && !me.unremoved)
|
|
461
|
+
if (box.get().cart === null && !me.unremoved) {
|
|
462
|
+
await claimSettled(signal);
|
|
463
|
+
return restore(signal);
|
|
464
|
+
}
|
|
735
465
|
return;
|
|
736
466
|
}
|
|
737
467
|
const next = ctx().cartStorage;
|
|
738
468
|
await write(next, id);
|
|
739
|
-
if (guest
|
|
740
|
-
|
|
741
|
-
|
|
469
|
+
if (guest !== next) {
|
|
470
|
+
await live(signal, guest.remove());
|
|
471
|
+
if (await live(signal, next.get()) !== id) await write(next, id);
|
|
472
|
+
}
|
|
473
|
+
if (resuming() && await claimSettled(signal))
|
|
474
|
+
await soft(
|
|
475
|
+
signal,
|
|
476
|
+
async () => supersede(signal, await read(signal, id), next)
|
|
477
|
+
);
|
|
742
478
|
}).catch((error) => {
|
|
743
479
|
if (!signal.aborted) box.set({ error: wrap(error) });
|
|
744
480
|
}).finally(() => {
|
|
@@ -758,6 +494,9 @@ function createController(home) {
|
|
|
758
494
|
if (was === void 0 || was === null || was === key) return;
|
|
759
495
|
me.abort.abort();
|
|
760
496
|
me.abort = new AbortController();
|
|
497
|
+
me.reads.clear();
|
|
498
|
+
for (const key2 of home.shared.looked ?? [])
|
|
499
|
+
if (key2.startsWith(`${was}\0`)) home.shared.looked?.delete(key2);
|
|
761
500
|
currency = void 0;
|
|
762
501
|
loaded = true;
|
|
763
502
|
reset();
|
|
@@ -765,9 +504,15 @@ function createController(home) {
|
|
|
765
504
|
const outgoing = prior ?? me.seen;
|
|
766
505
|
const left = me.moving;
|
|
767
506
|
me.moving = void 0;
|
|
507
|
+
const slots = [...me.slots];
|
|
508
|
+
me.slots.clear();
|
|
768
509
|
const remove = async () => {
|
|
769
510
|
await outgoing.remove();
|
|
770
|
-
|
|
511
|
+
await forgetSlots(outgoing, slots);
|
|
512
|
+
if (left !== void 0 && left !== outgoing) {
|
|
513
|
+
await left.remove();
|
|
514
|
+
await forgetSlots(left, slots);
|
|
515
|
+
}
|
|
771
516
|
};
|
|
772
517
|
me.tail = me.writing.then(remove).catch(remove).then(
|
|
773
518
|
() => {
|
|
@@ -784,12 +529,16 @@ function createController(home) {
|
|
|
784
529
|
box,
|
|
785
530
|
/** The customer the cart belongs to (`undefined` before the first look). */
|
|
786
531
|
owner: () => me.owner,
|
|
532
|
+
hint: (next) => {
|
|
533
|
+
hinted = next;
|
|
534
|
+
},
|
|
787
535
|
identify,
|
|
788
536
|
load,
|
|
789
537
|
run,
|
|
790
538
|
mutate,
|
|
791
539
|
create,
|
|
792
540
|
session,
|
|
541
|
+
wanted,
|
|
793
542
|
async switchCurrency(signal, options, next) {
|
|
794
543
|
checkPause(ctx().store);
|
|
795
544
|
const storage = ctx().cartStorage;
|
|
@@ -806,21 +555,60 @@ function createController(home) {
|
|
|
806
555
|
}
|
|
807
556
|
const before = currency;
|
|
808
557
|
currency = next;
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
558
|
+
const code = next.toUpperCase();
|
|
559
|
+
const from = box.get().sessionId;
|
|
560
|
+
const cart = box.get().cart;
|
|
561
|
+
const items = itemsOf(cart);
|
|
562
|
+
if (from === null || items.size === 0) {
|
|
563
|
+
if (from !== null) await live(signal, storage.remove());
|
|
564
|
+
if (items.size === 0) reset();
|
|
813
565
|
return;
|
|
814
566
|
}
|
|
567
|
+
if (cart?.currency.toUpperCase() === code) return;
|
|
568
|
+
let target;
|
|
569
|
+
let moved;
|
|
815
570
|
try {
|
|
816
|
-
await
|
|
571
|
+
target = await findTarget(signal, code, storage) ?? await start(signal, options, /* @__PURE__ */ new Map());
|
|
572
|
+
moved = await live(signal, session(target.id).absorb(from, { signal }));
|
|
817
573
|
} catch (error) {
|
|
818
574
|
if (!signal.aborted) currency = before;
|
|
819
575
|
throw error;
|
|
820
576
|
}
|
|
577
|
+
me.reads.set(target.id, Date.now());
|
|
578
|
+
box.set({
|
|
579
|
+
sessionId: target.id,
|
|
580
|
+
cartUrl: target.cartUrl,
|
|
581
|
+
cart: moved.cart,
|
|
582
|
+
status: "ready",
|
|
583
|
+
error: void 0,
|
|
584
|
+
warnings: moved.warnings.filter(
|
|
585
|
+
(warning) => warning.code !== "lines_stale"
|
|
586
|
+
)
|
|
587
|
+
});
|
|
588
|
+
let unsaved;
|
|
589
|
+
try {
|
|
590
|
+
await save(signal, storage, target.id);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (signal.aborted) throw error;
|
|
593
|
+
unsaved = error;
|
|
594
|
+
try {
|
|
595
|
+
await live(signal, storage.remove());
|
|
596
|
+
} catch (failure) {
|
|
597
|
+
if (signal.aborted) throw failure;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (ctx().customerKey === null && storage.setFor !== void 0) {
|
|
601
|
+
me.slots.add(code);
|
|
602
|
+
await live(signal, storage.setFor(code, target.id));
|
|
603
|
+
}
|
|
604
|
+
if (unsaved !== void 0) throw unsaved;
|
|
821
605
|
},
|
|
822
606
|
async clear(signal) {
|
|
823
|
-
|
|
607
|
+
const storage = ctx().cartStorage;
|
|
608
|
+
const shown = box.get().cart?.currency;
|
|
609
|
+
await live(signal, storage.remove());
|
|
610
|
+
if (shown !== void 0)
|
|
611
|
+
await live(signal, storage.removeFor?.(shown.toUpperCase()));
|
|
824
612
|
currency = void 0;
|
|
825
613
|
reset();
|
|
826
614
|
}
|
|
@@ -830,18 +618,22 @@ function useCart(options = {}) {
|
|
|
830
618
|
const context = useCatalogContext();
|
|
831
619
|
const home = context.cart;
|
|
832
620
|
const controller = home.current ?? (home.current = createController(home));
|
|
833
|
-
const state =
|
|
621
|
+
const state = useSyncExternalStore(
|
|
834
622
|
controller.box.subscribe,
|
|
835
623
|
controller.box.get,
|
|
836
624
|
controller.box.get
|
|
837
625
|
);
|
|
838
626
|
const { customerKey } = context;
|
|
839
|
-
|
|
627
|
+
const hooked = options.currency;
|
|
628
|
+
useEffect(() => {
|
|
629
|
+
controller.hint(hooked);
|
|
630
|
+
}, [controller, hooked]);
|
|
631
|
+
useEffect(() => {
|
|
840
632
|
controller.identify(customerKey);
|
|
841
633
|
controller.load();
|
|
842
634
|
}, [controller, customerKey]);
|
|
843
635
|
const { currency, locale } = options;
|
|
844
|
-
const actions =
|
|
636
|
+
const actions = useMemo(() => {
|
|
845
637
|
const opts = {};
|
|
846
638
|
if (currency !== void 0) opts.currency = currency;
|
|
847
639
|
if (locale !== void 0) opts.locale = locale;
|
|
@@ -933,12 +725,14 @@ function useCart(options = {}) {
|
|
|
933
725
|
};
|
|
934
726
|
}, [controller, home, currency, locale]);
|
|
935
727
|
const owner = controller.owner();
|
|
936
|
-
const view = owner !== void 0 && owner !== null && owner !== customerKey ? { ...
|
|
937
|
-
|
|
728
|
+
const view = owner !== void 0 && owner !== null && owner !== customerKey ? { ...EMPTY, pending: state.pending } : state;
|
|
729
|
+
const shownCurrency = view.cart?.currency ?? controller.wanted(options) ?? "";
|
|
730
|
+
const format = (minor) => formatMoney(minor, shownCurrency);
|
|
731
|
+
return { ...view, ...actions, format };
|
|
938
732
|
}
|
|
939
733
|
|
|
940
734
|
// src/react/use-submit-review.ts
|
|
941
|
-
import { useCallback
|
|
735
|
+
import { useCallback, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
942
736
|
var IDLE = {
|
|
943
737
|
status: "idle",
|
|
944
738
|
error: void 0,
|
|
@@ -946,7 +740,7 @@ var IDLE = {
|
|
|
946
740
|
};
|
|
947
741
|
function useSubmitReview(handle) {
|
|
948
742
|
const { client, store, customerKey } = useCatalogContext();
|
|
949
|
-
const scope =
|
|
743
|
+
const scope = useMemo2(
|
|
950
744
|
() => ({
|
|
951
745
|
box: createBox(IDLE),
|
|
952
746
|
flight: null,
|
|
@@ -956,8 +750,8 @@ function useSubmitReview(handle) {
|
|
|
956
750
|
[handle, customerKey]
|
|
957
751
|
);
|
|
958
752
|
const { box } = scope;
|
|
959
|
-
const state =
|
|
960
|
-
const submit =
|
|
753
|
+
const state = useSyncExternalStore2(box.subscribe, box.get, box.get);
|
|
754
|
+
const submit = useCallback(
|
|
961
755
|
(input) => {
|
|
962
756
|
if (scope.flight !== null) return scope.flight;
|
|
963
757
|
const fail = (error) => {
|
|
@@ -992,7 +786,7 @@ function useSubmitReview(handle) {
|
|
|
992
786
|
},
|
|
993
787
|
[client, store, scope, box, handle]
|
|
994
788
|
);
|
|
995
|
-
const reset =
|
|
789
|
+
const reset = useCallback(() => {
|
|
996
790
|
scope.flight = scope.key = null;
|
|
997
791
|
box.set(IDLE);
|
|
998
792
|
}, [scope, box]);
|
|
@@ -1008,6 +802,7 @@ export {
|
|
|
1008
802
|
useCategories,
|
|
1009
803
|
useCollection,
|
|
1010
804
|
useConfig,
|
|
805
|
+
useCustomerKey,
|
|
1011
806
|
useFilterValues,
|
|
1012
807
|
useProduct,
|
|
1013
808
|
useProducts,
|