@behio/storefront-sdk 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -509,6 +509,7 @@ const tracked = await shop.orders.track('tracking-token-uuid');
509
509
  | Hook | Purpose | Auth required |
510
510
  |------|---------|---------------|
511
511
  | [`useShopInfo()`](#useshopinfo) | E-shop info (name, currencies, languages) | No |
512
+ | [`useShopSeo(opts?)`](#useshopseo) | Per-locale SEO metadata for the homepage | No |
512
513
  | [`useProducts(query?)`](#useproducts) | Product list with filters, pagination, search | No |
513
514
  | [`useProduct(slug)`](#useproduct) | Product detail | No |
514
515
  | [`useCategories(locale?)`](#usecategories) | Category tree | No |
@@ -525,6 +526,9 @@ const tracked = await shop.orders.track('tracking-token-uuid');
525
526
  | [`useCustomer()`](#usecustomer) | Profile + update | Yes |
526
527
  | [`useAddresses()`](#useaddresses) | Address CRUD | Yes |
527
528
  | [`usePages()` / `usePage(slug)`](#usepages) | CMS pages | No |
529
+ | [`useBundles()` / `useBundle(slug)`](#usebundles) | Active bundles (sets) with auto-computed savings | No |
530
+ | [`useCrossSell(productSlug)`](#usecrosssell) | Related / upsell / cross-sell products per product | No |
531
+ | [`useProductPromotions(productSlug)`](#useproductpromotions) | Active promotions applicable to a product (with countdown) | No |
528
532
 
529
533
  \* Guest checkout works without auth if the e-shop allows it.
530
534
 
@@ -534,6 +538,55 @@ const { data, isLoading, error } = useShopInfo();
534
538
  // data: ShopInfo
535
539
  ```
536
540
 
541
+ ### useShopSeo
542
+
543
+ Returns per-locale SEO metadata (title, description, keywords, OG tags). Falls back to the shop's default locale when `locale` is omitted, and to `metaTitle / metaDescription` on the shop itself if there's no per-locale override.
544
+
545
+ ```tsx
546
+ const { data: seo } = useShopSeo({ locale: 'cs' });
547
+ // data: { locale, title, description, keywords, ogTitle, ogDescription, ogImage }
548
+ ```
549
+
550
+ **Server-side / SSR** — use the core client directly to fetch at request time and pass as `initialData`:
551
+
552
+ ```tsx
553
+ // app/[locale]/page.tsx (Next.js RSC)
554
+ import { BehioStorefront } from '@behio/storefront-sdk';
555
+ import type { Metadata } from 'next';
556
+
557
+ const shop = new BehioStorefront({ apiKey: process.env.BEHIO_API_KEY! });
558
+
559
+ export async function generateMetadata({ params }: { params: { locale: string } }): Promise<Metadata> {
560
+ const seo = await shop.getShopSeo(params.locale);
561
+ return {
562
+ title: seo.title ?? undefined,
563
+ description: seo.description ?? undefined,
564
+ openGraph: {
565
+ title: seo.ogTitle ?? seo.title ?? undefined,
566
+ description: seo.ogDescription ?? seo.description ?? undefined,
567
+ images: seo.ogImage ? [seo.ogImage] : undefined,
568
+ },
569
+ };
570
+ }
571
+
572
+ export default async function Home({ params }: { params: { locale: string } }) {
573
+ const seo = await shop.getShopSeo(params.locale);
574
+ // Pass to client component for React Query hydration:
575
+ // <ClientHome initialSeo={seo} />
576
+ }
577
+ ```
578
+
579
+ Client hydration:
580
+ ```tsx
581
+ 'use client';
582
+ import { useShopSeo } from '@behio/storefront-sdk/react';
583
+
584
+ export function ClientHome({ initialSeo }: { initialSeo: ShopSeo }) {
585
+ const { data } = useShopSeo({ locale: 'cs', initialData: initialSeo });
586
+ return <h1>{data.title}</h1>;
587
+ }
588
+ ```
589
+
537
590
  ### useProducts
538
591
 
539
592
  Supports **both** traditional pagination and infinite scroll from one hook.
@@ -735,6 +788,336 @@ const { data: pages } = usePages('cs');
735
788
  const { data: page } = usePage('about-us', 'cs');
736
789
  ```
737
790
 
791
+ ### useBundles / useBundle
792
+
793
+ **What it is.** A "bundle" is a merchant-curated set of products sold together
794
+ at a single fixed price that's (usually) lower than buying the components
795
+ individually — think *starter kit*, *holiday gift set*, *3-for-2 deals*, or
796
+ *"breakfast combo"*. The merchant defines what goes in (which products, what
797
+ quantities, what cover image) and sets one total price for the whole thing.
798
+
799
+ **Why it matters.** Bundles are one of the highest-ROI features in e-commerce:
800
+ they raise average order value without having to discount individual products,
801
+ they give customers a clear "good deal" signal (the savings badge), and they
802
+ let you clear slow-moving inventory by pairing it with fast-movers. Most eshop
803
+ platforms treat bundles as paid add-ons or plugins — here it's native.
804
+
805
+ **Where you use it.**
806
+
807
+ - **Homepage / landing pages** — list active bundles as hero cards with cover
808
+ image + `-25%` badge. Drives impulse purchase.
809
+ - **Category pages** — show a relevant bundle ("vše na grilování") next to
810
+ individual products in the same category.
811
+ - **Cart / checkout** — suggest a bundle as upsell ("Přidejte ještě tohle a
812
+ dostanete celý set s 20% slevou").
813
+ - **Dedicated `/bundles` page** — marketing landing for all active sets.
814
+
815
+ **What's in the data:**
816
+
817
+ - `bundlePrice` — what the customer pays for the whole set
818
+ - `itemsSum` — what the components would cost individually (sum of default prices)
819
+ - `savings` — `itemsSum - bundlePrice` (absolute savings in the currency)
820
+ - `savingsPercent` — pre-computed percent so you don't have to do the math in JSX
821
+ - `items[]` — the components with their quantities (for display and stock check)
822
+ - `endsAt` — optional expiry timestamp; if set, the bundle auto-deactivates
823
+
824
+ ```tsx
825
+ import { useBundles, useBundle, useCart, useBehio } from '@behio/storefront-sdk/react';
826
+
827
+ // ---- Homepage hero: all active bundles ----
828
+ function BundlesGrid() {
829
+ const { data, isLoading } = useBundles();
830
+ if (isLoading) return <Skeleton />;
831
+ if (!data?.items.length) return null; // no active bundles → hide section
832
+
833
+ return (
834
+ <section className="grid grid-cols-3 gap-4">
835
+ {data.items.map((bundle) => (
836
+ <a key={bundle.id} href={`/bundle/${bundle.slug}`} className="relative">
837
+ {bundle.coverImage && <img src={bundle.coverImage} alt={bundle.name} />}
838
+ <h3>{bundle.name}</h3>
839
+ <div>
840
+ <strong>{bundle.bundlePrice} {bundle.currency}</strong>
841
+ {bundle.savings > 0 && (
842
+ <>
843
+ <s>{bundle.itemsSum} {bundle.currency}</s>
844
+ <span className="badge">-{bundle.savingsPercent}%</span>
845
+ </>
846
+ )}
847
+ </div>
848
+ <p>{bundle.items.length} produktů ušetříte {bundle.savings} {bundle.currency}</p>
849
+ </a>
850
+ ))}
851
+ </section>
852
+ );
853
+ }
854
+
855
+ // ---- Bundle detail page with "Add to cart" ----
856
+ function BundleDetail({ slug }: { slug: string }) {
857
+ const { data: bundle, isLoading } = useBundle(slug);
858
+ const { client } = useBehio();
859
+ const { refresh } = useCart();
860
+
861
+ if (isLoading) return <Skeleton />;
862
+ if (!bundle) return <NotFound />;
863
+
864
+ async function addToCart() {
865
+ await client.cart.addBundle(bundle.id, 1);
866
+ await refresh(); // cart badge updates everywhere
867
+ }
868
+
869
+ return (
870
+ <article>
871
+ <h1>{bundle.name}</h1>
872
+ <p>{bundle.description}</p>
873
+
874
+ <ul>
875
+ {bundle.items.map((item) => (
876
+ <li key={item.productId}>
877
+ {item.quantity}× {item.name}
878
+ {item.defaultPrice && (
879
+ <span className="text-muted">
880
+ ({item.defaultPrice} {bundle.currency} /ks běžně)
881
+ </span>
882
+ )}
883
+ </li>
884
+ ))}
885
+ </ul>
886
+
887
+ <div className="price-box">
888
+ <strong>{bundle.bundlePrice} {bundle.currency}</strong>
889
+ {bundle.savings > 0 && (
890
+ <p>
891
+ Jednotlivě by stálo <s>{bundle.itemsSum} {bundle.currency}</s> —
892
+ ušetříte <strong>{bundle.savings} {bundle.currency}</strong>
893
+ ({bundle.savingsPercent}%)
894
+ </p>
895
+ )}
896
+ <button onClick={addToCart}>Přidat celý balíček do košíku</button>
897
+ </div>
898
+ </article>
899
+ );
900
+ }
901
+ ```
902
+
903
+ **Behind the scenes at checkout.** When the customer buys a bundle, Behio
904
+ rozpadá balíček do order-itemů s proporčně rozpočítanou cenou podle
905
+ defaultních cen komponent (zachováno pro účetnictví a skladové odpisy), ale
906
+ total na faktuře odpovídá `bundlePrice` — zákazník vidí jednu celistvou
907
+ položku, sklady se odepisují správně z komponent.
908
+
909
+ ---
910
+
911
+ ### useCrossSell
912
+
913
+ **What it is.** Three related lists of product recommendations shown on a
914
+ product detail page:
915
+
916
+ - **Related (Podobné)** — alternativy ke stejnému účelu (jiné vodítko, jiné
917
+ krmivo). Když tě tenhle produkt zaujal, tady jsou jiné stejné kategorie.
918
+ - **Upsell (Lepší varianta)** — dražší / vybavenější verze. „Vidíš levný
919
+ obojek? Tady je verze s koženým prošitím za 2×." Cílem je zvýšit
920
+ average order value.
921
+ - **Cross-sell (Často kupováno s)** — komplementární produkty. K obojku
922
+ vodítko, k misce čistič. Čistý AOV boost na PDP a v košíku.
923
+
924
+ **Why to split them.** Každý z těchto typů má jinou UX roli a měla by být
925
+ různě formulována. Když se to smíchá, ztratí se kontext — zákazník nepozná,
926
+ proč mu to zobrazuješ.
927
+
928
+ **Where to use it.**
929
+
930
+ - **Product detail page** — tři samostatné sekce pod popisem (nebo v sidebar
931
+ sloupci). Nejvyšší konverzní dopad je **Cross-sell "často kupováno s"**
932
+ přímo u tlačítka "Přidat do košíku".
933
+ - **Cart sidebar** — mini-cross-sell widget ("Nezapomeňte ještě na tohle").
934
+ - **Post-purchase page** — "Chcete ještě tohle?" pro druhou objednávku.
935
+
936
+ **Pozor.** Endpoint vrací **jen aktivní produkty ve stejném eshopu**. Když
937
+ jsi v adminu nějaký linknul a pak ho deaktivoval/smazal, zmizí ze seznamu
938
+ automaticky — nemusíš to řešit na FE.
939
+
940
+ ```tsx
941
+ import { useCrossSell, useBehio } from '@behio/storefront-sdk/react';
942
+
943
+ function CrossSellSection({ title, items }: { title: string; items: CrossSellItem[] }) {
944
+ if (!items?.length) return null; // nezobrazuj prázdnou sekci
945
+ return (
946
+ <section>
947
+ <h2>{title}</h2>
948
+ <div className="carousel">
949
+ {items.map((item) => (
950
+ <a key={item.productId} href={`/produkt/${item.slug}`} className="card">
951
+ {item.imageUrl && <img src={item.imageUrl} alt={item.name} />}
952
+ <h4>{item.name}</h4>
953
+ <span>{item.price} Kč</span>
954
+ {item.stockCached === 0 && <span className="text-red">Vyprodáno</span>}
955
+ </a>
956
+ ))}
957
+ </div>
958
+ </section>
959
+ );
960
+ }
961
+
962
+ function ProductDetail({ slug }: { slug: string }) {
963
+ const { data: crossSell } = useCrossSell(slug);
964
+
965
+ return (
966
+ <>
967
+ {/* ... product info ... */}
968
+
969
+ <CrossSellSection
970
+ title="Často kupováno s"
971
+ items={crossSell?.crossSell ?? []}
972
+ />
973
+ <CrossSellSection
974
+ title="Možná vás zaujme i"
975
+ items={crossSell?.related ?? []}
976
+ />
977
+ <CrossSellSection
978
+ title="Chcete raději lepší variantu?"
979
+ items={crossSell?.upsell ?? []}
980
+ />
981
+ </>
982
+ );
983
+ }
984
+ ```
985
+
986
+ **Tip — merge do jedné sekce.** Pokud máš málo content a chceš to zjednodušit:
987
+
988
+ ```tsx
989
+ const allRecommendations = [
990
+ ...(crossSell?.crossSell ?? []),
991
+ ...(crossSell?.related ?? []),
992
+ ...(crossSell?.upsell ?? []),
993
+ ].slice(0, 6);
994
+ ```
995
+
996
+ ---
997
+
998
+ ### useProductPromotions
999
+
1000
+ **What it is.** Vrací aktuálně platné akce (`Eshop_Promotion`) týkající se
1001
+ konkrétního produktu — tedy všechny promotions, kde `startsAt <= teď < endsAt`
1002
+ a produkt spadá do scope akce (může to být `ALL_PRODUCTS`, `SPECIFIC_PRODUCTS`,
1003
+ `CATEGORIES` nebo `LABELS`, backend to vyřeší za tebe).
1004
+
1005
+ **Co to není.** Není to výpočet *konečné ceny po slevě* — na to je samostatná
1006
+ vrstva (cart evaluator). Tenhle hook je čistě pro **zobrazovací vrstvu**: badge,
1007
+ countdown, upozornění „akční cena do půlnoci".
1008
+
1009
+ **Proč je to oddělené.** Konverzně nejsilnější marketingový prvek v eshopu je
1010
+ **urgency + scarcity**. „Do konce akce: 2h 14m 37s" přímo na PDP zvyšuje
1011
+ conversion rate měřitelně — je to letitá best practice (Amazon Lightning
1012
+ Deals, Booking "rezervováno 3× za posledních 24h", atd.). Hook ti dodá data,
1013
+ ty uděláš countdown komponentu.
1014
+
1015
+ **Kde to použít.**
1016
+
1017
+ - **Product card v listu** — badge `-20%` nebo vlajka „AKCE".
1018
+ - **Product detail** — velký banner s countdown nad cenou: „Koupíte-li do
1019
+ půlnoci, ušetříte 200 Kč."
1020
+ - **Cart** — varování „Vaše sleva vyprší za 5 minut" aby tlačilo k
1021
+ dokončení objednávky.
1022
+
1023
+ **Pole z API:**
1024
+
1025
+ - `id`, `name`, `slug` — identifikace akce
1026
+ - `type` — `FLASH_SALE` / `SEASONAL` / `CLEARANCE` / `BOGO` / `BUNDLE` / `LOYALTY`
1027
+ - `discountType` — `PERCENTAGE` / `FIXED_AMOUNT` / `FREE_SHIPPING` / `BUY_X_GET_Y`
1028
+ - `discountValue` — hodnota (u PERCENTAGE je to %, u FIXED_AMOUNT Kč, …)
1029
+ - `endsAt` — timestamp konce. Pokud `null`, akce je na neurčito.
1030
+ - `badgeText`, `badgeColor` — merchant ručně nastavil text a barvu badge
1031
+ (např. „-30%" s červenou); pokud oboje `null`, vymysli default podle `discountType`.
1032
+ - `showCountdown` — **respect this!** Když merchant nechce countdown, nerender
1033
+ ho. Ne všechny akce (CLEARANCE, LOYALTY) mají smysl odpočítávat.
1034
+ - `couponRequired` — akce se uplatní jen po zadání kódu na checkoutu.
1035
+ Na PDP ukaž info box („Použijte kód LETO pro uplatnění"), neukazuj jako
1036
+ automatickou slevu.
1037
+
1038
+ ```tsx
1039
+ import { useProductPromotions } from '@behio/storefront-sdk/react';
1040
+ import { useEffect, useState } from 'react';
1041
+
1042
+ function ProductPromotionBanner({ slug }: { slug: string }) {
1043
+ // Refetch každé 4 minuty aby se promotion aktualizovala když mezitím
1044
+ // skončila jedna a začala další. Pro pouhý countdown nemusíš refetchovat —
1045
+ // countdown děláš lokálně z endsAt.
1046
+ const { data } = useProductPromotions(slug, { refetchIntervalMs: 4 * 60_000 });
1047
+ const promotions = data?.items ?? [];
1048
+
1049
+ if (!promotions.length) return null;
1050
+
1051
+ // Nejvyšší priority akce (BE už vrací seřazené priority DESC + createdAt ASC)
1052
+ const promo = promotions[0];
1053
+
1054
+ const badge = promo.badgeText ?? formatDefaultBadge(promo);
1055
+ const badgeColor = promo.badgeColor ?? '#ef4444';
1056
+
1057
+ return (
1058
+ <div className="promo-banner" style={{ backgroundColor: badgeColor + '20', borderColor: badgeColor }}>
1059
+ <div>
1060
+ <span className="badge" style={{ backgroundColor: badgeColor }}>{badge}</span>
1061
+ <strong>{promo.name}</strong>
1062
+ {promo.couponRequired && (
1063
+ <p>Uplatníte zadáním kódu <code>{promo.name}</code> v košíku</p>
1064
+ )}
1065
+ </div>
1066
+ {promo.showCountdown && promo.endsAt && <Countdown endsAt={promo.endsAt} />}
1067
+ </div>
1068
+ );
1069
+ }
1070
+
1071
+ function Countdown({ endsAt }: { endsAt: number }) {
1072
+ const [now, setNow] = useState(Date.now());
1073
+ useEffect(() => {
1074
+ const id = setInterval(() => setNow(Date.now()), 1000);
1075
+ return () => clearInterval(id);
1076
+ }, []);
1077
+
1078
+ const ms = Math.max(0, endsAt - now);
1079
+ if (ms === 0) return <span>Akce skončila</span>;
1080
+
1081
+ const d = Math.floor(ms / 86400_000);
1082
+ const h = Math.floor((ms % 86400_000) / 3600_000);
1083
+ const m = Math.floor((ms % 3600_000) / 60_000);
1084
+ const s = Math.floor((ms % 60_000) / 1000);
1085
+
1086
+ if (d > 0) return <span>Končí za {d}d {h}h {m}m</span>;
1087
+ return <span>Končí za {h}h {m}m {s}s</span>;
1088
+ }
1089
+
1090
+ function formatDefaultBadge(p: { discountType: string; discountValue: number }) {
1091
+ switch (p.discountType) {
1092
+ case 'PERCENTAGE': return `-${p.discountValue}%`;
1093
+ case 'FIXED_AMOUNT': return `-${p.discountValue} Kč`;
1094
+ case 'FREE_SHIPPING': return 'Doprava zdarma';
1095
+ case 'BUY_X_GET_Y': return `${p.discountValue}+1 ZDARMA`;
1096
+ default: return 'AKCE';
1097
+ }
1098
+ }
1099
+ ```
1100
+
1101
+ **Pattern — product card badge.** Když chceš jen malou vlaječku na kartě
1102
+ produktu v listu (bez countdown), zavolej hook per-card a vezmi první promo:
1103
+
1104
+ ```tsx
1105
+ function ProductCardBadge({ slug }: { slug: string }) {
1106
+ const { data } = useProductPromotions(slug);
1107
+ const promo = data?.items[0];
1108
+ if (!promo) return null;
1109
+ return (
1110
+ <span className="badge" style={{ background: promo.badgeColor ?? '#ef4444' }}>
1111
+ {promo.badgeText ?? formatDefaultBadge(promo)}
1112
+ </span>
1113
+ );
1114
+ }
1115
+ ```
1116
+
1117
+ Pozor — v listovém kontextu to dělá N dotazů (jeden per karta). Pokud máš
1118
+ 200 produktů, radši vytvoř vlastní bulk endpoint nebo invaliduj méně často
1119
+ přes React Query `staleTime`.
1120
+
738
1121
  ## All API methods
739
1122
 
740
1123
  ### Catalog
@@ -748,6 +1131,17 @@ shop.catalog.getLabels(locale?) → { labels: ProductLabel[] }
748
1131
  shop.catalog.getFeatured() → PaginatedResponse<ProductListItem>
749
1132
  shop.catalog.getFilters() → { filters: FilterField[] }
750
1133
  shop.catalog.search(query, opts?) → PaginatedResponse<ProductListItem>
1134
+ shop.catalog.getBundles() → { items: Bundle[] }
1135
+ shop.catalog.getBundle(slug) → Bundle
1136
+ shop.catalog.getCrossSell(productSlug) → { related, upsell, crossSell: CrossSellItem[] }
1137
+ shop.catalog.getProductPromotions(slug) → { items: ActivePromotion[] }
1138
+ ```
1139
+
1140
+ ### Cart — bundles
1141
+ ```typescript
1142
+ shop.cart.addBundle(bundleId, quantity?) → Cart
1143
+ shop.cart.updateBundleQuantity(id, qty) → Cart
1144
+ shop.cart.removeBundle(bundleId) → Cart
751
1145
  ```
752
1146
 
753
1147
  ### Auth
@@ -807,6 +1201,7 @@ shop.pages.get(slug, locale?) → PageDetail
807
1201
  ### Instance utilities
808
1202
  ```typescript
809
1203
  shop.getShopInfo() → ShopInfo
1204
+ shop.getShopSeo(locale?) → ShopSeo (per-locale SEO for SSR/metadata)
810
1205
  shop.setTokens({ accessToken, refreshToken })
811
1206
  shop.clearTokens()
812
1207
  shop.getAccessToken() → string | undefined
@@ -108,6 +108,12 @@ var BehioStorefront = class {
108
108
  async getShopInfo() {
109
109
  return this.request("GET", "/shop");
110
110
  }
111
+ /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
112
+ async getShopSeo(locale) {
113
+ return this.request("GET", "/shop/seo", {
114
+ query: locale ? { locale } : void 0
115
+ });
116
+ }
111
117
  /** Set auth tokens (e.g. from localStorage) */
112
118
  setTokens(tokens) {
113
119
  this.accessToken = tokens.accessToken;
@@ -396,6 +402,22 @@ var CatalogModule = class {
396
402
  async search(query, options) {
397
403
  return this.getProducts({ search: query, ...options });
398
404
  }
405
+ /** List all active bundles */
406
+ async getBundles() {
407
+ return this.client.request("GET", "/catalog/bundles");
408
+ }
409
+ /** Get a single bundle by slug */
410
+ async getBundle(slug) {
411
+ return this.client.request("GET", `/catalog/bundles/${slug}`);
412
+ }
413
+ /** Cross-sell / related / upsell products for a product */
414
+ async getCrossSell(productSlug) {
415
+ return this.client.request("GET", `/catalog/products/${productSlug}/cross-sell`);
416
+ }
417
+ /** Active promotions applicable to a product (with countdown end time) */
418
+ async getProductPromotions(productSlug) {
419
+ return this.client.request("GET", `/catalog/products/${productSlug}/promotions`);
420
+ }
399
421
  };
400
422
  var AuthModule = class {
401
423
  constructor(client) {
@@ -507,6 +529,28 @@ var CartModule = class {
507
529
  this.client.emit("cart:cleared");
508
530
  return result;
509
531
  }
532
+ /** Add a bundle to the cart (price is locked at the bundle's current price) */
533
+ async addBundle(bundleId, quantity = 1) {
534
+ const result = await this.client.request("POST", "/cart/bundles", {
535
+ body: { bundleId, quantity }
536
+ });
537
+ this.client.emit("cart:updated", result);
538
+ return result;
539
+ }
540
+ /** Update quantity of a bundle already in the cart */
541
+ async updateBundleQuantity(bundleId, quantity) {
542
+ const result = await this.client.request("PATCH", `/cart/bundles/${bundleId}`, {
543
+ body: { quantity }
544
+ });
545
+ this.client.emit("cart:updated", result);
546
+ return result;
547
+ }
548
+ /** Remove a bundle from the cart */
549
+ async removeBundle(bundleId) {
550
+ const result = await this.client.request("DELETE", `/cart/bundles/${bundleId}`);
551
+ this.client.emit("cart:updated", result);
552
+ return result;
553
+ }
510
554
  /** Merge anonymous cart into authenticated customer cart */
511
555
  async merge() {
512
556
  const result = await this.client.request("POST", "/cart/merge");
@@ -561,7 +605,7 @@ var OrdersModule = class {
561
605
  async cancel(orderNumber) {
562
606
  return this.client.request("POST", `/orders/${orderNumber}/cancel`);
563
607
  }
564
- /** Track order by tracking token (no auth required) */
608
+ /** Track order by tracking token (no customer login required, only API key) */
565
609
  async track(trackingToken) {
566
610
  return this.client.request("GET", `/orders/track/${trackingToken}`, { auth: false });
567
611
  }
@@ -108,6 +108,12 @@ var BehioStorefront = class {
108
108
  async getShopInfo() {
109
109
  return this.request("GET", "/shop");
110
110
  }
111
+ /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
112
+ async getShopSeo(locale) {
113
+ return this.request("GET", "/shop/seo", {
114
+ query: locale ? { locale } : void 0
115
+ });
116
+ }
111
117
  /** Set auth tokens (e.g. from localStorage) */
112
118
  setTokens(tokens) {
113
119
  this.accessToken = tokens.accessToken;
@@ -396,6 +402,22 @@ var CatalogModule = class {
396
402
  async search(query, options) {
397
403
  return this.getProducts({ search: query, ...options });
398
404
  }
405
+ /** List all active bundles */
406
+ async getBundles() {
407
+ return this.client.request("GET", "/catalog/bundles");
408
+ }
409
+ /** Get a single bundle by slug */
410
+ async getBundle(slug) {
411
+ return this.client.request("GET", `/catalog/bundles/${slug}`);
412
+ }
413
+ /** Cross-sell / related / upsell products for a product */
414
+ async getCrossSell(productSlug) {
415
+ return this.client.request("GET", `/catalog/products/${productSlug}/cross-sell`);
416
+ }
417
+ /** Active promotions applicable to a product (with countdown end time) */
418
+ async getProductPromotions(productSlug) {
419
+ return this.client.request("GET", `/catalog/products/${productSlug}/promotions`);
420
+ }
399
421
  };
400
422
  var AuthModule = class {
401
423
  constructor(client) {
@@ -507,6 +529,28 @@ var CartModule = class {
507
529
  this.client.emit("cart:cleared");
508
530
  return result;
509
531
  }
532
+ /** Add a bundle to the cart (price is locked at the bundle's current price) */
533
+ async addBundle(bundleId, quantity = 1) {
534
+ const result = await this.client.request("POST", "/cart/bundles", {
535
+ body: { bundleId, quantity }
536
+ });
537
+ this.client.emit("cart:updated", result);
538
+ return result;
539
+ }
540
+ /** Update quantity of a bundle already in the cart */
541
+ async updateBundleQuantity(bundleId, quantity) {
542
+ const result = await this.client.request("PATCH", `/cart/bundles/${bundleId}`, {
543
+ body: { quantity }
544
+ });
545
+ this.client.emit("cart:updated", result);
546
+ return result;
547
+ }
548
+ /** Remove a bundle from the cart */
549
+ async removeBundle(bundleId) {
550
+ const result = await this.client.request("DELETE", `/cart/bundles/${bundleId}`);
551
+ this.client.emit("cart:updated", result);
552
+ return result;
553
+ }
510
554
  /** Merge anonymous cart into authenticated customer cart */
511
555
  async merge() {
512
556
  const result = await this.client.request("POST", "/cart/merge");
@@ -561,7 +605,7 @@ var OrdersModule = class {
561
605
  async cancel(orderNumber) {
562
606
  return this.client.request("POST", `/orders/${orderNumber}/cancel`);
563
607
  }
564
- /** Track order by tracking token (no auth required) */
608
+ /** Track order by tracking token (no customer login required, only API key) */
565
609
  async track(trackingToken) {
566
610
  return this.client.request("GET", `/orders/track/${trackingToken}`, { auth: false });
567
611
  }
package/dist/index.d.mts CHANGED
@@ -41,6 +41,15 @@ interface ShopInfo {
41
41
  metaDescription?: string;
42
42
  allowGuestCheckout: boolean;
43
43
  }
44
+ interface ShopSeo {
45
+ locale: string;
46
+ title: string | null;
47
+ description: string | null;
48
+ keywords: string | null;
49
+ ogTitle: string | null;
50
+ ogDescription: string | null;
51
+ ogImage: string | null;
52
+ }
44
53
  interface ProductPrice {
45
54
  amount: number;
46
55
  currency: string;
@@ -381,6 +390,52 @@ interface ResponseInterceptorData {
381
390
  interface ResponseInterceptor {
382
391
  (response: ResponseInterceptorData): void | Promise<void>;
383
392
  }
393
+ interface BundleItem {
394
+ productId: string;
395
+ slug: string | null;
396
+ name: string;
397
+ sku: string;
398
+ quantity: number;
399
+ imageUrl: string | null;
400
+ defaultPrice: number | null;
401
+ }
402
+ interface Bundle {
403
+ id: string;
404
+ slug: string;
405
+ name: string;
406
+ description: string | null;
407
+ bundlePrice: number;
408
+ currency: string;
409
+ coverImage: string | null;
410
+ endsAt: number | null;
411
+ itemsSum: number;
412
+ savings: number;
413
+ savingsPercent: number;
414
+ items: BundleItem[];
415
+ }
416
+ interface CrossSellItem {
417
+ productId: string;
418
+ slug: string | null;
419
+ name: string;
420
+ sku: string;
421
+ price: number | null;
422
+ imageUrl: string | null;
423
+ stockCached: number;
424
+ }
425
+ interface ActivePromotion {
426
+ id: string;
427
+ name: string;
428
+ slug: string;
429
+ type: string;
430
+ discountType: string;
431
+ discountValue: number;
432
+ startsAt: number;
433
+ endsAt: number | null;
434
+ badgeText: string | null;
435
+ badgeColor: string | null;
436
+ showCountdown: boolean;
437
+ couponRequired: boolean;
438
+ }
384
439
 
385
440
  declare class BehioStorefront {
386
441
  private baseUrl;
@@ -411,6 +466,8 @@ declare class BehioStorefront {
411
466
  readonly pages: PagesModule;
412
467
  /** Get basic shop info */
413
468
  getShopInfo(): Promise<ShopInfo>;
469
+ /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
470
+ getShopSeo(locale?: string): Promise<ShopSeo>;
414
471
  /** Set auth tokens (e.g. from localStorage) */
415
472
  setTokens(tokens: {
416
473
  accessToken: string;
@@ -488,6 +545,22 @@ declare class CatalogModule {
488
545
  page?: number;
489
546
  limit?: number;
490
547
  }): Promise<PaginatedResponse<ProductListItem>>;
548
+ /** List all active bundles */
549
+ getBundles(): Promise<{
550
+ items: Bundle[];
551
+ }>;
552
+ /** Get a single bundle by slug */
553
+ getBundle(slug: string): Promise<Bundle>;
554
+ /** Cross-sell / related / upsell products for a product */
555
+ getCrossSell(productSlug: string): Promise<{
556
+ related: CrossSellItem[];
557
+ upsell: CrossSellItem[];
558
+ crossSell: CrossSellItem[];
559
+ }>;
560
+ /** Active promotions applicable to a product (with countdown end time) */
561
+ getProductPromotions(productSlug: string): Promise<{
562
+ items: ActivePromotion[];
563
+ }>;
491
564
  }
492
565
  declare class AuthModule {
493
566
  private client;
@@ -524,6 +597,12 @@ declare class CartModule {
524
597
  removeItem(itemId: string): Promise<Cart>;
525
598
  /** Clear entire cart */
526
599
  clear(): Promise<void>;
600
+ /** Add a bundle to the cart (price is locked at the bundle's current price) */
601
+ addBundle(bundleId: string, quantity?: number): Promise<Cart>;
602
+ /** Update quantity of a bundle already in the cart */
603
+ updateBundleQuantity(bundleId: string, quantity: number): Promise<Cart>;
604
+ /** Remove a bundle from the cart */
605
+ removeBundle(bundleId: string): Promise<Cart>;
527
606
  /** Merge anonymous cart into authenticated customer cart */
528
607
  merge(): Promise<Cart>;
529
608
  /** Apply discount code */
@@ -549,7 +628,7 @@ declare class OrdersModule {
549
628
  get(orderNumber: string): Promise<OrderDetail>;
550
629
  /** Cancel a PENDING order (requires auth) */
551
630
  cancel(orderNumber: string): Promise<OrderDetail>;
552
- /** Track order by tracking token (no auth required) */
631
+ /** Track order by tracking token (no customer login required, only API key) */
553
632
  track(trackingToken: string): Promise<OrderDetail>;
554
633
  }
555
634
  declare class CustomerModule {
@@ -583,4 +662,4 @@ declare class PagesModule {
583
662
  get(slug: string, locale?: string): Promise<PageDetail>;
584
663
  }
585
664
 
586
- export { type AddToCartInput, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ShopInfo };
665
+ export { type ActivePromotion, type AddToCartInput, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ShopInfo, type ShopSeo };
package/dist/index.d.ts CHANGED
@@ -41,6 +41,15 @@ interface ShopInfo {
41
41
  metaDescription?: string;
42
42
  allowGuestCheckout: boolean;
43
43
  }
44
+ interface ShopSeo {
45
+ locale: string;
46
+ title: string | null;
47
+ description: string | null;
48
+ keywords: string | null;
49
+ ogTitle: string | null;
50
+ ogDescription: string | null;
51
+ ogImage: string | null;
52
+ }
44
53
  interface ProductPrice {
45
54
  amount: number;
46
55
  currency: string;
@@ -381,6 +390,52 @@ interface ResponseInterceptorData {
381
390
  interface ResponseInterceptor {
382
391
  (response: ResponseInterceptorData): void | Promise<void>;
383
392
  }
393
+ interface BundleItem {
394
+ productId: string;
395
+ slug: string | null;
396
+ name: string;
397
+ sku: string;
398
+ quantity: number;
399
+ imageUrl: string | null;
400
+ defaultPrice: number | null;
401
+ }
402
+ interface Bundle {
403
+ id: string;
404
+ slug: string;
405
+ name: string;
406
+ description: string | null;
407
+ bundlePrice: number;
408
+ currency: string;
409
+ coverImage: string | null;
410
+ endsAt: number | null;
411
+ itemsSum: number;
412
+ savings: number;
413
+ savingsPercent: number;
414
+ items: BundleItem[];
415
+ }
416
+ interface CrossSellItem {
417
+ productId: string;
418
+ slug: string | null;
419
+ name: string;
420
+ sku: string;
421
+ price: number | null;
422
+ imageUrl: string | null;
423
+ stockCached: number;
424
+ }
425
+ interface ActivePromotion {
426
+ id: string;
427
+ name: string;
428
+ slug: string;
429
+ type: string;
430
+ discountType: string;
431
+ discountValue: number;
432
+ startsAt: number;
433
+ endsAt: number | null;
434
+ badgeText: string | null;
435
+ badgeColor: string | null;
436
+ showCountdown: boolean;
437
+ couponRequired: boolean;
438
+ }
384
439
 
385
440
  declare class BehioStorefront {
386
441
  private baseUrl;
@@ -411,6 +466,8 @@ declare class BehioStorefront {
411
466
  readonly pages: PagesModule;
412
467
  /** Get basic shop info */
413
468
  getShopInfo(): Promise<ShopInfo>;
469
+ /** Get SEO metadata for the shop homepage in the given locale (defaults to shop default). */
470
+ getShopSeo(locale?: string): Promise<ShopSeo>;
414
471
  /** Set auth tokens (e.g. from localStorage) */
415
472
  setTokens(tokens: {
416
473
  accessToken: string;
@@ -488,6 +545,22 @@ declare class CatalogModule {
488
545
  page?: number;
489
546
  limit?: number;
490
547
  }): Promise<PaginatedResponse<ProductListItem>>;
548
+ /** List all active bundles */
549
+ getBundles(): Promise<{
550
+ items: Bundle[];
551
+ }>;
552
+ /** Get a single bundle by slug */
553
+ getBundle(slug: string): Promise<Bundle>;
554
+ /** Cross-sell / related / upsell products for a product */
555
+ getCrossSell(productSlug: string): Promise<{
556
+ related: CrossSellItem[];
557
+ upsell: CrossSellItem[];
558
+ crossSell: CrossSellItem[];
559
+ }>;
560
+ /** Active promotions applicable to a product (with countdown end time) */
561
+ getProductPromotions(productSlug: string): Promise<{
562
+ items: ActivePromotion[];
563
+ }>;
491
564
  }
492
565
  declare class AuthModule {
493
566
  private client;
@@ -524,6 +597,12 @@ declare class CartModule {
524
597
  removeItem(itemId: string): Promise<Cart>;
525
598
  /** Clear entire cart */
526
599
  clear(): Promise<void>;
600
+ /** Add a bundle to the cart (price is locked at the bundle's current price) */
601
+ addBundle(bundleId: string, quantity?: number): Promise<Cart>;
602
+ /** Update quantity of a bundle already in the cart */
603
+ updateBundleQuantity(bundleId: string, quantity: number): Promise<Cart>;
604
+ /** Remove a bundle from the cart */
605
+ removeBundle(bundleId: string): Promise<Cart>;
527
606
  /** Merge anonymous cart into authenticated customer cart */
528
607
  merge(): Promise<Cart>;
529
608
  /** Apply discount code */
@@ -549,7 +628,7 @@ declare class OrdersModule {
549
628
  get(orderNumber: string): Promise<OrderDetail>;
550
629
  /** Cancel a PENDING order (requires auth) */
551
630
  cancel(orderNumber: string): Promise<OrderDetail>;
552
- /** Track order by tracking token (no auth required) */
631
+ /** Track order by tracking token (no customer login required, only API key) */
553
632
  track(trackingToken: string): Promise<OrderDetail>;
554
633
  }
555
634
  declare class CustomerModule {
@@ -583,4 +662,4 @@ declare class PagesModule {
583
662
  get(slug: string, locale?: string): Promise<PageDetail>;
584
663
  }
585
664
 
586
- export { type AddToCartInput, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ShopInfo };
665
+ export { type ActivePromotion, type AddToCartInput, type AddressType, AddressTypes, type AuthTokens, BehioApiError, type BehioErrorCode, type BehioEventHandler, type BehioEventType, BehioNetworkError, BehioStorefront, type BehioStorefrontConfig, type Bundle, type BundleItem, type Cart, type CartDiscount, type CartItem, type CartItemProduct, type Category, type CategoryDetail, type CheckoutAddress, type CheckoutInput, type CrossSellItem, type CustomerAddress, type CustomerProfile, type DataGroupFieldType, type FilterField, type FulfillmentStatus, FulfillmentStatuses, type LoginInput, type MessageResponse, type OrderDetail, type OrderItem, type OrderListItem, type OrderStatus, type OrderStatusHistory, OrderStatuses, type Page, type PageDetail, type PaginatedResponse, type PaymentStatus, PaymentStatuses, type ProductDetail, type ProductLabel, type ProductListItem, type ProductPrice, ProductSort, type ProductSortValue, type ProductVariant, type ProductVolumePrice, type ProductsQuery, type RegisterInput, type RequestInterceptor, type RequestInterceptorConfig, type ResponseInterceptor, type ResponseInterceptorData, type ShopInfo, type ShopSeo };
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkGGAO5T5Pjs = require('./chunk-GGAO5T5P.js');
10
+ var _chunkQOEYSUF2js = require('./chunk-QOEYSUF2.js');
11
11
 
12
12
 
13
13
 
@@ -17,4 +17,4 @@ var _chunkGGAO5T5Pjs = require('./chunk-GGAO5T5P.js');
17
17
 
18
18
 
19
19
 
20
- exports.AddressTypes = _chunkGGAO5T5Pjs.AddressTypes; exports.BehioApiError = _chunkGGAO5T5Pjs.BehioApiError; exports.BehioNetworkError = _chunkGGAO5T5Pjs.BehioNetworkError; exports.BehioStorefront = _chunkGGAO5T5Pjs.BehioStorefront; exports.FulfillmentStatuses = _chunkGGAO5T5Pjs.FulfillmentStatuses; exports.OrderStatuses = _chunkGGAO5T5Pjs.OrderStatuses; exports.PaymentStatuses = _chunkGGAO5T5Pjs.PaymentStatuses; exports.ProductSort = _chunkGGAO5T5Pjs.ProductSort;
20
+ exports.AddressTypes = _chunkQOEYSUF2js.AddressTypes; exports.BehioApiError = _chunkQOEYSUF2js.BehioApiError; exports.BehioNetworkError = _chunkQOEYSUF2js.BehioNetworkError; exports.BehioStorefront = _chunkQOEYSUF2js.BehioStorefront; exports.FulfillmentStatuses = _chunkQOEYSUF2js.FulfillmentStatuses; exports.OrderStatuses = _chunkQOEYSUF2js.OrderStatuses; exports.PaymentStatuses = _chunkQOEYSUF2js.PaymentStatuses; exports.ProductSort = _chunkQOEYSUF2js.ProductSort;
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  OrderStatuses,
8
8
  PaymentStatuses,
9
9
  ProductSort
10
- } from "./chunk-S4DOL3OV.mjs";
10
+ } from "./chunk-HYKJO2IB.mjs";
11
11
  export {
12
12
  AddressTypes,
13
13
  BehioApiError,
package/dist/react.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _tanstack_react_query from '@tanstack/react-query';
3
3
  import { QueryClient } from '@tanstack/react-query';
4
- import { BehioStorefront, ProductsQuery, PaginatedResponse, ProductListItem, ProductDetail, Category, CategoryDetail, ProductLabel, FilterField, Cart, CustomerProfile, RegisterInput, CustomerAddress, OrderListItem, OrderDetail, CheckoutInput, PageDetail, Page, ShopInfo } from './index.mjs';
5
- export { AddToCartInput, AuthTokens, BehioApiError, CartDiscount, CartItem, CheckoutAddress, FulfillmentStatus, LoginInput, MessageResponse, OrderItem, OrderStatus, PaymentStatus, ProductPrice, ProductVariant } from './index.mjs';
4
+ import { BehioStorefront, ProductsQuery, PaginatedResponse, ProductListItem, ProductDetail, Category, CategoryDetail, ProductLabel, FilterField, Cart, CustomerProfile, RegisterInput, CustomerAddress, OrderListItem, OrderDetail, CheckoutInput, PageDetail, Page, ShopInfo, ShopSeo, Bundle, CrossSellItem, ActivePromotion } from './index.mjs';
5
+ export { AddToCartInput, AuthTokens, BehioApiError, BundleItem, CartDiscount, CartItem, CheckoutAddress, FulfillmentStatus, LoginInput, MessageResponse, OrderItem, OrderStatus, PaymentStatus, ProductPrice, ProductVariant } from './index.mjs';
6
6
  import * as _tanstack_query_core from '@tanstack/query-core';
7
7
 
8
8
  interface StorageAdapter {
@@ -265,6 +265,62 @@ interface UseShopInfoOptions {
265
265
  }
266
266
  declare function useShopInfo(options?: UseShopInfoOptions): _tanstack_react_query.UseQueryResult<ShopInfo, Error>;
267
267
 
268
+ interface UseShopSeoOptions {
269
+ /** Override locale (ISO-639-1). Defaults to the shop's default locale. */
270
+ locale?: string;
271
+ /** Hydrate from SSR-fetched data (use `client.getShopSeo(locale)` on the server). */
272
+ initialData?: ShopSeo;
273
+ /** Disable the query. */
274
+ enabled?: boolean;
275
+ }
276
+ /**
277
+ * React Query hook for per-locale shop SEO metadata. Safe to render on the
278
+ * server via `initialData` from `client.getShopSeo(locale)`.
279
+ */
280
+ declare function useShopSeo(options?: UseShopSeoOptions): _tanstack_react_query.UseQueryResult<ShopSeo, Error>;
281
+
282
+ /** List all active bundles. */
283
+ declare function useBundles(options?: {
284
+ enabled?: boolean;
285
+ initialData?: {
286
+ items: Bundle[];
287
+ };
288
+ }): _tanstack_react_query.UseQueryResult<{
289
+ items: Bundle[];
290
+ }, Error>;
291
+ /** Get a single bundle by slug. */
292
+ declare function useBundle(slug: string | undefined, options?: {
293
+ enabled?: boolean;
294
+ initialData?: Bundle;
295
+ }): _tanstack_react_query.UseQueryResult<Bundle, Error>;
296
+
297
+ type CrossSellResponse = {
298
+ related: CrossSellItem[];
299
+ upsell: CrossSellItem[];
300
+ crossSell: CrossSellItem[];
301
+ };
302
+ /**
303
+ * Fetch related / upsell / cross-sell products for a given product.
304
+ * Returns all three lists separately so the UI can group them into
305
+ * different sections on the product detail page.
306
+ */
307
+ declare function useCrossSell(productSlug: string | undefined, options?: {
308
+ enabled?: boolean;
309
+ initialData?: CrossSellResponse;
310
+ }): _tanstack_react_query.UseQueryResult<CrossSellResponse, Error>;
311
+
312
+ /**
313
+ * Fetch currently active promotions applicable to a specific product.
314
+ * Use this on product detail pages to render countdown timers and
315
+ * "AKCE -20%" badges.
316
+ */
317
+ declare function useProductPromotions(productSlug: string | undefined, options?: {
318
+ enabled?: boolean;
319
+ refetchIntervalMs?: number;
320
+ }): _tanstack_react_query.UseQueryResult<{
321
+ items: ActivePromotion[];
322
+ }, Error>;
323
+
268
324
  /**
269
325
  * Returns the raw BehioStorefront client instance.
270
326
  *
@@ -283,4 +339,4 @@ declare function useBehioClient(): BehioStorefront;
283
339
  */
284
340
  declare function formatPrice(amount: number, currency: string, locale?: string): string;
285
341
 
286
- export { BehioProvider, type BehioProviderProps, Cart, Category, CategoryDetail, CheckoutInput, CustomerAddress, CustomerProfile, FilterField, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductsQuery, RegisterInput, ShopInfo, type StorageAdapter, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, cookieStorage, createMemoryStorage, detectStorage, formatPrice, localStorageAdapter, memoryStorage, useAddresses, useAuth, useBehio, useBehioClient, useCart, useCartCount, useCategories, useCategory, useCheckout, useCustomer, useFeatured, useFilters, useLabels, useOrder, useOrders, usePage, usePages, useProduct, useProducts, useSearch, useShopInfo };
342
+ export { ActivePromotion, BehioProvider, type BehioProviderProps, Bundle, Cart, Category, CategoryDetail, CheckoutInput, CrossSellItem, CustomerAddress, CustomerProfile, FilterField, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductsQuery, RegisterInput, ShopInfo, ShopSeo, type StorageAdapter, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, type UseShopSeoOptions, cookieStorage, createMemoryStorage, detectStorage, formatPrice, localStorageAdapter, memoryStorage, useAddresses, useAuth, useBehio, useBehioClient, useBundle, useBundles, useCart, useCartCount, useCategories, useCategory, useCheckout, useCrossSell, useCustomer, useFeatured, useFilters, useLabels, useOrder, useOrders, usePage, usePages, useProduct, useProductPromotions, useProducts, useSearch, useShopInfo, useShopSeo };
package/dist/react.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _tanstack_react_query from '@tanstack/react-query';
3
3
  import { QueryClient } from '@tanstack/react-query';
4
- import { BehioStorefront, ProductsQuery, PaginatedResponse, ProductListItem, ProductDetail, Category, CategoryDetail, ProductLabel, FilterField, Cart, CustomerProfile, RegisterInput, CustomerAddress, OrderListItem, OrderDetail, CheckoutInput, PageDetail, Page, ShopInfo } from './index.js';
5
- export { AddToCartInput, AuthTokens, BehioApiError, CartDiscount, CartItem, CheckoutAddress, FulfillmentStatus, LoginInput, MessageResponse, OrderItem, OrderStatus, PaymentStatus, ProductPrice, ProductVariant } from './index.js';
4
+ import { BehioStorefront, ProductsQuery, PaginatedResponse, ProductListItem, ProductDetail, Category, CategoryDetail, ProductLabel, FilterField, Cart, CustomerProfile, RegisterInput, CustomerAddress, OrderListItem, OrderDetail, CheckoutInput, PageDetail, Page, ShopInfo, ShopSeo, Bundle, CrossSellItem, ActivePromotion } from './index.js';
5
+ export { AddToCartInput, AuthTokens, BehioApiError, BundleItem, CartDiscount, CartItem, CheckoutAddress, FulfillmentStatus, LoginInput, MessageResponse, OrderItem, OrderStatus, PaymentStatus, ProductPrice, ProductVariant } from './index.js';
6
6
  import * as _tanstack_query_core from '@tanstack/query-core';
7
7
 
8
8
  interface StorageAdapter {
@@ -265,6 +265,62 @@ interface UseShopInfoOptions {
265
265
  }
266
266
  declare function useShopInfo(options?: UseShopInfoOptions): _tanstack_react_query.UseQueryResult<ShopInfo, Error>;
267
267
 
268
+ interface UseShopSeoOptions {
269
+ /** Override locale (ISO-639-1). Defaults to the shop's default locale. */
270
+ locale?: string;
271
+ /** Hydrate from SSR-fetched data (use `client.getShopSeo(locale)` on the server). */
272
+ initialData?: ShopSeo;
273
+ /** Disable the query. */
274
+ enabled?: boolean;
275
+ }
276
+ /**
277
+ * React Query hook for per-locale shop SEO metadata. Safe to render on the
278
+ * server via `initialData` from `client.getShopSeo(locale)`.
279
+ */
280
+ declare function useShopSeo(options?: UseShopSeoOptions): _tanstack_react_query.UseQueryResult<ShopSeo, Error>;
281
+
282
+ /** List all active bundles. */
283
+ declare function useBundles(options?: {
284
+ enabled?: boolean;
285
+ initialData?: {
286
+ items: Bundle[];
287
+ };
288
+ }): _tanstack_react_query.UseQueryResult<{
289
+ items: Bundle[];
290
+ }, Error>;
291
+ /** Get a single bundle by slug. */
292
+ declare function useBundle(slug: string | undefined, options?: {
293
+ enabled?: boolean;
294
+ initialData?: Bundle;
295
+ }): _tanstack_react_query.UseQueryResult<Bundle, Error>;
296
+
297
+ type CrossSellResponse = {
298
+ related: CrossSellItem[];
299
+ upsell: CrossSellItem[];
300
+ crossSell: CrossSellItem[];
301
+ };
302
+ /**
303
+ * Fetch related / upsell / cross-sell products for a given product.
304
+ * Returns all three lists separately so the UI can group them into
305
+ * different sections on the product detail page.
306
+ */
307
+ declare function useCrossSell(productSlug: string | undefined, options?: {
308
+ enabled?: boolean;
309
+ initialData?: CrossSellResponse;
310
+ }): _tanstack_react_query.UseQueryResult<CrossSellResponse, Error>;
311
+
312
+ /**
313
+ * Fetch currently active promotions applicable to a specific product.
314
+ * Use this on product detail pages to render countdown timers and
315
+ * "AKCE -20%" badges.
316
+ */
317
+ declare function useProductPromotions(productSlug: string | undefined, options?: {
318
+ enabled?: boolean;
319
+ refetchIntervalMs?: number;
320
+ }): _tanstack_react_query.UseQueryResult<{
321
+ items: ActivePromotion[];
322
+ }, Error>;
323
+
268
324
  /**
269
325
  * Returns the raw BehioStorefront client instance.
270
326
  *
@@ -283,4 +339,4 @@ declare function useBehioClient(): BehioStorefront;
283
339
  */
284
340
  declare function formatPrice(amount: number, currency: string, locale?: string): string;
285
341
 
286
- export { BehioProvider, type BehioProviderProps, Cart, Category, CategoryDetail, CheckoutInput, CustomerAddress, CustomerProfile, FilterField, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductsQuery, RegisterInput, ShopInfo, type StorageAdapter, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, cookieStorage, createMemoryStorage, detectStorage, formatPrice, localStorageAdapter, memoryStorage, useAddresses, useAuth, useBehio, useBehioClient, useCart, useCartCount, useCategories, useCategory, useCheckout, useCustomer, useFeatured, useFilters, useLabels, useOrder, useOrders, usePage, usePages, useProduct, useProducts, useSearch, useShopInfo };
342
+ export { ActivePromotion, BehioProvider, type BehioProviderProps, Bundle, Cart, Category, CategoryDetail, CheckoutInput, CrossSellItem, CustomerAddress, CustomerProfile, FilterField, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductsQuery, RegisterInput, ShopInfo, ShopSeo, type StorageAdapter, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, type UseShopSeoOptions, cookieStorage, createMemoryStorage, detectStorage, formatPrice, localStorageAdapter, memoryStorage, useAddresses, useAuth, useBehio, useBehioClient, useBundle, useBundles, useCart, useCartCount, useCategories, useCategory, useCheckout, useCrossSell, useCustomer, useFeatured, useFilters, useLabels, useOrder, useOrders, usePage, usePages, useProduct, useProductPromotions, useProducts, useSearch, useShopInfo, useShopSeo };
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkGGAO5T5Pjs = require('./chunk-GGAO5T5P.js');
3
+ var _chunkQOEYSUF2js = require('./chunk-QOEYSUF2.js');
4
4
 
5
5
  // src/react/provider.tsx
6
6
  var _react = require('react');
@@ -114,7 +114,7 @@ function BehioProvider({
114
114
  const storageAdapter = _react.useMemo.call(void 0, () => resolveStorage(storageOption), [storageOption]);
115
115
  const clientRef = _react.useRef.call(void 0, null);
116
116
  if (!clientRef.current) {
117
- clientRef.current = new (0, _chunkGGAO5T5Pjs.BehioStorefront)({
117
+ clientRef.current = new (0, _chunkQOEYSUF2js.BehioStorefront)({
118
118
  apiKey,
119
119
  baseUrl,
120
120
  locale,
@@ -872,6 +872,64 @@ function useShopInfo(options) {
872
872
  });
873
873
  }
874
874
 
875
+ // src/react/hooks/use-shop-seo.ts
876
+
877
+ function useShopSeo(options) {
878
+ const { client } = useBehio();
879
+ const { locale, initialData, enabled = true } = _nullishCoalesce(options, () => ( {}));
880
+ return _reactquery.useQuery.call(void 0, {
881
+ queryKey: ["behio", "shop-seo", _nullishCoalesce(locale, () => ( "_default"))],
882
+ queryFn: () => client.getShopSeo(locale),
883
+ initialData,
884
+ enabled
885
+ });
886
+ }
887
+
888
+ // src/react/hooks/use-bundles.ts
889
+
890
+ function useBundles(options) {
891
+ const { client } = useBehio();
892
+ return _reactquery.useQuery.call(void 0, {
893
+ queryKey: ["behio", "bundles"],
894
+ queryFn: () => client.catalog.getBundles(),
895
+ enabled: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _60 => _60.enabled]), () => ( true)),
896
+ initialData: _optionalChain([options, 'optionalAccess', _61 => _61.initialData])
897
+ });
898
+ }
899
+ function useBundle(slug, options) {
900
+ const { client } = useBehio();
901
+ return _reactquery.useQuery.call(void 0, {
902
+ queryKey: ["behio", "bundle", slug],
903
+ queryFn: () => client.catalog.getBundle(slug),
904
+ enabled: Boolean(slug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _62 => _62.enabled]), () => ( true))),
905
+ initialData: _optionalChain([options, 'optionalAccess', _63 => _63.initialData])
906
+ });
907
+ }
908
+
909
+ // src/react/hooks/use-cross-sell.ts
910
+
911
+ function useCrossSell(productSlug, options) {
912
+ const { client } = useBehio();
913
+ return _reactquery.useQuery.call(void 0, {
914
+ queryKey: ["behio", "cross-sell", productSlug],
915
+ queryFn: () => client.catalog.getCrossSell(productSlug),
916
+ enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _64 => _64.enabled]), () => ( true))),
917
+ initialData: _optionalChain([options, 'optionalAccess', _65 => _65.initialData])
918
+ });
919
+ }
920
+
921
+ // src/react/hooks/use-product-promotions.ts
922
+
923
+ function useProductPromotions(productSlug, options) {
924
+ const { client } = useBehio();
925
+ return _reactquery.useQuery.call(void 0, {
926
+ queryKey: ["behio", "product-promotions", productSlug],
927
+ queryFn: () => client.catalog.getProductPromotions(productSlug),
928
+ enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _66 => _66.enabled]), () => ( true))),
929
+ refetchInterval: _optionalChain([options, 'optionalAccess', _67 => _67.refetchIntervalMs])
930
+ });
931
+ }
932
+
875
933
  // src/react/hooks/use-behio-client.ts
876
934
  function useBehioClient() {
877
935
  return useBehio().client;
@@ -920,4 +978,9 @@ function formatPrice(amount, currency, locale) {
920
978
 
921
979
 
922
980
 
923
- exports.BehioProvider = BehioProvider; exports.cookieStorage = cookieStorage; exports.createMemoryStorage = createMemoryStorage; exports.detectStorage = detectStorage; exports.formatPrice = formatPrice; exports.localStorageAdapter = localStorageAdapter; exports.memoryStorage = memoryStorage; exports.useAddresses = useAddresses; exports.useAuth = useAuth; exports.useBehio = useBehio; exports.useBehioClient = useBehioClient; exports.useCart = useCart; exports.useCartCount = useCartCount; exports.useCategories = useCategories; exports.useCategory = useCategory; exports.useCheckout = useCheckout; exports.useCustomer = useCustomer; exports.useFeatured = useFeatured; exports.useFilters = useFilters; exports.useLabels = useLabels; exports.useOrder = useOrder; exports.useOrders = useOrders; exports.usePage = usePage; exports.usePages = usePages; exports.useProduct = useProduct; exports.useProducts = useProducts; exports.useSearch = useSearch; exports.useShopInfo = useShopInfo;
981
+
982
+
983
+
984
+
985
+
986
+ exports.BehioProvider = BehioProvider; exports.cookieStorage = cookieStorage; exports.createMemoryStorage = createMemoryStorage; exports.detectStorage = detectStorage; exports.formatPrice = formatPrice; exports.localStorageAdapter = localStorageAdapter; exports.memoryStorage = memoryStorage; exports.useAddresses = useAddresses; exports.useAuth = useAuth; exports.useBehio = useBehio; exports.useBehioClient = useBehioClient; exports.useBundle = useBundle; exports.useBundles = useBundles; exports.useCart = useCart; exports.useCartCount = useCartCount; exports.useCategories = useCategories; exports.useCategory = useCategory; exports.useCheckout = useCheckout; exports.useCrossSell = useCrossSell; exports.useCustomer = useCustomer; exports.useFeatured = useFeatured; exports.useFilters = useFilters; exports.useLabels = useLabels; exports.useOrder = useOrder; exports.useOrders = useOrders; exports.usePage = usePage; exports.usePages = usePages; exports.useProduct = useProduct; exports.useProductPromotions = useProductPromotions; exports.useProducts = useProducts; exports.useSearch = useSearch; exports.useShopInfo = useShopInfo; exports.useShopSeo = useShopSeo;
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BehioStorefront
3
- } from "./chunk-S4DOL3OV.mjs";
3
+ } from "./chunk-HYKJO2IB.mjs";
4
4
 
5
5
  // src/react/provider.tsx
6
6
  import { useRef, useEffect, useMemo } from "react";
@@ -872,6 +872,64 @@ function useShopInfo(options) {
872
872
  });
873
873
  }
874
874
 
875
+ // src/react/hooks/use-shop-seo.ts
876
+ import { useQuery as useQuery15 } from "@tanstack/react-query";
877
+ function useShopSeo(options) {
878
+ const { client } = useBehio();
879
+ const { locale, initialData, enabled = true } = options ?? {};
880
+ return useQuery15({
881
+ queryKey: ["behio", "shop-seo", locale ?? "_default"],
882
+ queryFn: () => client.getShopSeo(locale),
883
+ initialData,
884
+ enabled
885
+ });
886
+ }
887
+
888
+ // src/react/hooks/use-bundles.ts
889
+ import { useQuery as useQuery16 } from "@tanstack/react-query";
890
+ function useBundles(options) {
891
+ const { client } = useBehio();
892
+ return useQuery16({
893
+ queryKey: ["behio", "bundles"],
894
+ queryFn: () => client.catalog.getBundles(),
895
+ enabled: options?.enabled ?? true,
896
+ initialData: options?.initialData
897
+ });
898
+ }
899
+ function useBundle(slug, options) {
900
+ const { client } = useBehio();
901
+ return useQuery16({
902
+ queryKey: ["behio", "bundle", slug],
903
+ queryFn: () => client.catalog.getBundle(slug),
904
+ enabled: Boolean(slug) && (options?.enabled ?? true),
905
+ initialData: options?.initialData
906
+ });
907
+ }
908
+
909
+ // src/react/hooks/use-cross-sell.ts
910
+ import { useQuery as useQuery17 } from "@tanstack/react-query";
911
+ function useCrossSell(productSlug, options) {
912
+ const { client } = useBehio();
913
+ return useQuery17({
914
+ queryKey: ["behio", "cross-sell", productSlug],
915
+ queryFn: () => client.catalog.getCrossSell(productSlug),
916
+ enabled: Boolean(productSlug) && (options?.enabled ?? true),
917
+ initialData: options?.initialData
918
+ });
919
+ }
920
+
921
+ // src/react/hooks/use-product-promotions.ts
922
+ import { useQuery as useQuery18 } from "@tanstack/react-query";
923
+ function useProductPromotions(productSlug, options) {
924
+ const { client } = useBehio();
925
+ return useQuery18({
926
+ queryKey: ["behio", "product-promotions", productSlug],
927
+ queryFn: () => client.catalog.getProductPromotions(productSlug),
928
+ enabled: Boolean(productSlug) && (options?.enabled ?? true),
929
+ refetchInterval: options?.refetchIntervalMs
930
+ });
931
+ }
932
+
875
933
  // src/react/hooks/use-behio-client.ts
876
934
  function useBehioClient() {
877
935
  return useBehio().client;
@@ -903,11 +961,14 @@ export {
903
961
  useAuth,
904
962
  useBehio,
905
963
  useBehioClient,
964
+ useBundle,
965
+ useBundles,
906
966
  useCart,
907
967
  useCartCount,
908
968
  useCategories,
909
969
  useCategory,
910
970
  useCheckout,
971
+ useCrossSell,
911
972
  useCustomer,
912
973
  useFeatured,
913
974
  useFilters,
@@ -917,7 +978,9 @@ export {
917
978
  usePage,
918
979
  usePages,
919
980
  useProduct,
981
+ useProductPromotions,
920
982
  useProducts,
921
983
  useSearch,
922
- useShopInfo
984
+ useShopInfo,
985
+ useShopSeo
923
986
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",