@behio/storefront-sdk 0.37.0 → 0.41.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/dist/{chunk-QKEW2EU5.js → chunk-3TPJG2WV.js} +589 -145
- package/dist/{chunk-5C6MNGGB.mjs → chunk-QOZYQMK2.mjs} +587 -143
- package/dist/{client-BgYibdTK.d.mts → client-sN7fZvjG.d.mts} +363 -7
- package/dist/{client-BgYibdTK.d.ts → client-sN7fZvjG.d.ts} +363 -7
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +142 -40
- package/dist/index.mjs +111 -9
- package/dist/next.d.mts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +2 -2
- package/dist/next.mjs +1 -1
- package/dist/react.d.mts +3026 -4
- package/dist/react.d.ts +3026 -4
- package/dist/react.js +2612 -467
- package/dist/react.mjs +2128 -86
- package/package.json +2 -2
- package/dist/chunk-CZRSJULD.js +0 -118
- package/dist/chunk-QUU76QUB.mjs +0 -118
package/dist/react.mjs
CHANGED
|
@@ -1,19 +1,1702 @@
|
|
|
1
|
-
|
|
2
|
-
formatPrice,
|
|
3
|
-
generateVisitorId,
|
|
4
|
-
getStoredVisitorId,
|
|
5
|
-
grantAnalyticsConsent,
|
|
6
|
-
revokeAnalyticsConsent,
|
|
7
|
-
trackEcommerceEvent
|
|
8
|
-
} from "./chunk-QUU76QUB.mjs";
|
|
9
|
-
import {
|
|
10
|
-
BehioStorefront
|
|
11
|
-
} from "./chunk-5C6MNGGB.mjs";
|
|
1
|
+
"use client";
|
|
12
2
|
|
|
13
3
|
// src/react/provider.tsx
|
|
14
4
|
import { useRef, useEffect, useMemo, useState, useCallback } from "react";
|
|
15
5
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
16
6
|
|
|
7
|
+
// src/types.ts
|
|
8
|
+
var BehioApiError = class _BehioApiError extends Error {
|
|
9
|
+
constructor(status, body, message) {
|
|
10
|
+
super(message || `API Error ${status}`);
|
|
11
|
+
this.name = "BehioApiError";
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
this.code = _BehioApiError.resolveCode(status, body);
|
|
15
|
+
this.isRetryable = status >= 500 || status === 429;
|
|
16
|
+
}
|
|
17
|
+
static resolveCode(status, body) {
|
|
18
|
+
if (status === 401) return "UNAUTHORIZED";
|
|
19
|
+
if (status === 403) return "FORBIDDEN";
|
|
20
|
+
if (status === 404) return "NOT_FOUND";
|
|
21
|
+
if (status === 409) return "EMAIL_ALREADY_EXISTS";
|
|
22
|
+
if (status === 429) return "RATE_LIMITED";
|
|
23
|
+
if (status >= 500) return "INTERNAL_ERROR";
|
|
24
|
+
const msg = (body?.message || "").toLowerCase();
|
|
25
|
+
if (msg.includes("invalid") && msg.includes("password"))
|
|
26
|
+
return "INVALID_CREDENTIALS";
|
|
27
|
+
if (msg.includes("invalid") && msg.includes("email"))
|
|
28
|
+
return "INVALID_CREDENTIALS";
|
|
29
|
+
if (msg.includes("cart") && msg.includes("empty")) return "CART_EMPTY";
|
|
30
|
+
if (msg.includes("product") && msg.includes("not found"))
|
|
31
|
+
return "PRODUCT_NOT_FOUND";
|
|
32
|
+
if (msg.includes("discount") && msg.includes("expired"))
|
|
33
|
+
return "DISCOUNT_EXPIRED";
|
|
34
|
+
if (msg.includes("discount") && msg.includes("invalid"))
|
|
35
|
+
return "INVALID_DISCOUNT";
|
|
36
|
+
if (msg.includes("token") && msg.includes("expired"))
|
|
37
|
+
return "TOKEN_EXPIRED";
|
|
38
|
+
if (msg.includes("cancel")) return "ORDER_NOT_CANCELLABLE";
|
|
39
|
+
if (status === 400) return "VALIDATION_ERROR";
|
|
40
|
+
return "UNKNOWN";
|
|
41
|
+
}
|
|
42
|
+
/** Check if this is a specific error type */
|
|
43
|
+
is(code) {
|
|
44
|
+
return this.code === code;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var BehioNetworkError = class extends Error {
|
|
48
|
+
constructor(message, isTimeout = false) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.isRetryable = true;
|
|
51
|
+
this.name = "BehioNetworkError";
|
|
52
|
+
this.code = isTimeout ? "TIMEOUT" : "NETWORK_ERROR";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function ok(data) {
|
|
56
|
+
return { data, error: null };
|
|
57
|
+
}
|
|
58
|
+
function toSdkError(err) {
|
|
59
|
+
if (err instanceof BehioApiError) {
|
|
60
|
+
return {
|
|
61
|
+
code: err.code,
|
|
62
|
+
message: err.message,
|
|
63
|
+
status: err.status,
|
|
64
|
+
body: err.body,
|
|
65
|
+
isRetryable: err.isRetryable,
|
|
66
|
+
cause: err
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (err instanceof BehioNetworkError) {
|
|
70
|
+
return {
|
|
71
|
+
code: err.code,
|
|
72
|
+
message: err.message,
|
|
73
|
+
status: null,
|
|
74
|
+
isRetryable: err.isRetryable,
|
|
75
|
+
cause: err
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
code: "UNKNOWN",
|
|
80
|
+
message: err instanceof Error ? err.message : String(err),
|
|
81
|
+
status: null,
|
|
82
|
+
isRetryable: false,
|
|
83
|
+
cause: err
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/client.ts
|
|
88
|
+
var BehioStorefront = class {
|
|
89
|
+
constructor(config) {
|
|
90
|
+
/** Consent-gated persistent visitor id — set by the analytics tracker. */
|
|
91
|
+
this.analyticsVisitorId = null;
|
|
92
|
+
// Token refresh lock
|
|
93
|
+
this.isRefreshing = false;
|
|
94
|
+
this.refreshPromise = null;
|
|
95
|
+
// Event emitter
|
|
96
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
97
|
+
// Interceptors
|
|
98
|
+
this.requestInterceptors = [];
|
|
99
|
+
this.responseInterceptors = [];
|
|
100
|
+
// Rate limit tracking
|
|
101
|
+
this.rateLimitRemaining = null;
|
|
102
|
+
this.rateLimitReset = null;
|
|
103
|
+
this.baseUrl = (config.baseUrl || "https://be.behio.com").replace(
|
|
104
|
+
/\/$/,
|
|
105
|
+
""
|
|
106
|
+
);
|
|
107
|
+
this.apiKey = config.apiKey;
|
|
108
|
+
this.shopDomain = config.shopDomain;
|
|
109
|
+
this.defaultLocale = config.locale;
|
|
110
|
+
this.defaultCurrency = config.currency;
|
|
111
|
+
this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);
|
|
112
|
+
this.timeout = config.timeout ?? 3e4;
|
|
113
|
+
this.retries = config.retries ?? 1;
|
|
114
|
+
this.retryDelay = config.retryDelay ?? 1e3;
|
|
115
|
+
this.catalog = new CatalogModule(this);
|
|
116
|
+
this.auth = new AuthModule(this);
|
|
117
|
+
this.cart = new CartModule(this);
|
|
118
|
+
this.checkout = new CheckoutModule(this);
|
|
119
|
+
this.orders = new OrdersModule(this);
|
|
120
|
+
this.customer = new CustomerModule(this);
|
|
121
|
+
this.pages = new PagesModule(this);
|
|
122
|
+
this.wishlist = new WishlistModule(this);
|
|
123
|
+
this.reviews = new ReviewsModule(this);
|
|
124
|
+
this.returns = new ReturnsModule(this);
|
|
125
|
+
this.consent = new ConsentModule(this);
|
|
126
|
+
this.quotes = new QuotesModule(this);
|
|
127
|
+
this.addresses = new AddressModule(this);
|
|
128
|
+
this.shipping = new ShippingModule(this);
|
|
129
|
+
this.newsletter = new NewsletterModule(this);
|
|
130
|
+
this.subscriptions = new SubscriptionsModule(this);
|
|
131
|
+
this.certificates = new CourseCertificatesModule(this);
|
|
132
|
+
}
|
|
133
|
+
// --- Public methods ---
|
|
134
|
+
/**
|
|
135
|
+
* Called by the analytics tracker when the visitor grants (id) or revokes
|
|
136
|
+
* (null) analytics consent. When set, requests carry the X-Behio-Vid header
|
|
137
|
+
* so the backend can attribute orders to the visitor journey.
|
|
138
|
+
*/
|
|
139
|
+
setAnalyticsVisitorId(id) {
|
|
140
|
+
this.analyticsVisitorId = id;
|
|
141
|
+
}
|
|
142
|
+
/** The consent-gated visitor id, if analytics consent was granted. */
|
|
143
|
+
getAnalyticsVisitorId() {
|
|
144
|
+
return this.analyticsVisitorId;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
|
|
148
|
+
* the interceptor pipeline) so flushes on pagehide still land, and swallows
|
|
149
|
+
* every error — analytics must never break the shop. The client sends no
|
|
150
|
+
* identity; the visitor hash is computed server-side from a daily salt.
|
|
151
|
+
*/
|
|
152
|
+
async sendAnalyticsEvents(input) {
|
|
153
|
+
try {
|
|
154
|
+
const headers = {
|
|
155
|
+
"X-Api-Key": this.apiKey,
|
|
156
|
+
"Content-Type": "application/json"
|
|
157
|
+
};
|
|
158
|
+
if (this.shopDomain) headers["X-Shop-Domain"] = this.shopDomain;
|
|
159
|
+
await fetch(`${this.baseUrl}/storefront/v1/analytics/events`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
keepalive: true,
|
|
162
|
+
headers,
|
|
163
|
+
body: JSON.stringify(input)
|
|
164
|
+
});
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Personal behavioral offers for this visitor (consent-gated id). Returns
|
|
170
|
+
* only the visitor's own offers; requires the analytics visitor id.
|
|
171
|
+
*/
|
|
172
|
+
async getPersonalOffers(visitorId) {
|
|
173
|
+
return this.request("GET", "/offers", { query: { visitorId } });
|
|
174
|
+
}
|
|
175
|
+
/** Email-gate completion: trade an e-mail for the personal discount code. */
|
|
176
|
+
async claimOfferByEmail(offerId, input) {
|
|
177
|
+
return this.request("POST", `/offers/${offerId}/claim-email`, {
|
|
178
|
+
body: input
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** Get basic shop info */
|
|
182
|
+
async getShopInfo() {
|
|
183
|
+
return this.request("GET", "/shop");
|
|
184
|
+
}
|
|
185
|
+
/** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
|
|
186
|
+
async getShopSeo(locale) {
|
|
187
|
+
return this.request("GET", "/shop/seo", {
|
|
188
|
+
query: locale ? { locale } : void 0
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get the merchant-defined scripts (analytics, pixels, verification, custom
|
|
193
|
+
* head/body markup) to inject into the storefront. Returns only enabled
|
|
194
|
+
* entries. Render typed entries into their snippet and RAW verbatim; gate
|
|
195
|
+
* any entry with `consentRequired` behind the visitor's analytics consent.
|
|
196
|
+
*/
|
|
197
|
+
async getShopScripts() {
|
|
198
|
+
return this.request("GET", "/shop/scripts");
|
|
199
|
+
}
|
|
200
|
+
/** Set auth tokens (e.g. from localStorage) */
|
|
201
|
+
setTokens(tokens) {
|
|
202
|
+
this.accessToken = tokens.accessToken;
|
|
203
|
+
this.refreshToken = tokens.refreshToken;
|
|
204
|
+
}
|
|
205
|
+
/** Clear auth tokens */
|
|
206
|
+
clearTokens() {
|
|
207
|
+
this.accessToken = void 0;
|
|
208
|
+
this.refreshToken = void 0;
|
|
209
|
+
}
|
|
210
|
+
/** Get current access token */
|
|
211
|
+
getAccessToken() {
|
|
212
|
+
return this.accessToken;
|
|
213
|
+
}
|
|
214
|
+
/** Get current refresh token */
|
|
215
|
+
getRefreshToken() {
|
|
216
|
+
return this.refreshToken;
|
|
217
|
+
}
|
|
218
|
+
/** Set cart session token (e.g. from cookie) */
|
|
219
|
+
setCartSession(token) {
|
|
220
|
+
this.cartSession = token;
|
|
221
|
+
}
|
|
222
|
+
/** Get cart session token */
|
|
223
|
+
getCartSession() {
|
|
224
|
+
return this.cartSession;
|
|
225
|
+
}
|
|
226
|
+
/** Clear cart session */
|
|
227
|
+
clearCartSession() {
|
|
228
|
+
this.cartSession = void 0;
|
|
229
|
+
}
|
|
230
|
+
// --- Default currency / locale (runtime-switchable) ---
|
|
231
|
+
/**
|
|
232
|
+
* Set the default currency sent on every catalog request (unless a per-call
|
|
233
|
+
* `currency` overrides it). Read live at request time, so changing it takes
|
|
234
|
+
* effect immediately without rebuilding the client. Pass undefined to clear
|
|
235
|
+
* (falls back to the shop's default currency server-side).
|
|
236
|
+
*/
|
|
237
|
+
setCurrency(currency) {
|
|
238
|
+
this.defaultCurrency = currency || void 0;
|
|
239
|
+
}
|
|
240
|
+
/** Get the current default currency, if any. */
|
|
241
|
+
getCurrency() {
|
|
242
|
+
return this.defaultCurrency;
|
|
243
|
+
}
|
|
244
|
+
/** Set the default locale sent on every catalog request (per-call wins). */
|
|
245
|
+
setLocale(locale) {
|
|
246
|
+
this.defaultLocale = locale || void 0;
|
|
247
|
+
}
|
|
248
|
+
/** Get the current default locale, if any. */
|
|
249
|
+
getLocale() {
|
|
250
|
+
return this.defaultLocale;
|
|
251
|
+
}
|
|
252
|
+
// --- Event emitter ---
|
|
253
|
+
/** Subscribe to SDK events. Returns an unsubscribe function. */
|
|
254
|
+
on(event, handler) {
|
|
255
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
256
|
+
this.listeners.get(event).add(handler);
|
|
257
|
+
return () => {
|
|
258
|
+
this.listeners.get(event)?.delete(handler);
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** @internal Emit an event (fire-and-forget, handler errors are swallowed) */
|
|
262
|
+
emit(event, data) {
|
|
263
|
+
this.listeners.get(event)?.forEach((fn) => {
|
|
264
|
+
try {
|
|
265
|
+
fn(data);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// --- Interceptors ---
|
|
271
|
+
/** Add a request interceptor. Returns an unsubscribe function. */
|
|
272
|
+
addRequestInterceptor(fn) {
|
|
273
|
+
this.requestInterceptors.push(fn);
|
|
274
|
+
return () => {
|
|
275
|
+
this.requestInterceptors = this.requestInterceptors.filter(
|
|
276
|
+
(f) => f !== fn
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** Add a response interceptor. Returns an unsubscribe function. */
|
|
281
|
+
addResponseInterceptor(fn) {
|
|
282
|
+
this.responseInterceptors.push(fn);
|
|
283
|
+
return () => {
|
|
284
|
+
this.responseInterceptors = this.responseInterceptors.filter(
|
|
285
|
+
(f) => f !== fn
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
// --- Rate limit ---
|
|
290
|
+
/** Get current rate limit info from latest response headers */
|
|
291
|
+
getRateLimitInfo() {
|
|
292
|
+
return { remaining: this.rateLimitRemaining, reset: this.rateLimitReset };
|
|
293
|
+
}
|
|
294
|
+
// --- Token refresh ---
|
|
295
|
+
async handleTokenRefresh() {
|
|
296
|
+
if (this.isRefreshing) {
|
|
297
|
+
if (this.refreshPromise) await this.refreshPromise;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
this.isRefreshing = true;
|
|
301
|
+
this.refreshPromise = (async () => {
|
|
302
|
+
try {
|
|
303
|
+
const res = await this.auth.refresh();
|
|
304
|
+
if (res.error) {
|
|
305
|
+
this.clearTokens();
|
|
306
|
+
this.emit("auth:token-refresh-failed");
|
|
307
|
+
throw new BehioApiError(401, null, "Token refresh failed");
|
|
308
|
+
}
|
|
309
|
+
this.emit("auth:token-refresh");
|
|
310
|
+
} catch (err) {
|
|
311
|
+
this.clearTokens();
|
|
312
|
+
this.emit("auth:token-refresh-failed");
|
|
313
|
+
if (err instanceof BehioApiError) throw err;
|
|
314
|
+
throw new BehioApiError(401, null, "Token refresh failed");
|
|
315
|
+
} finally {
|
|
316
|
+
this.isRefreshing = false;
|
|
317
|
+
this.refreshPromise = null;
|
|
318
|
+
}
|
|
319
|
+
})();
|
|
320
|
+
return this.refreshPromise;
|
|
321
|
+
}
|
|
322
|
+
// --- Public request wrapper (SdkResult) ---
|
|
323
|
+
/**
|
|
324
|
+
* Every public module method funnels through here. Internally calls
|
|
325
|
+
* `rawRequest` (which throws on failure) and maps thrown errors to
|
|
326
|
+
* `SdkError` so the public surface can return `SdkResult<T>`.
|
|
327
|
+
*
|
|
328
|
+
* @internal — don't call from outside the SDK; use the typed module
|
|
329
|
+
* methods (behio.catalog.*, behio.cart.*, …) instead.
|
|
330
|
+
*/
|
|
331
|
+
async request(method, path, options) {
|
|
332
|
+
try {
|
|
333
|
+
const data = await this.rawRequest(method, path, options);
|
|
334
|
+
return ok(data);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
return { data: null, error: toSdkError(err) };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Binary GET (PDF downloads). Same auth headers as `request()`, but the
|
|
341
|
+
* response is returned as a Blob instead of parsed JSON.
|
|
342
|
+
*
|
|
343
|
+
* @internal — use the typed module methods (behio.certificates.downloadPdf).
|
|
344
|
+
*/
|
|
345
|
+
async requestBlob(path) {
|
|
346
|
+
try {
|
|
347
|
+
const headers = { "X-Api-Key": this.apiKey };
|
|
348
|
+
if (this.accessToken)
|
|
349
|
+
headers.Authorization = `Bearer ${this.accessToken}`;
|
|
350
|
+
const res = await this.fetchFn(`${this.baseUrl}/storefront/v1${path}`, {
|
|
351
|
+
headers
|
|
352
|
+
});
|
|
353
|
+
if (!res.ok) {
|
|
354
|
+
let body = null;
|
|
355
|
+
try {
|
|
356
|
+
body = await res.json();
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
throw new BehioApiError(res.status, body);
|
|
360
|
+
}
|
|
361
|
+
return ok(await res.blob());
|
|
362
|
+
} catch (err) {
|
|
363
|
+
return { data: null, error: toSdkError(err) };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Throws on failure (API error / network / timeout). Kept private so
|
|
368
|
+
* internal auth refresh recursion keeps its existing control flow —
|
|
369
|
+
* public callers must go through `request()` which returns Result.
|
|
370
|
+
*/
|
|
371
|
+
async rawRequest(method, path, options) {
|
|
372
|
+
const params = new URLSearchParams();
|
|
373
|
+
if (options?.query) {
|
|
374
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
375
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
376
|
+
if (Array.isArray(value)) {
|
|
377
|
+
for (const v of value) {
|
|
378
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
379
|
+
params.append(key, String(v));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
params.set(key, String(value));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (this.defaultLocale && !params.has("locale")) {
|
|
388
|
+
params.set("locale", this.defaultLocale);
|
|
389
|
+
}
|
|
390
|
+
if (this.defaultCurrency && !params.has("currency")) {
|
|
391
|
+
params.set("currency", this.defaultCurrency);
|
|
392
|
+
}
|
|
393
|
+
const qs = params.toString();
|
|
394
|
+
const url = `${this.baseUrl}/storefront/v1${path}${qs ? `?${qs}` : ""}`;
|
|
395
|
+
const headers = {
|
|
396
|
+
"X-Api-Key": this.apiKey,
|
|
397
|
+
"Content-Type": "application/json"
|
|
398
|
+
};
|
|
399
|
+
if (this.shopDomain) {
|
|
400
|
+
headers["X-Shop-Domain"] = this.shopDomain;
|
|
401
|
+
}
|
|
402
|
+
if (this.accessToken && options?.auth !== false) {
|
|
403
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
404
|
+
}
|
|
405
|
+
if (this.cartSession) {
|
|
406
|
+
headers["X-Cart-Session"] = this.cartSession;
|
|
407
|
+
}
|
|
408
|
+
if (this.analyticsVisitorId) {
|
|
409
|
+
headers["X-Behio-Vid"] = this.analyticsVisitorId;
|
|
410
|
+
}
|
|
411
|
+
if (options?.headers) {
|
|
412
|
+
Object.assign(headers, options.headers);
|
|
413
|
+
}
|
|
414
|
+
const bodyStr = options?.body ? JSON.stringify(options.body) : void 0;
|
|
415
|
+
let interceptedConfig = {
|
|
416
|
+
url,
|
|
417
|
+
method,
|
|
418
|
+
headers,
|
|
419
|
+
body: bodyStr
|
|
420
|
+
};
|
|
421
|
+
for (const interceptor of this.requestInterceptors) {
|
|
422
|
+
interceptedConfig = await interceptor(interceptedConfig);
|
|
423
|
+
}
|
|
424
|
+
this.emit("request", { method, path });
|
|
425
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
426
|
+
const controller = new AbortController();
|
|
427
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
428
|
+
if (options?.signal) {
|
|
429
|
+
if (options.signal.aborted) {
|
|
430
|
+
clearTimeout(timeoutId);
|
|
431
|
+
throw new BehioNetworkError("Request aborted", false);
|
|
432
|
+
}
|
|
433
|
+
options.signal.addEventListener("abort", () => controller.abort(), {
|
|
434
|
+
once: true
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
let res;
|
|
438
|
+
try {
|
|
439
|
+
res = await this.fetchFn(interceptedConfig.url, {
|
|
440
|
+
method: interceptedConfig.method,
|
|
441
|
+
headers: interceptedConfig.headers,
|
|
442
|
+
body: interceptedConfig.body,
|
|
443
|
+
signal: controller.signal
|
|
444
|
+
});
|
|
445
|
+
} catch (err) {
|
|
446
|
+
clearTimeout(timeoutId);
|
|
447
|
+
const isAbort = err instanceof DOMException && err.name === "AbortError";
|
|
448
|
+
const networkErr = new BehioNetworkError(
|
|
449
|
+
isAbort ? "Request timed out" : err.message || "Network error",
|
|
450
|
+
isAbort
|
|
451
|
+
);
|
|
452
|
+
this.emit("error", networkErr);
|
|
453
|
+
if (attempt < this.retries) {
|
|
454
|
+
await new Promise(
|
|
455
|
+
(r) => setTimeout(r, this.retryDelay * (attempt + 1))
|
|
456
|
+
);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
throw networkErr;
|
|
460
|
+
} finally {
|
|
461
|
+
clearTimeout(timeoutId);
|
|
462
|
+
}
|
|
463
|
+
const remaining = res.headers.get("X-RateLimit-Remaining");
|
|
464
|
+
const reset = res.headers.get("X-RateLimit-Reset");
|
|
465
|
+
if (remaining) this.rateLimitRemaining = parseInt(remaining, 10);
|
|
466
|
+
if (reset) this.rateLimitReset = parseInt(reset, 10);
|
|
467
|
+
if (this.rateLimitRemaining !== null && this.rateLimitRemaining <= 5) {
|
|
468
|
+
this.emit("rate-limit-warning", {
|
|
469
|
+
remaining: this.rateLimitRemaining,
|
|
470
|
+
reset: this.rateLimitReset
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
this.emit("response", { method, path, status: res.status });
|
|
474
|
+
if (!res.ok) {
|
|
475
|
+
const body = await res.json().catch(() => null);
|
|
476
|
+
const apiError = new BehioApiError(
|
|
477
|
+
res.status,
|
|
478
|
+
body,
|
|
479
|
+
body?.message || `API Error ${res.status}`
|
|
480
|
+
);
|
|
481
|
+
if (res.status === 401 && this.refreshToken && options?.auth !== false && !options?._isRetryAfterRefresh) {
|
|
482
|
+
try {
|
|
483
|
+
await this.handleTokenRefresh();
|
|
484
|
+
return this.rawRequest(method, path, {
|
|
485
|
+
...options,
|
|
486
|
+
_isRetryAfterRefresh: true
|
|
487
|
+
});
|
|
488
|
+
} catch {
|
|
489
|
+
this.emit("error", apiError);
|
|
490
|
+
throw apiError;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
this.emit("error", apiError);
|
|
494
|
+
if (attempt < this.retries && apiError.isRetryable) {
|
|
495
|
+
await new Promise(
|
|
496
|
+
(r) => setTimeout(r, this.retryDelay * (attempt + 1))
|
|
497
|
+
);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
throw apiError;
|
|
501
|
+
}
|
|
502
|
+
const responseData = res.status === 204 ? null : await res.json();
|
|
503
|
+
for (const interceptor of this.responseInterceptors) {
|
|
504
|
+
try {
|
|
505
|
+
await interceptor({
|
|
506
|
+
status: res.status,
|
|
507
|
+
data: responseData,
|
|
508
|
+
headers: res.headers
|
|
509
|
+
});
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return responseData;
|
|
514
|
+
}
|
|
515
|
+
throw new BehioNetworkError("Request failed after retries");
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
var CatalogModule = class {
|
|
519
|
+
constructor(client) {
|
|
520
|
+
this.client = client;
|
|
521
|
+
}
|
|
522
|
+
/** List products with filtering, pagination, search */
|
|
523
|
+
async getProducts(query) {
|
|
524
|
+
const q = {};
|
|
525
|
+
if (query) {
|
|
526
|
+
if (query.page) q.page = query.page;
|
|
527
|
+
if (query.limit) q.limit = query.limit;
|
|
528
|
+
if (query.category) q.category = query.category;
|
|
529
|
+
if (query.label) q.label = query.label;
|
|
530
|
+
if (query.priceMin) q.priceMin = query.priceMin;
|
|
531
|
+
if (query.priceMax) q.priceMax = query.priceMax;
|
|
532
|
+
if (query.currency) q.currency = query.currency;
|
|
533
|
+
if (query.locale) q.locale = query.locale;
|
|
534
|
+
if (query.sort) q.sort = query.sort;
|
|
535
|
+
if (query.inStock !== void 0) q.inStock = query.inStock;
|
|
536
|
+
if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
|
|
537
|
+
if (query.search) q.search = query.search;
|
|
538
|
+
if (query.customFields)
|
|
539
|
+
q.customFields = JSON.stringify(query.customFields);
|
|
540
|
+
if (query.facets) q.facets = JSON.stringify(query.facets);
|
|
541
|
+
if (query.ids && query.ids.length > 0) q.ids = query.ids;
|
|
542
|
+
if (query.slugs && query.slugs.length > 0) q.slugs = query.slugs;
|
|
543
|
+
if (query.labels && query.labels.length > 0) q.labels = query.labels;
|
|
544
|
+
if (query.categories && query.categories.length > 0)
|
|
545
|
+
q.categories = query.categories;
|
|
546
|
+
if (query.excludeIds && query.excludeIds.length > 0)
|
|
547
|
+
q.excludeIds = query.excludeIds;
|
|
548
|
+
if (query.excludeCategories && query.excludeCategories.length > 0)
|
|
549
|
+
q.excludeCategories = query.excludeCategories;
|
|
550
|
+
if (query.hasDiscount !== void 0) q.hasDiscount = query.hasDiscount;
|
|
551
|
+
if (query.isFeatured !== void 0) q.isFeatured = query.isFeatured;
|
|
552
|
+
if (query.createdAfter !== void 0) q.createdAfter = query.createdAfter;
|
|
553
|
+
}
|
|
554
|
+
return this.client.request(
|
|
555
|
+
"GET",
|
|
556
|
+
"/catalog/products",
|
|
557
|
+
{ query: q }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
/** Get product detail by slug */
|
|
561
|
+
async getProduct(slug, options) {
|
|
562
|
+
return this.client.request(
|
|
563
|
+
"GET",
|
|
564
|
+
`/catalog/products/${slug}`,
|
|
565
|
+
{
|
|
566
|
+
query: { locale: options?.locale, currency: options?.currency }
|
|
567
|
+
}
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
/** Get category tree */
|
|
571
|
+
async getCategories(locale) {
|
|
572
|
+
const res = await this.client.request("GET", "/catalog/categories", { query: { locale } });
|
|
573
|
+
if (res.error) return res;
|
|
574
|
+
return ok({ categories: res.data.categories || res.data.items || [] });
|
|
575
|
+
}
|
|
576
|
+
/** Get category detail by slug */
|
|
577
|
+
async getCategory(slug, locale) {
|
|
578
|
+
return this.client.request(
|
|
579
|
+
"GET",
|
|
580
|
+
`/catalog/categories/${slug}`,
|
|
581
|
+
{ query: { locale } }
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
/** Get products in a category */
|
|
585
|
+
async getCategoryProducts(slug, query) {
|
|
586
|
+
const q = {};
|
|
587
|
+
if (query) {
|
|
588
|
+
if (query.page) q.page = query.page;
|
|
589
|
+
if (query.limit) q.limit = query.limit;
|
|
590
|
+
if (query.sort) q.sort = query.sort;
|
|
591
|
+
if (query.locale) q.locale = query.locale;
|
|
592
|
+
if (query.currency) q.currency = query.currency;
|
|
593
|
+
}
|
|
594
|
+
return this.client.request(
|
|
595
|
+
"GET",
|
|
596
|
+
`/catalog/categories/${slug}/products`,
|
|
597
|
+
{ query: q }
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Resolved navigation menu by handle (e.g. "main", "footer"), built in the
|
|
602
|
+
* admin Navigace. Item labels resolve in `locale`; typed refs come back as
|
|
603
|
+
* {type, slug} for link building. Returns an SdkResult error (404) when the
|
|
604
|
+
* handle is unknown or inactive — callers fall back to their own source.
|
|
605
|
+
*/
|
|
606
|
+
async getMenu(handle, options) {
|
|
607
|
+
return this.client.request(
|
|
608
|
+
"GET",
|
|
609
|
+
`/catalog/menu/${encodeURIComponent(handle)}`,
|
|
610
|
+
{
|
|
611
|
+
query: { locale: options?.locale }
|
|
612
|
+
}
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
/** Get all labels */
|
|
616
|
+
async getLabels(locale) {
|
|
617
|
+
const res = await this.client.request("GET", "/catalog/labels", { query: { locale } });
|
|
618
|
+
if (res.error) return res;
|
|
619
|
+
return ok({ labels: res.data.labels || res.data.items || [] });
|
|
620
|
+
}
|
|
621
|
+
/** Get featured products */
|
|
622
|
+
async getFeatured(options) {
|
|
623
|
+
return this.client.request(
|
|
624
|
+
"GET",
|
|
625
|
+
"/catalog/featured",
|
|
626
|
+
{
|
|
627
|
+
query: { locale: options?.locale, currency: options?.currency }
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/** Get available filter fields for dynamic filter UI */
|
|
632
|
+
async getFilters() {
|
|
633
|
+
return this.client.request(
|
|
634
|
+
"GET",
|
|
635
|
+
"/catalog/filters"
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Facet groups + selection-aware counts for the current filter set (custom
|
|
640
|
+
* fields, labels, price, availability, rating, subcategories). Pass the SAME
|
|
641
|
+
* query you pass to `getProducts` (category, search, price, inStock, ratingMin,
|
|
642
|
+
* customFields, facets slugs, labels): counts for each facet are computed with
|
|
643
|
+
* that facet excluded, and values that drop to 0 are still returned (render
|
|
644
|
+
* them disabled). Use this to build an Alza-style filter sidebar.
|
|
645
|
+
*/
|
|
646
|
+
async getFacets(query) {
|
|
647
|
+
const q = {};
|
|
648
|
+
if (query) {
|
|
649
|
+
if (query.category) q.category = query.category;
|
|
650
|
+
if (query.label) q.label = query.label;
|
|
651
|
+
if (query.priceMin) q.priceMin = query.priceMin;
|
|
652
|
+
if (query.priceMax) q.priceMax = query.priceMax;
|
|
653
|
+
if (query.currency) q.currency = query.currency;
|
|
654
|
+
if (query.locale) q.locale = query.locale;
|
|
655
|
+
if (query.inStock !== void 0) q.inStock = query.inStock;
|
|
656
|
+
if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
|
|
657
|
+
if (query.search) q.search = query.search;
|
|
658
|
+
if (query.customFields)
|
|
659
|
+
q.customFields = JSON.stringify(query.customFields);
|
|
660
|
+
if (query.facets) q.facets = JSON.stringify(query.facets);
|
|
661
|
+
if (query.labels && query.labels.length > 0) q.labels = query.labels;
|
|
662
|
+
if (query.categories && query.categories.length > 0)
|
|
663
|
+
q.categories = query.categories;
|
|
664
|
+
if (query.excludeCategories && query.excludeCategories.length > 0)
|
|
665
|
+
q.excludeCategories = query.excludeCategories;
|
|
666
|
+
}
|
|
667
|
+
return this.client.request("GET", "/catalog/facets", {
|
|
668
|
+
query: q
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/** Search products */
|
|
672
|
+
async search(query, options) {
|
|
673
|
+
return this.getProducts({ search: query, ...options });
|
|
674
|
+
}
|
|
675
|
+
/** List all active bundles */
|
|
676
|
+
async getBundles() {
|
|
677
|
+
return this.client.request("GET", "/catalog/bundles");
|
|
678
|
+
}
|
|
679
|
+
/** Get a single bundle by slug */
|
|
680
|
+
async getBundle(slug) {
|
|
681
|
+
return this.client.request("GET", `/catalog/bundles/${slug}`);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* One product group ("collection") by slug with its products as standard
|
|
685
|
+
* list items. Groups are curated in the admin; use this to render curated
|
|
686
|
+
* product bands. Returns an SdkResult error (404) when the group is
|
|
687
|
+
* missing or inactive — callers should render nothing in that case.
|
|
688
|
+
*/
|
|
689
|
+
async getProductGroup(slug, options) {
|
|
690
|
+
return this.client.request(
|
|
691
|
+
"GET",
|
|
692
|
+
`/catalog/product-groups/${encodeURIComponent(slug)}`,
|
|
693
|
+
{
|
|
694
|
+
query: { locale: options?.locale, currency: options?.currency }
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Cross-sell / related / upsell products for a product. Returns three
|
|
700
|
+
* separate lists: `related` (podobné produkty), `upsell` (dražší
|
|
701
|
+
* alternativy) and `crossSell` (doporučené k nákupu). Items are localized
|
|
702
|
+
* and priced in the requested currency, ready to render with the same card
|
|
703
|
+
* component as `getFeatured` / `getProductGroup`.
|
|
704
|
+
*/
|
|
705
|
+
async getCrossSell(productSlug, options) {
|
|
706
|
+
return this.client.request(
|
|
707
|
+
"GET",
|
|
708
|
+
`/catalog/products/${encodeURIComponent(productSlug)}/cross-sell`,
|
|
709
|
+
{ query: { locale: options?.locale, currency: options?.currency } }
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
/** Active promotions applicable to a product (with countdown end time) */
|
|
713
|
+
/** Back-in-stock notification subscription for a sold-out product. */
|
|
714
|
+
async notifyWhenAvailable(productId, email) {
|
|
715
|
+
return this.client.request(
|
|
716
|
+
"POST",
|
|
717
|
+
`/catalog/products/${productId}/notify-when-available`,
|
|
718
|
+
{ body: { email } }
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
async getProductPromotions(productSlug) {
|
|
722
|
+
return this.client.request(
|
|
723
|
+
"GET",
|
|
724
|
+
`/catalog/products/${productSlug}/promotions`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
/** Check a gift card code — returns validity and remaining balance */
|
|
728
|
+
async checkGiftCard(code) {
|
|
729
|
+
return this.client.request(
|
|
730
|
+
"GET",
|
|
731
|
+
`/catalog/gift-cards/${encodeURIComponent(code)}/check`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Buy a gift card (GAP-39): creates a cart-independent order for the chosen
|
|
736
|
+
* amount. Redirect the customer to `paymentRedirectUrl` when present; the
|
|
737
|
+
* code is generated and emailed to the recipient once the order is paid.
|
|
738
|
+
*/
|
|
739
|
+
async purchaseGiftCard(input) {
|
|
740
|
+
return this.client.request(
|
|
741
|
+
"POST",
|
|
742
|
+
"/gift-cards/purchase",
|
|
743
|
+
{ body: input }
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
/** List configured payment methods (filtered by currency). */
|
|
747
|
+
async listPaymentMethods(opts) {
|
|
748
|
+
const query = {};
|
|
749
|
+
if (opts?.currency) query.currency = opts.currency;
|
|
750
|
+
return this.client.request(
|
|
751
|
+
"GET",
|
|
752
|
+
"/catalog/payment-methods",
|
|
753
|
+
{ query }
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
var AuthModule = class {
|
|
758
|
+
constructor(client) {
|
|
759
|
+
this.client = client;
|
|
760
|
+
}
|
|
761
|
+
/** Register a new customer */
|
|
762
|
+
async register(input) {
|
|
763
|
+
const res = await this.client.request(
|
|
764
|
+
"POST",
|
|
765
|
+
"/auth/register",
|
|
766
|
+
{
|
|
767
|
+
body: input,
|
|
768
|
+
auth: false
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
if (res.error) return res;
|
|
772
|
+
if (res.data.accessToken && res.data.refreshToken) {
|
|
773
|
+
this.client.setTokens({
|
|
774
|
+
accessToken: res.data.accessToken,
|
|
775
|
+
refreshToken: res.data.refreshToken
|
|
776
|
+
});
|
|
777
|
+
this.client.emit("auth:login", { email: input.email });
|
|
778
|
+
}
|
|
779
|
+
return res;
|
|
780
|
+
}
|
|
781
|
+
/** Login with email and password */
|
|
782
|
+
async login(input) {
|
|
783
|
+
const res = await this.client.request("POST", "/auth/login", {
|
|
784
|
+
body: input,
|
|
785
|
+
auth: false
|
|
786
|
+
});
|
|
787
|
+
if (res.error) return res;
|
|
788
|
+
this.client.setTokens(res.data);
|
|
789
|
+
this.client.emit("auth:login", { email: input.email });
|
|
790
|
+
return res;
|
|
791
|
+
}
|
|
792
|
+
/** Refresh access token using refresh token */
|
|
793
|
+
async refresh(refreshToken) {
|
|
794
|
+
const token = refreshToken || this.client.getRefreshToken();
|
|
795
|
+
if (!token) {
|
|
796
|
+
return {
|
|
797
|
+
data: null,
|
|
798
|
+
error: {
|
|
799
|
+
code: "UNAUTHORIZED",
|
|
800
|
+
message: "No refresh token available",
|
|
801
|
+
status: null,
|
|
802
|
+
isRetryable: false
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
const res = await this.client.request("POST", "/auth/refresh", {
|
|
807
|
+
body: { refreshToken: token },
|
|
808
|
+
auth: false
|
|
809
|
+
});
|
|
810
|
+
if (res.error) return res;
|
|
811
|
+
this.client.setTokens(res.data);
|
|
812
|
+
return res;
|
|
813
|
+
}
|
|
814
|
+
/** Logout (invalidate refresh token) */
|
|
815
|
+
async logout(refreshToken) {
|
|
816
|
+
const token = refreshToken || this.client.getRefreshToken();
|
|
817
|
+
const res = await this.client.request(
|
|
818
|
+
"POST",
|
|
819
|
+
"/auth/logout",
|
|
820
|
+
{
|
|
821
|
+
body: { refreshToken: token }
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
this.client.clearTokens();
|
|
825
|
+
this.client.emit("auth:logout");
|
|
826
|
+
return res;
|
|
827
|
+
}
|
|
828
|
+
/** Request password reset email */
|
|
829
|
+
async forgotPassword(email) {
|
|
830
|
+
return this.client.request(
|
|
831
|
+
"POST",
|
|
832
|
+
"/auth/forgot-password",
|
|
833
|
+
{
|
|
834
|
+
body: { email },
|
|
835
|
+
auth: false
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
/** Reset password with token */
|
|
840
|
+
async resetPassword(token, newPassword) {
|
|
841
|
+
return this.client.request(
|
|
842
|
+
"POST",
|
|
843
|
+
"/auth/reset-password",
|
|
844
|
+
{
|
|
845
|
+
body: { token, newPassword },
|
|
846
|
+
auth: false
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
/** Verify email with token */
|
|
851
|
+
async verifyEmail(token) {
|
|
852
|
+
return this.client.request("POST", "/auth/verify-email", {
|
|
853
|
+
body: { token },
|
|
854
|
+
auth: false
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
/** Check if user is logged in (has access token) */
|
|
858
|
+
isLoggedIn() {
|
|
859
|
+
return !!this.client.getAccessToken();
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
var CartModule = class {
|
|
863
|
+
constructor(client) {
|
|
864
|
+
this.client = client;
|
|
865
|
+
}
|
|
866
|
+
/** Get current cart */
|
|
867
|
+
async get() {
|
|
868
|
+
return this.client.request("GET", "/cart");
|
|
869
|
+
}
|
|
870
|
+
/** Add item to cart */
|
|
871
|
+
async addItem(input) {
|
|
872
|
+
const res = await this.client.request(
|
|
873
|
+
"POST",
|
|
874
|
+
"/cart/items",
|
|
875
|
+
{
|
|
876
|
+
body: { productId: input.productId, quantity: input.quantity }
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
if (res.error) return res;
|
|
880
|
+
if (res.data.newSessionToken) {
|
|
881
|
+
this.client.setCartSession(res.data.newSessionToken);
|
|
882
|
+
}
|
|
883
|
+
this.client.emit("cart:updated", res.data);
|
|
884
|
+
return res;
|
|
885
|
+
}
|
|
886
|
+
/** Update item quantity */
|
|
887
|
+
async updateQuantity(itemId, quantity) {
|
|
888
|
+
const res = await this.client.request(
|
|
889
|
+
"PATCH",
|
|
890
|
+
`/cart/items/${itemId}`,
|
|
891
|
+
{
|
|
892
|
+
body: { quantity }
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
if (res.error) return res;
|
|
896
|
+
this.client.emit("cart:updated", res.data);
|
|
897
|
+
return res;
|
|
898
|
+
}
|
|
899
|
+
/** Remove item from cart */
|
|
900
|
+
async removeItem(itemId) {
|
|
901
|
+
const res = await this.client.request(
|
|
902
|
+
"DELETE",
|
|
903
|
+
`/cart/items/${itemId}`
|
|
904
|
+
);
|
|
905
|
+
if (res.error) return res;
|
|
906
|
+
this.client.emit("cart:updated", res.data);
|
|
907
|
+
return res;
|
|
908
|
+
}
|
|
909
|
+
/** Clear entire cart */
|
|
910
|
+
async clear() {
|
|
911
|
+
const res = await this.client.request("DELETE", "/cart");
|
|
912
|
+
if (res.error) return res;
|
|
913
|
+
this.client.emit("cart:cleared");
|
|
914
|
+
return res;
|
|
915
|
+
}
|
|
916
|
+
/** Apply a gift card code to the cart. Balance is deducted at checkout. */
|
|
917
|
+
async applyGiftCard(code) {
|
|
918
|
+
const res = await this.client.request("POST", "/cart/gift-card", {
|
|
919
|
+
body: { code }
|
|
920
|
+
});
|
|
921
|
+
if (res.error) return res;
|
|
922
|
+
this.client.emit("cart:updated", res.data);
|
|
923
|
+
return res;
|
|
924
|
+
}
|
|
925
|
+
/** Remove a specific applied gift card from the cart by its code. */
|
|
926
|
+
async removeGiftCard(code) {
|
|
927
|
+
const res = await this.client.request(
|
|
928
|
+
"DELETE",
|
|
929
|
+
`/cart/gift-card/${encodeURIComponent(code)}`
|
|
930
|
+
);
|
|
931
|
+
if (res.error) return res;
|
|
932
|
+
this.client.emit("cart:updated", res.data);
|
|
933
|
+
return res;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Add a bundle to the cart. Price is snapshotted at the bundle's current
|
|
937
|
+
* price. Pass either the bundle id or its slug — slug is more ergonomic
|
|
938
|
+
* for static storefront wiring (`behio.cart.addBundle({slug: "morning-set"})`).
|
|
939
|
+
*
|
|
940
|
+
* Respects the bundle's `minQuantity`, `maxQuantity`, and `stockLimit`:
|
|
941
|
+
* the request rejects with HTTP 400 if the resulting cart line would
|
|
942
|
+
* violate any of them. The returned error includes the relevant field
|
|
943
|
+
* (`minQuantity`, `maxQuantity`, or `remaining`) so the storefront can
|
|
944
|
+
* surface a meaningful message.
|
|
945
|
+
*
|
|
946
|
+
* @param identifier Either `{id: bundleId}` or `{slug: bundleSlug}`. As a
|
|
947
|
+
* convenience, passing a plain string is treated as the
|
|
948
|
+
* bundle id for backwards compatibility.
|
|
949
|
+
* @param quantity How many bundles to add (defaults to 1). Capped by
|
|
950
|
+
* the bundle's `maxQuantity` if set.
|
|
951
|
+
*/
|
|
952
|
+
async addBundle(identifier, quantity = 1) {
|
|
953
|
+
const body = {
|
|
954
|
+
quantity
|
|
955
|
+
};
|
|
956
|
+
if (typeof identifier === "string") {
|
|
957
|
+
body.bundleId = identifier;
|
|
958
|
+
} else if ("id" in identifier) {
|
|
959
|
+
body.bundleId = identifier.id;
|
|
960
|
+
} else {
|
|
961
|
+
body.bundleSlug = identifier.slug;
|
|
962
|
+
}
|
|
963
|
+
const res = await this.client.request("POST", "/cart/bundles", {
|
|
964
|
+
body
|
|
965
|
+
});
|
|
966
|
+
if (res.error) return res;
|
|
967
|
+
this.client.emit("cart:updated", res.data);
|
|
968
|
+
return res;
|
|
969
|
+
}
|
|
970
|
+
/** Update quantity of a bundle already in the cart */
|
|
971
|
+
async updateBundleQuantity(bundleId, quantity) {
|
|
972
|
+
const res = await this.client.request(
|
|
973
|
+
"PATCH",
|
|
974
|
+
`/cart/bundles/${bundleId}`,
|
|
975
|
+
{
|
|
976
|
+
body: { quantity }
|
|
977
|
+
}
|
|
978
|
+
);
|
|
979
|
+
if (res.error) return res;
|
|
980
|
+
this.client.emit("cart:updated", res.data);
|
|
981
|
+
return res;
|
|
982
|
+
}
|
|
983
|
+
/** Remove a bundle from the cart */
|
|
984
|
+
async removeBundle(bundleId) {
|
|
985
|
+
const res = await this.client.request(
|
|
986
|
+
"DELETE",
|
|
987
|
+
`/cart/bundles/${bundleId}`
|
|
988
|
+
);
|
|
989
|
+
if (res.error) return res;
|
|
990
|
+
this.client.emit("cart:updated", res.data);
|
|
991
|
+
return res;
|
|
992
|
+
}
|
|
993
|
+
/** Merge anonymous cart into authenticated customer cart */
|
|
994
|
+
async merge() {
|
|
995
|
+
const res = await this.client.request("POST", "/cart/merge");
|
|
996
|
+
if (res.error) return res;
|
|
997
|
+
this.client.emit("cart:updated", res.data);
|
|
998
|
+
return res;
|
|
999
|
+
}
|
|
1000
|
+
/** Apply discount code */
|
|
1001
|
+
async applyDiscount(code) {
|
|
1002
|
+
const res = await this.client.request("POST", "/cart/discount", {
|
|
1003
|
+
body: { code }
|
|
1004
|
+
});
|
|
1005
|
+
if (res.error) return res;
|
|
1006
|
+
this.client.emit("cart:updated", res.data);
|
|
1007
|
+
return res;
|
|
1008
|
+
}
|
|
1009
|
+
/** Remove discount code */
|
|
1010
|
+
async removeDiscount() {
|
|
1011
|
+
const res = await this.client.request("DELETE", "/cart/discount");
|
|
1012
|
+
if (res.error) return res;
|
|
1013
|
+
this.client.emit("cart:updated", res.data);
|
|
1014
|
+
return res;
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
var CheckoutModule = class {
|
|
1018
|
+
constructor(client) {
|
|
1019
|
+
this.client = client;
|
|
1020
|
+
}
|
|
1021
|
+
/** Create order from cart */
|
|
1022
|
+
async createOrder(input) {
|
|
1023
|
+
const res = await this.client.request("POST", "/checkout", {
|
|
1024
|
+
body: input
|
|
1025
|
+
});
|
|
1026
|
+
if (res.error) return res;
|
|
1027
|
+
this.client.clearCartSession();
|
|
1028
|
+
this.client.emit("order:created", res.data);
|
|
1029
|
+
this.client.emit("cart:cleared");
|
|
1030
|
+
return res;
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
var OrdersModule = class {
|
|
1034
|
+
constructor(client) {
|
|
1035
|
+
this.client = client;
|
|
1036
|
+
}
|
|
1037
|
+
/** List customer orders (requires auth) */
|
|
1038
|
+
async list(options) {
|
|
1039
|
+
return this.client.request(
|
|
1040
|
+
"GET",
|
|
1041
|
+
"/orders",
|
|
1042
|
+
{
|
|
1043
|
+
query: { page: options?.page, limit: options?.limit }
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
/** Get order detail (requires auth) */
|
|
1048
|
+
async get(orderNumber) {
|
|
1049
|
+
return this.client.request("GET", `/orders/${orderNumber}`);
|
|
1050
|
+
}
|
|
1051
|
+
/** Cancel a PENDING order (requires auth) */
|
|
1052
|
+
async cancel(orderNumber) {
|
|
1053
|
+
return this.client.request(
|
|
1054
|
+
"POST",
|
|
1055
|
+
`/orders/${orderNumber}/cancel`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Track an order by tracking token (no login, only API key). Returns a
|
|
1060
|
+
* PII-minimized view: order status + items + masked email + destination
|
|
1061
|
+
* city, never full address / phone / billing — the token travels in URLs
|
|
1062
|
+
* and e-mails so it must not expose full personal data.
|
|
1063
|
+
*/
|
|
1064
|
+
async track(trackingToken) {
|
|
1065
|
+
return this.client.request(
|
|
1066
|
+
"GET",
|
|
1067
|
+
`/orders/track/${trackingToken}`,
|
|
1068
|
+
{ auth: false }
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Step 1 of guest order-access: request a 6-digit code e-mailed to the
|
|
1073
|
+
* address on the order. The response is always generic (success) regardless
|
|
1074
|
+
* of whether the order number + e-mail match, so order numbers can't be
|
|
1075
|
+
* enumerated. No login, only API key.
|
|
1076
|
+
*/
|
|
1077
|
+
async requestAccessCode(orderNumber, email) {
|
|
1078
|
+
return this.client.request(
|
|
1079
|
+
"POST",
|
|
1080
|
+
"/orders/access/request",
|
|
1081
|
+
{
|
|
1082
|
+
auth: false,
|
|
1083
|
+
body: { orderNumber, email }
|
|
1084
|
+
}
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Step 2 of guest order-access: verify the e-mailed code. On success returns
|
|
1089
|
+
* the FULL order detail plus a short-lived `accessToken` you can pass to
|
|
1090
|
+
* {@link getByAccessToken} to re-fetch the detail without re-entering the
|
|
1091
|
+
* code. No login, only API key.
|
|
1092
|
+
*/
|
|
1093
|
+
async verifyAccessCode(orderNumber, email, code) {
|
|
1094
|
+
return this.client.request(
|
|
1095
|
+
"POST",
|
|
1096
|
+
"/orders/access/verify",
|
|
1097
|
+
{
|
|
1098
|
+
auth: false,
|
|
1099
|
+
body: { orderNumber, email, code }
|
|
1100
|
+
}
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Re-fetch a guest order's full detail using the `accessToken` returned by
|
|
1105
|
+
* {@link verifyAccessCode}. The token is scoped to that single order and
|
|
1106
|
+
* expires after 30 minutes.
|
|
1107
|
+
*/
|
|
1108
|
+
async getByAccessToken(accessToken) {
|
|
1109
|
+
return this.client.request("GET", "/orders/access/detail", {
|
|
1110
|
+
auth: false,
|
|
1111
|
+
headers: { "X-Order-Access": accessToken }
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Guest digital download (GAP-07): mint a short-lived signed URL for a
|
|
1116
|
+
* download grant on a guest-accessed order, using the order-access token from
|
|
1117
|
+
* {@link verifyAccessCode}. Scoped to that one order.
|
|
1118
|
+
*/
|
|
1119
|
+
async getAccessDownloadUrl(accessToken, downloadId) {
|
|
1120
|
+
return this.client.request(
|
|
1121
|
+
"POST",
|
|
1122
|
+
`/orders/access/downloads/${downloadId}/url`,
|
|
1123
|
+
{
|
|
1124
|
+
auth: false,
|
|
1125
|
+
headers: { "X-Order-Access": accessToken }
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
var CustomerModule = class {
|
|
1131
|
+
constructor(client) {
|
|
1132
|
+
this.client = client;
|
|
1133
|
+
}
|
|
1134
|
+
/** Get customer profile */
|
|
1135
|
+
async getProfile() {
|
|
1136
|
+
return this.client.request("GET", "/customer/profile");
|
|
1137
|
+
}
|
|
1138
|
+
/** Update customer profile */
|
|
1139
|
+
async updateProfile(data) {
|
|
1140
|
+
return this.client.request("PATCH", "/customer/profile", {
|
|
1141
|
+
body: data
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/** Change password */
|
|
1145
|
+
async changePassword(currentPassword, newPassword) {
|
|
1146
|
+
return this.client.request("PUT", "/customer/password", {
|
|
1147
|
+
body: { currentPassword, newPassword }
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/** List addresses */
|
|
1151
|
+
async getAddresses() {
|
|
1152
|
+
return this.client.request(
|
|
1153
|
+
"GET",
|
|
1154
|
+
"/customer/addresses"
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
/** Create address */
|
|
1158
|
+
async createAddress(address) {
|
|
1159
|
+
return this.client.request("POST", "/customer/addresses", {
|
|
1160
|
+
body: address
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/** Update address */
|
|
1164
|
+
async updateAddress(addressId, data) {
|
|
1165
|
+
return this.client.request(
|
|
1166
|
+
"PATCH",
|
|
1167
|
+
`/customer/addresses/${addressId}`,
|
|
1168
|
+
{ body: data }
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
/** Delete address */
|
|
1172
|
+
async deleteAddress(addressId) {
|
|
1173
|
+
return this.client.request(
|
|
1174
|
+
"DELETE",
|
|
1175
|
+
`/customer/addresses/${addressId}`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Loyalty program summary for the logged-in customer (points balance +
|
|
1180
|
+
* value, current tier, next-tier progress, point ratio, referral code,
|
|
1181
|
+
* recent transactions). Requires an authenticated customer session.
|
|
1182
|
+
*/
|
|
1183
|
+
async getLoyalty() {
|
|
1184
|
+
return this.client.request("GET", "/customer/loyalty");
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Digital product delivery (GAP-07): list the logged-in customer's download
|
|
1188
|
+
* grants across all their orders (file name, product, remaining downloads,
|
|
1189
|
+
* expiry). Requires an authenticated customer session.
|
|
1190
|
+
*/
|
|
1191
|
+
async getDownloads() {
|
|
1192
|
+
return this.client.request(
|
|
1193
|
+
"GET",
|
|
1194
|
+
"/customer/downloads"
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Mint a short-lived signed URL for one download grant. Counts against the
|
|
1199
|
+
* grant's download budget and enforces the max-download + expiry limits
|
|
1200
|
+
* server-side. Requires an authenticated customer session.
|
|
1201
|
+
*/
|
|
1202
|
+
async getDownloadUrl(downloadId) {
|
|
1203
|
+
return this.client.request(
|
|
1204
|
+
"POST",
|
|
1205
|
+
`/customer/downloads/${downloadId}/url`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Online courses (LMS): list the logged-in customer's enrolled courses
|
|
1210
|
+
* with progress. Enrollment is created automatically when an order with a
|
|
1211
|
+
* course product is paid. Requires an authenticated customer session.
|
|
1212
|
+
*/
|
|
1213
|
+
async getCourses() {
|
|
1214
|
+
return this.client.request(
|
|
1215
|
+
"GET",
|
|
1216
|
+
"/customer/courses"
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Course player payload: modules and lessons in order, with per-lesson
|
|
1221
|
+
* drip-unlock state. Locked lessons never contain content — the server
|
|
1222
|
+
* withholds `videoUrl`/`content`/`attachments` until `unlockAt`.
|
|
1223
|
+
*/
|
|
1224
|
+
async getCourse(courseId) {
|
|
1225
|
+
return this.client.request(
|
|
1226
|
+
"GET",
|
|
1227
|
+
`/customer/courses/${courseId}`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
/** Mark an unlocked lesson as completed (idempotent). */
|
|
1231
|
+
async completeLesson(courseId, lessonId) {
|
|
1232
|
+
return this.client.request(
|
|
1233
|
+
"POST",
|
|
1234
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/complete`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Quiz for an unlocked lesson. Correct answers are never included — scoring
|
|
1239
|
+
* happens server-side in `submitLessonQuiz`. Locked lessons return 403.
|
|
1240
|
+
*/
|
|
1241
|
+
async getLessonQuiz(courseId, lessonId) {
|
|
1242
|
+
return this.client.request(
|
|
1243
|
+
"GET",
|
|
1244
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/quiz`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Submit quiz answers: returns the score, reveals correct answers and, when
|
|
1249
|
+
* the score reaches `passPercent` (70 %), marks the lesson completed
|
|
1250
|
+
* automatically.
|
|
1251
|
+
*/
|
|
1252
|
+
async submitLessonQuiz(courseId, lessonId, answers) {
|
|
1253
|
+
return this.client.request(
|
|
1254
|
+
"POST",
|
|
1255
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/quiz/submit`,
|
|
1256
|
+
{ body: { answers } }
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Completion certificates of the logged-in customer. Each carries a public
|
|
1261
|
+
* verification code for sharing (LinkedIn, CV).
|
|
1262
|
+
*/
|
|
1263
|
+
async getCourseCertificates() {
|
|
1264
|
+
return this.client.request(
|
|
1265
|
+
"GET",
|
|
1266
|
+
"/customer/courses/certificates"
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* AI tutor "Ask about this lesson": the student's private thread for one
|
|
1271
|
+
* lesson (oldest first). `enabled: false` = the merchant turned the tutor
|
|
1272
|
+
* off — hide the widget. Locked lessons return 403.
|
|
1273
|
+
*/
|
|
1274
|
+
async getLessonTutorThread(courseId, lessonId) {
|
|
1275
|
+
return this.client.request(
|
|
1276
|
+
"GET",
|
|
1277
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/tutor`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Ask the AI tutor a question about the lesson. The answer sticks to the
|
|
1282
|
+
* lesson topic and comes back in the language of the question (Czech by
|
|
1283
|
+
* default). Rate limited (20/min); 403 on locked lessons or when the
|
|
1284
|
+
* merchant disabled the tutor.
|
|
1285
|
+
*/
|
|
1286
|
+
async askLessonTutor(courseId, lessonId, question) {
|
|
1287
|
+
return this.client.request(
|
|
1288
|
+
"POST",
|
|
1289
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/tutor`,
|
|
1290
|
+
{ body: { question } }
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Lesson discussion: paginated top-level comments (newest first) with one
|
|
1295
|
+
* level of replies. Only enrolled customers with the lesson unlocked; 403
|
|
1296
|
+
* when the merchant disabled the discussion.
|
|
1297
|
+
*/
|
|
1298
|
+
async getLessonComments(courseId, lessonId, page = 1) {
|
|
1299
|
+
return this.client.request(
|
|
1300
|
+
"GET",
|
|
1301
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/comments`,
|
|
1302
|
+
{ query: { page: String(page) } }
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Post a comment under the lesson, or a reply when `parentId` points to a
|
|
1307
|
+
* top-level comment (replies go one level deep only). Max 5000 characters.
|
|
1308
|
+
*/
|
|
1309
|
+
async postLessonComment(courseId, lessonId, body, parentId) {
|
|
1310
|
+
return this.client.request(
|
|
1311
|
+
"POST",
|
|
1312
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/comments`,
|
|
1313
|
+
{ body: { body, ...parentId ? { parentId } : {} } }
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Delete the customer's OWN comment (including its replies). Someone
|
|
1318
|
+
* else's comment returns 404.
|
|
1319
|
+
*/
|
|
1320
|
+
async deleteLessonComment(courseId, lessonId, commentId) {
|
|
1321
|
+
return this.client.request(
|
|
1322
|
+
"DELETE",
|
|
1323
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/comments/${commentId}`
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* The student's private note for one lesson. `body` is an empty string
|
|
1328
|
+
* when no note exists yet. Visible only to the logged-in student.
|
|
1329
|
+
*/
|
|
1330
|
+
async getLessonNote(courseId, lessonId) {
|
|
1331
|
+
return this.client.request(
|
|
1332
|
+
"GET",
|
|
1333
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/note`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Save (upsert) the student's private lesson note. Autosave-friendly; an
|
|
1338
|
+
* empty string clears the note. Max 20 000 characters.
|
|
1339
|
+
*/
|
|
1340
|
+
async saveLessonNote(courseId, lessonId, body) {
|
|
1341
|
+
return this.client.request(
|
|
1342
|
+
"PUT",
|
|
1343
|
+
`/customer/courses/${courseId}/lessons/${lessonId}/note`,
|
|
1344
|
+
{ body: { body } }
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
var CourseCertificatesModule = class {
|
|
1349
|
+
constructor(client) {
|
|
1350
|
+
this.client = client;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Publicly verify a certificate code (no customer login needed) — build a
|
|
1354
|
+
* `/certifikat/{code}` page with this. Unknown codes return a 404 error.
|
|
1355
|
+
*/
|
|
1356
|
+
async verify(code) {
|
|
1357
|
+
return this.client.request(
|
|
1358
|
+
"GET",
|
|
1359
|
+
`/course-certificates/${encodeURIComponent(code)}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Download the certificate as a branded PDF (A5 landscape). Returns a Blob;
|
|
1364
|
+
* trigger a browser download via `URL.createObjectURL(blob)`.
|
|
1365
|
+
*/
|
|
1366
|
+
async downloadPdf(code) {
|
|
1367
|
+
return this.client.requestBlob(
|
|
1368
|
+
`/course-certificates/${encodeURIComponent(code)}/pdf`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
var PagesModule = class {
|
|
1373
|
+
constructor(client) {
|
|
1374
|
+
this.client = client;
|
|
1375
|
+
}
|
|
1376
|
+
/** List CMS pages */
|
|
1377
|
+
async list(locale) {
|
|
1378
|
+
return this.client.request("GET", "/pages", {
|
|
1379
|
+
query: { locale }
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
/** Get page by slug */
|
|
1383
|
+
async get(slug, locale) {
|
|
1384
|
+
return this.client.request("GET", `/pages/${slug}`, {
|
|
1385
|
+
query: { locale }
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
var WishlistModule = class {
|
|
1390
|
+
constructor(client) {
|
|
1391
|
+
this.client = client;
|
|
1392
|
+
}
|
|
1393
|
+
async get() {
|
|
1394
|
+
return this.client.request(
|
|
1395
|
+
"GET",
|
|
1396
|
+
"/customer/wishlist"
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
async add(productId) {
|
|
1400
|
+
return this.client.request(
|
|
1401
|
+
"POST",
|
|
1402
|
+
"/customer/wishlist",
|
|
1403
|
+
{ body: { productId } }
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
async remove(productId) {
|
|
1407
|
+
return this.client.request(
|
|
1408
|
+
"DELETE",
|
|
1409
|
+
`/customer/wishlist/${productId}`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
async isInWishlist(productId) {
|
|
1413
|
+
return this.client.request(
|
|
1414
|
+
"GET",
|
|
1415
|
+
`/customer/wishlist/${productId}/check`
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
var SubscriptionsModule = class {
|
|
1420
|
+
constructor(client) {
|
|
1421
|
+
this.client = client;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* List the logged-in customer's recurring-order subscriptions (products,
|
|
1425
|
+
* cadence, next order date, status). Requires an authenticated customer
|
|
1426
|
+
* session. Subscriptions are created by the merchant in v1.
|
|
1427
|
+
*/
|
|
1428
|
+
async list() {
|
|
1429
|
+
return this.client.request(
|
|
1430
|
+
"GET",
|
|
1431
|
+
"/customer/subscriptions"
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
/** Pause an active subscription (no orders are generated while paused). */
|
|
1435
|
+
async pause(subscriptionId) {
|
|
1436
|
+
return this.client.request(
|
|
1437
|
+
"POST",
|
|
1438
|
+
`/customer/subscriptions/${subscriptionId}/pause`
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
/** Resume a paused subscription (re-schedules the next order). */
|
|
1442
|
+
async resume(subscriptionId) {
|
|
1443
|
+
return this.client.request(
|
|
1444
|
+
"POST",
|
|
1445
|
+
`/customer/subscriptions/${subscriptionId}/resume`
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
/** Cancel a subscription permanently (no more orders). */
|
|
1449
|
+
async cancel(subscriptionId) {
|
|
1450
|
+
return this.client.request(
|
|
1451
|
+
"POST",
|
|
1452
|
+
`/customer/subscriptions/${subscriptionId}/cancel`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
var ReviewsModule = class {
|
|
1457
|
+
constructor(client) {
|
|
1458
|
+
this.client = client;
|
|
1459
|
+
}
|
|
1460
|
+
async getProductReviews(productId, page = 1, limit = 20) {
|
|
1461
|
+
return this.client.request(
|
|
1462
|
+
"GET",
|
|
1463
|
+
`/catalog/products/${productId}/reviews`,
|
|
1464
|
+
{
|
|
1465
|
+
query: { page: String(page), limit: String(limit) }
|
|
1466
|
+
}
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
async submit(input) {
|
|
1470
|
+
return this.client.request("POST", "/catalog/reviews", {
|
|
1471
|
+
body: input
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
async voteHelpful(reviewId, helpful) {
|
|
1475
|
+
return this.client.request(
|
|
1476
|
+
"POST",
|
|
1477
|
+
`/catalog/reviews/${reviewId}/vote`,
|
|
1478
|
+
{ body: { helpful } }
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
var ReturnsModule = class {
|
|
1483
|
+
constructor(client) {
|
|
1484
|
+
this.client = client;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Guest order lookup for the EU withdrawal form: order number + the email
|
|
1488
|
+
* used on the order resolve to the order id and per-item returnable
|
|
1489
|
+
* quantities. POST so the email never appears in a URL.
|
|
1490
|
+
*/
|
|
1491
|
+
async lookupOrder(orderNumber, email) {
|
|
1492
|
+
return this.client.request(
|
|
1493
|
+
"POST",
|
|
1494
|
+
"/returns/lookup-order",
|
|
1495
|
+
{
|
|
1496
|
+
body: { orderNumber, email }
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
async submit(input) {
|
|
1501
|
+
return this.client.request("POST", "/returns", {
|
|
1502
|
+
body: input
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
/** Email is the ownership gate; POST so it never lands in a URL / log. */
|
|
1506
|
+
async getStatus(returnId, email) {
|
|
1507
|
+
return this.client.request(
|
|
1508
|
+
"POST",
|
|
1509
|
+
`/returns/${returnId}/status`,
|
|
1510
|
+
{ body: { email } }
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
var ConsentModule = class {
|
|
1515
|
+
constructor(client) {
|
|
1516
|
+
this.client = client;
|
|
1517
|
+
}
|
|
1518
|
+
async record(input) {
|
|
1519
|
+
return this.client.request("POST", "/consent", {
|
|
1520
|
+
body: input,
|
|
1521
|
+
auth: false
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
async get(visitorId) {
|
|
1525
|
+
const result = await this.client.request("GET", `/consent/${visitorId}/status`, { auth: false });
|
|
1526
|
+
if (result.error && result.error.status === 404) {
|
|
1527
|
+
const legacy = await this.client.request(
|
|
1528
|
+
"GET",
|
|
1529
|
+
`/consent/${visitorId}`,
|
|
1530
|
+
{ auth: false }
|
|
1531
|
+
);
|
|
1532
|
+
if (legacy.error && legacy.error.status === 404)
|
|
1533
|
+
return { data: null, error: null };
|
|
1534
|
+
return legacy;
|
|
1535
|
+
}
|
|
1536
|
+
if (result.error) return { data: null, error: result.error };
|
|
1537
|
+
return {
|
|
1538
|
+
data: result.data?.consented ? result.data.consent : null,
|
|
1539
|
+
error: null
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
async revoke(visitorId) {
|
|
1543
|
+
return this.client.request(
|
|
1544
|
+
"DELETE",
|
|
1545
|
+
`/consent/${visitorId}`,
|
|
1546
|
+
{ auth: false }
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
var QuotesModule = class {
|
|
1551
|
+
constructor(client) {
|
|
1552
|
+
this.client = client;
|
|
1553
|
+
}
|
|
1554
|
+
async submit(input) {
|
|
1555
|
+
return this.client.request("POST", "/catalog/quote-request", {
|
|
1556
|
+
body: input
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
async accept(quoteId, email) {
|
|
1560
|
+
return this.client.request(
|
|
1561
|
+
"POST",
|
|
1562
|
+
`/quotes/${quoteId}/accept`,
|
|
1563
|
+
{ body: { email } }
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
/** Email is the ownership gate — quotes carry contact PII and negotiated
|
|
1567
|
+
* prices, so the id alone is never enough. POST keeps it out of URLs. */
|
|
1568
|
+
async getStatus(quoteId, email) {
|
|
1569
|
+
return this.client.request(
|
|
1570
|
+
"POST",
|
|
1571
|
+
`/quotes/${quoteId}/status`,
|
|
1572
|
+
{ body: { email } }
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* The logged-in customer's own quote requests ("Moje poptávky", GAP-18).
|
|
1577
|
+
* Requires an authenticated session; ownership is the auth token (customer id
|
|
1578
|
+
* + verified email), never a payload. Newest first.
|
|
1579
|
+
*/
|
|
1580
|
+
async listMine() {
|
|
1581
|
+
return this.client.request(
|
|
1582
|
+
"GET",
|
|
1583
|
+
"/customer/quotes"
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
var AddressModule = class {
|
|
1588
|
+
constructor(client) {
|
|
1589
|
+
this.client = client;
|
|
1590
|
+
}
|
|
1591
|
+
/** Search for address suggestions (debounce on your side, or use the React hook) */
|
|
1592
|
+
async autocomplete(query, country) {
|
|
1593
|
+
if (!query || query.length < 2) return ok({ suggestions: [] });
|
|
1594
|
+
return this.client.request(
|
|
1595
|
+
"GET",
|
|
1596
|
+
"/addresses/autocomplete",
|
|
1597
|
+
{
|
|
1598
|
+
query: { q: query, country }
|
|
1599
|
+
}
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
/** Get full structured address from a suggestion's placeId */
|
|
1603
|
+
async getDetail(placeId) {
|
|
1604
|
+
return this.client.request(
|
|
1605
|
+
"GET",
|
|
1606
|
+
"/addresses/place-detail",
|
|
1607
|
+
{
|
|
1608
|
+
query: { placeId }
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
var ShippingModule = class {
|
|
1614
|
+
constructor(client) {
|
|
1615
|
+
this.client = client;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Return the configured shipping methods that pass the current
|
|
1619
|
+
* currency + country filter. Fixed-price methods come back with
|
|
1620
|
+
* their `pricing[]` row resolved; live-quote methods come back with
|
|
1621
|
+
* `price` 0 here — call `quote()` to get the real live price.
|
|
1622
|
+
*/
|
|
1623
|
+
async listMethods(opts) {
|
|
1624
|
+
const query = {};
|
|
1625
|
+
if (opts?.currency) query.currency = opts.currency;
|
|
1626
|
+
if (opts?.country) query.country = opts.country;
|
|
1627
|
+
if (opts?.cartTotal != null) query.cartTotal = String(opts.cartTotal);
|
|
1628
|
+
if (opts?.cartWeightKg != null)
|
|
1629
|
+
query.cartWeightKg = String(opts.cartWeightKg);
|
|
1630
|
+
return this.client.request(
|
|
1631
|
+
"GET",
|
|
1632
|
+
"/catalog/shipping-methods",
|
|
1633
|
+
{ query }
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Quote shipping for a destination address + cart contents. Each
|
|
1638
|
+
* configured method is evaluated:
|
|
1639
|
+
* - `priceStrategy="fixed"` → resolved from the merchant's per-currency
|
|
1640
|
+
* `pricing[]` rows + free-shipping threshold check.
|
|
1641
|
+
* - `priceStrategy="live_quote"` → dispatched to the upstream
|
|
1642
|
+
* meta-provider (Zaslat, future Shippo / Sendcloud / …) and run
|
|
1643
|
+
* through the merchant's markup/rounding rules.
|
|
1644
|
+
*
|
|
1645
|
+
* Filter `available: true` for the checkout picker; `available: false`
|
|
1646
|
+
* rows carry a `reason` (`"no_rate_returned"`, `"live_quote_not_implemented"`,
|
|
1647
|
+
* …) you can log but should not display.
|
|
1648
|
+
*/
|
|
1649
|
+
async quote(input) {
|
|
1650
|
+
return this.client.request(
|
|
1651
|
+
"POST",
|
|
1652
|
+
"/catalog/shipping/quote",
|
|
1653
|
+
{ body: input }
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* List pickup points (parcel shops / lockers) for a method that has
|
|
1658
|
+
* `supportsPickupPoints`. Filter with `query` (name / city / zip) for a
|
|
1659
|
+
* "find your branch" box. Send the chosen point's `externalId` back as
|
|
1660
|
+
* `checkout.pickupPointId`.
|
|
1661
|
+
*/
|
|
1662
|
+
async getPickupPoints(input) {
|
|
1663
|
+
const query = { methodId: input.methodId };
|
|
1664
|
+
if (input.query) query.query = input.query;
|
|
1665
|
+
if (input.country) query.country = input.country;
|
|
1666
|
+
if (input.limit != null) query.limit = String(input.limit);
|
|
1667
|
+
return this.client.request(
|
|
1668
|
+
"GET",
|
|
1669
|
+
"/catalog/shipping/pickup-points",
|
|
1670
|
+
{ query }
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
var NewsletterModule = class {
|
|
1675
|
+
constructor(client) {
|
|
1676
|
+
this.client = client;
|
|
1677
|
+
}
|
|
1678
|
+
async subscribe(input) {
|
|
1679
|
+
return this.client.request(
|
|
1680
|
+
"POST",
|
|
1681
|
+
"/newsletter/subscribe",
|
|
1682
|
+
{
|
|
1683
|
+
body: input,
|
|
1684
|
+
auth: false
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
async unsubscribe(email) {
|
|
1689
|
+
return this.client.request(
|
|
1690
|
+
"POST",
|
|
1691
|
+
"/newsletter/unsubscribe",
|
|
1692
|
+
{
|
|
1693
|
+
body: { email },
|
|
1694
|
+
auth: false
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
|
|
17
1700
|
// src/react/context.ts
|
|
18
1701
|
import { createContext, useContext } from "react";
|
|
19
1702
|
var BehioContext = createContext(null);
|
|
@@ -900,27 +2583,268 @@ function useLoyalty(options) {
|
|
|
900
2583
|
return { loyalty: data, isLoading, error, refetch };
|
|
901
2584
|
}
|
|
902
2585
|
|
|
2586
|
+
// src/react/hooks/use-courses.ts
|
|
2587
|
+
import { useMutation as useMutation5, useQuery as useQuery16, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
|
|
2588
|
+
var COURSES_KEY = ["behio", "courses"];
|
|
2589
|
+
function useCourses(options) {
|
|
2590
|
+
const { client } = useBehio();
|
|
2591
|
+
const { data, isLoading, error, refetch } = useQuery16({
|
|
2592
|
+
queryKey: [...COURSES_KEY],
|
|
2593
|
+
queryFn: () => unwrap(client.customer.getCourses()),
|
|
2594
|
+
enabled: options?.enabled !== false && !!client.getAccessToken()
|
|
2595
|
+
});
|
|
2596
|
+
return { courses: data?.items ?? [], isLoading, error, refetch };
|
|
2597
|
+
}
|
|
2598
|
+
function useCourse(courseId, options) {
|
|
2599
|
+
const { client } = useBehio();
|
|
2600
|
+
const queryClient = useQueryClient7();
|
|
2601
|
+
const { data, isLoading, error, refetch } = useQuery16({
|
|
2602
|
+
queryKey: [...COURSES_KEY, courseId],
|
|
2603
|
+
queryFn: () => unwrap(client.customer.getCourse(courseId)),
|
|
2604
|
+
enabled: options?.enabled !== false && !!courseId && !!client.getAccessToken()
|
|
2605
|
+
});
|
|
2606
|
+
const complete = useMutation5({
|
|
2607
|
+
mutationFn: ({ lessonId }) => unwrap(client.customer.completeLesson(courseId, lessonId)),
|
|
2608
|
+
onSuccess: () => {
|
|
2609
|
+
void queryClient.invalidateQueries({ queryKey: [...COURSES_KEY] });
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
return {
|
|
2613
|
+
course: data,
|
|
2614
|
+
isLoading,
|
|
2615
|
+
error,
|
|
2616
|
+
refetch,
|
|
2617
|
+
completeLesson: (lessonId) => complete.mutateAsync({ lessonId }),
|
|
2618
|
+
isCompleting: complete.isPending
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
function useLessonQuiz(courseId, lessonId, options) {
|
|
2622
|
+
const { client } = useBehio();
|
|
2623
|
+
const queryClient = useQueryClient7();
|
|
2624
|
+
const { data, isLoading, error, refetch } = useQuery16({
|
|
2625
|
+
queryKey: [...COURSES_KEY, courseId, "quiz", lessonId],
|
|
2626
|
+
queryFn: () => unwrap(
|
|
2627
|
+
client.customer.getLessonQuiz(courseId, lessonId)
|
|
2628
|
+
),
|
|
2629
|
+
enabled: options?.enabled !== false && !!courseId && !!lessonId && !!client.getAccessToken()
|
|
2630
|
+
});
|
|
2631
|
+
const submit = useMutation5(
|
|
2632
|
+
{
|
|
2633
|
+
mutationFn: ({ answers }) => unwrap(
|
|
2634
|
+
client.customer.submitLessonQuiz(
|
|
2635
|
+
courseId,
|
|
2636
|
+
lessonId,
|
|
2637
|
+
answers
|
|
2638
|
+
)
|
|
2639
|
+
),
|
|
2640
|
+
onSuccess: (result) => {
|
|
2641
|
+
if (result.lessonCompleted) {
|
|
2642
|
+
void queryClient.invalidateQueries({ queryKey: [...COURSES_KEY] });
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
);
|
|
2647
|
+
return {
|
|
2648
|
+
quiz: data,
|
|
2649
|
+
isLoading,
|
|
2650
|
+
error,
|
|
2651
|
+
refetch,
|
|
2652
|
+
submitQuiz: (answers) => submit.mutateAsync({ answers }),
|
|
2653
|
+
isSubmitting: submit.isPending,
|
|
2654
|
+
result: submit.data ?? null
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
function useCourseCertificates(options) {
|
|
2658
|
+
const { client } = useBehio();
|
|
2659
|
+
const { data, isLoading, error, refetch } = useQuery16({
|
|
2660
|
+
queryKey: [...COURSES_KEY, "certificates"],
|
|
2661
|
+
queryFn: () => unwrap(client.customer.getCourseCertificates()),
|
|
2662
|
+
enabled: options?.enabled !== false && !!client.getAccessToken()
|
|
2663
|
+
});
|
|
2664
|
+
return { certificates: data?.items ?? [], isLoading, error, refetch };
|
|
2665
|
+
}
|
|
2666
|
+
function useCertificateVerification(code, options) {
|
|
2667
|
+
const { client } = useBehio();
|
|
2668
|
+
const { data, isLoading, error, refetch } = useQuery16(
|
|
2669
|
+
{
|
|
2670
|
+
queryKey: ["behio", "certificate-verification", code],
|
|
2671
|
+
queryFn: () => unwrap(client.certificates.verify(code)),
|
|
2672
|
+
enabled: options?.enabled !== false && !!code,
|
|
2673
|
+
retry: false
|
|
2674
|
+
}
|
|
2675
|
+
);
|
|
2676
|
+
return { verification: data ?? null, isLoading, error, refetch };
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
// src/react/hooks/use-lesson-tutor.ts
|
|
2680
|
+
import { useMutation as useMutation6, useQuery as useQuery17, useQueryClient as useQueryClient8 } from "@tanstack/react-query";
|
|
2681
|
+
var TUTOR_KEY = ["behio", "course-tutor"];
|
|
2682
|
+
function useLessonTutor(courseId, lessonId, options) {
|
|
2683
|
+
const { client } = useBehio();
|
|
2684
|
+
const queryClient = useQueryClient8();
|
|
2685
|
+
const { data, isLoading, error, refetch } = useQuery17({
|
|
2686
|
+
queryKey: [...TUTOR_KEY, courseId, lessonId],
|
|
2687
|
+
queryFn: () => unwrap(
|
|
2688
|
+
client.customer.getLessonTutorThread(
|
|
2689
|
+
courseId,
|
|
2690
|
+
lessonId
|
|
2691
|
+
)
|
|
2692
|
+
),
|
|
2693
|
+
enabled: options?.enabled !== false && !!courseId && !!lessonId && !!client.getAccessToken()
|
|
2694
|
+
});
|
|
2695
|
+
const ask = useMutation6({
|
|
2696
|
+
mutationFn: ({ question }) => unwrap(
|
|
2697
|
+
client.customer.askLessonTutor(
|
|
2698
|
+
courseId,
|
|
2699
|
+
lessonId,
|
|
2700
|
+
question
|
|
2701
|
+
)
|
|
2702
|
+
),
|
|
2703
|
+
onSuccess: (message) => {
|
|
2704
|
+
queryClient.setQueryData(
|
|
2705
|
+
[...TUTOR_KEY, courseId, lessonId],
|
|
2706
|
+
(prev) => prev ? { ...prev, items: [...prev.items, message] } : prev
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
return {
|
|
2711
|
+
messages: data?.items ?? [],
|
|
2712
|
+
/** False when the merchant disabled the AI tutor — hide the widget. */
|
|
2713
|
+
enabled: data?.enabled ?? true,
|
|
2714
|
+
isLoading,
|
|
2715
|
+
error,
|
|
2716
|
+
refetch,
|
|
2717
|
+
ask: (question) => ask.mutateAsync({ question }),
|
|
2718
|
+
isAsking: ask.isPending,
|
|
2719
|
+
askError: ask.error
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
// src/react/hooks/use-lesson-comments.ts
|
|
2724
|
+
import { useMutation as useMutation7, useQuery as useQuery18, useQueryClient as useQueryClient9 } from "@tanstack/react-query";
|
|
2725
|
+
var COMMENTS_KEY = ["behio", "course-comments"];
|
|
2726
|
+
function useLessonComments(courseId, lessonId, options) {
|
|
2727
|
+
const { client } = useBehio();
|
|
2728
|
+
const queryClient = useQueryClient9();
|
|
2729
|
+
const page = options?.page ?? 1;
|
|
2730
|
+
const { data, isLoading, error, refetch } = useQuery18({
|
|
2731
|
+
queryKey: [...COMMENTS_KEY, courseId, lessonId, page],
|
|
2732
|
+
queryFn: () => unwrap(
|
|
2733
|
+
client.customer.getLessonComments(
|
|
2734
|
+
courseId,
|
|
2735
|
+
lessonId,
|
|
2736
|
+
page
|
|
2737
|
+
)
|
|
2738
|
+
),
|
|
2739
|
+
enabled: options?.enabled !== false && !!courseId && !!lessonId && !!client.getAccessToken()
|
|
2740
|
+
});
|
|
2741
|
+
const invalidate = () => queryClient.invalidateQueries({
|
|
2742
|
+
queryKey: [...COMMENTS_KEY, courseId, lessonId]
|
|
2743
|
+
});
|
|
2744
|
+
const post = useMutation7({
|
|
2745
|
+
mutationFn: ({ body, parentId }) => unwrap(
|
|
2746
|
+
client.customer.postLessonComment(
|
|
2747
|
+
courseId,
|
|
2748
|
+
lessonId,
|
|
2749
|
+
body,
|
|
2750
|
+
parentId
|
|
2751
|
+
)
|
|
2752
|
+
),
|
|
2753
|
+
onSuccess: () => {
|
|
2754
|
+
void invalidate();
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
const remove = useMutation7({
|
|
2758
|
+
mutationFn: ({ commentId }) => unwrap(
|
|
2759
|
+
client.customer.deleteLessonComment(
|
|
2760
|
+
courseId,
|
|
2761
|
+
lessonId,
|
|
2762
|
+
commentId
|
|
2763
|
+
)
|
|
2764
|
+
),
|
|
2765
|
+
onSuccess: () => {
|
|
2766
|
+
void invalidate();
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
return {
|
|
2770
|
+
comments: data?.items ?? [],
|
|
2771
|
+
totalCount: data?.totalCount ?? 0,
|
|
2772
|
+
page: data?.page ?? page,
|
|
2773
|
+
pageSize: data?.pageSize ?? 20,
|
|
2774
|
+
/** False when the merchant disabled the discussion — hide the widget. */
|
|
2775
|
+
enabled: data?.enabled ?? true,
|
|
2776
|
+
isLoading,
|
|
2777
|
+
error,
|
|
2778
|
+
refetch,
|
|
2779
|
+
postComment: (body, parentId) => post.mutateAsync({ body, parentId }),
|
|
2780
|
+
isPosting: post.isPending,
|
|
2781
|
+
deleteComment: (commentId) => remove.mutateAsync({ commentId }),
|
|
2782
|
+
isDeleting: remove.isPending
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// src/react/hooks/use-lesson-note.ts
|
|
2787
|
+
import { useMutation as useMutation8, useQuery as useQuery19, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
|
|
2788
|
+
var NOTE_KEY = ["behio", "course-note"];
|
|
2789
|
+
function useLessonNote(courseId, lessonId, options) {
|
|
2790
|
+
const { client } = useBehio();
|
|
2791
|
+
const queryClient = useQueryClient10();
|
|
2792
|
+
const { data, isLoading, error, refetch } = useQuery19({
|
|
2793
|
+
queryKey: [...NOTE_KEY, courseId, lessonId],
|
|
2794
|
+
queryFn: () => unwrap(
|
|
2795
|
+
client.customer.getLessonNote(courseId, lessonId)
|
|
2796
|
+
),
|
|
2797
|
+
enabled: options?.enabled !== false && !!courseId && !!lessonId && !!client.getAccessToken()
|
|
2798
|
+
});
|
|
2799
|
+
const save = useMutation8({
|
|
2800
|
+
mutationFn: ({ body }) => unwrap(
|
|
2801
|
+
client.customer.saveLessonNote(
|
|
2802
|
+
courseId,
|
|
2803
|
+
lessonId,
|
|
2804
|
+
body
|
|
2805
|
+
)
|
|
2806
|
+
),
|
|
2807
|
+
onSuccess: (note) => {
|
|
2808
|
+
queryClient.setQueryData(
|
|
2809
|
+
[...NOTE_KEY, courseId, lessonId],
|
|
2810
|
+
note
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
return {
|
|
2815
|
+
note: data ?? null,
|
|
2816
|
+
body: data?.body ?? "",
|
|
2817
|
+
updatedAt: data?.updatedAt ?? null,
|
|
2818
|
+
isLoading,
|
|
2819
|
+
error,
|
|
2820
|
+
refetch,
|
|
2821
|
+
save: (body) => save.mutateAsync({ body }),
|
|
2822
|
+
isSaving: save.isPending,
|
|
2823
|
+
saveError: save.error
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
|
|
903
2827
|
// src/react/hooks/use-subscriptions.ts
|
|
904
|
-
import { useQuery as
|
|
2828
|
+
import { useQuery as useQuery20, useMutation as useMutation9, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
|
|
905
2829
|
var SUBSCRIPTIONS_KEY = ["behio", "subscriptions"];
|
|
906
2830
|
function useSubscriptions(options) {
|
|
907
2831
|
const { client } = useBehio();
|
|
908
|
-
const qc =
|
|
909
|
-
const query =
|
|
2832
|
+
const qc = useQueryClient11();
|
|
2833
|
+
const query = useQuery20({
|
|
910
2834
|
queryKey: [...SUBSCRIPTIONS_KEY],
|
|
911
2835
|
queryFn: () => unwrap(client.subscriptions.list()),
|
|
912
2836
|
enabled: options?.enabled !== false && !!client.getAccessToken()
|
|
913
2837
|
});
|
|
914
2838
|
const invalidate = () => qc.invalidateQueries({ queryKey: [...SUBSCRIPTIONS_KEY] });
|
|
915
|
-
const pauseMutation =
|
|
2839
|
+
const pauseMutation = useMutation9({
|
|
916
2840
|
mutationFn: (subscriptionId) => unwrap(client.subscriptions.pause(subscriptionId)),
|
|
917
2841
|
onSuccess: invalidate
|
|
918
2842
|
});
|
|
919
|
-
const resumeMutation =
|
|
2843
|
+
const resumeMutation = useMutation9({
|
|
920
2844
|
mutationFn: (subscriptionId) => unwrap(client.subscriptions.resume(subscriptionId)),
|
|
921
2845
|
onSuccess: invalidate
|
|
922
2846
|
});
|
|
923
|
-
const cancelMutation =
|
|
2847
|
+
const cancelMutation = useMutation9({
|
|
924
2848
|
mutationFn: (subscriptionId) => unwrap(client.subscriptions.cancel(subscriptionId)),
|
|
925
2849
|
onSuccess: invalidate
|
|
926
2850
|
});
|
|
@@ -939,11 +2863,11 @@ function useSubscriptions(options) {
|
|
|
939
2863
|
}
|
|
940
2864
|
|
|
941
2865
|
// src/react/hooks/use-pickup-points.ts
|
|
942
|
-
import { useQuery as
|
|
2866
|
+
import { useQuery as useQuery21 } from "@tanstack/react-query";
|
|
943
2867
|
function usePickupPoints(input) {
|
|
944
2868
|
const { client } = useBehio();
|
|
945
2869
|
const { methodId, query, country, limit, enabled } = input;
|
|
946
|
-
const { data, isLoading, error, refetch } =
|
|
2870
|
+
const { data, isLoading, error, refetch } = useQuery21({
|
|
947
2871
|
queryKey: ["behio", "pickup-points", methodId, query ?? "", country ?? "", limit ?? 30],
|
|
948
2872
|
queryFn: () => unwrap(
|
|
949
2873
|
client.shipping.getPickupPoints({
|
|
@@ -959,12 +2883,12 @@ function usePickupPoints(input) {
|
|
|
959
2883
|
}
|
|
960
2884
|
|
|
961
2885
|
// src/react/hooks/use-shipping-methods.ts
|
|
962
|
-
import { useQuery as
|
|
2886
|
+
import { useQuery as useQuery22 } from "@tanstack/react-query";
|
|
963
2887
|
function useShippingMethods(options) {
|
|
964
2888
|
const { client, currency: activeCurrency } = useBehio();
|
|
965
2889
|
const currency = options?.currency ?? activeCurrency;
|
|
966
2890
|
const { country, cartTotal, cartWeightKg } = options ?? {};
|
|
967
|
-
const { data, isLoading, error, refetch } =
|
|
2891
|
+
const { data, isLoading, error, refetch } = useQuery22({
|
|
968
2892
|
queryKey: [
|
|
969
2893
|
"behio",
|
|
970
2894
|
"shipping-methods",
|
|
@@ -980,12 +2904,12 @@ function useShippingMethods(options) {
|
|
|
980
2904
|
}
|
|
981
2905
|
|
|
982
2906
|
// src/react/hooks/use-shipping-quote.ts
|
|
983
|
-
import { useQuery as
|
|
2907
|
+
import { useQuery as useQuery23 } from "@tanstack/react-query";
|
|
984
2908
|
function useShippingQuote(options) {
|
|
985
2909
|
const { client, currency: activeCurrency } = useBehio();
|
|
986
2910
|
const { destinationAddress, items, cartTotal, enabled } = options ?? {};
|
|
987
2911
|
const currency = options?.currency ?? activeCurrency;
|
|
988
|
-
const { data, isLoading, error, refetch } =
|
|
2912
|
+
const { data, isLoading, error, refetch } = useQuery23({
|
|
989
2913
|
queryKey: [
|
|
990
2914
|
"behio",
|
|
991
2915
|
"shipping-quote",
|
|
@@ -1008,11 +2932,11 @@ function useShippingQuote(options) {
|
|
|
1008
2932
|
}
|
|
1009
2933
|
|
|
1010
2934
|
// src/react/hooks/use-payment-methods.ts
|
|
1011
|
-
import { useQuery as
|
|
2935
|
+
import { useQuery as useQuery24 } from "@tanstack/react-query";
|
|
1012
2936
|
function usePaymentMethods(options) {
|
|
1013
2937
|
const { client, currency: activeCurrency } = useBehio();
|
|
1014
2938
|
const currency = options?.currency ?? activeCurrency;
|
|
1015
|
-
const { data, isLoading, error, refetch } =
|
|
2939
|
+
const { data, isLoading, error, refetch } = useQuery24({
|
|
1016
2940
|
queryKey: ["behio", "payment-methods", currency ?? ""],
|
|
1017
2941
|
queryFn: () => unwrap(client.catalog.listPaymentMethods({ currency })),
|
|
1018
2942
|
enabled: options?.enabled !== false
|
|
@@ -1022,10 +2946,10 @@ function usePaymentMethods(options) {
|
|
|
1022
2946
|
|
|
1023
2947
|
// src/react/hooks/use-personal-offers.ts
|
|
1024
2948
|
import { useCallback as useCallback8, useEffect as useEffect4, useState as useState5 } from "react";
|
|
1025
|
-
import { useMutation as
|
|
2949
|
+
import { useMutation as useMutation10, useQuery as useQuery25, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
|
|
1026
2950
|
function usePersonalOffers(options) {
|
|
1027
2951
|
const { client } = useBehio();
|
|
1028
|
-
const queryClient =
|
|
2952
|
+
const queryClient = useQueryClient12();
|
|
1029
2953
|
const [polledVid, setPolledVid] = useState5(null);
|
|
1030
2954
|
const explicitVid = options?.visitorId;
|
|
1031
2955
|
useEffect4(() => {
|
|
@@ -1043,12 +2967,12 @@ function usePersonalOffers(options) {
|
|
|
1043
2967
|
}, [client, explicitVid]);
|
|
1044
2968
|
const visitorId = explicitVid ?? polledVid;
|
|
1045
2969
|
const queryKey = ["behio", "personal-offers", visitorId ?? ""];
|
|
1046
|
-
const { data, isLoading, error, refetch } =
|
|
2970
|
+
const { data, isLoading, error, refetch } = useQuery25({
|
|
1047
2971
|
queryKey,
|
|
1048
2972
|
queryFn: () => unwrap(client.getPersonalOffers(visitorId)),
|
|
1049
2973
|
enabled: options?.enabled !== false && !!visitorId
|
|
1050
2974
|
});
|
|
1051
|
-
const claimMutation =
|
|
2975
|
+
const claimMutation = useMutation10({
|
|
1052
2976
|
mutationFn: ({ offerId, email }) => unwrap(client.claimOfferByEmail(offerId, { visitorId, email })),
|
|
1053
2977
|
onSuccess: (result, { offerId }) => {
|
|
1054
2978
|
queryClient.setQueryData(
|
|
@@ -1110,16 +3034,16 @@ function useAnalyticsEvents() {
|
|
|
1110
3034
|
}
|
|
1111
3035
|
|
|
1112
3036
|
// src/react/hooks/use-newsletter.ts
|
|
1113
|
-
import { useMutation as
|
|
3037
|
+
import { useMutation as useMutation11 } from "@tanstack/react-query";
|
|
1114
3038
|
function useNewsletterSubscribe() {
|
|
1115
3039
|
const { client } = useBehio();
|
|
1116
|
-
return
|
|
3040
|
+
return useMutation11({
|
|
1117
3041
|
mutationFn: (input) => unwrap(client.newsletter.subscribe(input))
|
|
1118
3042
|
});
|
|
1119
3043
|
}
|
|
1120
3044
|
function useNewsletterUnsubscribe() {
|
|
1121
3045
|
const { client } = useBehio();
|
|
1122
|
-
return
|
|
3046
|
+
return useMutation11({
|
|
1123
3047
|
mutationFn: (email) => unwrap(client.newsletter.unsubscribe(email))
|
|
1124
3048
|
});
|
|
1125
3049
|
}
|
|
@@ -1177,20 +3101,20 @@ function useOrders(options) {
|
|
|
1177
3101
|
|
|
1178
3102
|
// src/react/hooks/use-order.ts
|
|
1179
3103
|
import { useCallback as useCallback11 } from "react";
|
|
1180
|
-
import { useQuery as
|
|
3104
|
+
import { useQuery as useQuery26, useMutation as useMutation12, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
|
|
1181
3105
|
function useOrder(orderNumber, options) {
|
|
1182
3106
|
const { client } = useBehio();
|
|
1183
|
-
const queryClient =
|
|
3107
|
+
const queryClient = useQueryClient13();
|
|
1184
3108
|
const {
|
|
1185
3109
|
data,
|
|
1186
3110
|
isLoading,
|
|
1187
3111
|
error
|
|
1188
|
-
} =
|
|
3112
|
+
} = useQuery26({
|
|
1189
3113
|
queryKey: ["behio", "order", orderNumber],
|
|
1190
3114
|
queryFn: () => unwrap(client.orders.get(orderNumber)),
|
|
1191
3115
|
enabled: options?.enabled !== false && !!orderNumber && !!client.getAccessToken()
|
|
1192
3116
|
});
|
|
1193
|
-
const cancelMutation =
|
|
3117
|
+
const cancelMutation = useMutation12({
|
|
1194
3118
|
mutationFn: () => unwrap(client.orders.cancel(orderNumber)),
|
|
1195
3119
|
onSuccess: (updated) => {
|
|
1196
3120
|
queryClient.setQueryData(["behio", "order", orderNumber], updated);
|
|
@@ -1212,21 +3136,21 @@ function useOrder(orderNumber, options) {
|
|
|
1212
3136
|
|
|
1213
3137
|
// src/react/hooks/use-order-access.ts
|
|
1214
3138
|
import { useCallback as useCallback12, useState as useState7 } from "react";
|
|
1215
|
-
import { useMutation as
|
|
3139
|
+
import { useMutation as useMutation13 } from "@tanstack/react-query";
|
|
1216
3140
|
function useOrderAccess() {
|
|
1217
3141
|
const { client } = useBehio();
|
|
1218
3142
|
const [orderNumber, setOrderNumber] = useState7(null);
|
|
1219
3143
|
const [email, setEmail] = useState7(null);
|
|
1220
3144
|
const [order, setOrder] = useState7(null);
|
|
1221
3145
|
const [accessToken, setAccessToken] = useState7(null);
|
|
1222
|
-
const requestMutation =
|
|
3146
|
+
const requestMutation = useMutation13({
|
|
1223
3147
|
mutationFn: (input) => unwrap(client.orders.requestAccessCode(input.orderNumber, input.email)),
|
|
1224
3148
|
onSuccess: (_data, input) => {
|
|
1225
3149
|
setOrderNumber(input.orderNumber);
|
|
1226
3150
|
setEmail(input.email);
|
|
1227
3151
|
}
|
|
1228
3152
|
});
|
|
1229
|
-
const verifyMutation =
|
|
3153
|
+
const verifyMutation = useMutation13({
|
|
1230
3154
|
mutationFn: (code) => {
|
|
1231
3155
|
if (!orderNumber || !email) {
|
|
1232
3156
|
throw new Error("Request a code before verifying");
|
|
@@ -1272,12 +3196,12 @@ function useOrderAccess() {
|
|
|
1272
3196
|
|
|
1273
3197
|
// src/react/hooks/use-checkout.ts
|
|
1274
3198
|
import { useState as useState8, useCallback as useCallback13 } from "react";
|
|
1275
|
-
import { useMutation as
|
|
3199
|
+
import { useMutation as useMutation14, useQueryClient as useQueryClient14 } from "@tanstack/react-query";
|
|
1276
3200
|
function useCheckout() {
|
|
1277
3201
|
const { client, storage } = useBehio();
|
|
1278
|
-
const queryClient =
|
|
3202
|
+
const queryClient = useQueryClient14();
|
|
1279
3203
|
const [order, setOrder] = useState8(null);
|
|
1280
|
-
const mutation =
|
|
3204
|
+
const mutation = useMutation14({
|
|
1281
3205
|
mutationFn: (input) => unwrap(client.checkout.createOrder(input)),
|
|
1282
3206
|
onSuccess: (result) => {
|
|
1283
3207
|
setOrder(result);
|
|
@@ -1304,10 +3228,10 @@ function useCheckout() {
|
|
|
1304
3228
|
}
|
|
1305
3229
|
|
|
1306
3230
|
// src/react/hooks/use-pages.ts
|
|
1307
|
-
import { useQuery as
|
|
3231
|
+
import { useQuery as useQuery27 } from "@tanstack/react-query";
|
|
1308
3232
|
function usePages(locale, options) {
|
|
1309
3233
|
const { client } = useBehio();
|
|
1310
|
-
return
|
|
3234
|
+
return useQuery27({
|
|
1311
3235
|
queryKey: ["behio", "pages", locale],
|
|
1312
3236
|
queryFn: async () => {
|
|
1313
3237
|
const result = await unwrap(client.pages.list(locale));
|
|
@@ -1318,7 +3242,7 @@ function usePages(locale, options) {
|
|
|
1318
3242
|
}
|
|
1319
3243
|
function usePage(slug, locale, options) {
|
|
1320
3244
|
const { client } = useBehio();
|
|
1321
|
-
return
|
|
3245
|
+
return useQuery27({
|
|
1322
3246
|
queryKey: ["behio", "page", slug, locale],
|
|
1323
3247
|
queryFn: () => unwrap(client.pages.get(slug, locale)),
|
|
1324
3248
|
enabled: options?.enabled !== false && !!slug
|
|
@@ -1326,10 +3250,10 @@ function usePage(slug, locale, options) {
|
|
|
1326
3250
|
}
|
|
1327
3251
|
|
|
1328
3252
|
// src/react/hooks/use-shop-info.ts
|
|
1329
|
-
import { useQuery as
|
|
3253
|
+
import { useQuery as useQuery28 } from "@tanstack/react-query";
|
|
1330
3254
|
function useShopInfo(options) {
|
|
1331
3255
|
const { client } = useBehio();
|
|
1332
|
-
return
|
|
3256
|
+
return useQuery28({
|
|
1333
3257
|
queryKey: ["behio", "shop-info"],
|
|
1334
3258
|
queryFn: () => unwrap(client.getShopInfo()),
|
|
1335
3259
|
enabled: options?.enabled !== false
|
|
@@ -1337,10 +3261,10 @@ function useShopInfo(options) {
|
|
|
1337
3261
|
}
|
|
1338
3262
|
|
|
1339
3263
|
// src/react/hooks/use-shop-scripts.ts
|
|
1340
|
-
import { useQuery as
|
|
3264
|
+
import { useQuery as useQuery29 } from "@tanstack/react-query";
|
|
1341
3265
|
function useShopScripts(options) {
|
|
1342
3266
|
const { client } = useBehio();
|
|
1343
|
-
return
|
|
3267
|
+
return useQuery29({
|
|
1344
3268
|
queryKey: ["behio", "shop-scripts"],
|
|
1345
3269
|
queryFn: () => unwrap(client.getShopScripts()),
|
|
1346
3270
|
enabled: options?.enabled !== false
|
|
@@ -1348,11 +3272,11 @@ function useShopScripts(options) {
|
|
|
1348
3272
|
}
|
|
1349
3273
|
|
|
1350
3274
|
// src/react/hooks/use-shop-seo.ts
|
|
1351
|
-
import { useQuery as
|
|
3275
|
+
import { useQuery as useQuery30 } from "@tanstack/react-query";
|
|
1352
3276
|
function useShopSeo(options) {
|
|
1353
3277
|
const { client } = useBehio();
|
|
1354
3278
|
const { locale, initialData, enabled = true } = options ?? {};
|
|
1355
|
-
return
|
|
3279
|
+
return useQuery30({
|
|
1356
3280
|
queryKey: ["behio", "shop-seo", locale ?? "_default"],
|
|
1357
3281
|
queryFn: () => unwrap(client.getShopSeo(locale)),
|
|
1358
3282
|
initialData,
|
|
@@ -1415,20 +3339,20 @@ function CurrencySwitcher({
|
|
|
1415
3339
|
import { useEffect as useEffect5, useMemo as useMemo4, useState as useState9 } from "react";
|
|
1416
3340
|
|
|
1417
3341
|
// src/react/hooks/use-consent.ts
|
|
1418
|
-
import { useQuery as
|
|
3342
|
+
import { useQuery as useQuery31, useMutation as useMutation15, useQueryClient as useQueryClient15 } from "@tanstack/react-query";
|
|
1419
3343
|
function useCookieConsent(visitorId) {
|
|
1420
3344
|
const { client } = useBehio();
|
|
1421
|
-
const qc =
|
|
1422
|
-
const query =
|
|
3345
|
+
const qc = useQueryClient15();
|
|
3346
|
+
const query = useQuery31({
|
|
1423
3347
|
queryKey: ["behio", "consent", visitorId],
|
|
1424
3348
|
queryFn: () => unwrap(client.consent.get(visitorId)),
|
|
1425
3349
|
enabled: Boolean(visitorId)
|
|
1426
3350
|
});
|
|
1427
|
-
const recordMutation =
|
|
3351
|
+
const recordMutation = useMutation15({
|
|
1428
3352
|
mutationFn: (input) => unwrap(client.consent.record(input)),
|
|
1429
3353
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
|
|
1430
3354
|
});
|
|
1431
|
-
const revokeMutation =
|
|
3355
|
+
const revokeMutation = useMutation15({
|
|
1432
3356
|
mutationFn: () => unwrap(client.consent.revoke(visitorId)),
|
|
1433
3357
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
|
|
1434
3358
|
});
|
|
@@ -1787,10 +3711,10 @@ function utmFromSearch(search) {
|
|
|
1787
3711
|
}
|
|
1788
3712
|
|
|
1789
3713
|
// src/react/hooks/use-bundles.ts
|
|
1790
|
-
import { useQuery as
|
|
3714
|
+
import { useQuery as useQuery32 } from "@tanstack/react-query";
|
|
1791
3715
|
function useBundles(options) {
|
|
1792
3716
|
const { client } = useBehio();
|
|
1793
|
-
return
|
|
3717
|
+
return useQuery32({
|
|
1794
3718
|
queryKey: ["behio", "bundles"],
|
|
1795
3719
|
queryFn: () => unwrap(client.catalog.getBundles()),
|
|
1796
3720
|
enabled: options?.enabled ?? true,
|
|
@@ -1799,7 +3723,7 @@ function useBundles(options) {
|
|
|
1799
3723
|
}
|
|
1800
3724
|
function useBundle(slug, options) {
|
|
1801
3725
|
const { client } = useBehio();
|
|
1802
|
-
return
|
|
3726
|
+
return useQuery32({
|
|
1803
3727
|
queryKey: ["behio", "bundle", slug],
|
|
1804
3728
|
queryFn: () => unwrap(client.catalog.getBundle(slug)),
|
|
1805
3729
|
enabled: Boolean(slug) && (options?.enabled ?? true),
|
|
@@ -1808,10 +3732,10 @@ function useBundle(slug, options) {
|
|
|
1808
3732
|
}
|
|
1809
3733
|
|
|
1810
3734
|
// src/react/hooks/use-product-group.ts
|
|
1811
|
-
import { useQuery as
|
|
3735
|
+
import { useQuery as useQuery33 } from "@tanstack/react-query";
|
|
1812
3736
|
function useProductGroup(slug, options) {
|
|
1813
3737
|
const { client } = useBehio();
|
|
1814
|
-
return
|
|
3738
|
+
return useQuery33({
|
|
1815
3739
|
queryKey: ["behio", "product-group", slug, options?.locale, options?.currency],
|
|
1816
3740
|
queryFn: () => unwrap(
|
|
1817
3741
|
client.catalog.getProductGroup(slug, {
|
|
@@ -1825,10 +3749,10 @@ function useProductGroup(slug, options) {
|
|
|
1825
3749
|
}
|
|
1826
3750
|
|
|
1827
3751
|
// src/react/hooks/use-cross-sell.ts
|
|
1828
|
-
import { useQuery as
|
|
3752
|
+
import { useQuery as useQuery34 } from "@tanstack/react-query";
|
|
1829
3753
|
function useCrossSell(productSlug, options) {
|
|
1830
3754
|
const { client } = useBehio();
|
|
1831
|
-
return
|
|
3755
|
+
return useQuery34({
|
|
1832
3756
|
queryKey: ["behio", "cross-sell", productSlug, options?.locale, options?.currency],
|
|
1833
3757
|
queryFn: () => unwrap(
|
|
1834
3758
|
client.catalog.getCrossSell(productSlug, {
|
|
@@ -1842,10 +3766,10 @@ function useCrossSell(productSlug, options) {
|
|
|
1842
3766
|
}
|
|
1843
3767
|
|
|
1844
3768
|
// src/react/hooks/use-product-promotions.ts
|
|
1845
|
-
import { useQuery as
|
|
3769
|
+
import { useQuery as useQuery35 } from "@tanstack/react-query";
|
|
1846
3770
|
function useProductPromotions(productSlug, options) {
|
|
1847
3771
|
const { client } = useBehio();
|
|
1848
|
-
return
|
|
3772
|
+
return useQuery35({
|
|
1849
3773
|
queryKey: ["behio", "product-promotions", productSlug],
|
|
1850
3774
|
queryFn: () => unwrap(client.catalog.getProductPromotions(productSlug)),
|
|
1851
3775
|
enabled: Boolean(productSlug) && (options?.enabled ?? true),
|
|
@@ -1854,11 +3778,11 @@ function useProductPromotions(productSlug, options) {
|
|
|
1854
3778
|
}
|
|
1855
3779
|
|
|
1856
3780
|
// src/react/hooks/use-gift-card.ts
|
|
1857
|
-
import { useQuery as
|
|
3781
|
+
import { useQuery as useQuery36 } from "@tanstack/react-query";
|
|
1858
3782
|
function useGiftCardBalance(code, options) {
|
|
1859
3783
|
const { client } = useBehio();
|
|
1860
3784
|
const trimmed = code?.trim();
|
|
1861
|
-
return
|
|
3785
|
+
return useQuery36({
|
|
1862
3786
|
queryKey: ["behio", "gift-card-balance", trimmed],
|
|
1863
3787
|
queryFn: () => unwrap(client.catalog.checkGiftCard(trimmed)),
|
|
1864
3788
|
enabled: Boolean(trimmed && trimmed.length >= 6) && (options?.enabled ?? true)
|
|
@@ -1866,20 +3790,20 @@ function useGiftCardBalance(code, options) {
|
|
|
1866
3790
|
}
|
|
1867
3791
|
|
|
1868
3792
|
// src/react/hooks/use-wishlist.ts
|
|
1869
|
-
import { useQuery as
|
|
3793
|
+
import { useQuery as useQuery37, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
|
|
1870
3794
|
function useWishlist(options) {
|
|
1871
3795
|
const { client } = useBehio();
|
|
1872
|
-
const qc =
|
|
1873
|
-
const query =
|
|
3796
|
+
const qc = useQueryClient16();
|
|
3797
|
+
const query = useQuery37({
|
|
1874
3798
|
queryKey: ["behio", "wishlist"],
|
|
1875
3799
|
queryFn: () => unwrap(client.wishlist.get()),
|
|
1876
3800
|
enabled: options?.enabled ?? true
|
|
1877
3801
|
});
|
|
1878
|
-
const addMutation =
|
|
3802
|
+
const addMutation = useMutation16({
|
|
1879
3803
|
mutationFn: (productId) => unwrap(client.wishlist.add(productId)),
|
|
1880
3804
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
|
|
1881
3805
|
});
|
|
1882
|
-
const removeMutation =
|
|
3806
|
+
const removeMutation = useMutation16({
|
|
1883
3807
|
mutationFn: (productId) => unwrap(client.wishlist.remove(productId)),
|
|
1884
3808
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
|
|
1885
3809
|
});
|
|
@@ -1893,7 +3817,7 @@ function useWishlist(options) {
|
|
|
1893
3817
|
}
|
|
1894
3818
|
function useIsInWishlist(productId) {
|
|
1895
3819
|
const { client } = useBehio();
|
|
1896
|
-
return
|
|
3820
|
+
return useQuery37({
|
|
1897
3821
|
queryKey: ["behio", "wishlist-check", productId],
|
|
1898
3822
|
queryFn: () => unwrap(client.wishlist.isInWishlist(productId)),
|
|
1899
3823
|
enabled: Boolean(productId)
|
|
@@ -1901,10 +3825,10 @@ function useIsInWishlist(productId) {
|
|
|
1901
3825
|
}
|
|
1902
3826
|
|
|
1903
3827
|
// src/react/hooks/use-reviews.ts
|
|
1904
|
-
import { useQuery as
|
|
3828
|
+
import { useQuery as useQuery38, useMutation as useMutation17, useQueryClient as useQueryClient17 } from "@tanstack/react-query";
|
|
1905
3829
|
function useProductReviews(productId, options) {
|
|
1906
3830
|
const { client } = useBehio();
|
|
1907
|
-
return
|
|
3831
|
+
return useQuery38({
|
|
1908
3832
|
queryKey: ["behio", "reviews", productId, options?.page ?? 1],
|
|
1909
3833
|
queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
|
|
1910
3834
|
enabled: Boolean(productId) && (options?.enabled ?? true)
|
|
@@ -1912,30 +3836,30 @@ function useProductReviews(productId, options) {
|
|
|
1912
3836
|
}
|
|
1913
3837
|
function useSubmitReview() {
|
|
1914
3838
|
const { client } = useBehio();
|
|
1915
|
-
const qc =
|
|
1916
|
-
return
|
|
3839
|
+
const qc = useQueryClient17();
|
|
3840
|
+
return useMutation17({
|
|
1917
3841
|
mutationFn: (input) => unwrap(client.reviews.submit(input)),
|
|
1918
3842
|
onSuccess: (_, input) => qc.invalidateQueries({ queryKey: ["behio", "reviews", input.productId] })
|
|
1919
3843
|
});
|
|
1920
3844
|
}
|
|
1921
3845
|
|
|
1922
3846
|
// src/react/hooks/use-returns.ts
|
|
1923
|
-
import { useQuery as
|
|
3847
|
+
import { useQuery as useQuery39, useMutation as useMutation18 } from "@tanstack/react-query";
|
|
1924
3848
|
function useLookupReturnableOrder() {
|
|
1925
3849
|
const { client } = useBehio();
|
|
1926
|
-
return
|
|
3850
|
+
return useMutation18({
|
|
1927
3851
|
mutationFn: ({ orderNumber, email }) => unwrap(client.returns.lookupOrder(orderNumber, email))
|
|
1928
3852
|
});
|
|
1929
3853
|
}
|
|
1930
3854
|
function useSubmitReturn() {
|
|
1931
3855
|
const { client } = useBehio();
|
|
1932
|
-
return
|
|
3856
|
+
return useMutation18({
|
|
1933
3857
|
mutationFn: (input) => unwrap(client.returns.submit(input))
|
|
1934
3858
|
});
|
|
1935
3859
|
}
|
|
1936
3860
|
function useReturnStatus(returnId, email) {
|
|
1937
3861
|
const { client } = useBehio();
|
|
1938
|
-
return
|
|
3862
|
+
return useQuery39({
|
|
1939
3863
|
queryKey: ["behio", "return-status", returnId],
|
|
1940
3864
|
queryFn: () => unwrap(client.returns.getStatus(returnId, email)),
|
|
1941
3865
|
enabled: Boolean(returnId && email)
|
|
@@ -1943,16 +3867,16 @@ function useReturnStatus(returnId, email) {
|
|
|
1943
3867
|
}
|
|
1944
3868
|
|
|
1945
3869
|
// src/react/hooks/use-quotes.ts
|
|
1946
|
-
import { useMutation as
|
|
3870
|
+
import { useMutation as useMutation19, useQuery as useQuery40 } from "@tanstack/react-query";
|
|
1947
3871
|
function useSubmitQuote() {
|
|
1948
3872
|
const { client } = useBehio();
|
|
1949
|
-
return
|
|
3873
|
+
return useMutation19({
|
|
1950
3874
|
mutationFn: (input) => unwrap(client.quotes.submit(input))
|
|
1951
3875
|
});
|
|
1952
3876
|
}
|
|
1953
3877
|
function useQuoteStatus(quoteId, email) {
|
|
1954
3878
|
const { client } = useBehio();
|
|
1955
|
-
return
|
|
3879
|
+
return useQuery40({
|
|
1956
3880
|
queryKey: ["behio", "quote-status", quoteId],
|
|
1957
3881
|
queryFn: () => unwrap(client.quotes.getStatus(quoteId, email)),
|
|
1958
3882
|
enabled: Boolean(quoteId && email)
|
|
@@ -1960,13 +3884,123 @@ function useQuoteStatus(quoteId, email) {
|
|
|
1960
3884
|
}
|
|
1961
3885
|
|
|
1962
3886
|
// src/react/hooks/use-back-in-stock.ts
|
|
1963
|
-
import { useMutation as
|
|
3887
|
+
import { useMutation as useMutation20 } from "@tanstack/react-query";
|
|
1964
3888
|
function useNotifyWhenAvailable() {
|
|
1965
3889
|
const { client } = useBehio();
|
|
1966
|
-
return
|
|
3890
|
+
return useMutation20({
|
|
1967
3891
|
mutationFn: ({ productId, email }) => unwrap(client.catalog.notifyWhenAvailable(productId, email))
|
|
1968
3892
|
});
|
|
1969
3893
|
}
|
|
3894
|
+
|
|
3895
|
+
// src/react/utils/format-price.ts
|
|
3896
|
+
function formatPrice(amount, currency, locale) {
|
|
3897
|
+
const resolvedLocale = locale ?? "cs";
|
|
3898
|
+
try {
|
|
3899
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
3900
|
+
style: "currency",
|
|
3901
|
+
currency,
|
|
3902
|
+
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
|
|
3903
|
+
maximumFractionDigits: 2
|
|
3904
|
+
}).format(amount);
|
|
3905
|
+
} catch {
|
|
3906
|
+
return `${amount} ${currency}`;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
// src/analytics.ts
|
|
3911
|
+
var GA4_NAME_MAP = {
|
|
3912
|
+
newsletter_signup: "generate_lead"
|
|
3913
|
+
};
|
|
3914
|
+
function trackEcommerceEvent(event, payload) {
|
|
3915
|
+
if (typeof window === "undefined") return;
|
|
3916
|
+
const w = window;
|
|
3917
|
+
try {
|
|
3918
|
+
w.__behioEcommerceSink?.(event, payload);
|
|
3919
|
+
} catch {
|
|
3920
|
+
}
|
|
3921
|
+
const gaName = GA4_NAME_MAP[event] ?? event;
|
|
3922
|
+
try {
|
|
3923
|
+
if (typeof w.gtag === "function") {
|
|
3924
|
+
w.gtag("event", gaName, payload);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
if (Array.isArray(w.dataLayer)) {
|
|
3928
|
+
w.dataLayer.push({ ecommerce: null });
|
|
3929
|
+
w.dataLayer.push({ event: gaName, ecommerce: payload });
|
|
3930
|
+
}
|
|
3931
|
+
} catch {
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
// src/consent-visitor.ts
|
|
3936
|
+
var VISITOR_KEY3 = "behio_visitor_id";
|
|
3937
|
+
var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
3938
|
+
function getStoredVisitorId() {
|
|
3939
|
+
if (typeof window === "undefined") return null;
|
|
3940
|
+
try {
|
|
3941
|
+
const ls = localStorage.getItem(VISITOR_KEY3);
|
|
3942
|
+
if (ls) return ls;
|
|
3943
|
+
} catch {
|
|
3944
|
+
}
|
|
3945
|
+
return readCookie(VISITOR_KEY3);
|
|
3946
|
+
}
|
|
3947
|
+
function generateVisitorId() {
|
|
3948
|
+
const bytes = new Uint8Array(18);
|
|
3949
|
+
try {
|
|
3950
|
+
globalThis.crypto?.getRandomValues?.(bytes);
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
let filled = false;
|
|
3954
|
+
for (const b of bytes) if (b !== 0) filled = true;
|
|
3955
|
+
if (!filled) for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
3956
|
+
let bin = "";
|
|
3957
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
3958
|
+
const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
3959
|
+
return `v${b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`.slice(0, 40);
|
|
3960
|
+
}
|
|
3961
|
+
function writeVisitorId(id) {
|
|
3962
|
+
if (typeof window === "undefined") return;
|
|
3963
|
+
try {
|
|
3964
|
+
localStorage.setItem(VISITOR_KEY3, id);
|
|
3965
|
+
} catch {
|
|
3966
|
+
}
|
|
3967
|
+
try {
|
|
3968
|
+
document.cookie = `${VISITOR_KEY3}=${encodeURIComponent(id)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;
|
|
3969
|
+
} catch {
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
function readCookie(name) {
|
|
3973
|
+
if (typeof document === "undefined") return null;
|
|
3974
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
|
|
3975
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
3976
|
+
}
|
|
3977
|
+
function emitConsentChanged() {
|
|
3978
|
+
if (typeof window === "undefined") return;
|
|
3979
|
+
try {
|
|
3980
|
+
window.dispatchEvent(new Event("behio:consent-changed"));
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
async function grantAnalyticsConsent(client, categories) {
|
|
3985
|
+
const id = getStoredVisitorId() ?? generateVisitorId();
|
|
3986
|
+
writeVisitorId(id);
|
|
3987
|
+
client.setAnalyticsVisitorId(id);
|
|
3988
|
+
const res = await client.consent.record({
|
|
3989
|
+
visitorId: id,
|
|
3990
|
+
analytics: true,
|
|
3991
|
+
marketing: categories?.marketing ?? false,
|
|
3992
|
+
preferences: categories?.preferences ?? false
|
|
3993
|
+
});
|
|
3994
|
+
emitConsentChanged();
|
|
3995
|
+
return res;
|
|
3996
|
+
}
|
|
3997
|
+
async function revokeAnalyticsConsent(client) {
|
|
3998
|
+
const id = getStoredVisitorId();
|
|
3999
|
+
client.setAnalyticsVisitorId(null);
|
|
4000
|
+
emitConsentChanged();
|
|
4001
|
+
if (!id) return { data: { success: true }, error: null };
|
|
4002
|
+
return client.consent.revoke(id);
|
|
4003
|
+
}
|
|
1970
4004
|
export {
|
|
1971
4005
|
BehioAnalyticsTracker,
|
|
1972
4006
|
BehioProvider,
|
|
@@ -1995,8 +4029,12 @@ export {
|
|
|
1995
4029
|
useCartCount,
|
|
1996
4030
|
useCategories,
|
|
1997
4031
|
useCategory,
|
|
4032
|
+
useCertificateVerification,
|
|
1998
4033
|
useCheckout,
|
|
1999
4034
|
useCookieConsent,
|
|
4035
|
+
useCourse,
|
|
4036
|
+
useCourseCertificates,
|
|
4037
|
+
useCourses,
|
|
2000
4038
|
useCrossSell,
|
|
2001
4039
|
useCurrency,
|
|
2002
4040
|
useCustomer,
|
|
@@ -2006,6 +4044,10 @@ export {
|
|
|
2006
4044
|
useGiftCardBalance,
|
|
2007
4045
|
useIsInWishlist,
|
|
2008
4046
|
useLabels,
|
|
4047
|
+
useLessonComments,
|
|
4048
|
+
useLessonNote,
|
|
4049
|
+
useLessonQuiz,
|
|
4050
|
+
useLessonTutor,
|
|
2009
4051
|
useLookupReturnableOrder,
|
|
2010
4052
|
useLoyalty,
|
|
2011
4053
|
useMenu,
|