create-brainerce-store 1.28.19 → 1.28.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.28.19",
3
+ "version": "1.28.21",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -11,12 +11,13 @@
11
11
  "messages"
12
12
  ],
13
13
  "scripts": {
14
- "build": "tsup src/index.ts --format cjs --dts --clean",
15
- "dev": "tsup src/index.ts --format cjs --dts --watch",
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
16
  "clean": "rimraf dist",
17
17
  "prepublishOnly": "npm run build"
18
18
  },
19
19
  "dependencies": {
20
+ "@brainerce/cli-shared": "workspace:*",
20
21
  "chalk": "^4.1.2",
21
22
  "commander": "^12.1.0",
22
23
  "ejs": "^3.1.10",
@@ -10,13 +10,13 @@
10
10
  "setup": "node scripts/fetch-store-info.mjs"
11
11
  },
12
12
  "dependencies": {
13
- "brainerce": "^1.11.0",
13
+ "brainerce": "<%= brainerceVersion %>",
14
14
  "next": "^15.0.0",
15
15
  "react": "^19.0.0",
16
16
  "react-dom": "^19.0.0",
17
17
  "clsx": "^2.1.0",
18
18
  "tailwind-merge": "^2.2.0",
19
- "isomorphic-dompurify": "^3.8.0"
19
+ "isomorphic-dompurify": "<%= isomorphicDompurifyVersion %>"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.0.0",
@@ -1,178 +1,178 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
- import { Link } from '@/lib/navigation';
5
- import type {
6
- CartRecommendationsResponse,
7
- CartUpgradesResponse,
8
- CartBundlesResponse,
9
- } from 'brainerce';
10
- import { getClient } from '@/lib/brainerce';
11
- import { useCart } from '@/providers/store-provider';
12
- import { CartItem } from '@/components/cart/cart-item';
13
- import { CartUpgradeBanner } from '@/components/cart/cart-upgrade-banner';
14
- import { CartBundleOfferCard } from '@/components/cart/cart-bundle-offer';
15
- import { CartSummary } from '@/components/cart/cart-summary';
16
- import { CouponInput } from '@/components/cart/coupon-input';
17
- import { CartNudges } from '@/components/cart/cart-nudges';
18
- import { FreeShippingBar } from '@/components/cart/free-shipping-bar';
19
- import { ReservationCountdown } from '@/components/cart/reservation-countdown';
20
- import { CartRecommendationSection } from '@/components/products/recommendation-section';
21
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
22
- import { useTranslations } from '@/lib/translations';
23
-
24
- export default function CartPage() {
25
- const { cart, cartLoading, refreshCart, itemCount } = useCart();
26
- const t = useTranslations('cart');
27
- const tc = useTranslations('common');
28
- const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
29
- const [upgrades, setUpgrades] = useState<CartUpgradesResponse | null>(null);
30
- const [bundles, setBundles] = useState<CartBundlesResponse | null>(null);
31
-
32
- // Load recommendations, upgrades, and bundles in a single request
33
- useEffect(() => {
34
- if (!cart?.id || cart.items.length === 0) {
35
- setCartRecs(null);
36
- setUpgrades(null);
37
- setBundles(null);
38
- return;
39
- }
40
- const client = getClient();
41
- client
42
- .getCart(cart.id, { include: ['recommendations', 'upgrades', 'bundles'] })
43
- .then((enriched) => {
44
- setCartRecs(enriched.recommendations ?? null);
45
- setUpgrades(enriched.upgrades ?? null);
46
- setBundles(enriched.bundles ?? null);
47
- })
48
- .catch(() => {});
49
- }, [cart?.id, cart?.items.length]);
50
-
51
- if (cartLoading) {
52
- return (
53
- <div className="flex min-h-[60vh] items-center justify-center">
54
- <LoadingSpinner size="lg" />
55
- </div>
56
- );
57
- }
58
-
59
- // Empty cart state
60
- if (!cart || cart.items.length === 0) {
61
- return (
62
- <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
63
- <svg
64
- className="text-muted-foreground mx-auto mb-4 h-16 w-16"
65
- fill="none"
66
- viewBox="0 0 24 24"
67
- stroke="currentColor"
68
- >
69
- <path
70
- strokeLinecap="round"
71
- strokeLinejoin="round"
72
- strokeWidth={1.5}
73
- d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
74
- />
75
- </svg>
76
- <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
77
- <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
78
- <Link
79
- href="/products"
80
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
81
- >
82
- {tc('continueShopping')}
83
- </Link>
84
- </div>
85
- );
86
- }
87
-
88
- return (
89
- <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
90
- <h1 className="text-foreground mb-6 text-2xl font-bold">
91
- {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
92
- </h1>
93
-
94
- {/* Reservation countdown */}
95
- {cart.reservation?.hasReservation && (
96
- <ReservationCountdown reservation={cart.reservation} className="mb-6" />
97
- )}
98
-
99
- <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
100
- {/* Cart Items */}
101
- <div className="lg:col-span-2">
102
- {/* Nudges */}
103
- {cart.nudges && cart.nudges.length > 0 && (
104
- <CartNudges nudges={cart.nudges} className="mb-4" />
105
- )}
106
-
107
- {/* Cart items */}
108
- <div>
109
- {cart.items.map((item) => (
110
- <div key={item.id}>
111
- <CartItem item={item} onUpdate={refreshCart} />
112
- {upgrades?.upgrades?.[item.productId] && (
113
- <CartUpgradeBanner
114
- suggestion={upgrades.upgrades[item.productId]}
115
- cartItem={item}
116
- onUpgrade={refreshCart}
117
- className="mb-2 ms-24"
118
- />
119
- )}
120
- </div>
121
- ))}
122
- </div>
123
-
124
- {/* Bundle offers */}
125
- {bundles?.bundles && bundles.bundles.length > 0 && (
126
- <div className="mt-6 space-y-3">
127
- <h3 className="text-foreground text-sm font-semibold">{t('bundleOffers')}</h3>
128
- {bundles.bundles.map((offer) => (
129
- <CartBundleOfferCard
130
- key={offer.id}
131
- offer={offer}
132
- cartId={cart.id}
133
- onAdd={refreshCart}
134
- />
135
- ))}
136
- </div>
137
- )}
138
-
139
- {/* Coupon input */}
140
- <div className="border-border mt-6 border-t pt-4">
141
- <CouponInput cart={cart} onUpdate={refreshCart} />
142
- </div>
143
- </div>
144
-
145
- {/* Summary sidebar */}
146
- <div className="lg:col-span-1">
147
- <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
148
- <FreeShippingBar className="mb-4" />
149
- <CartSummary />
150
-
151
- <Link
152
- href="/checkout"
153
- className="bg-primary text-primary-foreground mt-6 inline-flex w-full items-center justify-center rounded px-6 py-3 text-sm font-medium transition-opacity hover:opacity-90"
154
- >
155
- {t('proceedToCheckout')}
156
- </Link>
157
-
158
- <Link
159
- href="/products"
160
- className="text-muted-foreground hover:text-foreground mt-3 inline-flex w-full items-center justify-center px-6 py-2 text-sm transition-colors"
161
- >
162
- {tc('continueShopping')}
163
- </Link>
164
- </div>
165
- </div>
166
- </div>
167
-
168
- {/* Cross-sell recommendations */}
169
- {cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
170
- <CartRecommendationSection
171
- title={t('youMightAlsoNeed')}
172
- items={cartRecs.recommendations}
173
- className="mt-10"
174
- />
175
- )}
176
- </div>
177
- );
178
- }
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { Link } from '@/lib/navigation';
5
+ import type {
6
+ CartRecommendationsResponse,
7
+ CartUpgradesResponse,
8
+ CartBundlesResponse,
9
+ } from 'brainerce';
10
+ import { getClient } from '@/lib/brainerce';
11
+ import { useCart } from '@/providers/store-provider';
12
+ import { CartItem } from '@/components/cart/cart-item';
13
+ import { CartUpgradeBanner } from '@/components/cart/cart-upgrade-banner';
14
+ import { CartBundleOfferCard } from '@/components/cart/cart-bundle-offer';
15
+ import { CartSummary } from '@/components/cart/cart-summary';
16
+ import { CouponInput } from '@/components/cart/coupon-input';
17
+ import { CartNudges } from '@/components/cart/cart-nudges';
18
+ import { FreeShippingBar } from '@/components/cart/free-shipping-bar';
19
+ import { ReservationCountdown } from '@/components/cart/reservation-countdown';
20
+ import { CartRecommendationSection } from '@/components/products/recommendation-section';
21
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
22
+ import { useTranslations } from '@/lib/translations';
23
+
24
+ export default function CartPage() {
25
+ const { cart, cartLoading, refreshCart, itemCount } = useCart();
26
+ const t = useTranslations('cart');
27
+ const tc = useTranslations('common');
28
+ const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
29
+ const [upgrades, setUpgrades] = useState<CartUpgradesResponse | null>(null);
30
+ const [bundles, setBundles] = useState<CartBundlesResponse | null>(null);
31
+
32
+ // Load recommendations, upgrades, and bundles in a single request
33
+ useEffect(() => {
34
+ if (!cart?.id || cart.items.length === 0) {
35
+ setCartRecs(null);
36
+ setUpgrades(null);
37
+ setBundles(null);
38
+ return;
39
+ }
40
+ const client = getClient();
41
+ client
42
+ .getCart(cart.id, { include: ['recommendations', 'upgrades', 'bundles'] })
43
+ .then((enriched) => {
44
+ setCartRecs(enriched.recommendations ?? null);
45
+ setUpgrades(enriched.upgrades ?? null);
46
+ setBundles(enriched.bundles ?? null);
47
+ })
48
+ .catch(() => {});
49
+ }, [cart?.id, cart?.items.length]);
50
+
51
+ if (cartLoading) {
52
+ return (
53
+ <div className="flex min-h-[60vh] items-center justify-center">
54
+ <LoadingSpinner size="lg" />
55
+ </div>
56
+ );
57
+ }
58
+
59
+ // Empty cart state
60
+ if (!cart || cart.items.length === 0) {
61
+ return (
62
+ <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
63
+ <svg
64
+ className="text-muted-foreground mx-auto mb-4 h-16 w-16"
65
+ fill="none"
66
+ viewBox="0 0 24 24"
67
+ stroke="currentColor"
68
+ >
69
+ <path
70
+ strokeLinecap="round"
71
+ strokeLinejoin="round"
72
+ strokeWidth={1.5}
73
+ d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
74
+ />
75
+ </svg>
76
+ <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
77
+ <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
78
+ <Link
79
+ href="/products"
80
+ className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
81
+ >
82
+ {tc('continueShopping')}
83
+ </Link>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
90
+ <h1 className="text-foreground mb-6 text-2xl font-bold">
91
+ {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
92
+ </h1>
93
+
94
+ {/* Reservation countdown */}
95
+ {cart.reservation?.hasReservation && (
96
+ <ReservationCountdown reservation={cart.reservation} className="mb-6" />
97
+ )}
98
+
99
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
100
+ {/* Cart Items */}
101
+ <div className="lg:col-span-2">
102
+ {/* Nudges */}
103
+ {cart.nudges && cart.nudges.length > 0 && (
104
+ <CartNudges nudges={cart.nudges} className="mb-4" />
105
+ )}
106
+
107
+ {/* Cart items */}
108
+ <div>
109
+ {cart.items.map((item) => (
110
+ <div key={item.id}>
111
+ <CartItem item={item} onUpdate={refreshCart} />
112
+ {upgrades?.upgrades?.[item.productId] && (
113
+ <CartUpgradeBanner
114
+ suggestion={upgrades.upgrades[item.productId]}
115
+ cartItem={item}
116
+ onUpgrade={refreshCart}
117
+ className="mb-2 ms-24"
118
+ />
119
+ )}
120
+ </div>
121
+ ))}
122
+ </div>
123
+
124
+ {/* Bundle offers */}
125
+ {bundles?.bundles && bundles.bundles.length > 0 && (
126
+ <div className="mt-6 space-y-3">
127
+ <h3 className="text-foreground text-sm font-semibold">{t('bundleOffers')}</h3>
128
+ {bundles.bundles.map((offer) => (
129
+ <CartBundleOfferCard
130
+ key={offer.id}
131
+ offer={offer}
132
+ cartId={cart.id}
133
+ onAdd={refreshCart}
134
+ />
135
+ ))}
136
+ </div>
137
+ )}
138
+
139
+ {/* Coupon input */}
140
+ <div className="border-border mt-6 border-t pt-4">
141
+ <CouponInput cart={cart} onUpdate={refreshCart} />
142
+ </div>
143
+ </div>
144
+
145
+ {/* Summary sidebar */}
146
+ <div className="lg:col-span-1">
147
+ <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
148
+ <FreeShippingBar className="mb-4" />
149
+ <CartSummary />
150
+
151
+ <Link
152
+ href="/checkout"
153
+ className="bg-primary text-primary-foreground mt-6 inline-flex w-full items-center justify-center rounded px-6 py-3 text-sm font-medium transition-opacity hover:opacity-90"
154
+ >
155
+ {t('proceedToCheckout')}
156
+ </Link>
157
+
158
+ <Link
159
+ href="/products"
160
+ className="text-muted-foreground hover:text-foreground mt-3 inline-flex w-full items-center justify-center px-6 py-2 text-sm transition-colors"
161
+ >
162
+ {tc('continueShopping')}
163
+ </Link>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ {/* Cross-sell recommendations */}
169
+ {cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
170
+ <CartRecommendationSection
171
+ title={t('youMightAlsoNeed')}
172
+ items={cartRecs.recommendations}
173
+ className="mt-10"
174
+ />
175
+ )}
176
+ </div>
177
+ );
178
+ }