@behio/storefront-sdk 0.38.0 → 0.42.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-MF5LMHZ4.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);
@@ -935,28 +2618,233 @@ function useCourse(courseId, options) {
935
2618
  isCompleting: complete.isPending
936
2619
  };
937
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
+ }
938
2826
 
939
2827
  // src/react/hooks/use-subscriptions.ts
940
- import { useQuery as useQuery17, useMutation as useMutation6, useQueryClient as useQueryClient8 } from "@tanstack/react-query";
2828
+ import { useQuery as useQuery20, useMutation as useMutation9, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
941
2829
  var SUBSCRIPTIONS_KEY = ["behio", "subscriptions"];
942
2830
  function useSubscriptions(options) {
943
2831
  const { client } = useBehio();
944
- const qc = useQueryClient8();
945
- const query = useQuery17({
2832
+ const qc = useQueryClient11();
2833
+ const query = useQuery20({
946
2834
  queryKey: [...SUBSCRIPTIONS_KEY],
947
2835
  queryFn: () => unwrap(client.subscriptions.list()),
948
2836
  enabled: options?.enabled !== false && !!client.getAccessToken()
949
2837
  });
950
2838
  const invalidate = () => qc.invalidateQueries({ queryKey: [...SUBSCRIPTIONS_KEY] });
951
- const pauseMutation = useMutation6({
2839
+ const pauseMutation = useMutation9({
952
2840
  mutationFn: (subscriptionId) => unwrap(client.subscriptions.pause(subscriptionId)),
953
2841
  onSuccess: invalidate
954
2842
  });
955
- const resumeMutation = useMutation6({
2843
+ const resumeMutation = useMutation9({
956
2844
  mutationFn: (subscriptionId) => unwrap(client.subscriptions.resume(subscriptionId)),
957
2845
  onSuccess: invalidate
958
2846
  });
959
- const cancelMutation = useMutation6({
2847
+ const cancelMutation = useMutation9({
960
2848
  mutationFn: (subscriptionId) => unwrap(client.subscriptions.cancel(subscriptionId)),
961
2849
  onSuccess: invalidate
962
2850
  });
@@ -975,11 +2863,11 @@ function useSubscriptions(options) {
975
2863
  }
976
2864
 
977
2865
  // src/react/hooks/use-pickup-points.ts
978
- import { useQuery as useQuery18 } from "@tanstack/react-query";
2866
+ import { useQuery as useQuery21 } from "@tanstack/react-query";
979
2867
  function usePickupPoints(input) {
980
2868
  const { client } = useBehio();
981
2869
  const { methodId, query, country, limit, enabled } = input;
982
- const { data, isLoading, error, refetch } = useQuery18({
2870
+ const { data, isLoading, error, refetch } = useQuery21({
983
2871
  queryKey: ["behio", "pickup-points", methodId, query ?? "", country ?? "", limit ?? 30],
984
2872
  queryFn: () => unwrap(
985
2873
  client.shipping.getPickupPoints({
@@ -995,12 +2883,12 @@ function usePickupPoints(input) {
995
2883
  }
996
2884
 
997
2885
  // src/react/hooks/use-shipping-methods.ts
998
- import { useQuery as useQuery19 } from "@tanstack/react-query";
2886
+ import { useQuery as useQuery22 } from "@tanstack/react-query";
999
2887
  function useShippingMethods(options) {
1000
2888
  const { client, currency: activeCurrency } = useBehio();
1001
2889
  const currency = options?.currency ?? activeCurrency;
1002
2890
  const { country, cartTotal, cartWeightKg } = options ?? {};
1003
- const { data, isLoading, error, refetch } = useQuery19({
2891
+ const { data, isLoading, error, refetch } = useQuery22({
1004
2892
  queryKey: [
1005
2893
  "behio",
1006
2894
  "shipping-methods",
@@ -1016,12 +2904,12 @@ function useShippingMethods(options) {
1016
2904
  }
1017
2905
 
1018
2906
  // src/react/hooks/use-shipping-quote.ts
1019
- import { useQuery as useQuery20 } from "@tanstack/react-query";
2907
+ import { useQuery as useQuery23 } from "@tanstack/react-query";
1020
2908
  function useShippingQuote(options) {
1021
2909
  const { client, currency: activeCurrency } = useBehio();
1022
2910
  const { destinationAddress, items, cartTotal, enabled } = options ?? {};
1023
2911
  const currency = options?.currency ?? activeCurrency;
1024
- const { data, isLoading, error, refetch } = useQuery20({
2912
+ const { data, isLoading, error, refetch } = useQuery23({
1025
2913
  queryKey: [
1026
2914
  "behio",
1027
2915
  "shipping-quote",
@@ -1044,11 +2932,11 @@ function useShippingQuote(options) {
1044
2932
  }
1045
2933
 
1046
2934
  // src/react/hooks/use-payment-methods.ts
1047
- import { useQuery as useQuery21 } from "@tanstack/react-query";
2935
+ import { useQuery as useQuery24 } from "@tanstack/react-query";
1048
2936
  function usePaymentMethods(options) {
1049
2937
  const { client, currency: activeCurrency } = useBehio();
1050
2938
  const currency = options?.currency ?? activeCurrency;
1051
- const { data, isLoading, error, refetch } = useQuery21({
2939
+ const { data, isLoading, error, refetch } = useQuery24({
1052
2940
  queryKey: ["behio", "payment-methods", currency ?? ""],
1053
2941
  queryFn: () => unwrap(client.catalog.listPaymentMethods({ currency })),
1054
2942
  enabled: options?.enabled !== false
@@ -1058,10 +2946,10 @@ function usePaymentMethods(options) {
1058
2946
 
1059
2947
  // src/react/hooks/use-personal-offers.ts
1060
2948
  import { useCallback as useCallback8, useEffect as useEffect4, useState as useState5 } from "react";
1061
- import { useMutation as useMutation7, useQuery as useQuery22, useQueryClient as useQueryClient9 } from "@tanstack/react-query";
2949
+ import { useMutation as useMutation10, useQuery as useQuery25, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
1062
2950
  function usePersonalOffers(options) {
1063
2951
  const { client } = useBehio();
1064
- const queryClient = useQueryClient9();
2952
+ const queryClient = useQueryClient12();
1065
2953
  const [polledVid, setPolledVid] = useState5(null);
1066
2954
  const explicitVid = options?.visitorId;
1067
2955
  useEffect4(() => {
@@ -1079,12 +2967,12 @@ function usePersonalOffers(options) {
1079
2967
  }, [client, explicitVid]);
1080
2968
  const visitorId = explicitVid ?? polledVid;
1081
2969
  const queryKey = ["behio", "personal-offers", visitorId ?? ""];
1082
- const { data, isLoading, error, refetch } = useQuery22({
2970
+ const { data, isLoading, error, refetch } = useQuery25({
1083
2971
  queryKey,
1084
2972
  queryFn: () => unwrap(client.getPersonalOffers(visitorId)),
1085
2973
  enabled: options?.enabled !== false && !!visitorId
1086
2974
  });
1087
- const claimMutation = useMutation7({
2975
+ const claimMutation = useMutation10({
1088
2976
  mutationFn: ({ offerId, email }) => unwrap(client.claimOfferByEmail(offerId, { visitorId, email })),
1089
2977
  onSuccess: (result, { offerId }) => {
1090
2978
  queryClient.setQueryData(
@@ -1146,16 +3034,16 @@ function useAnalyticsEvents() {
1146
3034
  }
1147
3035
 
1148
3036
  // src/react/hooks/use-newsletter.ts
1149
- import { useMutation as useMutation8 } from "@tanstack/react-query";
3037
+ import { useMutation as useMutation11 } from "@tanstack/react-query";
1150
3038
  function useNewsletterSubscribe() {
1151
3039
  const { client } = useBehio();
1152
- return useMutation8({
3040
+ return useMutation11({
1153
3041
  mutationFn: (input) => unwrap(client.newsletter.subscribe(input))
1154
3042
  });
1155
3043
  }
1156
3044
  function useNewsletterUnsubscribe() {
1157
3045
  const { client } = useBehio();
1158
- return useMutation8({
3046
+ return useMutation11({
1159
3047
  mutationFn: (email) => unwrap(client.newsletter.unsubscribe(email))
1160
3048
  });
1161
3049
  }
@@ -1213,20 +3101,20 @@ function useOrders(options) {
1213
3101
 
1214
3102
  // src/react/hooks/use-order.ts
1215
3103
  import { useCallback as useCallback11 } from "react";
1216
- import { useQuery as useQuery23, useMutation as useMutation9, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
3104
+ import { useQuery as useQuery26, useMutation as useMutation12, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
1217
3105
  function useOrder(orderNumber, options) {
1218
3106
  const { client } = useBehio();
1219
- const queryClient = useQueryClient10();
3107
+ const queryClient = useQueryClient13();
1220
3108
  const {
1221
3109
  data,
1222
3110
  isLoading,
1223
3111
  error
1224
- } = useQuery23({
3112
+ } = useQuery26({
1225
3113
  queryKey: ["behio", "order", orderNumber],
1226
3114
  queryFn: () => unwrap(client.orders.get(orderNumber)),
1227
3115
  enabled: options?.enabled !== false && !!orderNumber && !!client.getAccessToken()
1228
3116
  });
1229
- const cancelMutation = useMutation9({
3117
+ const cancelMutation = useMutation12({
1230
3118
  mutationFn: () => unwrap(client.orders.cancel(orderNumber)),
1231
3119
  onSuccess: (updated) => {
1232
3120
  queryClient.setQueryData(["behio", "order", orderNumber], updated);
@@ -1248,21 +3136,21 @@ function useOrder(orderNumber, options) {
1248
3136
 
1249
3137
  // src/react/hooks/use-order-access.ts
1250
3138
  import { useCallback as useCallback12, useState as useState7 } from "react";
1251
- import { useMutation as useMutation10 } from "@tanstack/react-query";
3139
+ import { useMutation as useMutation13 } from "@tanstack/react-query";
1252
3140
  function useOrderAccess() {
1253
3141
  const { client } = useBehio();
1254
3142
  const [orderNumber, setOrderNumber] = useState7(null);
1255
3143
  const [email, setEmail] = useState7(null);
1256
3144
  const [order, setOrder] = useState7(null);
1257
3145
  const [accessToken, setAccessToken] = useState7(null);
1258
- const requestMutation = useMutation10({
3146
+ const requestMutation = useMutation13({
1259
3147
  mutationFn: (input) => unwrap(client.orders.requestAccessCode(input.orderNumber, input.email)),
1260
3148
  onSuccess: (_data, input) => {
1261
3149
  setOrderNumber(input.orderNumber);
1262
3150
  setEmail(input.email);
1263
3151
  }
1264
3152
  });
1265
- const verifyMutation = useMutation10({
3153
+ const verifyMutation = useMutation13({
1266
3154
  mutationFn: (code) => {
1267
3155
  if (!orderNumber || !email) {
1268
3156
  throw new Error("Request a code before verifying");
@@ -1308,12 +3196,12 @@ function useOrderAccess() {
1308
3196
 
1309
3197
  // src/react/hooks/use-checkout.ts
1310
3198
  import { useState as useState8, useCallback as useCallback13 } from "react";
1311
- import { useMutation as useMutation11, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
3199
+ import { useMutation as useMutation14, useQueryClient as useQueryClient14 } from "@tanstack/react-query";
1312
3200
  function useCheckout() {
1313
3201
  const { client, storage } = useBehio();
1314
- const queryClient = useQueryClient11();
3202
+ const queryClient = useQueryClient14();
1315
3203
  const [order, setOrder] = useState8(null);
1316
- const mutation = useMutation11({
3204
+ const mutation = useMutation14({
1317
3205
  mutationFn: (input) => unwrap(client.checkout.createOrder(input)),
1318
3206
  onSuccess: (result) => {
1319
3207
  setOrder(result);
@@ -1340,10 +3228,10 @@ function useCheckout() {
1340
3228
  }
1341
3229
 
1342
3230
  // src/react/hooks/use-pages.ts
1343
- import { useQuery as useQuery24 } from "@tanstack/react-query";
3231
+ import { useQuery as useQuery27 } from "@tanstack/react-query";
1344
3232
  function usePages(locale, options) {
1345
3233
  const { client } = useBehio();
1346
- return useQuery24({
3234
+ return useQuery27({
1347
3235
  queryKey: ["behio", "pages", locale],
1348
3236
  queryFn: async () => {
1349
3237
  const result = await unwrap(client.pages.list(locale));
@@ -1354,7 +3242,7 @@ function usePages(locale, options) {
1354
3242
  }
1355
3243
  function usePage(slug, locale, options) {
1356
3244
  const { client } = useBehio();
1357
- return useQuery24({
3245
+ return useQuery27({
1358
3246
  queryKey: ["behio", "page", slug, locale],
1359
3247
  queryFn: () => unwrap(client.pages.get(slug, locale)),
1360
3248
  enabled: options?.enabled !== false && !!slug
@@ -1362,10 +3250,10 @@ function usePage(slug, locale, options) {
1362
3250
  }
1363
3251
 
1364
3252
  // src/react/hooks/use-shop-info.ts
1365
- import { useQuery as useQuery25 } from "@tanstack/react-query";
3253
+ import { useQuery as useQuery28 } from "@tanstack/react-query";
1366
3254
  function useShopInfo(options) {
1367
3255
  const { client } = useBehio();
1368
- return useQuery25({
3256
+ return useQuery28({
1369
3257
  queryKey: ["behio", "shop-info"],
1370
3258
  queryFn: () => unwrap(client.getShopInfo()),
1371
3259
  enabled: options?.enabled !== false
@@ -1373,10 +3261,10 @@ function useShopInfo(options) {
1373
3261
  }
1374
3262
 
1375
3263
  // src/react/hooks/use-shop-scripts.ts
1376
- import { useQuery as useQuery26 } from "@tanstack/react-query";
3264
+ import { useQuery as useQuery29 } from "@tanstack/react-query";
1377
3265
  function useShopScripts(options) {
1378
3266
  const { client } = useBehio();
1379
- return useQuery26({
3267
+ return useQuery29({
1380
3268
  queryKey: ["behio", "shop-scripts"],
1381
3269
  queryFn: () => unwrap(client.getShopScripts()),
1382
3270
  enabled: options?.enabled !== false
@@ -1384,11 +3272,11 @@ function useShopScripts(options) {
1384
3272
  }
1385
3273
 
1386
3274
  // src/react/hooks/use-shop-seo.ts
1387
- import { useQuery as useQuery27 } from "@tanstack/react-query";
3275
+ import { useQuery as useQuery30 } from "@tanstack/react-query";
1388
3276
  function useShopSeo(options) {
1389
3277
  const { client } = useBehio();
1390
3278
  const { locale, initialData, enabled = true } = options ?? {};
1391
- return useQuery27({
3279
+ return useQuery30({
1392
3280
  queryKey: ["behio", "shop-seo", locale ?? "_default"],
1393
3281
  queryFn: () => unwrap(client.getShopSeo(locale)),
1394
3282
  initialData,
@@ -1451,20 +3339,20 @@ function CurrencySwitcher({
1451
3339
  import { useEffect as useEffect5, useMemo as useMemo4, useState as useState9 } from "react";
1452
3340
 
1453
3341
  // src/react/hooks/use-consent.ts
1454
- import { useQuery as useQuery28, useMutation as useMutation12, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
3342
+ import { useQuery as useQuery31, useMutation as useMutation15, useQueryClient as useQueryClient15 } from "@tanstack/react-query";
1455
3343
  function useCookieConsent(visitorId) {
1456
3344
  const { client } = useBehio();
1457
- const qc = useQueryClient12();
1458
- const query = useQuery28({
3345
+ const qc = useQueryClient15();
3346
+ const query = useQuery31({
1459
3347
  queryKey: ["behio", "consent", visitorId],
1460
3348
  queryFn: () => unwrap(client.consent.get(visitorId)),
1461
3349
  enabled: Boolean(visitorId)
1462
3350
  });
1463
- const recordMutation = useMutation12({
3351
+ const recordMutation = useMutation15({
1464
3352
  mutationFn: (input) => unwrap(client.consent.record(input)),
1465
3353
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
1466
3354
  });
1467
- const revokeMutation = useMutation12({
3355
+ const revokeMutation = useMutation15({
1468
3356
  mutationFn: () => unwrap(client.consent.revoke(visitorId)),
1469
3357
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
1470
3358
  });
@@ -1823,10 +3711,10 @@ function utmFromSearch(search) {
1823
3711
  }
1824
3712
 
1825
3713
  // src/react/hooks/use-bundles.ts
1826
- import { useQuery as useQuery29 } from "@tanstack/react-query";
3714
+ import { useQuery as useQuery32 } from "@tanstack/react-query";
1827
3715
  function useBundles(options) {
1828
3716
  const { client } = useBehio();
1829
- return useQuery29({
3717
+ return useQuery32({
1830
3718
  queryKey: ["behio", "bundles"],
1831
3719
  queryFn: () => unwrap(client.catalog.getBundles()),
1832
3720
  enabled: options?.enabled ?? true,
@@ -1835,7 +3723,7 @@ function useBundles(options) {
1835
3723
  }
1836
3724
  function useBundle(slug, options) {
1837
3725
  const { client } = useBehio();
1838
- return useQuery29({
3726
+ return useQuery32({
1839
3727
  queryKey: ["behio", "bundle", slug],
1840
3728
  queryFn: () => unwrap(client.catalog.getBundle(slug)),
1841
3729
  enabled: Boolean(slug) && (options?.enabled ?? true),
@@ -1844,10 +3732,10 @@ function useBundle(slug, options) {
1844
3732
  }
1845
3733
 
1846
3734
  // src/react/hooks/use-product-group.ts
1847
- import { useQuery as useQuery30 } from "@tanstack/react-query";
3735
+ import { useQuery as useQuery33 } from "@tanstack/react-query";
1848
3736
  function useProductGroup(slug, options) {
1849
3737
  const { client } = useBehio();
1850
- return useQuery30({
3738
+ return useQuery33({
1851
3739
  queryKey: ["behio", "product-group", slug, options?.locale, options?.currency],
1852
3740
  queryFn: () => unwrap(
1853
3741
  client.catalog.getProductGroup(slug, {
@@ -1861,10 +3749,10 @@ function useProductGroup(slug, options) {
1861
3749
  }
1862
3750
 
1863
3751
  // src/react/hooks/use-cross-sell.ts
1864
- import { useQuery as useQuery31 } from "@tanstack/react-query";
3752
+ import { useQuery as useQuery34 } from "@tanstack/react-query";
1865
3753
  function useCrossSell(productSlug, options) {
1866
3754
  const { client } = useBehio();
1867
- return useQuery31({
3755
+ return useQuery34({
1868
3756
  queryKey: ["behio", "cross-sell", productSlug, options?.locale, options?.currency],
1869
3757
  queryFn: () => unwrap(
1870
3758
  client.catalog.getCrossSell(productSlug, {
@@ -1878,10 +3766,10 @@ function useCrossSell(productSlug, options) {
1878
3766
  }
1879
3767
 
1880
3768
  // src/react/hooks/use-product-promotions.ts
1881
- import { useQuery as useQuery32 } from "@tanstack/react-query";
3769
+ import { useQuery as useQuery35 } from "@tanstack/react-query";
1882
3770
  function useProductPromotions(productSlug, options) {
1883
3771
  const { client } = useBehio();
1884
- return useQuery32({
3772
+ return useQuery35({
1885
3773
  queryKey: ["behio", "product-promotions", productSlug],
1886
3774
  queryFn: () => unwrap(client.catalog.getProductPromotions(productSlug)),
1887
3775
  enabled: Boolean(productSlug) && (options?.enabled ?? true),
@@ -1890,11 +3778,11 @@ function useProductPromotions(productSlug, options) {
1890
3778
  }
1891
3779
 
1892
3780
  // src/react/hooks/use-gift-card.ts
1893
- import { useQuery as useQuery33 } from "@tanstack/react-query";
3781
+ import { useQuery as useQuery36 } from "@tanstack/react-query";
1894
3782
  function useGiftCardBalance(code, options) {
1895
3783
  const { client } = useBehio();
1896
3784
  const trimmed = code?.trim();
1897
- return useQuery33({
3785
+ return useQuery36({
1898
3786
  queryKey: ["behio", "gift-card-balance", trimmed],
1899
3787
  queryFn: () => unwrap(client.catalog.checkGiftCard(trimmed)),
1900
3788
  enabled: Boolean(trimmed && trimmed.length >= 6) && (options?.enabled ?? true)
@@ -1902,20 +3790,20 @@ function useGiftCardBalance(code, options) {
1902
3790
  }
1903
3791
 
1904
3792
  // src/react/hooks/use-wishlist.ts
1905
- import { useQuery as useQuery34, useMutation as useMutation13, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
3793
+ import { useQuery as useQuery37, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
1906
3794
  function useWishlist(options) {
1907
3795
  const { client } = useBehio();
1908
- const qc = useQueryClient13();
1909
- const query = useQuery34({
3796
+ const qc = useQueryClient16();
3797
+ const query = useQuery37({
1910
3798
  queryKey: ["behio", "wishlist"],
1911
3799
  queryFn: () => unwrap(client.wishlist.get()),
1912
3800
  enabled: options?.enabled ?? true
1913
3801
  });
1914
- const addMutation = useMutation13({
3802
+ const addMutation = useMutation16({
1915
3803
  mutationFn: (productId) => unwrap(client.wishlist.add(productId)),
1916
3804
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
1917
3805
  });
1918
- const removeMutation = useMutation13({
3806
+ const removeMutation = useMutation16({
1919
3807
  mutationFn: (productId) => unwrap(client.wishlist.remove(productId)),
1920
3808
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
1921
3809
  });
@@ -1929,7 +3817,7 @@ function useWishlist(options) {
1929
3817
  }
1930
3818
  function useIsInWishlist(productId) {
1931
3819
  const { client } = useBehio();
1932
- return useQuery34({
3820
+ return useQuery37({
1933
3821
  queryKey: ["behio", "wishlist-check", productId],
1934
3822
  queryFn: () => unwrap(client.wishlist.isInWishlist(productId)),
1935
3823
  enabled: Boolean(productId)
@@ -1937,10 +3825,10 @@ function useIsInWishlist(productId) {
1937
3825
  }
1938
3826
 
1939
3827
  // src/react/hooks/use-reviews.ts
1940
- import { useQuery as useQuery35, useMutation as useMutation14, useQueryClient as useQueryClient14 } from "@tanstack/react-query";
3828
+ import { useQuery as useQuery38, useMutation as useMutation17, useQueryClient as useQueryClient17 } from "@tanstack/react-query";
1941
3829
  function useProductReviews(productId, options) {
1942
3830
  const { client } = useBehio();
1943
- return useQuery35({
3831
+ return useQuery38({
1944
3832
  queryKey: ["behio", "reviews", productId, options?.page ?? 1],
1945
3833
  queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
1946
3834
  enabled: Boolean(productId) && (options?.enabled ?? true)
@@ -1948,30 +3836,30 @@ function useProductReviews(productId, options) {
1948
3836
  }
1949
3837
  function useSubmitReview() {
1950
3838
  const { client } = useBehio();
1951
- const qc = useQueryClient14();
1952
- return useMutation14({
3839
+ const qc = useQueryClient17();
3840
+ return useMutation17({
1953
3841
  mutationFn: (input) => unwrap(client.reviews.submit(input)),
1954
3842
  onSuccess: (_, input) => qc.invalidateQueries({ queryKey: ["behio", "reviews", input.productId] })
1955
3843
  });
1956
3844
  }
1957
3845
 
1958
3846
  // src/react/hooks/use-returns.ts
1959
- import { useQuery as useQuery36, useMutation as useMutation15 } from "@tanstack/react-query";
3847
+ import { useQuery as useQuery39, useMutation as useMutation18 } from "@tanstack/react-query";
1960
3848
  function useLookupReturnableOrder() {
1961
3849
  const { client } = useBehio();
1962
- return useMutation15({
3850
+ return useMutation18({
1963
3851
  mutationFn: ({ orderNumber, email }) => unwrap(client.returns.lookupOrder(orderNumber, email))
1964
3852
  });
1965
3853
  }
1966
3854
  function useSubmitReturn() {
1967
3855
  const { client } = useBehio();
1968
- return useMutation15({
3856
+ return useMutation18({
1969
3857
  mutationFn: (input) => unwrap(client.returns.submit(input))
1970
3858
  });
1971
3859
  }
1972
3860
  function useReturnStatus(returnId, email) {
1973
3861
  const { client } = useBehio();
1974
- return useQuery36({
3862
+ return useQuery39({
1975
3863
  queryKey: ["behio", "return-status", returnId],
1976
3864
  queryFn: () => unwrap(client.returns.getStatus(returnId, email)),
1977
3865
  enabled: Boolean(returnId && email)
@@ -1979,16 +3867,16 @@ function useReturnStatus(returnId, email) {
1979
3867
  }
1980
3868
 
1981
3869
  // src/react/hooks/use-quotes.ts
1982
- import { useMutation as useMutation16, useQuery as useQuery37 } from "@tanstack/react-query";
3870
+ import { useMutation as useMutation19, useQuery as useQuery40 } from "@tanstack/react-query";
1983
3871
  function useSubmitQuote() {
1984
3872
  const { client } = useBehio();
1985
- return useMutation16({
3873
+ return useMutation19({
1986
3874
  mutationFn: (input) => unwrap(client.quotes.submit(input))
1987
3875
  });
1988
3876
  }
1989
3877
  function useQuoteStatus(quoteId, email) {
1990
3878
  const { client } = useBehio();
1991
- return useQuery37({
3879
+ return useQuery40({
1992
3880
  queryKey: ["behio", "quote-status", quoteId],
1993
3881
  queryFn: () => unwrap(client.quotes.getStatus(quoteId, email)),
1994
3882
  enabled: Boolean(quoteId && email)
@@ -1996,13 +3884,123 @@ function useQuoteStatus(quoteId, email) {
1996
3884
  }
1997
3885
 
1998
3886
  // src/react/hooks/use-back-in-stock.ts
1999
- import { useMutation as useMutation17 } from "@tanstack/react-query";
3887
+ import { useMutation as useMutation20 } from "@tanstack/react-query";
2000
3888
  function useNotifyWhenAvailable() {
2001
3889
  const { client } = useBehio();
2002
- return useMutation17({
3890
+ return useMutation20({
2003
3891
  mutationFn: ({ productId, email }) => unwrap(client.catalog.notifyWhenAvailable(productId, email))
2004
3892
  });
2005
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
+ }
2006
4004
  export {
2007
4005
  BehioAnalyticsTracker,
2008
4006
  BehioProvider,
@@ -2031,9 +4029,11 @@ export {
2031
4029
  useCartCount,
2032
4030
  useCategories,
2033
4031
  useCategory,
4032
+ useCertificateVerification,
2034
4033
  useCheckout,
2035
4034
  useCookieConsent,
2036
4035
  useCourse,
4036
+ useCourseCertificates,
2037
4037
  useCourses,
2038
4038
  useCrossSell,
2039
4039
  useCurrency,
@@ -2044,6 +4044,10 @@ export {
2044
4044
  useGiftCardBalance,
2045
4045
  useIsInWishlist,
2046
4046
  useLabels,
4047
+ useLessonComments,
4048
+ useLessonNote,
4049
+ useLessonQuiz,
4050
+ useLessonTutor,
2047
4051
  useLookupReturnableOrder,
2048
4052
  useLoyalty,
2049
4053
  useMenu,