@cimplify/sdk 0.6.7 → 0.6.9
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/README.md +19 -0
- package/dist/advanced.d.mts +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.js +127 -98
- package/dist/advanced.mjs +127 -98
- package/dist/{client-B4etj3AD.d.mts → client-COpV6Yuu.d.ts} +39 -34
- package/dist/{client-CYVVuP5J.d.ts → client-vmXPt1j0.d.mts} +39 -34
- package/dist/{index-DzNb32O3.d.mts → index-DaKJxoEh.d.mts} +13 -12
- package/dist/{index-BOYF-efj.d.ts → index-pztT_bcJ.d.ts} +13 -12
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +165 -99
- package/dist/index.mjs +162 -100
- package/dist/{payment-pjpfIKX8.d.mts → payment-Cu75tmUc.d.mts} +22 -7
- package/dist/{payment-pjpfIKX8.d.ts → payment-Cu75tmUc.d.ts} +22 -7
- package/dist/react.d.mts +99 -4
- package/dist/react.d.ts +99 -4
- package/dist/react.js +845 -39
- package/dist/react.mjs +840 -40
- package/dist/utils.d.mts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +76 -4
- package/dist/utils.mjs +76 -4
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -713,6 +713,15 @@ function CimplifyCheckout({
|
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
// src/types/common.ts
|
|
716
|
+
function money(value) {
|
|
717
|
+
return value;
|
|
718
|
+
}
|
|
719
|
+
function moneyFromNumber(value) {
|
|
720
|
+
return value.toFixed(2);
|
|
721
|
+
}
|
|
722
|
+
function currencyCode(value) {
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
716
725
|
var ErrorCode = {
|
|
717
726
|
// General
|
|
718
727
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
@@ -824,6 +833,11 @@ function err(error) {
|
|
|
824
833
|
return { ok: false, error };
|
|
825
834
|
}
|
|
826
835
|
|
|
836
|
+
// src/query/builder.ts
|
|
837
|
+
function escapeQueryValue(value) {
|
|
838
|
+
return value.replace(/'/g, "\\'");
|
|
839
|
+
}
|
|
840
|
+
|
|
827
841
|
// src/catalogue.ts
|
|
828
842
|
function toCimplifyError(error) {
|
|
829
843
|
if (error instanceof CimplifyError) return enrichError(error);
|
|
@@ -904,7 +918,7 @@ var CatalogueQueries = class {
|
|
|
904
918
|
let query = "products";
|
|
905
919
|
const filters = [];
|
|
906
920
|
if (options?.category) {
|
|
907
|
-
filters.push(`@.category_id=='${options.category}'`);
|
|
921
|
+
filters.push(`@.category_id=='${escapeQueryValue(options.category)}'`);
|
|
908
922
|
}
|
|
909
923
|
if (options?.featured !== void 0) {
|
|
910
924
|
filters.push(`@.featured==${options.featured}`);
|
|
@@ -913,7 +927,7 @@ var CatalogueQueries = class {
|
|
|
913
927
|
filters.push(`@.in_stock==${options.in_stock}`);
|
|
914
928
|
}
|
|
915
929
|
if (options?.search) {
|
|
916
|
-
filters.push(`@.name contains '${options.search}'`);
|
|
930
|
+
filters.push(`@.name contains '${escapeQueryValue(options.search)}'`);
|
|
917
931
|
}
|
|
918
932
|
if (options?.min_price !== void 0) {
|
|
919
933
|
filters.push(`@.price>=${options.min_price}`);
|
|
@@ -944,7 +958,9 @@ var CatalogueQueries = class {
|
|
|
944
958
|
}
|
|
945
959
|
async getProductBySlug(slug) {
|
|
946
960
|
const filteredResult = await safe(
|
|
947
|
-
this.client.query(
|
|
961
|
+
this.client.query(
|
|
962
|
+
`products[?(@.slug=='${escapeQueryValue(slug)}')]`
|
|
963
|
+
)
|
|
948
964
|
);
|
|
949
965
|
if (!filteredResult.ok) return filteredResult;
|
|
950
966
|
const exactMatch = findProductBySlug(filteredResult.value, slug);
|
|
@@ -996,7 +1012,7 @@ var CatalogueQueries = class {
|
|
|
996
1012
|
}
|
|
997
1013
|
async getCategoryBySlug(slug) {
|
|
998
1014
|
const result = await safe(
|
|
999
|
-
this.client.query(`categories[?(@.slug=='${slug}')]`)
|
|
1015
|
+
this.client.query(`categories[?(@.slug=='${escapeQueryValue(slug)}')]`)
|
|
1000
1016
|
);
|
|
1001
1017
|
if (!result.ok) return result;
|
|
1002
1018
|
if (!result.value.length) {
|
|
@@ -1005,7 +1021,11 @@ var CatalogueQueries = class {
|
|
|
1005
1021
|
return ok(result.value[0]);
|
|
1006
1022
|
}
|
|
1007
1023
|
async getCategoryProducts(categoryId) {
|
|
1008
|
-
return safe(
|
|
1024
|
+
return safe(
|
|
1025
|
+
this.client.query(
|
|
1026
|
+
`products[?(@.category_id=='${escapeQueryValue(categoryId)}')]`
|
|
1027
|
+
)
|
|
1028
|
+
);
|
|
1009
1029
|
}
|
|
1010
1030
|
async getCollections() {
|
|
1011
1031
|
return safe(this.client.query("collections"));
|
|
@@ -1015,7 +1035,9 @@ var CatalogueQueries = class {
|
|
|
1015
1035
|
}
|
|
1016
1036
|
async getCollectionBySlug(slug) {
|
|
1017
1037
|
const result = await safe(
|
|
1018
|
-
this.client.query(
|
|
1038
|
+
this.client.query(
|
|
1039
|
+
`collections[?(@.slug=='${escapeQueryValue(slug)}')]`
|
|
1040
|
+
)
|
|
1019
1041
|
);
|
|
1020
1042
|
if (!result.ok) return result;
|
|
1021
1043
|
if (!result.value.length) {
|
|
@@ -1028,7 +1050,9 @@ var CatalogueQueries = class {
|
|
|
1028
1050
|
}
|
|
1029
1051
|
async searchCollections(query, limit = 20) {
|
|
1030
1052
|
return safe(
|
|
1031
|
-
this.client.query(
|
|
1053
|
+
this.client.query(
|
|
1054
|
+
`collections[?(@.name contains '${escapeQueryValue(query)}')]#limit(${limit})`
|
|
1055
|
+
)
|
|
1032
1056
|
);
|
|
1033
1057
|
}
|
|
1034
1058
|
async getBundles() {
|
|
@@ -1039,7 +1063,9 @@ var CatalogueQueries = class {
|
|
|
1039
1063
|
}
|
|
1040
1064
|
async getBundleBySlug(slug) {
|
|
1041
1065
|
const result = await safe(
|
|
1042
|
-
this.client.query(
|
|
1066
|
+
this.client.query(
|
|
1067
|
+
`bundles[?(@.slug=='${escapeQueryValue(slug)}')]`
|
|
1068
|
+
)
|
|
1043
1069
|
);
|
|
1044
1070
|
if (!result.ok) return result;
|
|
1045
1071
|
if (!result.value.length) {
|
|
@@ -1049,7 +1075,9 @@ var CatalogueQueries = class {
|
|
|
1049
1075
|
}
|
|
1050
1076
|
async searchBundles(query, limit = 20) {
|
|
1051
1077
|
return safe(
|
|
1052
|
-
this.client.query(
|
|
1078
|
+
this.client.query(
|
|
1079
|
+
`bundles[?(@.name contains '${escapeQueryValue(query)}')]#limit(${limit})`
|
|
1080
|
+
)
|
|
1053
1081
|
);
|
|
1054
1082
|
}
|
|
1055
1083
|
async getComposites(options) {
|
|
@@ -1089,9 +1117,9 @@ var CatalogueQueries = class {
|
|
|
1089
1117
|
}
|
|
1090
1118
|
async search(query, options) {
|
|
1091
1119
|
const limit = options?.limit ?? 20;
|
|
1092
|
-
let searchQuery = `products[?(@.name contains '${query}')]`;
|
|
1120
|
+
let searchQuery = `products[?(@.name contains '${escapeQueryValue(query)}')]`;
|
|
1093
1121
|
if (options?.category) {
|
|
1094
|
-
searchQuery = `products[?(@.name contains '${query}' && @.category_id=='${options.category}')]`;
|
|
1122
|
+
searchQuery = `products[?(@.name contains '${escapeQueryValue(query)}' && @.category_id=='${escapeQueryValue(options.category)}')]`;
|
|
1095
1123
|
}
|
|
1096
1124
|
searchQuery += `#limit(${limit})`;
|
|
1097
1125
|
return safe(this.client.query(searchQuery));
|
|
@@ -1108,7 +1136,7 @@ var CatalogueQueries = class {
|
|
|
1108
1136
|
async getMenu(options) {
|
|
1109
1137
|
let query = "menu";
|
|
1110
1138
|
if (options?.category) {
|
|
1111
|
-
query = `menu[?(@.category=='${options.category}')]`;
|
|
1139
|
+
query = `menu[?(@.category=='${escapeQueryValue(options.category)}')]`;
|
|
1112
1140
|
}
|
|
1113
1141
|
if (options?.limit) {
|
|
1114
1142
|
query += `#limit(${options.limit})`;
|
|
@@ -1255,9 +1283,6 @@ var CartOperations = class {
|
|
|
1255
1283
|
|
|
1256
1284
|
// src/constants.ts
|
|
1257
1285
|
var LINK_QUERY = {
|
|
1258
|
-
DATA: "link.data",
|
|
1259
|
-
ADDRESSES: "link.addresses",
|
|
1260
|
-
MOBILE_MONEY: "link.mobile_money",
|
|
1261
1286
|
PREFERENCES: "link.preferences"};
|
|
1262
1287
|
var LINK_MUTATION = {
|
|
1263
1288
|
CHECK_STATUS: "link.check_status",
|
|
@@ -1266,12 +1291,8 @@ var LINK_MUTATION = {
|
|
|
1266
1291
|
UPDATE_PREFERENCES: "link.update_preferences",
|
|
1267
1292
|
CREATE_ADDRESS: "link.create_address",
|
|
1268
1293
|
UPDATE_ADDRESS: "link.update_address",
|
|
1269
|
-
DELETE_ADDRESS: "link.delete_address",
|
|
1270
|
-
SET_DEFAULT_ADDRESS: "link.set_default_address",
|
|
1271
1294
|
TRACK_ADDRESS_USAGE: "link.track_address_usage",
|
|
1272
1295
|
CREATE_MOBILE_MONEY: "link.create_mobile_money",
|
|
1273
|
-
DELETE_MOBILE_MONEY: "link.delete_mobile_money",
|
|
1274
|
-
SET_DEFAULT_MOBILE_MONEY: "link.set_default_mobile_money",
|
|
1275
1296
|
TRACK_MOBILE_MONEY_USAGE: "link.track_mobile_money_usage",
|
|
1276
1297
|
VERIFY_MOBILE_MONEY: "link.verify_mobile_money"};
|
|
1277
1298
|
var AUTH_MUTATION = {
|
|
@@ -1402,6 +1423,8 @@ function normalizeStatusResponse(response) {
|
|
|
1402
1423
|
}
|
|
1403
1424
|
const res = response;
|
|
1404
1425
|
const normalizedStatus = normalizePaymentStatusValue(res.status ?? void 0);
|
|
1426
|
+
const normalizedAmount = typeof res.amount === "string" ? money(res.amount) : typeof res.amount === "number" && Number.isFinite(res.amount) ? moneyFromNumber(res.amount) : void 0;
|
|
1427
|
+
const normalizedCurrency = typeof res.currency === "string" && res.currency.trim().length > 0 ? currencyCode(res.currency) : void 0;
|
|
1405
1428
|
const paidValue = res.paid === true;
|
|
1406
1429
|
const derivedPaid = paidValue || [
|
|
1407
1430
|
"success",
|
|
@@ -1414,8 +1437,8 @@ function normalizeStatusResponse(response) {
|
|
|
1414
1437
|
return {
|
|
1415
1438
|
status: normalizedStatus,
|
|
1416
1439
|
paid: derivedPaid,
|
|
1417
|
-
amount:
|
|
1418
|
-
currency:
|
|
1440
|
+
amount: normalizedAmount,
|
|
1441
|
+
currency: normalizedCurrency,
|
|
1419
1442
|
reference: res.reference,
|
|
1420
1443
|
message: res.message || ""
|
|
1421
1444
|
};
|
|
@@ -2107,14 +2130,17 @@ var CheckoutService = class {
|
|
|
2107
2130
|
pay_currency: data.pay_currency,
|
|
2108
2131
|
fx_quote_id: data.fx_quote_id
|
|
2109
2132
|
};
|
|
2110
|
-
const baseCurrency = (
|
|
2133
|
+
const baseCurrency = currencyCode(
|
|
2134
|
+
(cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase()
|
|
2135
|
+
);
|
|
2111
2136
|
const payCurrency = data.pay_currency?.trim().toUpperCase();
|
|
2137
|
+
const payCurrencyCode = payCurrency ? currencyCode(payCurrency) : void 0;
|
|
2112
2138
|
const cartTotalAmount = Number.parseFloat(cart.pricing.total_price || "0");
|
|
2113
|
-
if (
|
|
2139
|
+
if (payCurrencyCode && payCurrencyCode !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
|
|
2114
2140
|
const fxQuoteResult = await this.client.fx.lockQuote({
|
|
2115
2141
|
from: baseCurrency,
|
|
2116
|
-
to:
|
|
2117
|
-
amount:
|
|
2142
|
+
to: payCurrencyCode,
|
|
2143
|
+
amount: cart.pricing.total_price
|
|
2118
2144
|
});
|
|
2119
2145
|
if (!fxQuoteResult.ok) {
|
|
2120
2146
|
return ok(
|
|
@@ -2125,7 +2151,7 @@ var CheckoutService = class {
|
|
|
2125
2151
|
)
|
|
2126
2152
|
);
|
|
2127
2153
|
}
|
|
2128
|
-
checkoutData.pay_currency =
|
|
2154
|
+
checkoutData.pay_currency = payCurrencyCode;
|
|
2129
2155
|
checkoutData.fx_quote_id = fxQuoteResult.value.id;
|
|
2130
2156
|
}
|
|
2131
2157
|
data.on_status_change?.("processing", {});
|
|
@@ -2230,6 +2256,9 @@ var LinkService = class {
|
|
|
2230
2256
|
constructor(client) {
|
|
2231
2257
|
this.client = client;
|
|
2232
2258
|
}
|
|
2259
|
+
async getProfile() {
|
|
2260
|
+
return safe5(this.client.linkGet("/v1/link/profile"));
|
|
2261
|
+
}
|
|
2233
2262
|
async requestOtp(input) {
|
|
2234
2263
|
return safe5(this.client.linkPost("/v1/link/auth/request-otp", input));
|
|
2235
2264
|
}
|
|
@@ -2257,13 +2286,13 @@ var LinkService = class {
|
|
|
2257
2286
|
);
|
|
2258
2287
|
}
|
|
2259
2288
|
async getLinkData() {
|
|
2260
|
-
return safe5(this.client.
|
|
2289
|
+
return safe5(this.client.linkGet("/v1/link/data"));
|
|
2261
2290
|
}
|
|
2262
2291
|
async getAddresses() {
|
|
2263
|
-
return safe5(this.client.
|
|
2292
|
+
return safe5(this.client.linkGet("/v1/link/addresses"));
|
|
2264
2293
|
}
|
|
2265
2294
|
async getMobileMoney() {
|
|
2266
|
-
return safe5(this.client.
|
|
2295
|
+
return safe5(this.client.linkGet("/v1/link/mobile-money"));
|
|
2267
2296
|
}
|
|
2268
2297
|
async getPreferences() {
|
|
2269
2298
|
return safe5(this.client.query(LINK_QUERY.PREFERENCES));
|
|
@@ -2286,10 +2315,10 @@ var LinkService = class {
|
|
|
2286
2315
|
return safe5(this.client.call(LINK_MUTATION.UPDATE_ADDRESS, input));
|
|
2287
2316
|
}
|
|
2288
2317
|
async deleteAddress(addressId) {
|
|
2289
|
-
return safe5(this.client.
|
|
2318
|
+
return safe5(this.client.linkDelete(`/v1/link/addresses/${addressId}`));
|
|
2290
2319
|
}
|
|
2291
2320
|
async setDefaultAddress(addressId) {
|
|
2292
|
-
return safe5(this.client.
|
|
2321
|
+
return safe5(this.client.linkPost(`/v1/link/addresses/${addressId}/default`));
|
|
2293
2322
|
}
|
|
2294
2323
|
async trackAddressUsage(addressId) {
|
|
2295
2324
|
return safe5(
|
|
@@ -2302,11 +2331,13 @@ var LinkService = class {
|
|
|
2302
2331
|
return safe5(this.client.call(LINK_MUTATION.CREATE_MOBILE_MONEY, input));
|
|
2303
2332
|
}
|
|
2304
2333
|
async deleteMobileMoney(mobileMoneyId) {
|
|
2305
|
-
return safe5(
|
|
2334
|
+
return safe5(
|
|
2335
|
+
this.client.linkDelete(`/v1/link/mobile-money/${mobileMoneyId}`)
|
|
2336
|
+
);
|
|
2306
2337
|
}
|
|
2307
2338
|
async setDefaultMobileMoney(mobileMoneyId) {
|
|
2308
2339
|
return safe5(
|
|
2309
|
-
this.client.
|
|
2340
|
+
this.client.linkPost(`/v1/link/mobile-money/${mobileMoneyId}/default`)
|
|
2310
2341
|
);
|
|
2311
2342
|
}
|
|
2312
2343
|
async trackMobileMoneyUsage(mobileMoneyId) {
|
|
@@ -3432,7 +3463,7 @@ var CimplifyClient = class {
|
|
|
3432
3463
|
this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
|
|
3433
3464
|
this.hooks = config.hooks ?? {};
|
|
3434
3465
|
this.accessToken = this.loadAccessToken();
|
|
3435
|
-
if (!this.publicKey) {
|
|
3466
|
+
if (!this.publicKey && !config.suppressPublicKeyWarning) {
|
|
3436
3467
|
console.warn(
|
|
3437
3468
|
'[Cimplify] No public key found. Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY in your environment, or pass { publicKey: "pk_..." } to createCimplifyClient().'
|
|
3438
3469
|
);
|
|
@@ -3545,9 +3576,12 @@ var CimplifyClient = class {
|
|
|
3545
3576
|
}
|
|
3546
3577
|
getHeaders() {
|
|
3547
3578
|
const headers = {
|
|
3548
|
-
"Content-Type": "application/json"
|
|
3549
|
-
"X-API-Key": this.publicKey
|
|
3579
|
+
"Content-Type": "application/json"
|
|
3550
3580
|
};
|
|
3581
|
+
const trimmedPublicKey = this.publicKey.trim();
|
|
3582
|
+
if (trimmedPublicKey) {
|
|
3583
|
+
headers["X-API-Key"] = trimmedPublicKey;
|
|
3584
|
+
}
|
|
3551
3585
|
if (this.accessToken) {
|
|
3552
3586
|
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
3553
3587
|
}
|
|
@@ -3576,7 +3610,7 @@ var CimplifyClient = class {
|
|
|
3576
3610
|
signal: controller.signal
|
|
3577
3611
|
});
|
|
3578
3612
|
clearTimeout(timeoutId);
|
|
3579
|
-
if (response.ok
|
|
3613
|
+
if (response.ok) {
|
|
3580
3614
|
this.hooks.onRequestSuccess?.({
|
|
3581
3615
|
...context,
|
|
3582
3616
|
status: response.status,
|
|
@@ -3584,6 +3618,21 @@ var CimplifyClient = class {
|
|
|
3584
3618
|
});
|
|
3585
3619
|
return response;
|
|
3586
3620
|
}
|
|
3621
|
+
if (response.status >= 400 && response.status < 500) {
|
|
3622
|
+
this.hooks.onRequestError?.({
|
|
3623
|
+
...context,
|
|
3624
|
+
error: new CimplifyError(
|
|
3625
|
+
`HTTP_${response.status}`,
|
|
3626
|
+
`Request failed with status ${response.status}`,
|
|
3627
|
+
false
|
|
3628
|
+
),
|
|
3629
|
+
status: response.status,
|
|
3630
|
+
durationMs: Date.now() - startTime,
|
|
3631
|
+
retryCount,
|
|
3632
|
+
retryable: false
|
|
3633
|
+
});
|
|
3634
|
+
return response;
|
|
3635
|
+
}
|
|
3587
3636
|
if (response.status >= 500 && attempt < this.maxRetries) {
|
|
3588
3637
|
retryCount++;
|
|
3589
3638
|
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
@@ -3596,10 +3645,17 @@ var CimplifyClient = class {
|
|
|
3596
3645
|
await sleep(delay);
|
|
3597
3646
|
continue;
|
|
3598
3647
|
}
|
|
3599
|
-
this.hooks.
|
|
3648
|
+
this.hooks.onRequestError?.({
|
|
3600
3649
|
...context,
|
|
3650
|
+
error: new CimplifyError(
|
|
3651
|
+
"SERVER_ERROR",
|
|
3652
|
+
`Server error ${response.status} after ${retryCount} retries`,
|
|
3653
|
+
false
|
|
3654
|
+
),
|
|
3601
3655
|
status: response.status,
|
|
3602
|
-
durationMs: Date.now() - startTime
|
|
3656
|
+
durationMs: Date.now() - startTime,
|
|
3657
|
+
retryCount,
|
|
3658
|
+
retryable: false
|
|
3603
3659
|
});
|
|
3604
3660
|
return response;
|
|
3605
3661
|
} catch (error) {
|
|
@@ -5085,6 +5141,750 @@ function useLocations(options = {}) {
|
|
|
5085
5141
|
isLoading
|
|
5086
5142
|
};
|
|
5087
5143
|
}
|
|
5144
|
+
var collectionsCache = /* @__PURE__ */ new Map();
|
|
5145
|
+
var collectionsInflight = /* @__PURE__ */ new Map();
|
|
5146
|
+
function buildCollectionsCacheKey(client, locationId) {
|
|
5147
|
+
return JSON.stringify({
|
|
5148
|
+
key: client.getPublicKey(),
|
|
5149
|
+
location_id: locationId || "__none__"
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
function useCollections(options = {}) {
|
|
5153
|
+
const context = useOptionalCimplify();
|
|
5154
|
+
const client = options.client ?? context?.client;
|
|
5155
|
+
if (!client) {
|
|
5156
|
+
throw new Error("useCollections must be used within CimplifyProvider or passed { client }.");
|
|
5157
|
+
}
|
|
5158
|
+
const enabled = options.enabled ?? true;
|
|
5159
|
+
const locationId = client.getLocationId();
|
|
5160
|
+
const previousLocationIdRef = react.useRef(locationId);
|
|
5161
|
+
const requestIdRef = react.useRef(0);
|
|
5162
|
+
const cacheKey = react.useMemo(
|
|
5163
|
+
() => buildCollectionsCacheKey(client, locationId),
|
|
5164
|
+
[client, locationId]
|
|
5165
|
+
);
|
|
5166
|
+
const cached = collectionsCache.get(cacheKey);
|
|
5167
|
+
const [collections, setCollections] = react.useState(cached?.collections ?? []);
|
|
5168
|
+
const [isLoading, setIsLoading] = react.useState(enabled && !cached);
|
|
5169
|
+
const [error, setError] = react.useState(null);
|
|
5170
|
+
react.useEffect(() => {
|
|
5171
|
+
if (previousLocationIdRef.current !== locationId) {
|
|
5172
|
+
collectionsCache.clear();
|
|
5173
|
+
collectionsInflight.clear();
|
|
5174
|
+
previousLocationIdRef.current = locationId;
|
|
5175
|
+
}
|
|
5176
|
+
}, [locationId]);
|
|
5177
|
+
const load = react.useCallback(
|
|
5178
|
+
async (force = false) => {
|
|
5179
|
+
if (!enabled) {
|
|
5180
|
+
setIsLoading(false);
|
|
5181
|
+
return;
|
|
5182
|
+
}
|
|
5183
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5184
|
+
setError(null);
|
|
5185
|
+
if (!force) {
|
|
5186
|
+
const cacheEntry = collectionsCache.get(cacheKey);
|
|
5187
|
+
if (cacheEntry) {
|
|
5188
|
+
setCollections(cacheEntry.collections);
|
|
5189
|
+
setIsLoading(false);
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
setIsLoading(true);
|
|
5194
|
+
try {
|
|
5195
|
+
const existing = collectionsInflight.get(cacheKey);
|
|
5196
|
+
const promise = existing ?? (async () => {
|
|
5197
|
+
const result = await client.catalogue.getCollections();
|
|
5198
|
+
if (!result.ok) {
|
|
5199
|
+
throw result.error;
|
|
5200
|
+
}
|
|
5201
|
+
return result.value;
|
|
5202
|
+
})();
|
|
5203
|
+
if (!existing) {
|
|
5204
|
+
collectionsInflight.set(cacheKey, promise);
|
|
5205
|
+
promise.finally(() => {
|
|
5206
|
+
collectionsInflight.delete(cacheKey);
|
|
5207
|
+
}).catch(() => void 0);
|
|
5208
|
+
}
|
|
5209
|
+
const value = await promise;
|
|
5210
|
+
collectionsCache.set(cacheKey, { collections: value });
|
|
5211
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5212
|
+
setCollections(value);
|
|
5213
|
+
setError(null);
|
|
5214
|
+
}
|
|
5215
|
+
} catch (loadError) {
|
|
5216
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5217
|
+
setError(loadError);
|
|
5218
|
+
}
|
|
5219
|
+
} finally {
|
|
5220
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5221
|
+
setIsLoading(false);
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
},
|
|
5225
|
+
[cacheKey, client, enabled]
|
|
5226
|
+
);
|
|
5227
|
+
react.useEffect(() => {
|
|
5228
|
+
void load(false);
|
|
5229
|
+
}, [load]);
|
|
5230
|
+
const refetch = react.useCallback(async () => {
|
|
5231
|
+
collectionsCache.delete(cacheKey);
|
|
5232
|
+
await load(true);
|
|
5233
|
+
}, [cacheKey, load]);
|
|
5234
|
+
return { collections, isLoading, error, refetch };
|
|
5235
|
+
}
|
|
5236
|
+
var collectionCache = /* @__PURE__ */ new Map();
|
|
5237
|
+
var collectionInflight = /* @__PURE__ */ new Map();
|
|
5238
|
+
function isLikelySlug2(value) {
|
|
5239
|
+
return /^[a-z0-9-]+$/.test(value);
|
|
5240
|
+
}
|
|
5241
|
+
function buildCollectionCacheKey(client, locationId, idOrSlug) {
|
|
5242
|
+
return JSON.stringify({
|
|
5243
|
+
key: client.getPublicKey(),
|
|
5244
|
+
location_id: locationId || "__none__",
|
|
5245
|
+
collection: idOrSlug
|
|
5246
|
+
});
|
|
5247
|
+
}
|
|
5248
|
+
function useCollection(idOrSlug, options = {}) {
|
|
5249
|
+
const context = useOptionalCimplify();
|
|
5250
|
+
const client = options.client ?? context?.client;
|
|
5251
|
+
if (!client) {
|
|
5252
|
+
throw new Error("useCollection must be used within CimplifyProvider or passed { client }.");
|
|
5253
|
+
}
|
|
5254
|
+
const enabled = options.enabled ?? true;
|
|
5255
|
+
const locationId = client.getLocationId();
|
|
5256
|
+
const previousLocationIdRef = react.useRef(locationId);
|
|
5257
|
+
const requestIdRef = react.useRef(0);
|
|
5258
|
+
const normalizedIdOrSlug = react.useMemo(() => (idOrSlug || "").trim(), [idOrSlug]);
|
|
5259
|
+
const cacheKey = react.useMemo(
|
|
5260
|
+
() => buildCollectionCacheKey(client, locationId, normalizedIdOrSlug),
|
|
5261
|
+
[client, locationId, normalizedIdOrSlug]
|
|
5262
|
+
);
|
|
5263
|
+
const cached = collectionCache.get(cacheKey);
|
|
5264
|
+
const [collection, setCollection] = react.useState(cached?.collection ?? null);
|
|
5265
|
+
const [products, setProducts] = react.useState(cached?.products ?? []);
|
|
5266
|
+
const [isLoading, setIsLoading] = react.useState(
|
|
5267
|
+
enabled && normalizedIdOrSlug.length > 0 && !cached
|
|
5268
|
+
);
|
|
5269
|
+
const [error, setError] = react.useState(null);
|
|
5270
|
+
react.useEffect(() => {
|
|
5271
|
+
if (previousLocationIdRef.current !== locationId) {
|
|
5272
|
+
collectionCache.clear();
|
|
5273
|
+
collectionInflight.clear();
|
|
5274
|
+
previousLocationIdRef.current = locationId;
|
|
5275
|
+
}
|
|
5276
|
+
}, [locationId]);
|
|
5277
|
+
const load = react.useCallback(
|
|
5278
|
+
async (force = false) => {
|
|
5279
|
+
if (!enabled || normalizedIdOrSlug.length === 0) {
|
|
5280
|
+
setCollection(null);
|
|
5281
|
+
setProducts([]);
|
|
5282
|
+
setIsLoading(false);
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5286
|
+
setError(null);
|
|
5287
|
+
if (!force) {
|
|
5288
|
+
const cacheEntry = collectionCache.get(cacheKey);
|
|
5289
|
+
if (cacheEntry) {
|
|
5290
|
+
setCollection(cacheEntry.collection);
|
|
5291
|
+
setProducts(cacheEntry.products);
|
|
5292
|
+
setIsLoading(false);
|
|
5293
|
+
return;
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
setIsLoading(true);
|
|
5297
|
+
try {
|
|
5298
|
+
const existing = collectionInflight.get(cacheKey);
|
|
5299
|
+
const promise = existing ?? (async () => {
|
|
5300
|
+
const collectionResult = isLikelySlug2(normalizedIdOrSlug) ? await client.catalogue.getCollectionBySlug(normalizedIdOrSlug) : await client.catalogue.getCollection(normalizedIdOrSlug);
|
|
5301
|
+
if (!collectionResult.ok) {
|
|
5302
|
+
throw collectionResult.error;
|
|
5303
|
+
}
|
|
5304
|
+
const productsResult = await client.catalogue.getCollectionProducts(
|
|
5305
|
+
collectionResult.value.id
|
|
5306
|
+
);
|
|
5307
|
+
if (!productsResult.ok) {
|
|
5308
|
+
throw productsResult.error;
|
|
5309
|
+
}
|
|
5310
|
+
return {
|
|
5311
|
+
collection: collectionResult.value,
|
|
5312
|
+
products: productsResult.value
|
|
5313
|
+
};
|
|
5314
|
+
})();
|
|
5315
|
+
if (!existing) {
|
|
5316
|
+
collectionInflight.set(cacheKey, promise);
|
|
5317
|
+
promise.finally(() => {
|
|
5318
|
+
collectionInflight.delete(cacheKey);
|
|
5319
|
+
}).catch(() => void 0);
|
|
5320
|
+
}
|
|
5321
|
+
const value = await promise;
|
|
5322
|
+
collectionCache.set(cacheKey, value);
|
|
5323
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5324
|
+
setCollection(value.collection);
|
|
5325
|
+
setProducts(value.products);
|
|
5326
|
+
setError(null);
|
|
5327
|
+
}
|
|
5328
|
+
} catch (loadError) {
|
|
5329
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5330
|
+
setError(loadError);
|
|
5331
|
+
}
|
|
5332
|
+
} finally {
|
|
5333
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5334
|
+
setIsLoading(false);
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
},
|
|
5338
|
+
[cacheKey, client, enabled, normalizedIdOrSlug]
|
|
5339
|
+
);
|
|
5340
|
+
react.useEffect(() => {
|
|
5341
|
+
void load(false);
|
|
5342
|
+
}, [load]);
|
|
5343
|
+
const refetch = react.useCallback(async () => {
|
|
5344
|
+
collectionCache.delete(cacheKey);
|
|
5345
|
+
await load(true);
|
|
5346
|
+
}, [cacheKey, load]);
|
|
5347
|
+
return { collection, products, isLoading, error, refetch };
|
|
5348
|
+
}
|
|
5349
|
+
var bundleCache = /* @__PURE__ */ new Map();
|
|
5350
|
+
var bundleInflight = /* @__PURE__ */ new Map();
|
|
5351
|
+
function isLikelySlug3(value) {
|
|
5352
|
+
return /^[a-z0-9-]+$/.test(value);
|
|
5353
|
+
}
|
|
5354
|
+
function buildBundleCacheKey(client, locationId, idOrSlug) {
|
|
5355
|
+
return JSON.stringify({
|
|
5356
|
+
key: client.getPublicKey(),
|
|
5357
|
+
location_id: locationId || "__none__",
|
|
5358
|
+
bundle: idOrSlug
|
|
5359
|
+
});
|
|
5360
|
+
}
|
|
5361
|
+
function useBundle(idOrSlug, options = {}) {
|
|
5362
|
+
const context = useOptionalCimplify();
|
|
5363
|
+
const client = options.client ?? context?.client;
|
|
5364
|
+
if (!client) {
|
|
5365
|
+
throw new Error("useBundle must be used within CimplifyProvider or passed { client }.");
|
|
5366
|
+
}
|
|
5367
|
+
const enabled = options.enabled ?? true;
|
|
5368
|
+
const locationId = client.getLocationId();
|
|
5369
|
+
const previousLocationIdRef = react.useRef(locationId);
|
|
5370
|
+
const requestIdRef = react.useRef(0);
|
|
5371
|
+
const normalizedIdOrSlug = react.useMemo(() => (idOrSlug || "").trim(), [idOrSlug]);
|
|
5372
|
+
const cacheKey = react.useMemo(
|
|
5373
|
+
() => buildBundleCacheKey(client, locationId, normalizedIdOrSlug),
|
|
5374
|
+
[client, locationId, normalizedIdOrSlug]
|
|
5375
|
+
);
|
|
5376
|
+
const cached = bundleCache.get(cacheKey);
|
|
5377
|
+
const [bundle, setBundle] = react.useState(cached?.bundle ?? null);
|
|
5378
|
+
const [isLoading, setIsLoading] = react.useState(
|
|
5379
|
+
enabled && normalizedIdOrSlug.length > 0 && !cached
|
|
5380
|
+
);
|
|
5381
|
+
const [error, setError] = react.useState(null);
|
|
5382
|
+
react.useEffect(() => {
|
|
5383
|
+
if (previousLocationIdRef.current !== locationId) {
|
|
5384
|
+
bundleCache.clear();
|
|
5385
|
+
bundleInflight.clear();
|
|
5386
|
+
previousLocationIdRef.current = locationId;
|
|
5387
|
+
}
|
|
5388
|
+
}, [locationId]);
|
|
5389
|
+
const load = react.useCallback(
|
|
5390
|
+
async (force = false) => {
|
|
5391
|
+
if (!enabled || normalizedIdOrSlug.length === 0) {
|
|
5392
|
+
setBundle(null);
|
|
5393
|
+
setIsLoading(false);
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5397
|
+
setError(null);
|
|
5398
|
+
if (!force) {
|
|
5399
|
+
const cacheEntry = bundleCache.get(cacheKey);
|
|
5400
|
+
if (cacheEntry) {
|
|
5401
|
+
setBundle(cacheEntry.bundle);
|
|
5402
|
+
setIsLoading(false);
|
|
5403
|
+
return;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
setIsLoading(true);
|
|
5407
|
+
try {
|
|
5408
|
+
const existing = bundleInflight.get(cacheKey);
|
|
5409
|
+
const promise = existing ?? (async () => {
|
|
5410
|
+
const result = isLikelySlug3(normalizedIdOrSlug) ? await client.catalogue.getBundleBySlug(normalizedIdOrSlug) : await client.catalogue.getBundle(normalizedIdOrSlug);
|
|
5411
|
+
if (!result.ok) {
|
|
5412
|
+
throw result.error;
|
|
5413
|
+
}
|
|
5414
|
+
return result.value;
|
|
5415
|
+
})();
|
|
5416
|
+
if (!existing) {
|
|
5417
|
+
bundleInflight.set(cacheKey, promise);
|
|
5418
|
+
promise.finally(() => {
|
|
5419
|
+
bundleInflight.delete(cacheKey);
|
|
5420
|
+
}).catch(() => void 0);
|
|
5421
|
+
}
|
|
5422
|
+
const value = await promise;
|
|
5423
|
+
bundleCache.set(cacheKey, { bundle: value });
|
|
5424
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5425
|
+
setBundle(value);
|
|
5426
|
+
setError(null);
|
|
5427
|
+
}
|
|
5428
|
+
} catch (loadError) {
|
|
5429
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5430
|
+
setError(loadError);
|
|
5431
|
+
}
|
|
5432
|
+
} finally {
|
|
5433
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5434
|
+
setIsLoading(false);
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
},
|
|
5438
|
+
[cacheKey, client, enabled, normalizedIdOrSlug]
|
|
5439
|
+
);
|
|
5440
|
+
react.useEffect(() => {
|
|
5441
|
+
void load(false);
|
|
5442
|
+
}, [load]);
|
|
5443
|
+
const refetch = react.useCallback(async () => {
|
|
5444
|
+
bundleCache.delete(cacheKey);
|
|
5445
|
+
await load(true);
|
|
5446
|
+
}, [cacheKey, load]);
|
|
5447
|
+
return { bundle, isLoading, error, refetch };
|
|
5448
|
+
}
|
|
5449
|
+
var compositeCache = /* @__PURE__ */ new Map();
|
|
5450
|
+
var compositeInflight = /* @__PURE__ */ new Map();
|
|
5451
|
+
function shouldFetchByProductId(idOrProductId, byProductId) {
|
|
5452
|
+
if (typeof byProductId === "boolean") {
|
|
5453
|
+
return byProductId;
|
|
5454
|
+
}
|
|
5455
|
+
return idOrProductId.startsWith("prod_");
|
|
5456
|
+
}
|
|
5457
|
+
function buildCompositeCacheKey(client, locationId, idOrProductId, byProductId) {
|
|
5458
|
+
return JSON.stringify({
|
|
5459
|
+
key: client.getPublicKey(),
|
|
5460
|
+
location_id: locationId || "__none__",
|
|
5461
|
+
composite: idOrProductId,
|
|
5462
|
+
by_product_id: byProductId
|
|
5463
|
+
});
|
|
5464
|
+
}
|
|
5465
|
+
function useComposite(idOrProductId, options = {}) {
|
|
5466
|
+
const context = useOptionalCimplify();
|
|
5467
|
+
const client = options.client ?? context?.client;
|
|
5468
|
+
if (!client) {
|
|
5469
|
+
throw new Error("useComposite must be used within CimplifyProvider or passed { client }.");
|
|
5470
|
+
}
|
|
5471
|
+
const enabled = options.enabled ?? true;
|
|
5472
|
+
const locationId = client.getLocationId();
|
|
5473
|
+
const previousLocationIdRef = react.useRef(locationId);
|
|
5474
|
+
const requestIdRef = react.useRef(0);
|
|
5475
|
+
const priceRequestIdRef = react.useRef(0);
|
|
5476
|
+
const normalizedIdOrProductId = react.useMemo(
|
|
5477
|
+
() => (idOrProductId || "").trim(),
|
|
5478
|
+
[idOrProductId]
|
|
5479
|
+
);
|
|
5480
|
+
const byProductId = react.useMemo(
|
|
5481
|
+
() => shouldFetchByProductId(normalizedIdOrProductId, options.byProductId),
|
|
5482
|
+
[normalizedIdOrProductId, options.byProductId]
|
|
5483
|
+
);
|
|
5484
|
+
const cacheKey = react.useMemo(
|
|
5485
|
+
() => buildCompositeCacheKey(client, locationId, normalizedIdOrProductId, byProductId),
|
|
5486
|
+
[byProductId, client, locationId, normalizedIdOrProductId]
|
|
5487
|
+
);
|
|
5488
|
+
const cached = compositeCache.get(cacheKey);
|
|
5489
|
+
const [composite, setComposite] = react.useState(cached?.composite ?? null);
|
|
5490
|
+
const [isLoading, setIsLoading] = react.useState(
|
|
5491
|
+
enabled && normalizedIdOrProductId.length > 0 && !cached
|
|
5492
|
+
);
|
|
5493
|
+
const [error, setError] = react.useState(null);
|
|
5494
|
+
const [priceResult, setPriceResult] = react.useState(null);
|
|
5495
|
+
const [isPriceLoading, setIsPriceLoading] = react.useState(false);
|
|
5496
|
+
react.useEffect(() => {
|
|
5497
|
+
if (previousLocationIdRef.current !== locationId) {
|
|
5498
|
+
compositeCache.clear();
|
|
5499
|
+
compositeInflight.clear();
|
|
5500
|
+
previousLocationIdRef.current = locationId;
|
|
5501
|
+
}
|
|
5502
|
+
}, [locationId]);
|
|
5503
|
+
const load = react.useCallback(
|
|
5504
|
+
async (force = false) => {
|
|
5505
|
+
if (!enabled || normalizedIdOrProductId.length === 0) {
|
|
5506
|
+
setComposite(null);
|
|
5507
|
+
setPriceResult(null);
|
|
5508
|
+
setIsLoading(false);
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5512
|
+
setError(null);
|
|
5513
|
+
if (!force) {
|
|
5514
|
+
const cacheEntry = compositeCache.get(cacheKey);
|
|
5515
|
+
if (cacheEntry) {
|
|
5516
|
+
setComposite(cacheEntry.composite);
|
|
5517
|
+
setIsLoading(false);
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
setIsLoading(true);
|
|
5522
|
+
try {
|
|
5523
|
+
const existing = compositeInflight.get(cacheKey);
|
|
5524
|
+
const promise = existing ?? (async () => {
|
|
5525
|
+
const result = byProductId ? await client.catalogue.getCompositeByProductId(normalizedIdOrProductId) : await client.catalogue.getComposite(normalizedIdOrProductId);
|
|
5526
|
+
if (!result.ok) {
|
|
5527
|
+
throw result.error;
|
|
5528
|
+
}
|
|
5529
|
+
return result.value;
|
|
5530
|
+
})();
|
|
5531
|
+
if (!existing) {
|
|
5532
|
+
compositeInflight.set(cacheKey, promise);
|
|
5533
|
+
promise.finally(() => {
|
|
5534
|
+
compositeInflight.delete(cacheKey);
|
|
5535
|
+
}).catch(() => void 0);
|
|
5536
|
+
}
|
|
5537
|
+
const value = await promise;
|
|
5538
|
+
compositeCache.set(cacheKey, { composite: value });
|
|
5539
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5540
|
+
setComposite(value);
|
|
5541
|
+
setPriceResult(null);
|
|
5542
|
+
setError(null);
|
|
5543
|
+
}
|
|
5544
|
+
} catch (loadError) {
|
|
5545
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5546
|
+
setError(loadError);
|
|
5547
|
+
}
|
|
5548
|
+
} finally {
|
|
5549
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5550
|
+
setIsLoading(false);
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
},
|
|
5554
|
+
[byProductId, cacheKey, client, enabled, normalizedIdOrProductId]
|
|
5555
|
+
);
|
|
5556
|
+
react.useEffect(() => {
|
|
5557
|
+
void load(false);
|
|
5558
|
+
}, [load]);
|
|
5559
|
+
const calculatePrice = react.useCallback(
|
|
5560
|
+
async (selections, overrideLocationId) => {
|
|
5561
|
+
if (!composite) {
|
|
5562
|
+
return null;
|
|
5563
|
+
}
|
|
5564
|
+
const nextRequestId = ++priceRequestIdRef.current;
|
|
5565
|
+
setIsPriceLoading(true);
|
|
5566
|
+
try {
|
|
5567
|
+
const result = await client.catalogue.calculateCompositePrice(
|
|
5568
|
+
composite.id,
|
|
5569
|
+
selections,
|
|
5570
|
+
overrideLocationId
|
|
5571
|
+
);
|
|
5572
|
+
if (!result.ok) {
|
|
5573
|
+
throw result.error;
|
|
5574
|
+
}
|
|
5575
|
+
if (nextRequestId === priceRequestIdRef.current) {
|
|
5576
|
+
setPriceResult(result.value);
|
|
5577
|
+
setError(null);
|
|
5578
|
+
}
|
|
5579
|
+
return result.value;
|
|
5580
|
+
} catch (loadError) {
|
|
5581
|
+
if (nextRequestId === priceRequestIdRef.current) {
|
|
5582
|
+
setError(loadError);
|
|
5583
|
+
}
|
|
5584
|
+
return null;
|
|
5585
|
+
} finally {
|
|
5586
|
+
if (nextRequestId === priceRequestIdRef.current) {
|
|
5587
|
+
setIsPriceLoading(false);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
},
|
|
5591
|
+
[client, composite]
|
|
5592
|
+
);
|
|
5593
|
+
const refetch = react.useCallback(async () => {
|
|
5594
|
+
compositeCache.delete(cacheKey);
|
|
5595
|
+
await load(true);
|
|
5596
|
+
}, [cacheKey, load]);
|
|
5597
|
+
return { composite, isLoading, error, refetch, calculatePrice, priceResult, isPriceLoading };
|
|
5598
|
+
}
|
|
5599
|
+
function useSearch(options = {}) {
|
|
5600
|
+
const context = useOptionalCimplify();
|
|
5601
|
+
const client = options.client ?? context?.client;
|
|
5602
|
+
if (!client) {
|
|
5603
|
+
throw new Error("useSearch must be used within CimplifyProvider or passed { client }.");
|
|
5604
|
+
}
|
|
5605
|
+
const minLength = Math.max(0, options.minLength ?? 2);
|
|
5606
|
+
const debounceMs = Math.max(0, options.debounceMs ?? 300);
|
|
5607
|
+
const limit = Math.max(1, options.limit ?? 20);
|
|
5608
|
+
const [query, setQueryState] = react.useState("");
|
|
5609
|
+
const [results, setResults] = react.useState([]);
|
|
5610
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
5611
|
+
const [error, setError] = react.useState(null);
|
|
5612
|
+
const requestIdRef = react.useRef(0);
|
|
5613
|
+
const timerRef = react.useRef(null);
|
|
5614
|
+
react.useEffect(() => {
|
|
5615
|
+
if (timerRef.current) {
|
|
5616
|
+
clearTimeout(timerRef.current);
|
|
5617
|
+
timerRef.current = null;
|
|
5618
|
+
}
|
|
5619
|
+
const trimmedQuery = query.trim();
|
|
5620
|
+
if (trimmedQuery.length < minLength) {
|
|
5621
|
+
setResults([]);
|
|
5622
|
+
setError(null);
|
|
5623
|
+
setIsLoading(false);
|
|
5624
|
+
return;
|
|
5625
|
+
}
|
|
5626
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5627
|
+
setError(null);
|
|
5628
|
+
setIsLoading(true);
|
|
5629
|
+
timerRef.current = setTimeout(() => {
|
|
5630
|
+
void (async () => {
|
|
5631
|
+
try {
|
|
5632
|
+
const result = await client.catalogue.searchProducts(trimmedQuery, {
|
|
5633
|
+
limit,
|
|
5634
|
+
category: options.category
|
|
5635
|
+
});
|
|
5636
|
+
if (!result.ok) {
|
|
5637
|
+
throw result.error;
|
|
5638
|
+
}
|
|
5639
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5640
|
+
setResults(result.value);
|
|
5641
|
+
setError(null);
|
|
5642
|
+
}
|
|
5643
|
+
} catch (loadError) {
|
|
5644
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5645
|
+
setError(loadError);
|
|
5646
|
+
}
|
|
5647
|
+
} finally {
|
|
5648
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5649
|
+
setIsLoading(false);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
})();
|
|
5653
|
+
}, debounceMs);
|
|
5654
|
+
return () => {
|
|
5655
|
+
if (timerRef.current) {
|
|
5656
|
+
clearTimeout(timerRef.current);
|
|
5657
|
+
timerRef.current = null;
|
|
5658
|
+
}
|
|
5659
|
+
};
|
|
5660
|
+
}, [client, debounceMs, limit, minLength, options.category, query]);
|
|
5661
|
+
const setQuery = react.useCallback((nextQuery) => {
|
|
5662
|
+
setQueryState(nextQuery);
|
|
5663
|
+
}, []);
|
|
5664
|
+
const clear = react.useCallback(() => {
|
|
5665
|
+
requestIdRef.current += 1;
|
|
5666
|
+
if (timerRef.current) {
|
|
5667
|
+
clearTimeout(timerRef.current);
|
|
5668
|
+
timerRef.current = null;
|
|
5669
|
+
}
|
|
5670
|
+
setQueryState("");
|
|
5671
|
+
setResults([]);
|
|
5672
|
+
setError(null);
|
|
5673
|
+
setIsLoading(false);
|
|
5674
|
+
}, []);
|
|
5675
|
+
return { results, isLoading, error, query, setQuery, clear };
|
|
5676
|
+
}
|
|
5677
|
+
var quoteCache = /* @__PURE__ */ new Map();
|
|
5678
|
+
var quoteInflight = /* @__PURE__ */ new Map();
|
|
5679
|
+
function buildQuoteCacheKey(client, locationId, inputSignature) {
|
|
5680
|
+
return JSON.stringify({
|
|
5681
|
+
key: client.getPublicKey(),
|
|
5682
|
+
location_id: locationId || "__none__",
|
|
5683
|
+
input: inputSignature
|
|
5684
|
+
});
|
|
5685
|
+
}
|
|
5686
|
+
function isQuoteExpired(quote) {
|
|
5687
|
+
if (!quote?.expires_at) {
|
|
5688
|
+
return false;
|
|
5689
|
+
}
|
|
5690
|
+
const expiresAt = Date.parse(quote.expires_at);
|
|
5691
|
+
if (!Number.isFinite(expiresAt)) {
|
|
5692
|
+
return false;
|
|
5693
|
+
}
|
|
5694
|
+
return expiresAt <= Date.now();
|
|
5695
|
+
}
|
|
5696
|
+
function normalizeInput(input, fallbackLocationId) {
|
|
5697
|
+
const productId = input.productId.trim();
|
|
5698
|
+
const variantId = input.variantId?.trim();
|
|
5699
|
+
const locationId = input.locationId?.trim() || fallbackLocationId || void 0;
|
|
5700
|
+
return {
|
|
5701
|
+
product_id: productId,
|
|
5702
|
+
variant_id: variantId && variantId.length > 0 ? variantId : void 0,
|
|
5703
|
+
location_id: locationId && locationId.length > 0 ? locationId : void 0,
|
|
5704
|
+
quantity: input.quantity,
|
|
5705
|
+
add_on_option_ids: input.addOnOptionIds,
|
|
5706
|
+
bundle_selections: input.bundleSelections,
|
|
5707
|
+
composite_selections: input.compositeSelections
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
function useQuote(input, options = {}) {
|
|
5711
|
+
const context = useOptionalCimplify();
|
|
5712
|
+
const client = options.client ?? context?.client;
|
|
5713
|
+
if (!client) {
|
|
5714
|
+
throw new Error("useQuote must be used within CimplifyProvider or passed { client }.");
|
|
5715
|
+
}
|
|
5716
|
+
const enabled = options.enabled ?? true;
|
|
5717
|
+
const autoRefresh = options.autoRefresh ?? true;
|
|
5718
|
+
const refreshBeforeExpiryMs = Math.max(0, options.refreshBeforeExpiryMs ?? 3e4);
|
|
5719
|
+
const locationId = client.getLocationId();
|
|
5720
|
+
const requestIdRef = react.useRef(0);
|
|
5721
|
+
const refreshTimerRef = react.useRef(null);
|
|
5722
|
+
const expiryTimerRef = react.useRef(null);
|
|
5723
|
+
const inputSignature = react.useMemo(() => JSON.stringify(input ?? null), [input]);
|
|
5724
|
+
const normalizedInput = react.useMemo(() => {
|
|
5725
|
+
if (!input) {
|
|
5726
|
+
return null;
|
|
5727
|
+
}
|
|
5728
|
+
const normalized = normalizeInput(input, locationId);
|
|
5729
|
+
return normalized.product_id.length > 0 ? normalized : null;
|
|
5730
|
+
}, [inputSignature, locationId]);
|
|
5731
|
+
const cacheKey = react.useMemo(
|
|
5732
|
+
() => buildQuoteCacheKey(client, locationId, inputSignature),
|
|
5733
|
+
[client, inputSignature, locationId]
|
|
5734
|
+
);
|
|
5735
|
+
const cached = quoteCache.get(cacheKey);
|
|
5736
|
+
const [quote, setQuote] = react.useState(cached?.quote ?? null);
|
|
5737
|
+
const [isLoading, setIsLoading] = react.useState(enabled && normalizedInput !== null && !cached);
|
|
5738
|
+
const [error, setError] = react.useState(null);
|
|
5739
|
+
const [isExpired, setIsExpired] = react.useState(isQuoteExpired(cached?.quote ?? null));
|
|
5740
|
+
const [messages, setMessages] = react.useState(cached?.quote?.ui_messages ?? []);
|
|
5741
|
+
const load = react.useCallback(
|
|
5742
|
+
async (force = false) => {
|
|
5743
|
+
if (!enabled || !normalizedInput) {
|
|
5744
|
+
setQuote(null);
|
|
5745
|
+
setMessages([]);
|
|
5746
|
+
setIsExpired(false);
|
|
5747
|
+
setError(null);
|
|
5748
|
+
setIsLoading(false);
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5752
|
+
setError(null);
|
|
5753
|
+
if (!force) {
|
|
5754
|
+
const cacheEntry = quoteCache.get(cacheKey);
|
|
5755
|
+
if (cacheEntry) {
|
|
5756
|
+
setQuote(cacheEntry.quote);
|
|
5757
|
+
setMessages(cacheEntry.quote?.ui_messages ?? []);
|
|
5758
|
+
setIsExpired(isQuoteExpired(cacheEntry.quote));
|
|
5759
|
+
setIsLoading(false);
|
|
5760
|
+
return;
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
setIsLoading(true);
|
|
5764
|
+
try {
|
|
5765
|
+
const existing = quoteInflight.get(cacheKey);
|
|
5766
|
+
const promise = existing ?? (async () => {
|
|
5767
|
+
const result = await client.catalogue.fetchQuote(normalizedInput);
|
|
5768
|
+
if (!result.ok) {
|
|
5769
|
+
throw result.error;
|
|
5770
|
+
}
|
|
5771
|
+
return result.value;
|
|
5772
|
+
})();
|
|
5773
|
+
if (!existing) {
|
|
5774
|
+
quoteInflight.set(cacheKey, promise);
|
|
5775
|
+
promise.finally(() => {
|
|
5776
|
+
quoteInflight.delete(cacheKey);
|
|
5777
|
+
}).catch(() => void 0);
|
|
5778
|
+
}
|
|
5779
|
+
const value = await promise;
|
|
5780
|
+
quoteCache.set(cacheKey, { quote: value });
|
|
5781
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5782
|
+
setQuote(value);
|
|
5783
|
+
setMessages(value.ui_messages ?? []);
|
|
5784
|
+
setIsExpired(isQuoteExpired(value));
|
|
5785
|
+
setError(null);
|
|
5786
|
+
}
|
|
5787
|
+
} catch (loadError) {
|
|
5788
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5789
|
+
setError(loadError);
|
|
5790
|
+
}
|
|
5791
|
+
} finally {
|
|
5792
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5793
|
+
setIsLoading(false);
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
},
|
|
5797
|
+
[cacheKey, client, enabled, normalizedInput]
|
|
5798
|
+
);
|
|
5799
|
+
react.useEffect(() => {
|
|
5800
|
+
void load(false);
|
|
5801
|
+
}, [load]);
|
|
5802
|
+
const refresh = react.useCallback(async () => {
|
|
5803
|
+
if (!enabled || !normalizedInput) {
|
|
5804
|
+
return;
|
|
5805
|
+
}
|
|
5806
|
+
if (!quote?.quote_id) {
|
|
5807
|
+
await load(true);
|
|
5808
|
+
return;
|
|
5809
|
+
}
|
|
5810
|
+
const nextRequestId = ++requestIdRef.current;
|
|
5811
|
+
setError(null);
|
|
5812
|
+
setIsLoading(true);
|
|
5813
|
+
try {
|
|
5814
|
+
const result = await client.catalogue.refreshQuote({
|
|
5815
|
+
quote_id: quote.quote_id,
|
|
5816
|
+
...normalizedInput
|
|
5817
|
+
});
|
|
5818
|
+
if (!result.ok) {
|
|
5819
|
+
throw result.error;
|
|
5820
|
+
}
|
|
5821
|
+
const refreshed = result.value.quote;
|
|
5822
|
+
quoteCache.set(cacheKey, { quote: refreshed });
|
|
5823
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5824
|
+
setQuote(refreshed);
|
|
5825
|
+
setMessages(refreshed.ui_messages ?? []);
|
|
5826
|
+
setIsExpired(isQuoteExpired(refreshed));
|
|
5827
|
+
setError(null);
|
|
5828
|
+
}
|
|
5829
|
+
} catch (refreshError) {
|
|
5830
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5831
|
+
setError(refreshError);
|
|
5832
|
+
}
|
|
5833
|
+
} finally {
|
|
5834
|
+
if (nextRequestId === requestIdRef.current) {
|
|
5835
|
+
setIsLoading(false);
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
}, [cacheKey, client, enabled, load, normalizedInput, quote]);
|
|
5839
|
+
react.useEffect(() => {
|
|
5840
|
+
if (expiryTimerRef.current) {
|
|
5841
|
+
clearTimeout(expiryTimerRef.current);
|
|
5842
|
+
expiryTimerRef.current = null;
|
|
5843
|
+
}
|
|
5844
|
+
const expiresAt = quote?.expires_at ? Date.parse(quote.expires_at) : NaN;
|
|
5845
|
+
if (!Number.isFinite(expiresAt)) {
|
|
5846
|
+
setIsExpired(false);
|
|
5847
|
+
return;
|
|
5848
|
+
}
|
|
5849
|
+
const expired = expiresAt <= Date.now();
|
|
5850
|
+
setIsExpired(expired);
|
|
5851
|
+
if (!expired) {
|
|
5852
|
+
expiryTimerRef.current = setTimeout(() => {
|
|
5853
|
+
setIsExpired(true);
|
|
5854
|
+
}, Math.max(0, expiresAt - Date.now()));
|
|
5855
|
+
}
|
|
5856
|
+
return () => {
|
|
5857
|
+
if (expiryTimerRef.current) {
|
|
5858
|
+
clearTimeout(expiryTimerRef.current);
|
|
5859
|
+
expiryTimerRef.current = null;
|
|
5860
|
+
}
|
|
5861
|
+
};
|
|
5862
|
+
}, [quote?.expires_at, quote?.quote_id]);
|
|
5863
|
+
react.useEffect(() => {
|
|
5864
|
+
if (refreshTimerRef.current) {
|
|
5865
|
+
clearTimeout(refreshTimerRef.current);
|
|
5866
|
+
refreshTimerRef.current = null;
|
|
5867
|
+
}
|
|
5868
|
+
if (!autoRefresh || !enabled || !quote?.expires_at) {
|
|
5869
|
+
return;
|
|
5870
|
+
}
|
|
5871
|
+
const expiresAt = Date.parse(quote.expires_at);
|
|
5872
|
+
if (!Number.isFinite(expiresAt)) {
|
|
5873
|
+
return;
|
|
5874
|
+
}
|
|
5875
|
+
const delay = Math.max(0, expiresAt - Date.now() - refreshBeforeExpiryMs);
|
|
5876
|
+
refreshTimerRef.current = setTimeout(() => {
|
|
5877
|
+
void refresh();
|
|
5878
|
+
}, delay);
|
|
5879
|
+
return () => {
|
|
5880
|
+
if (refreshTimerRef.current) {
|
|
5881
|
+
clearTimeout(refreshTimerRef.current);
|
|
5882
|
+
refreshTimerRef.current = null;
|
|
5883
|
+
}
|
|
5884
|
+
};
|
|
5885
|
+
}, [autoRefresh, enabled, quote?.expires_at, quote?.quote_id, refresh, refreshBeforeExpiryMs]);
|
|
5886
|
+
return { quote, isLoading, error, refresh, isExpired, messages };
|
|
5887
|
+
}
|
|
5088
5888
|
var ElementsContext = react.createContext({
|
|
5089
5889
|
elements: null,
|
|
5090
5890
|
isReady: false
|
|
@@ -5254,10 +6054,14 @@ exports.CimplifyProvider = CimplifyProvider;
|
|
|
5254
6054
|
exports.ElementsProvider = ElementsProvider;
|
|
5255
6055
|
exports.PaymentElement = PaymentElement;
|
|
5256
6056
|
exports.useAds = useAds;
|
|
6057
|
+
exports.useBundle = useBundle;
|
|
5257
6058
|
exports.useCart = useCart;
|
|
5258
6059
|
exports.useCategories = useCategories;
|
|
5259
6060
|
exports.useCheckout = useCheckout;
|
|
5260
6061
|
exports.useCimplify = useCimplify;
|
|
6062
|
+
exports.useCollection = useCollection;
|
|
6063
|
+
exports.useCollections = useCollections;
|
|
6064
|
+
exports.useComposite = useComposite;
|
|
5261
6065
|
exports.useElements = useElements;
|
|
5262
6066
|
exports.useElementsReady = useElementsReady;
|
|
5263
6067
|
exports.useLocations = useLocations;
|
|
@@ -5265,3 +6069,5 @@ exports.useOptionalCimplify = useOptionalCimplify;
|
|
|
5265
6069
|
exports.useOrder = useOrder;
|
|
5266
6070
|
exports.useProduct = useProduct;
|
|
5267
6071
|
exports.useProducts = useProducts;
|
|
6072
|
+
exports.useQuote = useQuote;
|
|
6073
|
+
exports.useSearch = useSearch;
|