create-brainerce-store 1.79.0 → 1.81.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.
Files changed (34) hide show
  1. package/dist/index.js +168 -10
  2. package/messages/en.json +13 -4
  3. package/messages/he.json +13 -4
  4. package/package.json +10 -1
  5. package/templates/nextjs/base/.eslintrc.json +2 -0
  6. package/templates/nextjs/base/AI-GUIDE.md +32 -2
  7. package/templates/nextjs/base/package.json.ejs +53 -52
  8. package/templates/nextjs/base/scripts/connect.mjs +149 -0
  9. package/templates/nextjs/base/src/app/checkout/page.tsx +18 -0
  10. package/templates/nextjs/base/src/components/account/profile-section.tsx +7 -1
  11. package/templates/nextjs/base/src/components/auth/register-form.tsx +18 -1
  12. package/templates/nextjs/base/src/components/tracking-bootstrap.tsx +1 -1
  13. package/templates/nextjs/base/src/core/hooks/use-product-page.ts +22 -0
  14. package/templates/nextjs/base/src/core/lib/add-to-cart-error.ts +49 -0
  15. package/templates/nextjs/base/src/ui/cart/cart-bundle-offer.tsx +18 -0
  16. package/templates/nextjs/base/src/ui/cart/cart-item.tsx +49 -0
  17. package/templates/nextjs/base/src/ui/cart/cart-upgrade-banner.tsx +96 -2
  18. package/templates/nextjs/base/src/ui/layout/newsletter-signup.tsx +59 -12
  19. package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +17 -0
  20. package/templates/nextjs/base/src/ui/product/product-card.tsx +17 -0
  21. package/templates/nextjs/base/src/ui/product/product-client-section.tsx +13 -0
  22. package/templates/nextjs/designs/atelier/ui/cart/cart-bundle-offer.tsx +18 -0
  23. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +49 -0
  24. package/templates/nextjs/designs/atelier/ui/cart/cart-upgrade-banner.tsx +101 -5
  25. package/templates/nextjs/designs/atelier/ui/product/frequently-bought-together.tsx +17 -0
  26. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +17 -0
  27. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +13 -0
  28. package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +16 -0
  29. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +45 -0
  30. package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +95 -2
  31. package/templates/nextjs/ui-canvas/layout/newsletter-signup.tsx +50 -8
  32. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +15 -0
  33. package/templates/nextjs/ui-canvas/product/product-card.tsx +15 -0
  34. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +13 -0
@@ -7,6 +7,7 @@ import { formatPrice } from 'brainerce';
7
7
  import { useCart, useStoreInfo } from '@/core/providers/store-provider';
8
8
  import { useCurrency } from '@/core/lib/use-currency';
9
9
  import { useTranslations } from '@/core/lib/translations';
10
+ import { toAddToCartError, type AddToCartError } from '@/core/lib/add-to-cart-error';
10
11
  import { cn } from '@/core/lib/utils';
11
12
 
