create-brainerce-store 1.16.0 → 1.17.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/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "create-brainerce-store",
34
- version: "1.15.2",
34
+ version: "1.16.0",
35
35
  description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
36
36
  bin: {
37
37
  "create-brainerce-store": "dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -2,7 +2,11 @@
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
4
  import Link from 'next/link';
5
- import type { CartRecommendationsResponse, CartUpgradesResponse, CartBundlesResponse } from 'brainerce';
5
+ import type {
6
+ CartRecommendationsResponse,
7
+ CartUpgradesResponse,
8
+ CartBundlesResponse,
9
+ } from 'brainerce';
6
10
  import { getClient } from '@/lib/brainerce';
7
11
  import { useCart } from '@/providers/store-provider';
8
12
  import { useStoreInfo } from '@/providers/store-provider';
@@ -42,7 +46,11 @@ export default function CartPage() {
42
46
 
43
47
  // Load upgrade suggestions when cart changes
44
48
  useEffect(() => {
45
- if (!cart?.id || cart.items.length === 0 || storeInfo?.upsell?.cartUpgradeBannerEnabled === false) {
49
+ if (
50
+ !cart?.id ||
51
+ cart.items.length === 0 ||
52
+ storeInfo?.upsell?.cartUpgradeBannerEnabled === false
53
+ ) {
46
54
  setUpgrades(null);
47
55
  return;
48
56
  }
@@ -144,7 +152,7 @@ export default function CartPage() {
144
152
  <div className="mt-6 space-y-3">
145
153
  <h3 className="text-foreground text-sm font-semibold">{t('bundleOffers')}</h3>
146
154
  {bundles.bundles.map((offer) => (
147
- <CartBundleOfferCard key={offer.id} offer={offer} onAdd={refreshCart} />
155
+ <CartBundleOfferCard key={offer.id} offer={offer} cartId={cart.id} onAdd={refreshCart} />
148
156
  ))}
149
157
  </div>
150
158
  )}
@@ -4,18 +4,18 @@ import { useState } from 'react';
4
4
  import Image from 'next/image';
5
5
  import type { CartBundleOffer as CartBundleOfferType } from 'brainerce';
6
6
  import { formatPrice } from 'brainerce';
7
- import { getClient } from '@/lib/brainerce';
8
7
  import { useStoreInfo } from '@/providers/store-provider';
9
8
  import { useTranslations } from '@/lib/translations';
10
9
  import { cn } from '@/lib/utils';
11
10
 
12
11
  interface CartBundleOfferCardProps {
13
12
  offer: CartBundleOfferType;
13
+ cartId: string;
14
14
  onAdd: () => void;
15
15
  className?: string;
16
16
  }
17
17
 
18
- export function CartBundleOfferCard({ offer, onAdd, className }: CartBundleOfferCardProps) {
18
+ export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBundleOfferCardProps) {
19
19
  const { storeInfo } = useStoreInfo();
20
20
  const t = useTranslations('cart');
21
21
  const currency = storeInfo?.currency || 'USD';
@@ -23,24 +23,25 @@ export function CartBundleOfferCard({ offer, onAdd, className }: CartBundleOffer
23
23
 
24
24
  const product = offer.bundleProduct;
25
25
  const firstImage = product.images?.[0];
26
- const imageUrl = firstImage ? (typeof firstImage === 'string' ? firstImage : firstImage.url) : null;
26
+ const imageUrl = firstImage
27
+ ? typeof firstImage === 'string'
28
+ ? firstImage
29
+ : firstImage.url
30
+ : null;
27
31
  const originalPrice = parseFloat(offer.originalPrice);
28
32
  const discountedPrice = parseFloat(offer.discountedPrice);
29
33
  const discountLabel =
30
34
  offer.discountType === 'PERCENTAGE'
31
35
  ? `${offer.discountValue}%`
32
- : formatPrice(parseFloat(offer.discountValue), { currency }) as string;
36
+ : (formatPrice(parseFloat(offer.discountValue), { currency }) as string);
33
37
 
34
38
  async function handleAdd() {
35
39
  if (adding) return;
36
40
  try {
37
41
  setAdding(true);
42
+ const { getClient } = await import('@/lib/brainerce');
38
43
  const client = getClient();
39
- await client.smartAddToCart({
40
- productId: product.id,
41
- variantId: offer.bundleVariantId || undefined,
42
- quantity: 1,
43
- });
44
+ await client.addBundleToCart(cartId, offer.id);
44
45
  onAdd();
45
46
  } catch (err) {
46
47
  console.error('Failed to add bundle item:', err);
@@ -63,7 +64,12 @@ export function CartBundleOfferCard({ offer, onAdd, className }: CartBundleOffer
63
64
  ) : (
64
65
  <div className="text-muted-foreground flex h-full w-full items-center justify-center">
65
66
  <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
66
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
67
+ <path
68
+ strokeLinecap="round"
69
+ strokeLinejoin="round"
70
+ strokeWidth={1.5}
71
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
72
+ />
67
73
  </svg>
68
74
  </div>
69
75
  )}
@@ -35,20 +35,28 @@ export function CartUpgradeBanner({
35
35
  if (sessionStorage.getItem(storageKey)) {
36
36
  setDismissed(true);
37
37
  }
38
- } catch {}
38
+ } catch {
39
+ /* ignore */
40
+ }
39
41
  }, [storageKey]);
40
42
 
41
43
  if (dismissed) return null;
42
44
 
43
45
  const target = suggestion.targetProduct;
44
46
  const firstImage = target.images?.[0];
45
- const imageUrl = firstImage ? (typeof firstImage === 'string' ? firstImage : firstImage.url) : null;
47
+ const imageUrl = firstImage
48
+ ? typeof firstImage === 'string'
49
+ ? firstImage
50
+ : firstImage.url
51
+ : null;
46
52
  const formattedDelta = formatPrice(parseFloat(suggestion.priceDelta), { currency }) as string;
47
53
 
48
54
  function handleDismiss() {
49
55
  try {
50
56
  sessionStorage.setItem(storageKey, '1');
51
- } catch {}
57
+ } catch {
58
+ /* ignore */
59
+ }
52
60
  setDismissed(true);
53
61
  }
54
62
 
@@ -81,7 +89,13 @@ export function CartUpgradeBanner({
81
89
  className="text-muted-foreground hover:text-foreground absolute end-2 top-2 text-xs"
82
90
  aria-label={t('dismissUpgrade')}
83
91
  >
84
- <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
92
+ <svg
93
+ className="h-4 w-4"
94
+ fill="none"
95
+ viewBox="0 0 24 24"
96
+ stroke="currentColor"
97
+ strokeWidth={2}
98
+ >
85
99
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
86
100
  </svg>
87
101
  </button>
@@ -93,7 +107,12 @@ export function CartUpgradeBanner({
93
107
  ) : (
94
108
  <div className="text-muted-foreground flex h-full w-full items-center justify-center">
95
109
  <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
96
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
110
+ <path
111
+ strokeLinecap="round"
112
+ strokeLinejoin="round"
113
+ strokeWidth={1.5}
114
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
115
+ />
97
116
  </svg>
98
117
  </div>
99
118
  )}
@@ -33,12 +33,7 @@ export function FreeShippingBar({ className }: FreeShippingBarProps) {
33
33
  <div className={cn('rounded-lg border border-green-200 bg-green-50 p-3', className)}>
34
34
  <div className="flex items-center gap-2 text-sm font-medium text-green-700">
35
35
  <svg className="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
- <path
37
- strokeLinecap="round"
38
- strokeLinejoin="round"
39
- strokeWidth={2}
40
- d="M5 13l4 4L19 7"
41
- />
36
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
42
37
  </svg>
43
38
  {t('freeShippingQualified')}
44
39
  </div>
@@ -22,7 +22,11 @@ export function OrderBumpCard({ bump, isAdded, onToggle, loading, className }: O
22
22
 
23
23
  const product = bump.bumpProduct;
24
24
  const firstImage = product.images?.[0];
25
- const imageUrl = firstImage ? (typeof firstImage === 'string' ? firstImage : firstImage.url) : null;
25
+ const imageUrl = firstImage
26
+ ? typeof firstImage === 'string'
27
+ ? firstImage
28
+ : firstImage.url
29
+ : null;
26
30
  const originalPrice = parseFloat(bump.originalPrice);
27
31
  const hasDiscount = bump.discountedPrice != null;
28
32
  const discountedPrice = hasDiscount ? parseFloat(bump.discountedPrice!) : null;
@@ -59,8 +59,18 @@ function ProductThumb({
59
59
  <Image src={imageUrl} alt={name} fill sizes="80px" className="object-cover" />
60
60
  ) : (
61
61
  <div className="flex h-full w-full items-center justify-center">
62
- <svg className="text-muted-foreground h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
63
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
62
+ <svg
63
+ className="text-muted-foreground h-8 w-8"
64
+ fill="none"
65
+ viewBox="0 0 24 24"
66
+ stroke="currentColor"
67
+ >
68
+ <path
69
+ strokeLinecap="round"
70
+ strokeLinejoin="round"
71
+ strokeWidth={1.5}
72
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
73
+ />
64
74
  </svg>
65
75
  </div>
66
76
  )}
@@ -137,7 +147,9 @@ export function FrequentlyBoughtTogether({
137
147
 
138
148
  return (
139
149
  <div className={cn('border-border rounded-lg border p-6', className)}>
140
- <h2 className="text-foreground mb-4 text-xl font-semibold">{t('frequentlyBoughtTogether')}</h2>
150
+ <h2 className="text-foreground mb-4 text-xl font-semibold">
151
+ {t('frequentlyBoughtTogether')}
152
+ </h2>
141
153
 
142
154
  <div className="flex flex-wrap items-center gap-3">
143
155
  {/* Current product (always included, no checkbox) */}