@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/react.mjs CHANGED
@@ -1,19 +1,1702 @@
1
- import {
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 useQuery16, useMutation as useMutation5, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
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 = useQueryClient7();
909
- const query = useQuery16({
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 = useMutation5({
2839
+ const pauseMutation = useMutation9({
916
2840
  mutationFn: (subscriptionId) => unwrap(client.subscriptions.pause(subscriptionId)),
917
2841
  onSuccess: invalidate
918
2842
  });
919
- const resumeMutation = useMutation5({
2843
+ const resumeMutation = useMutation9({
920
2844
  mutationFn: (subscriptionId) => unwrap(client.subscriptions.resume(subscriptionId)),
921
2845
  onSuccess: invalidate
922
2846
  });
923
- const cancelMutation = useMutation5({
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 useQuery17 } from "@tanstack/react-query";
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 } = useQuery17({
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 useQuery18 } from "@tanstack/react-query";
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 } = useQuery18({
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 useQuery19 } from "@tanstack/react-query";
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 } = useQuery19({
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 useQuery20 } from "@tanstack/react-query";
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 } = useQuery20({
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 useMutation6, useQuery as useQuery21, useQueryClient as useQueryClient8 } from "@tanstack/react-query";
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 = useQueryClient8();
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 } = useQuery21({
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 = useMutation6({
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 useMutation7 } from "@tanstack/react-query";
3037
+ import { useMutation as useMutation11 } from "@tanstack/react-query";
1114
3038
  function useNewsletterSubscribe() {
1115
3039
  const { client } = useBehio();
1116
- return useMutation7({
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 useMutation7({
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 useQuery22, useMutation as useMutation8, useQueryClient as useQueryClient9 } from "@tanstack/react-query";
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 = useQueryClient9();
3107
+ const queryClient = useQueryClient13();
1184
3108
  const {
1185
3109
  data,
1186
3110
  isLoading,
1187
3111
  error
1188
- } = useQuery22({
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 = useMutation8({
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 useMutation9 } from "@tanstack/react-query";
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 = useMutation9({
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 = useMutation9({
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 useMutation10, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
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 = useQueryClient10();
3202
+ const queryClient = useQueryClient14();
1279
3203
  const [order, setOrder] = useState8(null);
1280
- const mutation = useMutation10({
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 useQuery23 } from "@tanstack/react-query";
3231
+ import { useQuery as useQuery27 } from "@tanstack/react-query";
1308
3232
  function usePages(locale, options) {
1309
3233
  const { client } = useBehio();
1310
- return useQuery23({
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 useQuery23({
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 useQuery24 } from "@tanstack/react-query";
3253
+ import { useQuery as useQuery28 } from "@tanstack/react-query";
1330
3254
  function useShopInfo(options) {
1331
3255
  const { client } = useBehio();
1332
- return useQuery24({
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 useQuery25 } from "@tanstack/react-query";
3264
+ import { useQuery as useQuery29 } from "@tanstack/react-query";
1341
3265
  function useShopScripts(options) {
1342
3266
  const { client } = useBehio();
1343
- return useQuery25({
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 useQuery26 } from "@tanstack/react-query";
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 useQuery26({
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 useQuery27, useMutation as useMutation11, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
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 = useQueryClient11();
1422
- const query = useQuery27({
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 = useMutation11({
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 = useMutation11({
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 useQuery28 } from "@tanstack/react-query";
3714
+ import { useQuery as useQuery32 } from "@tanstack/react-query";
1791
3715
  function useBundles(options) {
1792
3716
  const { client } = useBehio();
1793
- return useQuery28({
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 useQuery28({
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 useQuery29 } from "@tanstack/react-query";
3735
+ import { useQuery as useQuery33 } from "@tanstack/react-query";
1812
3736
  function useProductGroup(slug, options) {
1813
3737
  const { client } = useBehio();
1814
- return useQuery29({
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 useQuery30 } from "@tanstack/react-query";
3752
+ import { useQuery as useQuery34 } from "@tanstack/react-query";
1829
3753
  function useCrossSell(productSlug, options) {
1830
3754
  const { client } = useBehio();
1831
- return useQuery30({
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 useQuery31 } from "@tanstack/react-query";
3769
+ import { useQuery as useQuery35 } from "@tanstack/react-query";
1846
3770
  function useProductPromotions(productSlug, options) {
1847
3771
  const { client } = useBehio();
1848
- return useQuery31({
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 useQuery32 } from "@tanstack/react-query";
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 useQuery32({
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 useQuery33, useMutation as useMutation12, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
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 = useQueryClient12();
1873
- const query = useQuery33({
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 = useMutation12({
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 = useMutation12({
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 useQuery33({
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 useQuery34, useMutation as useMutation13, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
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 useQuery34({
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 = useQueryClient13();
1916
- return useMutation13({
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 useQuery35, useMutation as useMutation14 } from "@tanstack/react-query";
3847
+ import { useQuery as useQuery39, useMutation as useMutation18 } from "@tanstack/react-query";
1924
3848
  function useLookupReturnableOrder() {
1925
3849
  const { client } = useBehio();
1926
- return useMutation14({
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 useMutation14({
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 useQuery35({
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 useMutation15, useQuery as useQuery36 } from "@tanstack/react-query";
3870
+ import { useMutation as useMutation19, useQuery as useQuery40 } from "@tanstack/react-query";
1947
3871
  function useSubmitQuote() {
1948
3872
  const { client } = useBehio();
1949
- return useMutation15({
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 useQuery36({
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 useMutation16 } from "@tanstack/react-query";
3887
+ import { useMutation as useMutation20 } from "@tanstack/react-query";
1964
3888
  function useNotifyWhenAvailable() {
1965
3889
  const { client } = useBehio();
1966
- return useMutation16({
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,