create-brainerce-store 1.9.1 → 1.11.1

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.
@@ -75,9 +75,11 @@ async function proxyRequest(
75
75
  };
76
76
 
77
77
  // Forward Origin/Referer so backend BrowserOriginGuard accepts proxied requests
78
- const origin = request.headers.get('origin');
78
+ // Always send Origin — same-origin GET requests may not include it, but the backend
79
+ // uses its presence to distinguish fetch() calls from direct browser navigation
80
+ const origin = request.headers.get('origin') || request.nextUrl.origin;
79
81
  const referer = request.headers.get('referer');
80
- if (origin) headers['Origin'] = origin;
82
+ headers['Origin'] = origin;
81
83
  if (referer) headers['Referer'] = referer;
82
84
 
83
85
  // Forward SDK version header if present
@@ -1,134 +1,137 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
- import Link from 'next/link';
5
- import type { CartRecommendationsResponse } from 'brainerce';
6
- import { getClient } from '@/lib/brainerce';
7
- import { useCart } from '@/providers/store-provider';
8
- import { CartItem } from '@/components/cart/cart-item';
9
- import { CartSummary } from '@/components/cart/cart-summary';
10
- import { CouponInput } from '@/components/cart/coupon-input';
11
- import { CartNudges } from '@/components/cart/cart-nudges';
12
- import { ReservationCountdown } from '@/components/cart/reservation-countdown';
13
- import { CartRecommendationSection } from '@/components/products/recommendation-section';
14
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
15
- import { useTranslations } from '@/lib/translations';
16
-
17
- export default function CartPage() {
18
- const { cart, cartLoading, refreshCart, itemCount } = useCart();
19
- const t = useTranslations('cart');
20
- const tc = useTranslations('common');
21
- const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
22
-
23
- // Load cross-sell recommendations when cart changes
24
- useEffect(() => {
25
- if (!cart?.id || cart.items.length === 0) {
26
- setCartRecs(null);
27
- return;
28
- }
29
- const client = getClient();
30
- client.getCartRecommendations(cart.id, 4).then(setCartRecs).catch(() => {});
31
- }, [cart?.id, cart?.items.length]);
32
-
33
- if (cartLoading) {
34
- return (
35
- <div className="flex min-h-[60vh] items-center justify-center">
36
- <LoadingSpinner size="lg" />
37
- </div>
38
- );
39
- }
40
-
41
- // Empty cart state
42
- if (!cart || cart.items.length === 0) {
43
- return (
44
- <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
45
- <svg
46
- className="text-muted-foreground mx-auto mb-4 h-16 w-16"
47
- fill="none"
48
- viewBox="0 0 24 24"
49
- stroke="currentColor"
50
- >
51
- <path
52
- strokeLinecap="round"
53
- strokeLinejoin="round"
54
- strokeWidth={1.5}
55
- d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
56
- />
57
- </svg>
58
- <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
59
- <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
60
- <Link
61
- href="/products"
62
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
63
- >
64
- {tc('continueShopping')}
65
- </Link>
66
- </div>
67
- );
68
- }
69
-
70
- return (
71
- <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
72
- <h1 className="text-foreground mb-6 text-2xl font-bold">
73
- {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
74
- </h1>
75
-
76
- {/* Reservation countdown */}
77
- {cart.reservation?.hasReservation && (
78
- <ReservationCountdown reservation={cart.reservation} className="mb-6" />
79
- )}
80
-
81
- <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
82
- {/* Cart Items */}
83
- <div className="lg:col-span-2">
84
- {/* Nudges */}
85
- {cart.nudges && cart.nudges.length > 0 && (
86
- <CartNudges nudges={cart.nudges} className="mb-4" />
87
- )}
88
-
89
- {/* Cart items */}
90
- <div>
91
- {cart.items.map((item) => (
92
- <CartItem key={item.id} item={item} onUpdate={refreshCart} />
93
- ))}
94
- </div>
95
-
96
- {/* Coupon input */}
97
- <div className="border-border mt-6 border-t pt-4">
98
- <CouponInput cart={cart} onUpdate={refreshCart} />
99
- </div>
100
- </div>
101
-
102
- {/* Summary sidebar */}
103
- <div className="lg:col-span-1">
104
- <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
105
- <CartSummary />
106
-
107
- <Link
108
- href="/checkout"
109
- 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"
110
- >
111
- {t('proceedToCheckout')}
112
- </Link>
113
-
114
- <Link
115
- href="/products"
116
- 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"
117
- >
118
- {tc('continueShopping')}
119
- </Link>
120
- </div>
121
- </div>
122
- </div>
123
-
124
- {/* Cross-sell recommendations */}
125
- {cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
126
- <CartRecommendationSection
127
- title={t('youMightAlsoNeed')}
128
- items={cartRecs.recommendations}
129
- className="mt-10"
130
- />
131
- )}
132
- </div>
133
- );
134
- }
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import type { CartRecommendationsResponse } from 'brainerce';
6
+ import { getClient } from '@/lib/brainerce';
7
+ import { useCart } from '@/providers/store-provider';
8
+ import { CartItem } from '@/components/cart/cart-item';
9
+ import { CartSummary } from '@/components/cart/cart-summary';
10
+ import { CouponInput } from '@/components/cart/coupon-input';
11
+ import { CartNudges } from '@/components/cart/cart-nudges';
12
+ import { ReservationCountdown } from '@/components/cart/reservation-countdown';
13
+ import { CartRecommendationSection } from '@/components/products/recommendation-section';
14
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
15
+ import { useTranslations } from '@/lib/translations';
16
+
17
+ export default function CartPage() {
18
+ const { cart, cartLoading, refreshCart, itemCount } = useCart();
19
+ const t = useTranslations('cart');
20
+ const tc = useTranslations('common');
21
+ const [cartRecs, setCartRecs] = useState<CartRecommendationsResponse | null>(null);
22
+
23
+ // Load cross-sell recommendations when cart changes
24
+ useEffect(() => {
25
+ if (!cart?.id || cart.items.length === 0) {
26
+ setCartRecs(null);
27
+ return;
28
+ }
29
+ const client = getClient();
30
+ client
31
+ .getCartRecommendations(cart.id, 4)
32
+ .then(setCartRecs)
33
+ .catch(() => {});
34
+ }, [cart?.id, cart?.items.length]);
35
+
36
+ if (cartLoading) {
37
+ return (
38
+ <div className="flex min-h-[60vh] items-center justify-center">
39
+ <LoadingSpinner size="lg" />
40
+ </div>
41
+ );
42
+ }
43
+
44
+ // Empty cart state
45
+ if (!cart || cart.items.length === 0) {
46
+ return (
47
+ <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
48
+ <svg
49
+ className="text-muted-foreground mx-auto mb-4 h-16 w-16"
50
+ fill="none"
51
+ viewBox="0 0 24 24"
52
+ stroke="currentColor"
53
+ >
54
+ <path
55
+ strokeLinecap="round"
56
+ strokeLinejoin="round"
57
+ strokeWidth={1.5}
58
+ d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
59
+ />
60
+ </svg>
61
+ <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
62
+ <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
63
+ <Link
64
+ href="/products"
65
+ className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
66
+ >
67
+ {tc('continueShopping')}
68
+ </Link>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ return (
74
+ <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
75
+ <h1 className="text-foreground mb-6 text-2xl font-bold">
76
+ {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
77
+ </h1>
78
+
79
+ {/* Reservation countdown */}
80
+ {cart.reservation?.hasReservation && (
81
+ <ReservationCountdown reservation={cart.reservation} className="mb-6" />
82
+ )}
83
+
84
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
85
+ {/* Cart Items */}
86
+ <div className="lg:col-span-2">
87
+ {/* Nudges */}
88
+ {cart.nudges && cart.nudges.length > 0 && (
89
+ <CartNudges nudges={cart.nudges} className="mb-4" />
90
+ )}
91
+
92
+ {/* Cart items */}
93
+ <div>
94
+ {cart.items.map((item) => (
95
+ <CartItem key={item.id} item={item} onUpdate={refreshCart} />
96
+ ))}
97
+ </div>
98
+
99
+ {/* Coupon input */}
100
+ <div className="border-border mt-6 border-t pt-4">
101
+ <CouponInput cart={cart} onUpdate={refreshCart} />
102
+ </div>
103
+ </div>
104
+
105
+ {/* Summary sidebar */}
106
+ <div className="lg:col-span-1">
107
+ <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
108
+ <CartSummary />
109
+
110
+ <Link
111
+ href="/checkout"
112
+ 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"
113
+ >
114
+ {t('proceedToCheckout')}
115
+ </Link>
116
+
117
+ <Link
118
+ href="/products"
119
+ 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"
120
+ >
121
+ {tc('continueShopping')}
122
+ </Link>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ {/* Cross-sell recommendations */}
128
+ {cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
129
+ <CartRecommendationSection
130
+ title={t('youMightAlsoNeed')}
131
+ items={cartRecs.recommendations}
132
+ className="mt-10"
133
+ />
134
+ )}
135
+ </div>
136
+ );
137
+ }
@@ -13,7 +13,7 @@ import type {
13
13
  } from 'brainerce';
14
14
  import { formatPrice } from 'brainerce';
15
15
  import { getClient } from '@/lib/brainerce';
16
- import { useStoreInfo, useAuth, useCart } from '@/providers/store-provider';
16
+ import { useStoreInfo, useCart } from '@/providers/store-provider';
17
17
  import { CheckoutForm } from '@/components/checkout/checkout-form';
18
18
  import { ShippingStep } from '@/components/checkout/shipping-step';
19
19
  import { PaymentStep } from '@/components/checkout/payment-step';
@@ -31,7 +31,6 @@ type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'payment';
31
31
  function CheckoutContent() {
32
32
  const searchParams = useSearchParams();
33
33
  const { storeInfo } = useStoreInfo();
34
- const { isLoggedIn } = useAuth();
35
34
  const { cart, refreshCart } = useCart();
36
35
  const currency = storeInfo?.currency || 'USD';
37
36
  const t = useTranslations('checkout');
@@ -92,20 +91,8 @@ function CheckoutContent() {
92
91
 
93
92
  // Create new checkout — cart is always server-side now
94
93
  if (cart && cart.id) {
95
- if (isLoggedIn) {
96
- // Logged-in user: create checkout from customer cart
97
- const newCheckout = await client.createCheckout({ cartId: cart.id });
98
- setCheckout(newCheckout);
99
- } else {
100
- // Guest user: start guest checkout (creates checkout from session cart)
101
- const result = await client.startGuestCheckout();
102
- if (result.tracked) {
103
- const newCheckout = await client.getCheckout(result.checkoutId);
104
- setCheckout(newCheckout);
105
- } else {
106
- setError(t('failedToStartCheckout'));
107
- }
108
- }
94
+ const newCheckout = await client.createCheckout({ cartId: cart.id });
95
+ setCheckout(newCheckout);
109
96
  } else {
110
97
  setError(t('cartIsEmpty'));
111
98
  }
@@ -120,7 +107,7 @@ function CheckoutContent() {
120
107
  } finally {
121
108
  setInitializing(false);
122
109
  }
123
- }, [existingCheckoutId, isLoggedIn, cart]);
110
+ }, [existingCheckoutId, cart]);
124
111
 
125
112
  const cartLoaded = cart !== null;
126
113
  useEffect(() => {