12
13
  interface FrequentlyBoughtTogetherProps {
@@ -90,6 +91,7 @@ export function FrequentlyBoughtTogether({
90
91
 
91
92
  const [selected, setSelected] = useState<Set<string>>(() => new Set(crossSells.map((i) => i.id)));
92
93
  const [adding, setAdding] = useState(false);
94
+ const [addError, setAddError] = useState<AddToCartError | null>(null);
93
95
 
94
96
  if (!storeInfo?.upsell?.frequentlyBoughtTogetherEnabled) return null;
95
97
  if (crossSells.length === 0) return null;
@@ -120,6 +122,7 @@ export function FrequentlyBoughtTogether({
120
122
 
121
123
  async function handleAddAll() {
122
124
  if (adding || selected.size === 0) return;
125
+ setAddError(null);
123
126
  try {
124
127
  setAdding(true);
125
128
  const { getClient } = await import('@/core/lib/brainerce');
@@ -130,6 +133,10 @@ export function FrequentlyBoughtTogether({
130
133
  }
131
134
  await refreshCart();
132
135
  } catch (err) {
136
+ // The loop is not transactional: if the cart hits its 50-line cap
137
+ // part-way through, the earlier items ARE in the cart and the rest are
138
+ // not. Saying so beats a silent stop the shopper reads as a dead button.
139
+ setAddError(toAddToCartError(err));
133
140
  console.error('Failed to add items to cart:', err);
134
141
  } finally {
135
142
  setAdding(false);
@@ -177,6 +184,14 @@ export function FrequentlyBoughtTogether({
177
184
  {adding ? t('addingAll') : t('addSelectedToCart')}
178
185
  </button>
179
186
  </div>
187
+
188
+ {/*
189
+ Why the add was refused. Some items may already be in the cart:
190
+ the loop above adds one at a time and stops at the first refusal.
191
+ */}
192
+ {addError && (
193
+ <p role="alert">{addError === 'CART_FULL' ? t('cartFull') : t('addToCartFailed')}</p>
194
+ )}
180
195
  </section>
181
196
  );
182
197
  }
@@ -14,6 +14,7 @@ import { DiscountBadge } from '@/ui/product/discount-badge';
14
14
  import { useCart } from '@/core/providers/store-provider';
15
15
  import { useCurrency } from '@/core/lib/use-currency';
16
16
  import { trackAddToCart } from '@/core/lib/tracking';
17
+ import { toAddToCartError, type AddToCartError } from '@/core/lib/add-to-cart-error';
17
18
  import { cn } from '@/core/lib/utils';
18
19
 
19
20
  interface ProductCardProps {
@@ -58,6 +59,7 @@ export function ProductCard({ product, className }: ProductCardProps) {
58
59
 
59
60
  const [adding, setAdding] = useState(false);
60
61
  const [added, setAdded] = useState(false);
62
+ const [addError, setAddError] = useState<AddToCartError | null>(null);
61
63
 
62
64
  // ⛔ A KIT carries NO `inventory` object — its stock is on `kitAvailable`
63
65
  // (`null` = unlimited, `0` = not sellable). Reading `inventory` here left a
@@ -79,6 +81,7 @@ export function ProductCard({ product, className }: ProductCardProps) {
79
81
  }
80
82
 
81
83
  if (adding || !canPurchase) return;
84
+ setAddError(null);
82
85
 
83
86
  try {
84
87
  setAdding(true);
@@ -94,6 +97,10 @@ export function ProductCard({ product, className }: ProductCardProps) {
94
97
  setAdded(true);
95
98
  setTimeout(() => setAdded(false), 2000);
96
99
  } catch (err) {
100
+ // Quick-add used to fail in silence: the button spun, reset, and the
101
+ // item never appeared. A cart at the 50-line cap is the commonest
102
+ // cause and the shopper can fix it, so say so on the card.
103
+ setAddError(toAddToCartError(err));
97
104
  console.error('Failed to add to cart:', err);
98
105
  } finally {
99
106
  setAdding(false);
@@ -169,6 +176,14 @@ export function ProductCard({ product, className }: ProductCardProps) {
169
176
  {added ? tp('addedToCart') : isVariable ? tProd('selectOptions') : tp('addToCart')}
170
177
  </button>
171
178
  )}
179
+
180
+ {/*
181
+ Quick-add refusal. The card has no other error surface, and without
182
+ this the shopper taps, the button resets, and nothing appears.
183
+ */}
184
+ {addError && (
185
+ <p role="alert">{addError === 'CART_FULL' ? tp('cartFull') : tp('addToCartFailed')}</p>
186
+ )}
172
187
  </article>
173
188
  );
174
189
  }
@@ -107,6 +107,7 @@ export function ProductClientSection({
107
107
  setQuantity,
108
108
  addingToCart,
109
109
  addedMessage,
110
+ addToCartError,
110
111
  handleAddToCart,
111
112
  customizationFields,
112
113
  customizationValues,
@@ -347,6 +348,18 @@ export function ProductClientSection({
347
348
  </button>
348
349
  </div>
349
350
 
351
+ {/*
352
+ Why the add was refused. Deliberately OUTSIDE the modifier block
353
+ above: that block only renders when the product HAS modifier
354
+ groups, so a refusal shown there would be invisible on almost
355
+ every product and the shopper would just watch the button reset.
356
+ */}
357
+ {addToCartError && (
358
+ <p role="alert">
359
+ {addToCartError === 'CART_FULL' ? t('cartFull') : t('addToCartFailed')}
360
+ </p>
361
+ )}
362
+
350
363
  {/*
351
364
  Sold out is not the end of the page. `canOfferStockAlert` is the whole
352
365
  gate — it also covers the merchant's switch and backorderable items,