@behio/storefront-sdk 0.1.9 → 0.1.10

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.
Files changed (2) hide show
  1. package/README.md +91 -85
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -806,10 +806,10 @@ platforms treat bundles as paid add-ons or plugins — here it's native.
806
806
 
807
807
  - **Homepage / landing pages** — list active bundles as hero cards with cover
808
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").
809
+ - **Category pages** — show a relevant bundle ("Everything for grilling") next
810
+ to individual products in the same category.
811
+ - **Cart / checkout** — suggest a bundle as upsell ("Add these two more items
812
+ and get the whole set with a 20% discount").
813
813
  - **Dedicated `/bundles` page** — marketing landing for all active sets.
814
814
 
815
815
  **What's in the data:**
@@ -845,7 +845,7 @@ function BundlesGrid() {
845
845
  </>
846
846
  )}
847
847
  </div>
848
- <p>{bundle.items.length} produktů ušetříte {bundle.savings} {bundle.currency}</p>
848
+ <p>{bundle.items.length} products · save {bundle.savings} {bundle.currency}</p>
849
849
  </a>
850
850
  ))}
851
851
  </section>
@@ -877,7 +877,7 @@ function BundleDetail({ slug }: { slug: string }) {
877
877
  {item.quantity}× {item.name}
878
878
  {item.defaultPrice && (
879
879
  <span className="text-muted">
880
- ({item.defaultPrice} {bundle.currency} /ks běžně)
880
+ ({item.defaultPrice} {bundle.currency} /pc regular price)
881
881
  </span>
882
882
  )}
883
883
  </li>
@@ -888,12 +888,12 @@ function BundleDetail({ slug }: { slug: string }) {
888
888
  <strong>{bundle.bundlePrice} {bundle.currency}</strong>
889
889
  {bundle.savings > 0 && (
890
890
  <p>
891
- Jednotlivě by stálo <s>{bundle.itemsSum} {bundle.currency}</s> —
892
- ušetříte <strong>{bundle.savings} {bundle.currency}</strong>
891
+ Individually it would cost <s>{bundle.itemsSum} {bundle.currency}</s> —
892
+ you save <strong>{bundle.savings} {bundle.currency}</strong>
893
893
  ({bundle.savingsPercent}%)
894
894
  </p>
895
895
  )}
896
- <button onClick={addToCart}>Přidat celý balíček do košíku</button>
896
+ <button onClick={addToCart}>Add the whole bundle to cart</button>
897
897
  </div>
898
898
  </article>
899
899
  );
@@ -901,10 +901,10 @@ function BundleDetail({ slug }: { slug: string }) {
901
901
  ```
902
902
 
903
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.
904
+ splits it into individual order items with prices distributed proportionally
905
+ based on each component's default price (this is kept so accounting and
906
+ stock decrements work correctly). The invoice total matches `bundlePrice` —
907
+ the customer sees one line item, inventory is decremented component-by-component.
908
908
 
909
909
  ---
910
910
 
@@ -913,45 +913,46 @@ položku, sklady se odepisují správně z komponent.
913
913
  **What it is.** Three related lists of product recommendations shown on a
914
914
  product detail page:
915
915
 
916
- - **Related (Podobné)** — alternativy ke stejnému účelu (jiné vodítko, jiné
917
- krmivo). Když 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.
916
+ - **Related** — alternatives for the same need (another leash, another dog
917
+ food). "If this one caught your eye, here are similar options in the same
918
+ category."
919
+ - **Upsell** a better / more premium version. "Looking at the basic collar?
920
+ Here's the leather-stitched premium version for 2× the price." Goal: raise
921
+ average order value by steering to a better margin.
922
+ - **Cross-sell** complementary products. Leash for the collar, cleaner for
923
+ the bowl. Pure AOV boost on PDP and in cart.
923
924
 
924
- **Why to split them.** Každý z těchto typů 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š.
925
+ **Why split them.** Each type has a different UX role and should be phrased
926
+ differently. Mixed together they lose context the customer can't tell why
927
+ they're being shown.
927
928
 
928
929
  **Where to use it.**
929
930
 
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.
931
+ - **Product detail page** — three separate sections below the description
932
+ (or in a sidebar column). Highest conversion impact is **Cross-sell
933
+ "Frequently bought together"** right next to the "Add to cart" button.
934
+ - **Cart sidebar** — mini cross-sell widget ("Don't forget these too").
935
+ - **Post-purchase page** — "Want to add these?" for a follow-up order.
935
936
 
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
- automatickynemusíš to řešit na FE.
937
+ **Note.** The endpoint returns **only active products from the same eshop**.
938
+ If you linked a product in admin and later disabled or deleted it, it drops
939
+ from the list automatically no need to handle that on the frontend.
939
940
 
940
941
  ```tsx
941
942
  import { useCrossSell, useBehio } from '@behio/storefront-sdk/react';
942
943
 
943
944
  function CrossSellSection({ title, items }: { title: string; items: CrossSellItem[] }) {
944
- if (!items?.length) return null; // nezobrazuj prázdnou sekci
945
+ if (!items?.length) return null; // hide empty section
945
946
  return (
946
947
  <section>
947
948
  <h2>{title}</h2>
948
949
  <div className="carousel">
949
950
  {items.map((item) => (
950
- <a key={item.productId} href={`/produkt/${item.slug}`} className="card">
951
+ <a key={item.productId} href={`/product/${item.slug}`} className="card">
951
952
  {item.imageUrl && <img src={item.imageUrl} alt={item.name} />}
952
953
  <h4>{item.name}</h4>
953
- <span>{item.price} Kč</span>
954
- {item.stockCached === 0 && <span className="text-red">Vyprodáno</span>}
954
+ <span>{item.price}</span>
955
+ {item.stockCached === 0 && <span className="text-red">Sold out</span>}
955
956
  </a>
956
957
  ))}
957
958
  </div>
@@ -967,15 +968,15 @@ function ProductDetail({ slug }: { slug: string }) {
967
968
  {/* ... product info ... */}
968
969
 
969
970
  <CrossSellSection
970
- title="Často kupováno s"
971
+ title="Frequently bought together"
971
972
  items={crossSell?.crossSell ?? []}
972
973
  />
973
974
  <CrossSellSection
974
- title="Možná vás zaujme i"
975
+ title="You might also like"
975
976
  items={crossSell?.related ?? []}
976
977
  />
977
978
  <CrossSellSection
978
- title="Chcete raději lepší variantu?"
979
+ title="Want a premium version?"
979
980
  items={crossSell?.upsell ?? []}
980
981
  />
981
982
  </>
@@ -983,7 +984,8 @@ function ProductDetail({ slug }: { slug: string }) {
983
984
  }
984
985
  ```
985
986
 
986
- **Tip — merge do jedné sekce.** Pokud máš málo content a chceš to zjednodušit:
987
+ **Tip — merge into a single section.** If you have sparse data and want
988
+ to keep it simple, combine all three into one list:
987
989
 
988
990
  ```tsx
989
991
  const allRecommendations = [
@@ -997,58 +999,61 @@ const allRecommendations = [
997
999
 
998
1000
  ### useProductPromotions
999
1001
 
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).
1002
+ **What it is.** Returns the currently-active promotions (`Eshop_Promotion`)
1003
+ applicable to a given product i.e. all promotions where
1004
+ `startsAt <= now < endsAt` and the product matches the promotion scope
1005
+ (`ALL_PRODUCTS`, `SPECIFIC_PRODUCTS`, `CATEGORIES`, or `LABELS`; the backend
1006
+ resolves it for you).
1004
1007
 
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
+ **What it is NOT.** It's not a computation of the *final discounted price*
1009
+ that's a separate layer (the cart evaluator). This hook is purely for the
1010
+ **display layer**: badges, countdowns, "sale price until midnight" banners.
1008
1011
 
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.
1012
+ **Why it's separate.** The most conversion-effective marketing element in
1013
+ e-commerce is **urgency + scarcity**. "Sale ends in 2h 14m 37s" right on the
1014
+ PDP measurably lifts conversion it's a well-established best practice
1015
+ (Amazon Lightning Deals, Booking.com "3 people booked in the last 24h",
1016
+ etc.). The hook delivers the data; you build the countdown component.
1014
1017
 
1015
- **Kde to použít.**
1018
+ **Where to use it.**
1016
1019
 
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.
1020
+ - **Product card in a list** — a `-20%` badge or a "SALE" flag.
1021
+ - **Product detail** — a large banner with countdown above the price: "Buy
1022
+ before midnight to save $20."
1023
+ - **Cart** — a warning like "Your discount expires in 5 minutes" to nudge
1024
+ checkout completion.
1022
1025
 
1023
- **Pole z API:**
1026
+ **Fields returned:**
1024
1027
 
1025
- - `id`, `name`, `slug` — identifikace akce
1028
+ - `id`, `name`, `slug` — promotion identity
1026
1029
  - `type` — `FLASH_SALE` / `SEASONAL` / `CLEARANCE` / `BOGO` / `BUNDLE` / `LOYALTY`
1027
1030
  - `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.
1031
+ - `discountValue` — the value (% for PERCENTAGE, currency amount for FIXED_AMOUNT, …)
1032
+ - `endsAt` — end timestamp. If `null`, the promotion runs indefinitely.
1033
+ - `badgeText`, `badgeColor` — merchant-set badge text and color (e.g. "-30%"
1034
+ in red). If both are `null`, fall back to a default derived from `discountType`.
1035
+ - `showCountdown` — **respect this!** If the merchant opted out of showing
1036
+ a countdown, don't render one. Not all promotions (CLEARANCE, LOYALTY)
1037
+ make sense to count down.
1038
+ - `couponRequired` the promotion applies only after the customer enters a
1039
+ code at checkout. On the PDP show an info box ("Use code SUMMER at
1040
+ checkout"); don't present it as an automatic discount.
1037
1041
 
1038
1042
  ```tsx
1039
1043
  import { useProductPromotions } from '@behio/storefront-sdk/react';
1040
1044
  import { useEffect, useState } from 'react';
1041
1045
 
1042
1046
  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.
1047
+ // Refetch every 4 minutes so the promotion list updates when one ends
1048
+ // and another begins. The countdown itself doesn't need a refetch
1049
+ // it ticks locally off `endsAt`.
1046
1050
  const { data } = useProductPromotions(slug, { refetchIntervalMs: 4 * 60_000 });
1047
1051
  const promotions = data?.items ?? [];
1048
1052
 
1049
1053
  if (!promotions.length) return null;
1050
1054
 
1051
- // Nejvyšší priority akce (BE vrací seřazené priority DESC + createdAt ASC)
1055
+ // Highest-priority promotion (the BE already sorts by priority DESC,
1056
+ // then createdAt ASC).
1052
1057
  const promo = promotions[0];
1053
1058
 
1054
1059
  const badge = promo.badgeText ?? formatDefaultBadge(promo);
@@ -1060,7 +1065,7 @@ function ProductPromotionBanner({ slug }: { slug: string }) {
1060
1065
  <span className="badge" style={{ backgroundColor: badgeColor }}>{badge}</span>
1061
1066
  <strong>{promo.name}</strong>
1062
1067
  {promo.couponRequired && (
1063
- <p>Uplatníte zadáním kódu <code>{promo.name}</code> v košíku</p>
1068
+ <p>Use code <code>{promo.name}</code> at checkout to redeem</p>
1064
1069
  )}
1065
1070
  </div>
1066
1071
  {promo.showCountdown && promo.endsAt && <Countdown endsAt={promo.endsAt} />}
@@ -1076,30 +1081,31 @@ function Countdown({ endsAt }: { endsAt: number }) {
1076
1081
  }, []);
1077
1082
 
1078
1083
  const ms = Math.max(0, endsAt - now);
1079
- if (ms === 0) return <span>Akce skončila</span>;
1084
+ if (ms === 0) return <span>Sale ended</span>;
1080
1085
 
1081
1086
  const d = Math.floor(ms / 86400_000);
1082
1087
  const h = Math.floor((ms % 86400_000) / 3600_000);
1083
1088
  const m = Math.floor((ms % 3600_000) / 60_000);
1084
1089
  const s = Math.floor((ms % 60_000) / 1000);
1085
1090
 
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>;
1091
+ if (d > 0) return <span>Ends in {d}d {h}h {m}m</span>;
1092
+ return <span>Ends in {h}h {m}m {s}s</span>;
1088
1093
  }
1089
1094
 
1090
1095
  function formatDefaultBadge(p: { discountType: string; discountValue: number }) {
1091
1096
  switch (p.discountType) {
1092
1097
  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';
1098
+ case 'FIXED_AMOUNT': return `-${p.discountValue}`;
1099
+ case 'FREE_SHIPPING': return 'Free shipping';
1100
+ case 'BUY_X_GET_Y': return `${p.discountValue}+1 FREE`;
1101
+ default: return 'SALE';
1097
1102
  }
1098
1103
  }
1099
1104
  ```
1100
1105
 
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:
1106
+ **Pattern — product card badge.** If you only need a small flag on a
1107
+ product card in a list (no countdown), call the hook per card and pick the
1108
+ first promo:
1103
1109
 
1104
1110
  ```tsx
1105
1111
  function ProductCardBadge({ slug }: { slug: string }) {
@@ -1114,9 +1120,9 @@ function ProductCardBadge({ slug }: { slug: string }) {
1114
1120
  }
1115
1121
  ```
1116
1122
 
1117
- Pozorv 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`.
1123
+ Heads-upin list contexts this fires N requests (one per card). For
1124
+ a 200-product page, either add a bulk endpoint of your own or keep the
1125
+ requests cheap with a long React Query `staleTime`.
1120
1126
 
1121
1127
  ## All API methods
1122
1128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",