@atomic-solutions/woocommerce-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/hooks/index.cjs +1159 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +4 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +1132 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index-BVhiD2zV.d.ts +8721 -0
- package/dist/index-DKVISMaw.d.cts +8721 -0
- package/dist/index.cjs +1712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +1644 -0
- package/dist/index.js.map +1 -0
- package/dist/provider/index.cjs +428 -0
- package/dist/provider/index.cjs.map +1 -0
- package/dist/provider/index.d.cts +20 -0
- package/dist/provider/index.d.ts +20 -0
- package/dist/provider/index.js +424 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/types-wgwJGLQ-.d.cts +187 -0
- package/dist/types-wgwJGLQ-.d.ts +187 -0
- package/package.json +93 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1644 @@
|
|
|
1
|
+
import { createContext, useMemo, useState, useEffect, useRef, useCallback, useContext } from 'react';
|
|
2
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { createClient, WooCommerceApiError } from '@atomic-solutions/woocommerce-utils';
|
|
4
|
+
export { addressSchema, checkoutSchema } from '@atomic-solutions/woocommerce-utils';
|
|
5
|
+
import { useQuery, useQueryClient, useMutation, useInfiniteQuery } from '@tanstack/react-query';
|
|
6
|
+
|
|
7
|
+
// src/provider/WooCommerceProvider.tsx
|
|
8
|
+
var EventContext = createContext(null);
|
|
9
|
+
var useEventEmitter = () => {
|
|
10
|
+
return useContext(EventContext);
|
|
11
|
+
};
|
|
12
|
+
var WooCommerceContext = createContext(null);
|
|
13
|
+
var WooCommerceProvider = ({
|
|
14
|
+
client,
|
|
15
|
+
queryKeys,
|
|
16
|
+
onEvent,
|
|
17
|
+
children
|
|
18
|
+
}) => {
|
|
19
|
+
const contextValue = useMemo(() => ({ client, queryKeys }), [client, queryKeys]);
|
|
20
|
+
return /* @__PURE__ */ jsx(WooCommerceContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(EventContext.Provider, { value: onEvent ?? null, children }) });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/guardrails/storeValidator.ts
|
|
24
|
+
function normalizeStoreUrl(url) {
|
|
25
|
+
let normalized = url.trim();
|
|
26
|
+
if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) {
|
|
27
|
+
normalized = `https://${normalized}`;
|
|
28
|
+
}
|
|
29
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
30
|
+
normalized = normalized.replace(/\/wp-json\/?$/, "");
|
|
31
|
+
return `${normalized}/wp-json`;
|
|
32
|
+
}
|
|
33
|
+
function getBaseUrl(normalizedUrl) {
|
|
34
|
+
return normalizedUrl.replace(/\/wp-json\/?$/, "");
|
|
35
|
+
}
|
|
36
|
+
async function validateWooCommerceStore(url, options = {}) {
|
|
37
|
+
const { timeout = 1e4, allowLocalhost = process.env.NODE_ENV !== "production" } = options;
|
|
38
|
+
try {
|
|
39
|
+
const normalizedUrl = normalizeStoreUrl(url);
|
|
40
|
+
const baseUrl = getBaseUrl(normalizedUrl);
|
|
41
|
+
const isLocalhost = baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");
|
|
42
|
+
if (isLocalhost && !allowLocalhost) {
|
|
43
|
+
return {
|
|
44
|
+
valid: false,
|
|
45
|
+
error: "Localhost URLs are not allowed in production"
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const storeApiUrl = `${normalizedUrl}/wc/store/v1`;
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await fetch(storeApiUrl, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
headers: {
|
|
56
|
+
"User-Agent": "@atomic-solutions/react-native-woocommerce",
|
|
57
|
+
Accept: "application/json"
|
|
58
|
+
},
|
|
59
|
+
signal: controller.signal
|
|
60
|
+
});
|
|
61
|
+
} finally {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
}
|
|
64
|
+
if (response.status === 404) {
|
|
65
|
+
return {
|
|
66
|
+
valid: false,
|
|
67
|
+
error: "WooCommerce Store API not found. Ensure WooCommerce is installed and REST API is enabled."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (response.status === 401 || response.status === 403) {
|
|
71
|
+
return {
|
|
72
|
+
valid: false,
|
|
73
|
+
error: "Access denied. Check that the REST API is publicly accessible for the Store API."
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
return {
|
|
78
|
+
valid: false,
|
|
79
|
+
error: `Unexpected response: ${response.status} ${response.statusText}`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let storeName;
|
|
83
|
+
let wcVersion;
|
|
84
|
+
try {
|
|
85
|
+
const rootResponse = await fetch(normalizedUrl, {
|
|
86
|
+
method: "GET",
|
|
87
|
+
headers: {
|
|
88
|
+
Accept: "application/json"
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (rootResponse.ok) {
|
|
92
|
+
const rootData = await rootResponse.json();
|
|
93
|
+
storeName = rootData.name;
|
|
94
|
+
if (rootData.namespaces) {
|
|
95
|
+
const hasWcNamespace = rootData.namespaces.some(
|
|
96
|
+
(ns) => ns.startsWith("wc/") || ns === "wc"
|
|
97
|
+
);
|
|
98
|
+
if (!hasWcNamespace) {
|
|
99
|
+
return {
|
|
100
|
+
valid: false,
|
|
101
|
+
error: "WooCommerce REST API namespace not found. Is WooCommerce installed?"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
valid: true,
|
|
110
|
+
storeName: storeName || "WooCommerce Store",
|
|
111
|
+
storeUrl: normalizedUrl,
|
|
112
|
+
wcVersion
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof Error) {
|
|
116
|
+
if (error.name === "AbortError") {
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
error: `Connection timed out after ${timeout}ms`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (error.message.includes("Network request failed")) {
|
|
123
|
+
return {
|
|
124
|
+
valid: false,
|
|
125
|
+
error: "Unable to connect to the store. Check the URL and your internet connection."
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: error.message
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
valid: false,
|
|
135
|
+
error: "An unexpected error occurred while validating the store"
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function isValidStoreUrlFormat(url) {
|
|
140
|
+
if (!url || typeof url !== "string") {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const trimmed = url.trim();
|
|
144
|
+
if (trimmed.length === 0) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const normalized = normalizeStoreUrl(trimmed);
|
|
149
|
+
new URL(normalized);
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/guardrails/rateLimiter.ts
|
|
157
|
+
var DEFAULT_CONFIG = {
|
|
158
|
+
requestsPerSecond: 10,
|
|
159
|
+
requestsPerMinute: 60,
|
|
160
|
+
burstLimit: 20
|
|
161
|
+
};
|
|
162
|
+
var RateLimiter = class {
|
|
163
|
+
constructor(config = {}) {
|
|
164
|
+
// tokens per millisecond
|
|
165
|
+
this.queue = [];
|
|
166
|
+
this.isProcessing = false;
|
|
167
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
168
|
+
this.maxTokens = mergedConfig.burstLimit;
|
|
169
|
+
this.tokens = this.maxTokens;
|
|
170
|
+
this.refillRate = mergedConfig.requestsPerSecond / 1e3;
|
|
171
|
+
this.lastRefill = Date.now();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Refills tokens based on elapsed time
|
|
175
|
+
*/
|
|
176
|
+
refill() {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
const elapsed = now - this.lastRefill;
|
|
179
|
+
const tokensToAdd = elapsed * this.refillRate;
|
|
180
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
|
|
181
|
+
this.lastRefill = now;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Process queued requests
|
|
185
|
+
*/
|
|
186
|
+
async processQueue() {
|
|
187
|
+
if (this.isProcessing) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.isProcessing = true;
|
|
191
|
+
while (this.queue.length > 0) {
|
|
192
|
+
this.refill();
|
|
193
|
+
if (this.tokens >= 1) {
|
|
194
|
+
this.tokens -= 1;
|
|
195
|
+
const request = this.queue.shift();
|
|
196
|
+
request?.resolve();
|
|
197
|
+
} else {
|
|
198
|
+
const waitTime = Math.ceil((1 - this.tokens) / this.refillRate);
|
|
199
|
+
await this.sleep(waitTime);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.isProcessing = false;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Sleep for specified milliseconds
|
|
206
|
+
*/
|
|
207
|
+
sleep(ms) {
|
|
208
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Acquire a token before making a request
|
|
212
|
+
* Returns immediately if tokens available, otherwise queues the request
|
|
213
|
+
*
|
|
214
|
+
* @returns Promise that resolves when request can proceed
|
|
215
|
+
*/
|
|
216
|
+
acquire() {
|
|
217
|
+
return new Promise((resolve, reject) => {
|
|
218
|
+
this.refill();
|
|
219
|
+
if (this.tokens >= 1) {
|
|
220
|
+
this.tokens -= 1;
|
|
221
|
+
resolve();
|
|
222
|
+
} else {
|
|
223
|
+
this.queue.push({ resolve, reject });
|
|
224
|
+
this.processQueue();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Try to acquire a token without waiting
|
|
230
|
+
*
|
|
231
|
+
* @returns true if token was acquired, false if rate limited
|
|
232
|
+
*/
|
|
233
|
+
tryAcquire() {
|
|
234
|
+
this.refill();
|
|
235
|
+
if (this.tokens >= 1) {
|
|
236
|
+
this.tokens -= 1;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get current number of available tokens
|
|
243
|
+
*/
|
|
244
|
+
getAvailableTokens() {
|
|
245
|
+
this.refill();
|
|
246
|
+
return Math.floor(this.tokens);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get number of queued requests
|
|
250
|
+
*/
|
|
251
|
+
getQueueLength() {
|
|
252
|
+
return this.queue.length;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Clear all queued requests (rejects them)
|
|
256
|
+
*/
|
|
257
|
+
clearQueue() {
|
|
258
|
+
while (this.queue.length > 0) {
|
|
259
|
+
const request = this.queue.shift();
|
|
260
|
+
request?.reject(new Error("Rate limiter queue cleared"));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Reset the rate limiter to initial state
|
|
265
|
+
*/
|
|
266
|
+
reset() {
|
|
267
|
+
this.clearQueue();
|
|
268
|
+
this.tokens = this.maxTokens;
|
|
269
|
+
this.lastRefill = Date.now();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var globalRateLimiter = null;
|
|
273
|
+
function getGlobalRateLimiter(config) {
|
|
274
|
+
if (!globalRateLimiter) {
|
|
275
|
+
globalRateLimiter = new RateLimiter(config);
|
|
276
|
+
}
|
|
277
|
+
return globalRateLimiter;
|
|
278
|
+
}
|
|
279
|
+
function configureGlobalRateLimiter(config) {
|
|
280
|
+
if (globalRateLimiter) {
|
|
281
|
+
globalRateLimiter.clearQueue();
|
|
282
|
+
}
|
|
283
|
+
globalRateLimiter = new RateLimiter(config);
|
|
284
|
+
}
|
|
285
|
+
function resetGlobalRateLimiter() {
|
|
286
|
+
globalRateLimiter?.clearQueue();
|
|
287
|
+
globalRateLimiter = null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/provider/types.ts
|
|
291
|
+
var DEFAULT_STORE_CONFIG = {
|
|
292
|
+
features: {
|
|
293
|
+
auth: false,
|
|
294
|
+
wishlist: false,
|
|
295
|
+
reviews: true,
|
|
296
|
+
search: true,
|
|
297
|
+
categories: true
|
|
298
|
+
},
|
|
299
|
+
locale: {
|
|
300
|
+
localeTag: "en-US",
|
|
301
|
+
currency: "USD",
|
|
302
|
+
currencyPosition: "before",
|
|
303
|
+
thousandsSeparator: ",",
|
|
304
|
+
decimalSeparator: ".",
|
|
305
|
+
decimals: 2
|
|
306
|
+
},
|
|
307
|
+
guardrails: {
|
|
308
|
+
validateStore: true,
|
|
309
|
+
rateLimit: {
|
|
310
|
+
requestsPerMinute: 60,
|
|
311
|
+
requestsPerSecond: 10,
|
|
312
|
+
burstLimit: 20
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
debug: process.env.NODE_ENV !== "production"
|
|
316
|
+
};
|
|
317
|
+
var WooCommerceStoreContext = createContext(null);
|
|
318
|
+
WooCommerceStoreContext.displayName = "WooCommerceStoreContext";
|
|
319
|
+
var PACKAGE_VERSION = "0.1.0";
|
|
320
|
+
function createCartHeadersAdapter() {
|
|
321
|
+
let headers = {};
|
|
322
|
+
return {
|
|
323
|
+
get: async () => headers,
|
|
324
|
+
save: async (newHeaders) => {
|
|
325
|
+
headers = { ...headers, ...newHeaders };
|
|
326
|
+
},
|
|
327
|
+
clear: async () => {
|
|
328
|
+
headers = {};
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function WooCommerceStoreProvider({
|
|
333
|
+
config,
|
|
334
|
+
queryKeys,
|
|
335
|
+
children,
|
|
336
|
+
skipValidation = false,
|
|
337
|
+
loadingFallback = null,
|
|
338
|
+
errorFallback
|
|
339
|
+
}) {
|
|
340
|
+
const [isValidating, setIsValidating] = useState(!skipValidation);
|
|
341
|
+
const [isValidated, setIsValidated] = useState(skipValidation);
|
|
342
|
+
const [validationResult, setValidationResult] = useState(
|
|
343
|
+
skipValidation ? { valid: true, storeUrl: normalizeStoreUrl(config.storeUrl) } : void 0
|
|
344
|
+
);
|
|
345
|
+
const [validationError, setValidationError] = useState();
|
|
346
|
+
const mergedConfig = useMemo(
|
|
347
|
+
() => ({
|
|
348
|
+
...config,
|
|
349
|
+
features: { ...DEFAULT_STORE_CONFIG.features, ...config.features },
|
|
350
|
+
locale: { ...DEFAULT_STORE_CONFIG.locale, ...config.locale },
|
|
351
|
+
guardrails: { ...DEFAULT_STORE_CONFIG.guardrails, ...config.guardrails },
|
|
352
|
+
debug: config.debug ?? DEFAULT_STORE_CONFIG.debug
|
|
353
|
+
}),
|
|
354
|
+
[config]
|
|
355
|
+
);
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (mergedConfig.guardrails.rateLimit) {
|
|
358
|
+
configureGlobalRateLimiter(mergedConfig.guardrails.rateLimit);
|
|
359
|
+
}
|
|
360
|
+
}, [mergedConfig.guardrails.rateLimit]);
|
|
361
|
+
const cartHeadersAdapter = useRef(createCartHeadersAdapter());
|
|
362
|
+
const client = useMemo(() => {
|
|
363
|
+
const normalizedUrl = normalizeStoreUrl(config.storeUrl);
|
|
364
|
+
return createClient({
|
|
365
|
+
baseURL: normalizedUrl,
|
|
366
|
+
storeApiVersion: "v1",
|
|
367
|
+
cartHeaders: cartHeadersAdapter.current,
|
|
368
|
+
validationMode: "warn",
|
|
369
|
+
debug: mergedConfig.debug,
|
|
370
|
+
headers: {
|
|
371
|
+
"User-Agent": `@atomic-solutions/react-native-woocommerce/${PACKAGE_VERSION}`,
|
|
372
|
+
"X-WC-Client": "atomic-woocommerce",
|
|
373
|
+
"X-WC-Client-Version": PACKAGE_VERSION
|
|
374
|
+
},
|
|
375
|
+
onRequest: async (requestConfig) => {
|
|
376
|
+
const rateLimiter = getGlobalRateLimiter();
|
|
377
|
+
await rateLimiter.acquire();
|
|
378
|
+
return requestConfig;
|
|
379
|
+
},
|
|
380
|
+
onError: (error) => {
|
|
381
|
+
if (mergedConfig.debug) {
|
|
382
|
+
console.error("[WooCommerceStore] API Error:", error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}, [config.storeUrl, mergedConfig.debug]);
|
|
387
|
+
const validateStore = useCallback(async () => {
|
|
388
|
+
if (skipValidation) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
setIsValidating(true);
|
|
392
|
+
setValidationError(void 0);
|
|
393
|
+
try {
|
|
394
|
+
const result = await validateWooCommerceStore(config.storeUrl, {
|
|
395
|
+
allowLocalhost: process.env.NODE_ENV !== "production"
|
|
396
|
+
});
|
|
397
|
+
setValidationResult(result);
|
|
398
|
+
if (result.valid) {
|
|
399
|
+
setIsValidated(true);
|
|
400
|
+
setValidationError(void 0);
|
|
401
|
+
if (mergedConfig.debug) {
|
|
402
|
+
console.log("[WooCommerceStore] Connected to:", result.storeName);
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
setIsValidated(false);
|
|
406
|
+
setValidationError(result.error);
|
|
407
|
+
if (mergedConfig.debug) {
|
|
408
|
+
console.warn("[WooCommerceStore] Validation failed:", result.error);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
413
|
+
setValidationError(errorMessage);
|
|
414
|
+
setIsValidated(false);
|
|
415
|
+
} finally {
|
|
416
|
+
setIsValidating(false);
|
|
417
|
+
}
|
|
418
|
+
}, [config.storeUrl, skipValidation, mergedConfig.debug]);
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
if (mergedConfig.guardrails.validateStore) {
|
|
421
|
+
validateStore();
|
|
422
|
+
} else {
|
|
423
|
+
setIsValidated(true);
|
|
424
|
+
setIsValidating(false);
|
|
425
|
+
}
|
|
426
|
+
}, [validateStore, mergedConfig.guardrails.validateStore]);
|
|
427
|
+
const contextValue = useMemo(
|
|
428
|
+
() => ({
|
|
429
|
+
config: mergedConfig,
|
|
430
|
+
isValidated,
|
|
431
|
+
isValidating,
|
|
432
|
+
validationError,
|
|
433
|
+
validationResult,
|
|
434
|
+
revalidate: validateStore
|
|
435
|
+
}),
|
|
436
|
+
[mergedConfig, isValidated, isValidating, validationError, validationResult, validateStore]
|
|
437
|
+
);
|
|
438
|
+
if (isValidating && loadingFallback) {
|
|
439
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
|
|
440
|
+
}
|
|
441
|
+
if (validationError && errorFallback) {
|
|
442
|
+
return /* @__PURE__ */ jsx(Fragment, { children: errorFallback(validationError, validateStore) });
|
|
443
|
+
}
|
|
444
|
+
return /* @__PURE__ */ jsx(WooCommerceStoreContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(WooCommerceProvider, { client, queryKeys, onEvent: config.onEvent, children }) });
|
|
445
|
+
}
|
|
446
|
+
var useWooCommerceClient = () => {
|
|
447
|
+
const context = useContext(WooCommerceContext);
|
|
448
|
+
if (!context) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
"useWooCommerceClient must be used within WooCommerceProvider. Wrap your app with <WooCommerceProvider client={client} queryKeys={queryKeys}>."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return context.client;
|
|
454
|
+
};
|
|
455
|
+
var useWooCommerceQueryKeys = () => {
|
|
456
|
+
const context = useContext(WooCommerceContext);
|
|
457
|
+
if (!context) {
|
|
458
|
+
throw new Error(
|
|
459
|
+
"useWooCommerceQueryKeys must be used within WooCommerceProvider. Wrap your app with <WooCommerceProvider client={client} queryKeys={queryKeys}>."
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return context.queryKeys;
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// src/constants/stale-times.ts
|
|
466
|
+
var STALE_TIME_SHORT = 60 * 1e3;
|
|
467
|
+
var STALE_TIME_LONG = 60 * 60 * 1e3;
|
|
468
|
+
var STALE_TIME_NONE = 0;
|
|
469
|
+
var StaleTimes = {
|
|
470
|
+
/** Cart data - always fresh */
|
|
471
|
+
cart: STALE_TIME_NONE,
|
|
472
|
+
/** Checkout data - always fresh */
|
|
473
|
+
checkout: STALE_TIME_NONE,
|
|
474
|
+
/** Product list - 1 minute */
|
|
475
|
+
products: STALE_TIME_SHORT,
|
|
476
|
+
/** Product detail - 1 minute */
|
|
477
|
+
product: STALE_TIME_SHORT,
|
|
478
|
+
/** Product categories - always fresh */
|
|
479
|
+
categories: STALE_TIME_NONE,
|
|
480
|
+
/** Orders - always fresh */
|
|
481
|
+
orders: STALE_TIME_NONE,
|
|
482
|
+
/** Default for infinite queries - 1 hour */
|
|
483
|
+
infinite: STALE_TIME_LONG
|
|
484
|
+
};
|
|
485
|
+
var useInfiniteWooQuery = ({
|
|
486
|
+
queryKey,
|
|
487
|
+
queryFn,
|
|
488
|
+
params,
|
|
489
|
+
perPage = 10,
|
|
490
|
+
staleTime = StaleTimes.infinite,
|
|
491
|
+
enabled
|
|
492
|
+
}) => {
|
|
493
|
+
const query = useInfiniteQuery({
|
|
494
|
+
queryKey,
|
|
495
|
+
queryFn: ({ pageParam }) => queryFn({
|
|
496
|
+
per_page: perPage,
|
|
497
|
+
...params,
|
|
498
|
+
page: pageParam
|
|
499
|
+
}),
|
|
500
|
+
initialPageParam: 1,
|
|
501
|
+
getNextPageParam: (lastPage) => lastPage.pagination.nextPage,
|
|
502
|
+
getPreviousPageParam: (firstPage) => firstPage.pagination.prevPage,
|
|
503
|
+
staleTime,
|
|
504
|
+
enabled
|
|
505
|
+
});
|
|
506
|
+
const flatData = query.data?.pages.flatMap((page) => page.data) ?? [];
|
|
507
|
+
return {
|
|
508
|
+
...query,
|
|
509
|
+
data: flatData
|
|
510
|
+
// Override data with flattened array
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/hooks/products.ts
|
|
515
|
+
var useProducts = (params) => {
|
|
516
|
+
const client = useWooCommerceClient();
|
|
517
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
518
|
+
return useQuery({
|
|
519
|
+
queryKey: queryKeys.products.list(params),
|
|
520
|
+
queryFn: () => client.products.list(params),
|
|
521
|
+
staleTime: StaleTimes.products
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
var useInfiniteProducts = (params) => {
|
|
525
|
+
const client = useWooCommerceClient();
|
|
526
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
527
|
+
return useInfiniteWooQuery({
|
|
528
|
+
queryKey: queryKeys.products.list(params),
|
|
529
|
+
queryFn: (p) => client.products.list({ ...p, page: p.page ?? 1 }),
|
|
530
|
+
params,
|
|
531
|
+
perPage: params?.per_page ?? 10
|
|
532
|
+
});
|
|
533
|
+
};
|
|
534
|
+
var useProduct = (id) => {
|
|
535
|
+
const client = useWooCommerceClient();
|
|
536
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
537
|
+
return useQuery({
|
|
538
|
+
queryKey: queryKeys.products.detail(id),
|
|
539
|
+
queryFn: () => client.products.get(id),
|
|
540
|
+
staleTime: StaleTimes.product
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
var useProductCategories = () => {
|
|
544
|
+
const client = useWooCommerceClient();
|
|
545
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
546
|
+
return useQuery({
|
|
547
|
+
queryKey: queryKeys.products.categories(),
|
|
548
|
+
queryFn: () => client.products.categories(),
|
|
549
|
+
staleTime: StaleTimes.categories
|
|
550
|
+
});
|
|
551
|
+
};
|
|
552
|
+
var groupBy = (fn, list) => {
|
|
553
|
+
return list.reduce(
|
|
554
|
+
(acc, item) => {
|
|
555
|
+
const key = fn(item);
|
|
556
|
+
if (!acc[key]) {
|
|
557
|
+
acc[key] = [];
|
|
558
|
+
}
|
|
559
|
+
acc[key].push(item);
|
|
560
|
+
return acc;
|
|
561
|
+
},
|
|
562
|
+
{}
|
|
563
|
+
);
|
|
564
|
+
};
|
|
565
|
+
var processCategoriesToTree = (data) => {
|
|
566
|
+
const rootCategories = data?.filter((item) => item.parent === 0);
|
|
567
|
+
const indexedData = groupBy((item) => String(item.parent), data ?? []);
|
|
568
|
+
const processCategory = (item) => ({
|
|
569
|
+
...item,
|
|
570
|
+
subCategories: indexedData[item.id]?.map(processCategory)
|
|
571
|
+
});
|
|
572
|
+
return rootCategories?.map(processCategory) ?? [];
|
|
573
|
+
};
|
|
574
|
+
var useProductCategoryTree = () => {
|
|
575
|
+
const query = useProductCategories();
|
|
576
|
+
const flatData = query.data ?? [];
|
|
577
|
+
const treeData = processCategoriesToTree(flatData);
|
|
578
|
+
const findById = (id) => {
|
|
579
|
+
if (!id) return;
|
|
580
|
+
const numId = typeof id === "string" ? parseInt(id, 10) : id;
|
|
581
|
+
return flatData.find((item) => item.id === numId);
|
|
582
|
+
};
|
|
583
|
+
return {
|
|
584
|
+
...query,
|
|
585
|
+
data: flatData,
|
|
586
|
+
treeData,
|
|
587
|
+
findById
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/events/types.ts
|
|
592
|
+
function createSuccessEvent(type, data) {
|
|
593
|
+
return { type, status: "success", data };
|
|
594
|
+
}
|
|
595
|
+
function createErrorEvent(type, error) {
|
|
596
|
+
return { type, status: "error", error };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/hooks/cart.ts
|
|
600
|
+
var useCart = () => {
|
|
601
|
+
const client = useWooCommerceClient();
|
|
602
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
603
|
+
const query = useQuery({
|
|
604
|
+
queryKey: queryKeys.cart.details(),
|
|
605
|
+
queryFn: () => client.cart.get(),
|
|
606
|
+
staleTime: StaleTimes.cart
|
|
607
|
+
});
|
|
608
|
+
const cart = query.data;
|
|
609
|
+
const isInCart = (productId) => {
|
|
610
|
+
return cart?.items.some((item) => item.id === productId) ?? false;
|
|
611
|
+
};
|
|
612
|
+
const getCartItem = (productId) => {
|
|
613
|
+
return cart?.items.find((item) => item.id === productId);
|
|
614
|
+
};
|
|
615
|
+
const getQuantity = (productId) => {
|
|
616
|
+
return getCartItem(productId)?.quantity ?? 0;
|
|
617
|
+
};
|
|
618
|
+
return {
|
|
619
|
+
...query,
|
|
620
|
+
isInCart,
|
|
621
|
+
getCartItem,
|
|
622
|
+
getQuantity
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
var useAddToCart = () => {
|
|
626
|
+
const client = useWooCommerceClient();
|
|
627
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
628
|
+
const queryClient = useQueryClient();
|
|
629
|
+
const emitEvent = useEventEmitter();
|
|
630
|
+
return useMutation({
|
|
631
|
+
mutationKey: queryKeys.cart.mutations.addItem(),
|
|
632
|
+
mutationFn: (input) => client.cart.addItem(input),
|
|
633
|
+
onSuccess: (cart, input) => {
|
|
634
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
635
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
636
|
+
const addedItem = cart.items.find((item) => item.id === input.id);
|
|
637
|
+
if (emitEvent && addedItem) {
|
|
638
|
+
emitEvent(createSuccessEvent("cart:item_added", { item: addedItem, cart }));
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
onError: (error) => {
|
|
642
|
+
if (emitEvent) {
|
|
643
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
644
|
+
code: "unknown_error",
|
|
645
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
646
|
+
statusCode: 500,
|
|
647
|
+
url: "",
|
|
648
|
+
method: "POST",
|
|
649
|
+
originalCode: "unknown_error"
|
|
650
|
+
});
|
|
651
|
+
emitEvent(createErrorEvent("cart:item_added", wooError));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
var useUpdateCartItem = () => {
|
|
657
|
+
const client = useWooCommerceClient();
|
|
658
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
659
|
+
const queryClient = useQueryClient();
|
|
660
|
+
const emitEvent = useEventEmitter();
|
|
661
|
+
return useMutation({
|
|
662
|
+
mutationKey: queryKeys.cart.mutations.updateItem(),
|
|
663
|
+
mutationFn: (input) => client.cart.updateItem(input),
|
|
664
|
+
onSuccess: (cart, input) => {
|
|
665
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
666
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
667
|
+
const updatedItem = cart.items.find((item) => item.key === input.key);
|
|
668
|
+
if (emitEvent && updatedItem) {
|
|
669
|
+
emitEvent(createSuccessEvent("cart:item_updated", { item: updatedItem, cart }));
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
onError: (error) => {
|
|
673
|
+
if (emitEvent) {
|
|
674
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
675
|
+
code: "unknown_error",
|
|
676
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
677
|
+
statusCode: 500,
|
|
678
|
+
url: "",
|
|
679
|
+
method: "POST",
|
|
680
|
+
originalCode: "unknown_error"
|
|
681
|
+
});
|
|
682
|
+
emitEvent(createErrorEvent("cart:item_updated", wooError));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
};
|
|
687
|
+
var useRemoveFromCart = () => {
|
|
688
|
+
const client = useWooCommerceClient();
|
|
689
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
690
|
+
const queryClient = useQueryClient();
|
|
691
|
+
const emitEvent = useEventEmitter();
|
|
692
|
+
return useMutation({
|
|
693
|
+
mutationKey: queryKeys.cart.mutations.removeItem(),
|
|
694
|
+
mutationFn: (input) => client.cart.removeItem(input),
|
|
695
|
+
onSuccess: (cart, input) => {
|
|
696
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
697
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
698
|
+
if (emitEvent) {
|
|
699
|
+
emitEvent(createSuccessEvent("cart:item_removed", { itemKey: input.key, cart }));
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
onError: (error) => {
|
|
703
|
+
if (emitEvent) {
|
|
704
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
705
|
+
code: "unknown_error",
|
|
706
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
707
|
+
statusCode: 500,
|
|
708
|
+
url: "",
|
|
709
|
+
method: "POST",
|
|
710
|
+
originalCode: "unknown_error"
|
|
711
|
+
});
|
|
712
|
+
emitEvent(createErrorEvent("cart:item_removed", wooError));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
};
|
|
717
|
+
var useApplyCoupon = () => {
|
|
718
|
+
const client = useWooCommerceClient();
|
|
719
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
720
|
+
const queryClient = useQueryClient();
|
|
721
|
+
const emitEvent = useEventEmitter();
|
|
722
|
+
return useMutation({
|
|
723
|
+
mutationKey: queryKeys.cart.mutations.applyCoupon(),
|
|
724
|
+
mutationFn: (input) => client.cart.applyCoupon(input),
|
|
725
|
+
onSuccess: (cart, input) => {
|
|
726
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
727
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
728
|
+
if (emitEvent) {
|
|
729
|
+
emitEvent(createSuccessEvent("cart:coupon_applied", { code: input.code, cart }));
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
onError: (error) => {
|
|
733
|
+
if (emitEvent) {
|
|
734
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
735
|
+
code: "unknown_error",
|
|
736
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
737
|
+
statusCode: 500,
|
|
738
|
+
url: "",
|
|
739
|
+
method: "POST",
|
|
740
|
+
originalCode: "unknown_error"
|
|
741
|
+
});
|
|
742
|
+
emitEvent(createErrorEvent("cart:coupon_applied", wooError));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
};
|
|
747
|
+
var useRemoveCoupon = () => {
|
|
748
|
+
const client = useWooCommerceClient();
|
|
749
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
750
|
+
const queryClient = useQueryClient();
|
|
751
|
+
const emitEvent = useEventEmitter();
|
|
752
|
+
return useMutation({
|
|
753
|
+
mutationKey: queryKeys.cart.mutations.removeCoupon(),
|
|
754
|
+
mutationFn: (input) => client.cart.removeCoupon(input),
|
|
755
|
+
onSuccess: (cart, input) => {
|
|
756
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
757
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
758
|
+
if (emitEvent) {
|
|
759
|
+
emitEvent(createSuccessEvent("cart:coupon_removed", { code: input.code, cart }));
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
onError: (error) => {
|
|
763
|
+
if (emitEvent) {
|
|
764
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
765
|
+
code: "unknown_error",
|
|
766
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
767
|
+
statusCode: 500,
|
|
768
|
+
url: "",
|
|
769
|
+
method: "POST",
|
|
770
|
+
originalCode: "unknown_error"
|
|
771
|
+
});
|
|
772
|
+
emitEvent(createErrorEvent("cart:coupon_removed", wooError));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
};
|
|
777
|
+
var useUpdateCustomer = () => {
|
|
778
|
+
const client = useWooCommerceClient();
|
|
779
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
780
|
+
const queryClient = useQueryClient();
|
|
781
|
+
const emitEvent = useEventEmitter();
|
|
782
|
+
return useMutation({
|
|
783
|
+
mutationKey: queryKeys.cart.mutations.updateCustomer(),
|
|
784
|
+
mutationFn: (input) => client.cart.updateCustomer(input),
|
|
785
|
+
onSuccess: (cart) => {
|
|
786
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
787
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
788
|
+
if (emitEvent) {
|
|
789
|
+
emitEvent(createSuccessEvent("cart:customer_updated", { cart }));
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
onError: (error) => {
|
|
793
|
+
if (emitEvent) {
|
|
794
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
795
|
+
code: "unknown_error",
|
|
796
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
797
|
+
statusCode: 500,
|
|
798
|
+
url: "",
|
|
799
|
+
method: "POST",
|
|
800
|
+
originalCode: "unknown_error"
|
|
801
|
+
});
|
|
802
|
+
emitEvent(createErrorEvent("cart:customer_updated", wooError));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
var useSelectShippingRate = () => {
|
|
808
|
+
const client = useWooCommerceClient();
|
|
809
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
810
|
+
const queryClient = useQueryClient();
|
|
811
|
+
const emitEvent = useEventEmitter();
|
|
812
|
+
return useMutation({
|
|
813
|
+
mutationKey: queryKeys.cart.mutations.selectShippingRate(),
|
|
814
|
+
mutationFn: (input) => client.cart.selectShippingRate(input),
|
|
815
|
+
onSuccess: (cart) => {
|
|
816
|
+
queryClient.setQueryData(queryKeys.cart.details(), cart);
|
|
817
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
818
|
+
if (emitEvent) {
|
|
819
|
+
emitEvent(createSuccessEvent("cart:shipping_selected", { cart }));
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
onError: (error) => {
|
|
823
|
+
if (emitEvent) {
|
|
824
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
825
|
+
code: "unknown_error",
|
|
826
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
827
|
+
statusCode: 500,
|
|
828
|
+
url: "",
|
|
829
|
+
method: "POST",
|
|
830
|
+
originalCode: "unknown_error"
|
|
831
|
+
});
|
|
832
|
+
emitEvent(createErrorEvent("cart:shipping_selected", wooError));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
var useCheckout = () => {
|
|
838
|
+
const client = useWooCommerceClient();
|
|
839
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
840
|
+
return useQuery({
|
|
841
|
+
queryKey: queryKeys.checkout.details(),
|
|
842
|
+
queryFn: () => client.checkout.get(),
|
|
843
|
+
staleTime: StaleTimes.checkout
|
|
844
|
+
});
|
|
845
|
+
};
|
|
846
|
+
var useProcessCheckout = () => {
|
|
847
|
+
const client = useWooCommerceClient();
|
|
848
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
849
|
+
const queryClient = useQueryClient();
|
|
850
|
+
const emitEvent = useEventEmitter();
|
|
851
|
+
return useMutation({
|
|
852
|
+
mutationKey: queryKeys.checkout.mutations.process(),
|
|
853
|
+
mutationFn: (input) => client.checkout.process(input),
|
|
854
|
+
onSuccess: (checkout) => {
|
|
855
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.cart.all });
|
|
856
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.checkout.all });
|
|
857
|
+
if (emitEvent) {
|
|
858
|
+
emitEvent(createSuccessEvent("checkout:processed", { checkout }));
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
onError: (error) => {
|
|
862
|
+
if (emitEvent) {
|
|
863
|
+
const wooError = error instanceof WooCommerceApiError ? error : new WooCommerceApiError({
|
|
864
|
+
code: "unknown_error",
|
|
865
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
866
|
+
statusCode: 500,
|
|
867
|
+
url: "",
|
|
868
|
+
method: "POST",
|
|
869
|
+
originalCode: "unknown_error"
|
|
870
|
+
});
|
|
871
|
+
emitEvent(createErrorEvent("checkout:processed", wooError));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
};
|
|
876
|
+
var useOrder = (input) => {
|
|
877
|
+
const client = useWooCommerceClient();
|
|
878
|
+
const queryKeys = useWooCommerceQueryKeys();
|
|
879
|
+
return useQuery({
|
|
880
|
+
queryKey: queryKeys.orders.detail(input.id, input.billing_email, input.key),
|
|
881
|
+
queryFn: () => client.orders.get(input),
|
|
882
|
+
staleTime: StaleTimes.orders
|
|
883
|
+
});
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// src/hooks/queryKeys.ts
|
|
887
|
+
var createQueryKeys = (prefix = ["woocommerce"]) => ({
|
|
888
|
+
// All keys start with prefix
|
|
889
|
+
all: prefix,
|
|
890
|
+
// Products
|
|
891
|
+
products: {
|
|
892
|
+
all: [...prefix, "products"],
|
|
893
|
+
lists: () => [...prefix, "products", "list"],
|
|
894
|
+
list: (params) => [...prefix, "products", "list", params],
|
|
895
|
+
details: () => [...prefix, "products", "detail"],
|
|
896
|
+
detail: (id) => [...prefix, "products", "detail", id],
|
|
897
|
+
categories: () => [...prefix, "products", "categories"]
|
|
898
|
+
},
|
|
899
|
+
// Cart
|
|
900
|
+
cart: {
|
|
901
|
+
all: [...prefix, "cart"],
|
|
902
|
+
details: () => [...prefix, "cart", "details"],
|
|
903
|
+
// Mutation keys
|
|
904
|
+
mutations: {
|
|
905
|
+
addItem: () => [...prefix, "cart", "mutation", "addItem"],
|
|
906
|
+
updateItem: () => [...prefix, "cart", "mutation", "updateItem"],
|
|
907
|
+
removeItem: () => [...prefix, "cart", "mutation", "removeItem"],
|
|
908
|
+
applyCoupon: () => [...prefix, "cart", "mutation", "applyCoupon"],
|
|
909
|
+
removeCoupon: () => [...prefix, "cart", "mutation", "removeCoupon"],
|
|
910
|
+
updateCustomer: () => [...prefix, "cart", "mutation", "updateCustomer"],
|
|
911
|
+
selectShippingRate: () => [...prefix, "cart", "mutation", "selectShippingRate"]
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
// Checkout
|
|
915
|
+
checkout: {
|
|
916
|
+
all: [...prefix, "checkout"],
|
|
917
|
+
details: () => [...prefix, "checkout", "details"],
|
|
918
|
+
// Mutation keys
|
|
919
|
+
mutations: {
|
|
920
|
+
process: () => [...prefix, "checkout", "mutation", "process"]
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
// Orders
|
|
924
|
+
orders: {
|
|
925
|
+
all: [...prefix, "orders"],
|
|
926
|
+
lists: () => [...prefix, "orders", "list"],
|
|
927
|
+
list: (params) => [...prefix, "orders", "list", params],
|
|
928
|
+
details: () => [...prefix, "orders", "detail"],
|
|
929
|
+
detail: (id, billing_email, key) => [...prefix, "orders", "detail", id, billing_email, key].filter(Boolean)
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
function useStore() {
|
|
933
|
+
const context = useContext(WooCommerceStoreContext);
|
|
934
|
+
if (!context) {
|
|
935
|
+
throw new Error("useStore must be used within a WooCommerceStoreProvider");
|
|
936
|
+
}
|
|
937
|
+
return context;
|
|
938
|
+
}
|
|
939
|
+
function useStoreFeatures() {
|
|
940
|
+
const { config } = useStore();
|
|
941
|
+
return config.features;
|
|
942
|
+
}
|
|
943
|
+
function useStoreLocale() {
|
|
944
|
+
const { config } = useStore();
|
|
945
|
+
return config.locale;
|
|
946
|
+
}
|
|
947
|
+
var DEFAULT_THEME = {
|
|
948
|
+
primary: "#007AFF",
|
|
949
|
+
// iOS blue
|
|
950
|
+
secondary: "#5856D6",
|
|
951
|
+
// iOS purple
|
|
952
|
+
mode: "auto",
|
|
953
|
+
fonts: {
|
|
954
|
+
heading: void 0,
|
|
955
|
+
body: void 0
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
function useStoreTheme(options = {}) {
|
|
959
|
+
const { externalTheme } = options;
|
|
960
|
+
const storeContext = useContext(WooCommerceStoreContext);
|
|
961
|
+
const storeTheme = storeContext?.config?.theme;
|
|
962
|
+
const theme = useMemo(() => {
|
|
963
|
+
return {
|
|
964
|
+
primary: externalTheme?.primary ?? storeTheme?.primary ?? DEFAULT_THEME.primary,
|
|
965
|
+
secondary: externalTheme?.secondary ?? storeTheme?.secondary ?? DEFAULT_THEME.secondary,
|
|
966
|
+
mode: externalTheme?.mode ?? storeTheme?.mode ?? DEFAULT_THEME.mode,
|
|
967
|
+
fonts: {
|
|
968
|
+
heading: externalTheme?.fonts?.heading ?? storeTheme?.fonts?.heading ?? DEFAULT_THEME.fonts.heading,
|
|
969
|
+
body: externalTheme?.fonts?.body ?? storeTheme?.fonts?.body ?? DEFAULT_THEME.fonts.body
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
}, [externalTheme, storeTheme]);
|
|
973
|
+
return {
|
|
974
|
+
theme,
|
|
975
|
+
storeTheme,
|
|
976
|
+
hasExternalTheme: !!externalTheme
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/utils/image.ts
|
|
981
|
+
var SIZE_SUFFIXES = {
|
|
982
|
+
thumbnail: "-150x150",
|
|
983
|
+
medium: "-300x300",
|
|
984
|
+
large: "-1024x1024",
|
|
985
|
+
full: ""
|
|
986
|
+
};
|
|
987
|
+
function getPrimaryImage(images) {
|
|
988
|
+
if (!images || images.length === 0) {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
return images[0];
|
|
992
|
+
}
|
|
993
|
+
function getImageUrl(images, size = "full", fallback) {
|
|
994
|
+
const primary = getPrimaryImage(images);
|
|
995
|
+
if (!primary) {
|
|
996
|
+
return fallback;
|
|
997
|
+
}
|
|
998
|
+
return getSizedImageUrl(primary.src, size);
|
|
999
|
+
}
|
|
1000
|
+
function getSizedImageUrl(url, size = "full") {
|
|
1001
|
+
if (!url || size === "full") {
|
|
1002
|
+
return url;
|
|
1003
|
+
}
|
|
1004
|
+
const hasSize = Object.values(SIZE_SUFFIXES).some((suffix2) => suffix2 && url.includes(suffix2));
|
|
1005
|
+
if (hasSize) {
|
|
1006
|
+
let result = url;
|
|
1007
|
+
Object.values(SIZE_SUFFIXES).forEach((suffix2) => {
|
|
1008
|
+
if (suffix2) {
|
|
1009
|
+
result = result.replace(suffix2, SIZE_SUFFIXES[size]);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
return result;
|
|
1013
|
+
}
|
|
1014
|
+
const suffix = SIZE_SUFFIXES[size];
|
|
1015
|
+
const lastDot = url.lastIndexOf(".");
|
|
1016
|
+
if (lastDot === -1) {
|
|
1017
|
+
return url;
|
|
1018
|
+
}
|
|
1019
|
+
return `${url.slice(0, lastDot)}${suffix}${url.slice(lastDot)}`;
|
|
1020
|
+
}
|
|
1021
|
+
function getAllImageUrls(images, size = "full") {
|
|
1022
|
+
if (!images || images.length === 0) {
|
|
1023
|
+
return [];
|
|
1024
|
+
}
|
|
1025
|
+
return images.map((img) => getSizedImageUrl(img.src, size));
|
|
1026
|
+
}
|
|
1027
|
+
var PLACEHOLDER_IMAGE_URL = "https://via.placeholder.com/300x300.png?text=No+Image";
|
|
1028
|
+
function getImageUrlOrPlaceholder(images, size = "medium") {
|
|
1029
|
+
return getImageUrl(images, size, PLACEHOLDER_IMAGE_URL) ?? PLACEHOLDER_IMAGE_URL;
|
|
1030
|
+
}
|
|
1031
|
+
async function preloadImage(_url) {
|
|
1032
|
+
return Promise.resolve();
|
|
1033
|
+
}
|
|
1034
|
+
async function preloadImages(urls) {
|
|
1035
|
+
await Promise.all(urls.map(preloadImage));
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/utils/price.ts
|
|
1039
|
+
var DEFAULT_LOCALE = {
|
|
1040
|
+
localeTag: "en-US",
|
|
1041
|
+
currency: "USD",
|
|
1042
|
+
currencyPosition: "before",
|
|
1043
|
+
thousandsSeparator: ",",
|
|
1044
|
+
decimalSeparator: ".",
|
|
1045
|
+
decimals: 2
|
|
1046
|
+
};
|
|
1047
|
+
var CURRENCY_SYMBOLS = {
|
|
1048
|
+
USD: "$",
|
|
1049
|
+
EUR: "\u20AC",
|
|
1050
|
+
GBP: "\xA3",
|
|
1051
|
+
CAD: "CA$",
|
|
1052
|
+
AUD: "A$",
|
|
1053
|
+
JPY: "\xA5",
|
|
1054
|
+
CNY: "\xA5",
|
|
1055
|
+
INR: "\u20B9",
|
|
1056
|
+
BRL: "R$",
|
|
1057
|
+
MXN: "MX$",
|
|
1058
|
+
RUB: "\u20BD",
|
|
1059
|
+
KRW: "\u20A9",
|
|
1060
|
+
CHF: "CHF",
|
|
1061
|
+
SEK: "kr",
|
|
1062
|
+
NOK: "kr",
|
|
1063
|
+
DKK: "kr",
|
|
1064
|
+
PLN: "z\u0142",
|
|
1065
|
+
TRY: "\u20BA",
|
|
1066
|
+
ZAR: "R",
|
|
1067
|
+
NZD: "NZ$",
|
|
1068
|
+
SGD: "S$",
|
|
1069
|
+
HKD: "HK$",
|
|
1070
|
+
THB: "\u0E3F",
|
|
1071
|
+
PHP: "\u20B1",
|
|
1072
|
+
// Balkans
|
|
1073
|
+
BAM: "KM",
|
|
1074
|
+
RSD: "din",
|
|
1075
|
+
HRK: "kn",
|
|
1076
|
+
MKD: "\u0434\u0435\u043D",
|
|
1077
|
+
BGN: "\u043B\u0432",
|
|
1078
|
+
RON: "lei"
|
|
1079
|
+
};
|
|
1080
|
+
function getCurrencySymbol(currencyCode) {
|
|
1081
|
+
return CURRENCY_SYMBOLS[currencyCode.toUpperCase()] ?? currencyCode;
|
|
1082
|
+
}
|
|
1083
|
+
function formatNumber(value, locale) {
|
|
1084
|
+
const fixed = value.toFixed(locale.decimals);
|
|
1085
|
+
const [integerPart, decimalPart] = fixed.split(".");
|
|
1086
|
+
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, locale.thousandsSeparator);
|
|
1087
|
+
if (locale.decimals > 0 && decimalPart) {
|
|
1088
|
+
return `${formattedInteger}${locale.decimalSeparator}${decimalPart}`;
|
|
1089
|
+
}
|
|
1090
|
+
return formattedInteger;
|
|
1091
|
+
}
|
|
1092
|
+
function formatPrice(price, locale = {}) {
|
|
1093
|
+
const mergedLocale = { ...DEFAULT_LOCALE, ...locale };
|
|
1094
|
+
const numericPrice = typeof price === "string" ? parseFloat(price) : price;
|
|
1095
|
+
if (isNaN(numericPrice)) {
|
|
1096
|
+
return "";
|
|
1097
|
+
}
|
|
1098
|
+
const symbol = getCurrencySymbol(mergedLocale.currency);
|
|
1099
|
+
const formattedNumber = formatNumber(numericPrice, mergedLocale);
|
|
1100
|
+
if (mergedLocale.currencyPosition === "after") {
|
|
1101
|
+
return `${formattedNumber}${symbol}`;
|
|
1102
|
+
}
|
|
1103
|
+
return `${symbol}${formattedNumber}`;
|
|
1104
|
+
}
|
|
1105
|
+
function formatPriceRange(minPrice, maxPrice, locale = {}) {
|
|
1106
|
+
const min = typeof minPrice === "string" ? parseFloat(minPrice) : minPrice;
|
|
1107
|
+
const max = typeof maxPrice === "string" ? parseFloat(maxPrice) : maxPrice;
|
|
1108
|
+
if (isNaN(min) || isNaN(max)) {
|
|
1109
|
+
return "";
|
|
1110
|
+
}
|
|
1111
|
+
if (min === max) {
|
|
1112
|
+
return formatPrice(min, locale);
|
|
1113
|
+
}
|
|
1114
|
+
return `${formatPrice(min, locale)} - ${formatPrice(max, locale)}`;
|
|
1115
|
+
}
|
|
1116
|
+
function calculateDiscount(regularPrice, salePrice) {
|
|
1117
|
+
const regular = typeof regularPrice === "string" ? parseFloat(regularPrice) : regularPrice;
|
|
1118
|
+
const sale = typeof salePrice === "string" ? parseFloat(salePrice) : salePrice;
|
|
1119
|
+
if (isNaN(regular) || isNaN(sale) || regular <= 0 || sale >= regular) {
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
return Math.round((regular - sale) / regular * 100);
|
|
1123
|
+
}
|
|
1124
|
+
function formatStoreApiPrice(priceInMinorUnits, currencyCode, locale = {}) {
|
|
1125
|
+
if (!priceInMinorUnits || !currencyCode) {
|
|
1126
|
+
return "-";
|
|
1127
|
+
}
|
|
1128
|
+
const cents = parseInt(priceInMinorUnits, 10);
|
|
1129
|
+
if (isNaN(cents)) {
|
|
1130
|
+
return "-";
|
|
1131
|
+
}
|
|
1132
|
+
const decimalPrice = cents / 100;
|
|
1133
|
+
return formatPrice(decimalPrice, { ...locale, currency: currencyCode });
|
|
1134
|
+
}
|
|
1135
|
+
function formatDiscountBadge(regularPrice, salePrice) {
|
|
1136
|
+
const discount = calculateDiscount(regularPrice, salePrice);
|
|
1137
|
+
return discount ? `-${discount}%` : null;
|
|
1138
|
+
}
|
|
1139
|
+
function formatStoreApiPriceIntl(priceInMinorUnits, currencyCode, localeTag = "en-US") {
|
|
1140
|
+
if (!priceInMinorUnits || !currencyCode) {
|
|
1141
|
+
return "-";
|
|
1142
|
+
}
|
|
1143
|
+
const cents = parseInt(priceInMinorUnits, 10);
|
|
1144
|
+
if (isNaN(cents)) {
|
|
1145
|
+
return "-";
|
|
1146
|
+
}
|
|
1147
|
+
const decimalPrice = cents / 100;
|
|
1148
|
+
try {
|
|
1149
|
+
const formatter = new Intl.NumberFormat(localeTag, {
|
|
1150
|
+
style: "currency",
|
|
1151
|
+
currency: currencyCode
|
|
1152
|
+
});
|
|
1153
|
+
return formatter.format(decimalPrice);
|
|
1154
|
+
} catch {
|
|
1155
|
+
return formatPrice(decimalPrice, { currency: currencyCode });
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function parseWooCommercePrice(priceString) {
|
|
1159
|
+
if (!priceString || typeof priceString !== "string") {
|
|
1160
|
+
return NaN;
|
|
1161
|
+
}
|
|
1162
|
+
const cleaned = priceString.replace(/[^\d.,-]/g, "").trim();
|
|
1163
|
+
if (cleaned.includes(",") && cleaned.includes(".")) {
|
|
1164
|
+
const lastComma = cleaned.lastIndexOf(",");
|
|
1165
|
+
const lastDot = cleaned.lastIndexOf(".");
|
|
1166
|
+
if (lastComma > lastDot) {
|
|
1167
|
+
return parseFloat(cleaned.replace(/\./g, "").replace(",", "."));
|
|
1168
|
+
} else {
|
|
1169
|
+
return parseFloat(cleaned.replace(/,/g, ""));
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (cleaned.includes(",") && !cleaned.includes(".")) {
|
|
1173
|
+
const commaPos = cleaned.lastIndexOf(",");
|
|
1174
|
+
const afterComma = cleaned.length - commaPos - 1;
|
|
1175
|
+
if (afterComma <= 2) {
|
|
1176
|
+
return parseFloat(cleaned.replace(",", "."));
|
|
1177
|
+
}
|
|
1178
|
+
return parseFloat(cleaned.replace(/,/g, ""));
|
|
1179
|
+
}
|
|
1180
|
+
return parseFloat(cleaned.replace(/,/g, ""));
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/hooks/useProductsFeed.ts
|
|
1184
|
+
function useProductsFeed(options = {}) {
|
|
1185
|
+
const { params, imageSize = "medium" } = options;
|
|
1186
|
+
const storeContext = useContext(WooCommerceStoreContext);
|
|
1187
|
+
const storeConfig = storeContext?.config;
|
|
1188
|
+
const localeTag = options.localeTag ?? storeConfig?.locale?.localeTag ?? DEFAULT_STORE_CONFIG.locale.localeTag;
|
|
1189
|
+
const fallbackImageUrl = options.fallbackImageUrl ?? storeConfig?.fallbackImageUrl;
|
|
1190
|
+
const imageUrlTransformer = storeConfig?.imageUrlTransformer;
|
|
1191
|
+
const {
|
|
1192
|
+
data: productsData,
|
|
1193
|
+
isLoading,
|
|
1194
|
+
isFetchingNextPage,
|
|
1195
|
+
error,
|
|
1196
|
+
hasNextPage = false,
|
|
1197
|
+
fetchNextPage,
|
|
1198
|
+
refetch,
|
|
1199
|
+
isRefetching
|
|
1200
|
+
} = useInfiniteProducts(params);
|
|
1201
|
+
const { treeData: categoryTree = [], findById } = useProductCategoryTree();
|
|
1202
|
+
const getProcessedImageUrl = useCallback(
|
|
1203
|
+
(images) => {
|
|
1204
|
+
const baseUrl = getImageUrl(images, imageSize, fallbackImageUrl);
|
|
1205
|
+
if (baseUrl && imageUrlTransformer) {
|
|
1206
|
+
return imageUrlTransformer(baseUrl, imageSize);
|
|
1207
|
+
}
|
|
1208
|
+
return baseUrl;
|
|
1209
|
+
},
|
|
1210
|
+
[imageSize, fallbackImageUrl, imageUrlTransformer]
|
|
1211
|
+
);
|
|
1212
|
+
const products = useMemo(() => {
|
|
1213
|
+
if (!productsData) return [];
|
|
1214
|
+
const productList = Array.isArray(productsData) ? productsData : [];
|
|
1215
|
+
return productList.map((product) => {
|
|
1216
|
+
const currencyCode = product.prices?.currency_code;
|
|
1217
|
+
return {
|
|
1218
|
+
id: product.id,
|
|
1219
|
+
name: product.name,
|
|
1220
|
+
slug: product.slug,
|
|
1221
|
+
permalink: product.permalink,
|
|
1222
|
+
imageUrl: getProcessedImageUrl(product.images),
|
|
1223
|
+
price: formatStoreApiPriceIntl(product.prices?.price, currencyCode, localeTag),
|
|
1224
|
+
regularPrice: formatStoreApiPriceIntl(
|
|
1225
|
+
product.prices?.regular_price,
|
|
1226
|
+
currencyCode,
|
|
1227
|
+
localeTag
|
|
1228
|
+
),
|
|
1229
|
+
isOnSale: product.on_sale,
|
|
1230
|
+
canAddToCart: product.is_purchasable && product.is_in_stock,
|
|
1231
|
+
raw: product
|
|
1232
|
+
};
|
|
1233
|
+
});
|
|
1234
|
+
}, [productsData, localeTag, getProcessedImageUrl]);
|
|
1235
|
+
const activeCategory = useMemo(() => {
|
|
1236
|
+
if (!params?.category) return void 0;
|
|
1237
|
+
return findById(params.category);
|
|
1238
|
+
}, [params, findById]);
|
|
1239
|
+
const refresh = useCallback(async () => {
|
|
1240
|
+
await refetch();
|
|
1241
|
+
}, [refetch]);
|
|
1242
|
+
const totalCount = products.length;
|
|
1243
|
+
return {
|
|
1244
|
+
products,
|
|
1245
|
+
isLoading,
|
|
1246
|
+
isFetchingNextPage,
|
|
1247
|
+
error,
|
|
1248
|
+
hasNextPage,
|
|
1249
|
+
fetchNextPage,
|
|
1250
|
+
refresh,
|
|
1251
|
+
isRefreshing: isRefetching,
|
|
1252
|
+
activeCategory,
|
|
1253
|
+
categoryTree,
|
|
1254
|
+
findCategoryById: findById,
|
|
1255
|
+
totalCount
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/utils/stock.ts
|
|
1260
|
+
var DEFAULT_CONFIG2 = {
|
|
1261
|
+
inStockText: "In Stock",
|
|
1262
|
+
outOfStockText: "Out of Stock",
|
|
1263
|
+
backorderText: "Available on Backorder",
|
|
1264
|
+
showQuantity: false,
|
|
1265
|
+
lowStockThreshold: 5,
|
|
1266
|
+
lowStockText: "Only {quantity} left"
|
|
1267
|
+
};
|
|
1268
|
+
function getStockStatusText(status, quantity, config = {}) {
|
|
1269
|
+
const mergedConfig = { ...DEFAULT_CONFIG2, ...config };
|
|
1270
|
+
switch (status) {
|
|
1271
|
+
case "instock":
|
|
1272
|
+
if (quantity !== null && quantity !== void 0 && quantity <= mergedConfig.lowStockThreshold && quantity > 0) {
|
|
1273
|
+
return mergedConfig.lowStockText.replace("{quantity}", quantity.toString());
|
|
1274
|
+
}
|
|
1275
|
+
if (mergedConfig.showQuantity && quantity !== null && quantity !== void 0) {
|
|
1276
|
+
return `${mergedConfig.inStockText} (${quantity})`;
|
|
1277
|
+
}
|
|
1278
|
+
return mergedConfig.inStockText;
|
|
1279
|
+
case "outofstock":
|
|
1280
|
+
return mergedConfig.outOfStockText;
|
|
1281
|
+
case "onbackorder":
|
|
1282
|
+
return mergedConfig.backorderText;
|
|
1283
|
+
default:
|
|
1284
|
+
return mergedConfig.inStockText;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
function isPurchasable(status, quantity, manageStock = true) {
|
|
1288
|
+
if (status === "outofstock") {
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1291
|
+
if (status === "onbackorder") {
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
if (manageStock && quantity !== null && quantity !== void 0) {
|
|
1295
|
+
return quantity > 0;
|
|
1296
|
+
}
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
function getStockSeverity(status, quantity, lowStockThreshold = 5) {
|
|
1300
|
+
switch (status) {
|
|
1301
|
+
case "outofstock":
|
|
1302
|
+
return "error";
|
|
1303
|
+
case "onbackorder":
|
|
1304
|
+
return "info";
|
|
1305
|
+
case "instock":
|
|
1306
|
+
if (quantity !== null && quantity !== void 0 && quantity <= lowStockThreshold && quantity > 0) {
|
|
1307
|
+
return "warning";
|
|
1308
|
+
}
|
|
1309
|
+
return "success";
|
|
1310
|
+
default:
|
|
1311
|
+
return "info";
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function isLowStock(status, quantity, threshold = 5) {
|
|
1315
|
+
return status === "instock" && quantity !== null && quantity !== void 0 && quantity <= threshold && quantity > 0;
|
|
1316
|
+
}
|
|
1317
|
+
function getMaxAddToCartQuantity(status, stockQuantity, cartQuantity = 0, maxPurchaseQuantity) {
|
|
1318
|
+
if (status === "outofstock") {
|
|
1319
|
+
return 0;
|
|
1320
|
+
}
|
|
1321
|
+
if (status === "onbackorder") {
|
|
1322
|
+
return maxPurchaseQuantity ?? 999;
|
|
1323
|
+
}
|
|
1324
|
+
let maxQty = stockQuantity ?? 999;
|
|
1325
|
+
if (maxPurchaseQuantity && maxPurchaseQuantity < maxQty) {
|
|
1326
|
+
maxQty = maxPurchaseQuantity;
|
|
1327
|
+
}
|
|
1328
|
+
return Math.max(0, maxQty - cartQuantity);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/hooks/useProductView.ts
|
|
1332
|
+
function useProductView(options) {
|
|
1333
|
+
const { productId, imageSize = "large", lowStockThreshold = 5 } = options;
|
|
1334
|
+
const storeContext = useContext(WooCommerceStoreContext);
|
|
1335
|
+
const storeConfig = storeContext?.config;
|
|
1336
|
+
const localeTag = options.localeTag ?? storeConfig?.locale?.localeTag ?? DEFAULT_STORE_CONFIG.locale.localeTag;
|
|
1337
|
+
const fallbackImageUrl = options.fallbackImageUrl ?? storeConfig?.fallbackImageUrl;
|
|
1338
|
+
const imageUrlTransformer = storeConfig?.imageUrlTransformer;
|
|
1339
|
+
const [draftQuantity, setDraftQuantity] = useState(1);
|
|
1340
|
+
const { data: productData, isLoading, error, refetch } = useProduct(productId);
|
|
1341
|
+
const { data: cartData } = useCart();
|
|
1342
|
+
const addToCartMutation = useAddToCart();
|
|
1343
|
+
const removeFromCartMutation = useRemoveFromCart();
|
|
1344
|
+
const updateCartMutation = useUpdateCartItem();
|
|
1345
|
+
const processImageUrl = useCallback(
|
|
1346
|
+
(url) => {
|
|
1347
|
+
if (!url) return url;
|
|
1348
|
+
if (imageUrlTransformer) {
|
|
1349
|
+
return imageUrlTransformer(url, imageSize);
|
|
1350
|
+
}
|
|
1351
|
+
return url;
|
|
1352
|
+
},
|
|
1353
|
+
[imageUrlTransformer, imageSize]
|
|
1354
|
+
);
|
|
1355
|
+
const product = useMemo(() => {
|
|
1356
|
+
if (!productData) return null;
|
|
1357
|
+
const currencyCode = productData.prices?.currency_code;
|
|
1358
|
+
const stockStatus = productData.is_on_backorder ? "onbackorder" : productData.is_in_stock ? "instock" : "outofstock";
|
|
1359
|
+
const lowStockRemaining = productData.low_stock_remaining;
|
|
1360
|
+
const baseImageUrl = getImageUrl(productData.images, imageSize, fallbackImageUrl);
|
|
1361
|
+
const baseImageUrls = getAllImageUrls(productData.images, imageSize);
|
|
1362
|
+
return {
|
|
1363
|
+
id: productData.id,
|
|
1364
|
+
name: productData.name,
|
|
1365
|
+
slug: productData.slug,
|
|
1366
|
+
permalink: productData.permalink,
|
|
1367
|
+
description: productData.description,
|
|
1368
|
+
shortDescription: productData.short_description,
|
|
1369
|
+
sku: productData.sku,
|
|
1370
|
+
imageUrl: processImageUrl(baseImageUrl),
|
|
1371
|
+
imageUrls: baseImageUrls.map((url) => processImageUrl(url) ?? url),
|
|
1372
|
+
price: formatStoreApiPriceIntl(productData.prices?.price, currencyCode, localeTag),
|
|
1373
|
+
regularPrice: formatStoreApiPriceIntl(
|
|
1374
|
+
productData.prices?.regular_price,
|
|
1375
|
+
currencyCode,
|
|
1376
|
+
localeTag
|
|
1377
|
+
),
|
|
1378
|
+
isOnSale: productData.on_sale,
|
|
1379
|
+
discountPercent: calculateDiscount(
|
|
1380
|
+
productData.prices?.regular_price ?? "0",
|
|
1381
|
+
productData.prices?.price ?? "0"
|
|
1382
|
+
),
|
|
1383
|
+
discountBadge: productData.on_sale ? `-${calculateDiscount(
|
|
1384
|
+
productData.prices?.regular_price ?? "0",
|
|
1385
|
+
productData.prices?.price ?? "0"
|
|
1386
|
+
)}%` : null,
|
|
1387
|
+
stockStatusText: getStockStatusText(stockStatus, lowStockRemaining ?? void 0, {
|
|
1388
|
+
lowStockThreshold
|
|
1389
|
+
}),
|
|
1390
|
+
isPurchasable: isPurchasable(
|
|
1391
|
+
stockStatus,
|
|
1392
|
+
lowStockRemaining ?? void 0,
|
|
1393
|
+
productData.sold_individually
|
|
1394
|
+
),
|
|
1395
|
+
isLowStock: isLowStock(stockStatus, lowStockRemaining ?? void 0, lowStockThreshold),
|
|
1396
|
+
maxQuantity: productData.add_to_cart?.maximum ?? 999,
|
|
1397
|
+
minQuantity: productData.add_to_cart?.minimum ?? 1,
|
|
1398
|
+
raw: productData
|
|
1399
|
+
};
|
|
1400
|
+
}, [productData, localeTag, fallbackImageUrl, imageSize, lowStockThreshold, processImageUrl]);
|
|
1401
|
+
const cartItem = useMemo(() => {
|
|
1402
|
+
return cartData?.items.find((item) => item.id === productId);
|
|
1403
|
+
}, [cartData?.items, productId]);
|
|
1404
|
+
const cartState = useMemo(
|
|
1405
|
+
() => ({
|
|
1406
|
+
isInCart: !!cartItem,
|
|
1407
|
+
quantityInCart: cartItem?.quantity ?? 0,
|
|
1408
|
+
cartItemKey: cartItem?.key ?? null,
|
|
1409
|
+
cartQuantityLimits: cartItem ? {
|
|
1410
|
+
minimum: cartItem.quantity_limits.minimum,
|
|
1411
|
+
maximum: cartItem.quantity_limits.maximum
|
|
1412
|
+
} : null
|
|
1413
|
+
}),
|
|
1414
|
+
[cartItem]
|
|
1415
|
+
);
|
|
1416
|
+
const addToCart = useCallback(async () => {
|
|
1417
|
+
await addToCartMutation.mutateAsync({
|
|
1418
|
+
id: productId,
|
|
1419
|
+
quantity: draftQuantity
|
|
1420
|
+
});
|
|
1421
|
+
setDraftQuantity(1);
|
|
1422
|
+
}, [addToCartMutation, productId, draftQuantity]);
|
|
1423
|
+
const removeFromCart = useCallback(async () => {
|
|
1424
|
+
if (!cartState.cartItemKey) return;
|
|
1425
|
+
await removeFromCartMutation.mutateAsync({
|
|
1426
|
+
key: cartState.cartItemKey
|
|
1427
|
+
});
|
|
1428
|
+
}, [removeFromCartMutation, cartState.cartItemKey]);
|
|
1429
|
+
const updateCartQuantity = useCallback(
|
|
1430
|
+
async (quantity) => {
|
|
1431
|
+
if (!cartState.cartItemKey) return;
|
|
1432
|
+
await updateCartMutation.mutateAsync({
|
|
1433
|
+
key: cartState.cartItemKey,
|
|
1434
|
+
quantity
|
|
1435
|
+
});
|
|
1436
|
+
},
|
|
1437
|
+
[updateCartMutation, cartState.cartItemKey]
|
|
1438
|
+
);
|
|
1439
|
+
const refresh = useCallback(async () => {
|
|
1440
|
+
await refetch();
|
|
1441
|
+
}, [refetch]);
|
|
1442
|
+
const isAddingToCart = addToCartMutation.isPending;
|
|
1443
|
+
const isRemovingFromCart = removeFromCartMutation.isPending;
|
|
1444
|
+
const isUpdatingCart = updateCartMutation.isPending;
|
|
1445
|
+
const isCartBusy = isAddingToCart || isRemovingFromCart || isUpdatingCart;
|
|
1446
|
+
return {
|
|
1447
|
+
product,
|
|
1448
|
+
isLoading,
|
|
1449
|
+
error,
|
|
1450
|
+
refresh,
|
|
1451
|
+
cartState,
|
|
1452
|
+
draftQuantity,
|
|
1453
|
+
setDraftQuantity,
|
|
1454
|
+
addToCart,
|
|
1455
|
+
removeFromCart,
|
|
1456
|
+
updateCartQuantity,
|
|
1457
|
+
isAddingToCart,
|
|
1458
|
+
isRemovingFromCart,
|
|
1459
|
+
isUpdatingCart,
|
|
1460
|
+
isCartBusy
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
var PAYMENT_METHOD_TITLES = {
|
|
1464
|
+
cod: "Cash on Delivery",
|
|
1465
|
+
monri: "Monri",
|
|
1466
|
+
bacs: "Bank Transfer",
|
|
1467
|
+
cheque: "Check Payment",
|
|
1468
|
+
paypal: "PayPal",
|
|
1469
|
+
stripe: "Credit Card"
|
|
1470
|
+
};
|
|
1471
|
+
function formatPaymentMethodTitle(methodId) {
|
|
1472
|
+
const knownTitle = PAYMENT_METHOD_TITLES[methodId];
|
|
1473
|
+
if (knownTitle) {
|
|
1474
|
+
return knownTitle;
|
|
1475
|
+
}
|
|
1476
|
+
return methodId.replace(/[_-]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1477
|
+
}
|
|
1478
|
+
function useCartView(options = {}) {
|
|
1479
|
+
const { imageSize = "thumbnail" } = options;
|
|
1480
|
+
const storeContext = useContext(WooCommerceStoreContext);
|
|
1481
|
+
const storeConfig = storeContext?.config;
|
|
1482
|
+
const localeTag = options.localeTag ?? storeConfig?.locale?.localeTag ?? DEFAULT_STORE_CONFIG.locale.localeTag;
|
|
1483
|
+
const fallbackImageUrl = options.fallbackImageUrl ?? storeConfig?.fallbackImageUrl;
|
|
1484
|
+
const imageUrlTransformer = storeConfig?.imageUrlTransformer;
|
|
1485
|
+
const { data: cartData, isLoading, error, refetch } = useCart();
|
|
1486
|
+
const updateItemMutation = useUpdateCartItem();
|
|
1487
|
+
const removeItemMutation = useRemoveFromCart();
|
|
1488
|
+
const applyCouponMutation = useApplyCoupon();
|
|
1489
|
+
const removeCouponMutation = useRemoveCoupon();
|
|
1490
|
+
const selectShippingMutation = useSelectShippingRate();
|
|
1491
|
+
const getProcessedImageUrl = useCallback(
|
|
1492
|
+
(images) => {
|
|
1493
|
+
const baseUrl = getImageUrl(images, imageSize, fallbackImageUrl);
|
|
1494
|
+
if (baseUrl && imageUrlTransformer) {
|
|
1495
|
+
return imageUrlTransformer(baseUrl, imageSize);
|
|
1496
|
+
}
|
|
1497
|
+
return baseUrl;
|
|
1498
|
+
},
|
|
1499
|
+
[imageSize, fallbackImageUrl, imageUrlTransformer]
|
|
1500
|
+
);
|
|
1501
|
+
const items = useMemo(() => {
|
|
1502
|
+
if (!cartData?.items) return [];
|
|
1503
|
+
const currencyCode = cartData.totals.currency_code;
|
|
1504
|
+
return cartData.items.map(
|
|
1505
|
+
(item) => ({
|
|
1506
|
+
key: item.key,
|
|
1507
|
+
id: item.id,
|
|
1508
|
+
name: item.name,
|
|
1509
|
+
imageUrl: getProcessedImageUrl(item.images),
|
|
1510
|
+
quantity: item.quantity,
|
|
1511
|
+
minQuantity: item.quantity_limits.minimum,
|
|
1512
|
+
maxQuantity: item.quantity_limits.maximum,
|
|
1513
|
+
lineTotal: formatStoreApiPriceIntl(item.totals.line_total, currencyCode, localeTag),
|
|
1514
|
+
unitPrice: formatStoreApiPriceIntl(item.prices.price, currencyCode, localeTag),
|
|
1515
|
+
raw: item
|
|
1516
|
+
})
|
|
1517
|
+
);
|
|
1518
|
+
}, [cartData?.items, cartData?.totals.currency_code, localeTag, getProcessedImageUrl]);
|
|
1519
|
+
const totals = useMemo(() => {
|
|
1520
|
+
if (!cartData?.totals) {
|
|
1521
|
+
return {
|
|
1522
|
+
subtotal: "-",
|
|
1523
|
+
shipping: "-",
|
|
1524
|
+
tax: "-",
|
|
1525
|
+
discount: "-",
|
|
1526
|
+
total: "-",
|
|
1527
|
+
itemCount: 0,
|
|
1528
|
+
currencyCode: "USD"
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
const { totals: t } = cartData;
|
|
1532
|
+
const currencyCode = t.currency_code;
|
|
1533
|
+
return {
|
|
1534
|
+
subtotal: formatStoreApiPriceIntl(t.total_items, currencyCode, localeTag),
|
|
1535
|
+
shipping: formatStoreApiPriceIntl(t.total_shipping, currencyCode, localeTag),
|
|
1536
|
+
tax: formatStoreApiPriceIntl(t.total_tax, currencyCode, localeTag),
|
|
1537
|
+
discount: formatStoreApiPriceIntl(t.total_discount, currencyCode, localeTag),
|
|
1538
|
+
total: formatStoreApiPriceIntl(t.total_price, currencyCode, localeTag),
|
|
1539
|
+
itemCount: cartData.items_count,
|
|
1540
|
+
currencyCode
|
|
1541
|
+
};
|
|
1542
|
+
}, [localeTag, cartData]);
|
|
1543
|
+
const coupons = useMemo(() => {
|
|
1544
|
+
if (!cartData?.coupons) return [];
|
|
1545
|
+
return cartData.coupons.map((coupon) => ({
|
|
1546
|
+
code: coupon.code,
|
|
1547
|
+
discount: formatStoreApiPriceIntl(
|
|
1548
|
+
coupon.totals.total_discount,
|
|
1549
|
+
coupon.totals.currency_code,
|
|
1550
|
+
localeTag
|
|
1551
|
+
)
|
|
1552
|
+
}));
|
|
1553
|
+
}, [cartData?.coupons, localeTag]);
|
|
1554
|
+
const shippingRates = useMemo(() => {
|
|
1555
|
+
if (!cartData?.shipping_rates?.[0]?.shipping_rates) return [];
|
|
1556
|
+
const currencyCode = cartData.totals.currency_code;
|
|
1557
|
+
return cartData.shipping_rates[0].shipping_rates.map((rate) => ({
|
|
1558
|
+
rateId: rate.rate_id,
|
|
1559
|
+
name: rate.name,
|
|
1560
|
+
price: formatStoreApiPriceIntl(rate.price, currencyCode, localeTag),
|
|
1561
|
+
isSelected: rate.selected
|
|
1562
|
+
}));
|
|
1563
|
+
}, [cartData?.shipping_rates, cartData?.totals.currency_code, localeTag]);
|
|
1564
|
+
const paymentMethods = useMemo(() => {
|
|
1565
|
+
if (!cartData?.payment_methods) return [];
|
|
1566
|
+
return cartData.payment_methods.map((methodId) => {
|
|
1567
|
+
const title = formatPaymentMethodTitle(methodId);
|
|
1568
|
+
return {
|
|
1569
|
+
id: methodId,
|
|
1570
|
+
title
|
|
1571
|
+
};
|
|
1572
|
+
});
|
|
1573
|
+
}, [cartData?.payment_methods]);
|
|
1574
|
+
const updateItemQuantity = useCallback(
|
|
1575
|
+
async (key, quantity) => {
|
|
1576
|
+
await updateItemMutation.mutateAsync({ key, quantity });
|
|
1577
|
+
},
|
|
1578
|
+
[updateItemMutation]
|
|
1579
|
+
);
|
|
1580
|
+
const removeItem = useCallback(
|
|
1581
|
+
async (key) => {
|
|
1582
|
+
await removeItemMutation.mutateAsync({ key });
|
|
1583
|
+
},
|
|
1584
|
+
[removeItemMutation]
|
|
1585
|
+
);
|
|
1586
|
+
const applyCoupon = useCallback(
|
|
1587
|
+
async (code) => {
|
|
1588
|
+
await applyCouponMutation.mutateAsync({ code });
|
|
1589
|
+
},
|
|
1590
|
+
[applyCouponMutation]
|
|
1591
|
+
);
|
|
1592
|
+
const removeCoupon = useCallback(
|
|
1593
|
+
async (code) => {
|
|
1594
|
+
await removeCouponMutation.mutateAsync({ code });
|
|
1595
|
+
},
|
|
1596
|
+
[removeCouponMutation]
|
|
1597
|
+
);
|
|
1598
|
+
const selectShippingRate = useCallback(
|
|
1599
|
+
async (rateId, packageId = 0) => {
|
|
1600
|
+
await selectShippingMutation.mutateAsync({
|
|
1601
|
+
rate_id: rateId,
|
|
1602
|
+
package_id: packageId
|
|
1603
|
+
});
|
|
1604
|
+
},
|
|
1605
|
+
[selectShippingMutation]
|
|
1606
|
+
);
|
|
1607
|
+
const refresh = useCallback(async () => {
|
|
1608
|
+
await refetch();
|
|
1609
|
+
}, [refetch]);
|
|
1610
|
+
const isUpdatingItem = updateItemMutation.isPending;
|
|
1611
|
+
const isRemovingItem = removeItemMutation.isPending;
|
|
1612
|
+
const isApplyingCoupon = applyCouponMutation.isPending;
|
|
1613
|
+
const isRemovingCoupon = removeCouponMutation.isPending;
|
|
1614
|
+
const isUpdatingShipping = selectShippingMutation.isPending;
|
|
1615
|
+
const isBusy = isUpdatingItem || isRemovingItem || isApplyingCoupon || isRemovingCoupon || isUpdatingShipping;
|
|
1616
|
+
return {
|
|
1617
|
+
items,
|
|
1618
|
+
totals,
|
|
1619
|
+
coupons,
|
|
1620
|
+
shippingRates,
|
|
1621
|
+
paymentMethods,
|
|
1622
|
+
isEmpty: items.length === 0,
|
|
1623
|
+
needsShipping: cartData?.needs_shipping ?? false,
|
|
1624
|
+
raw: cartData,
|
|
1625
|
+
isLoading,
|
|
1626
|
+
error,
|
|
1627
|
+
updateItemQuantity,
|
|
1628
|
+
removeItem,
|
|
1629
|
+
isUpdatingItem,
|
|
1630
|
+
isRemovingItem,
|
|
1631
|
+
applyCoupon,
|
|
1632
|
+
removeCoupon,
|
|
1633
|
+
isApplyingCoupon,
|
|
1634
|
+
isRemovingCoupon,
|
|
1635
|
+
selectShippingRate,
|
|
1636
|
+
isUpdatingShipping,
|
|
1637
|
+
refresh,
|
|
1638
|
+
isBusy
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
export { DEFAULT_STORE_CONFIG, PLACEHOLDER_IMAGE_URL, RateLimiter, WooCommerceContext, WooCommerceProvider, WooCommerceStoreContext, WooCommerceStoreProvider, calculateDiscount, configureGlobalRateLimiter, createQueryKeys, formatDiscountBadge, formatPrice, formatPriceRange, formatStoreApiPrice, formatStoreApiPriceIntl, getAllImageUrls, getBaseUrl, getCurrencySymbol, getGlobalRateLimiter, getImageUrl, getImageUrlOrPlaceholder, getMaxAddToCartQuantity, getPrimaryImage, getSizedImageUrl, getStockSeverity, getStockStatusText, isLowStock, isPurchasable, isValidStoreUrlFormat, normalizeStoreUrl, parseWooCommercePrice, preloadImage, preloadImages, resetGlobalRateLimiter, useAddToCart, useApplyCoupon, useCart, useCartView, useCheckout, useInfiniteProducts, useOrder, useProcessCheckout, useProduct, useProductCategories, useProductCategoryTree, useProductView, useProducts, useProductsFeed, useRemoveCoupon, useRemoveFromCart, useSelectShippingRate, useStore, useStoreFeatures, useStoreLocale, useStoreTheme, useUpdateCartItem, useUpdateCustomer, useWooCommerceClient, useWooCommerceQueryKeys, validateWooCommerceStore };
|
|
1643
|
+
//# sourceMappingURL=index.js.map
|
|
1644
|
+
//# sourceMappingURL=index.js.map
|