create-brainerce-store 1.5.0 → 1.5.2
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 +1 -1
- package/package.json +1 -1
- package/templates/nextjs/base/src/app/account/page.tsx +8 -4
- package/templates/nextjs/base/src/app/checkout/page.tsx +23 -1
- package/templates/nextjs/base/src/components/account/profile-section.tsx +21 -7
- package/templates/nextjs/base/src/providers/store-provider.tsx.ejs +5 -1
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.5.
|
|
34
|
+
version: "1.5.1",
|
|
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
|
@@ -12,7 +12,7 @@ import { useTranslations } from '@/lib/translations';
|
|
|
12
12
|
|
|
13
13
|
export default function AccountPage() {
|
|
14
14
|
const router = useRouter();
|
|
15
|
-
const { isLoggedIn, logout } = useAuth();
|
|
15
|
+
const { isLoggedIn, authLoading, logout } = useAuth();
|
|
16
16
|
const t = useTranslations('account');
|
|
17
17
|
const tc = useTranslations('common');
|
|
18
18
|
const [profile, setProfile] = useState<CustomerProfile | null>(null);
|
|
@@ -21,6 +21,8 @@ export default function AccountPage() {
|
|
|
21
21
|
const [error, setError] = useState<string | null>(null);
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
|
+
if (authLoading) return;
|
|
25
|
+
|
|
24
26
|
if (!isLoggedIn) {
|
|
25
27
|
router.push('/login');
|
|
26
28
|
return;
|
|
@@ -52,9 +54,9 @@ export default function AccountPage() {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
loadAccountData();
|
|
55
|
-
}, [isLoggedIn, router]);
|
|
57
|
+
}, [isLoggedIn, authLoading, router]);
|
|
56
58
|
|
|
57
|
-
if (!isLoggedIn) {
|
|
59
|
+
if (authLoading || !isLoggedIn) {
|
|
58
60
|
return null;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -96,7 +98,9 @@ export default function AccountPage() {
|
|
|
96
98
|
</div>
|
|
97
99
|
|
|
98
100
|
{/* Profile Section */}
|
|
99
|
-
{profile &&
|
|
101
|
+
{profile && (
|
|
102
|
+
<ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-8" />
|
|
103
|
+
)}
|
|
100
104
|
|
|
101
105
|
{/* Order History */}
|
|
102
106
|
<div>
|
|
@@ -20,6 +20,7 @@ import { PaymentStep } from '@/components/checkout/payment-step';
|
|
|
20
20
|
import { DeliveryMethodStep } from '@/components/checkout/delivery-method-step';
|
|
21
21
|
import { PickupStep } from '@/components/checkout/pickup-step';
|
|
22
22
|
import { TaxDisplay } from '@/components/checkout/tax-display';
|
|
23
|
+
import { CouponInput } from '@/components/cart/coupon-input';
|
|
23
24
|
import { ReservationCountdown } from '@/components/cart/reservation-countdown';
|
|
24
25
|
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
25
26
|
import { useTranslations } from '@/lib/translations';
|
|
@@ -31,7 +32,7 @@ function CheckoutContent() {
|
|
|
31
32
|
const searchParams = useSearchParams();
|
|
32
33
|
const { storeInfo } = useStoreInfo();
|
|
33
34
|
const { isLoggedIn } = useAuth();
|
|
34
|
-
const { cart } = useCart();
|
|
35
|
+
const { cart, refreshCart } = useCart();
|
|
35
36
|
const currency = storeInfo?.currency || 'USD';
|
|
36
37
|
const t = useTranslations('checkout');
|
|
37
38
|
const tc = useTranslations('common');
|
|
@@ -224,6 +225,20 @@ function CheckoutContent() {
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
// Refresh cart and checkout after coupon apply/remove
|
|
229
|
+
const handleCouponUpdate = useCallback(async () => {
|
|
230
|
+
await refreshCart();
|
|
231
|
+
if (checkout) {
|
|
232
|
+
try {
|
|
233
|
+
const client = getClient();
|
|
234
|
+
const updated = await client.getCheckout(checkout.id);
|
|
235
|
+
setCheckout(updated);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error('Failed to refresh checkout after coupon update:', err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}, [checkout, refreshCart]);
|
|
241
|
+
|
|
227
242
|
if (initializing) {
|
|
228
243
|
return (
|
|
229
244
|
<div className="flex min-h-[60vh] items-center justify-center">
|
|
@@ -541,6 +556,13 @@ function CheckoutContent() {
|
|
|
541
556
|
)
|
|
542
557
|
)}
|
|
543
558
|
|
|
559
|
+
{/* Coupon input */}
|
|
560
|
+
{cart && (
|
|
561
|
+
<div className="border-border border-t pt-4">
|
|
562
|
+
<CouponInput cart={cart} onUpdate={handleCouponUpdate} />
|
|
563
|
+
</div>
|
|
564
|
+
)}
|
|
565
|
+
|
|
544
566
|
{/* Totals */}
|
|
545
567
|
{checkout && (
|
|
546
568
|
<div className="border-border space-y-2 border-t pt-4 text-sm">
|
|
@@ -86,7 +86,7 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
86
86
|
type="text"
|
|
87
87
|
value={form.firstName}
|
|
88
88
|
onChange={(e) => setForm((f) => ({ ...f, firstName: e.target.value }))}
|
|
89
|
-
className="border-border bg-background text-foreground w-full rounded-md border px-3 py-1.5 text-sm outline-none
|
|
89
|
+
className="border-border bg-background text-foreground focus:border-primary w-full rounded-md border px-3 py-1.5 text-sm outline-none"
|
|
90
90
|
autoFocus
|
|
91
91
|
/>
|
|
92
92
|
</div>
|
|
@@ -98,7 +98,7 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
98
98
|
type="text"
|
|
99
99
|
value={form.lastName}
|
|
100
100
|
onChange={(e) => setForm((f) => ({ ...f, lastName: e.target.value }))}
|
|
101
|
-
className="border-border bg-background text-foreground w-full rounded-md border px-3 py-1.5 text-sm outline-none
|
|
101
|
+
className="border-border bg-background text-foreground focus:border-primary w-full rounded-md border px-3 py-1.5 text-sm outline-none"
|
|
102
102
|
/>
|
|
103
103
|
</div>
|
|
104
104
|
</div>
|
|
@@ -110,7 +110,7 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
110
110
|
type="tel"
|
|
111
111
|
value={form.phone}
|
|
112
112
|
onChange={(e) => setForm((f) => ({ ...f, phone: e.target.value }))}
|
|
113
|
-
className="border-border bg-background text-foreground w-full rounded-md border px-3 py-1.5 text-sm outline-none
|
|
113
|
+
className="border-border bg-background text-foreground focus:border-primary w-full rounded-md border px-3 py-1.5 text-sm outline-none"
|
|
114
114
|
/>
|
|
115
115
|
</div>
|
|
116
116
|
<p className="text-muted-foreground truncate text-sm">{profile.email}</p>
|
|
@@ -144,8 +144,18 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
144
144
|
className="text-muted-foreground hover:text-foreground flex-shrink-0 rounded p-1 transition-colors"
|
|
145
145
|
title={t('editProfile')}
|
|
146
146
|
>
|
|
147
|
-
<svg
|
|
148
|
-
|
|
147
|
+
<svg
|
|
148
|
+
className="h-4 w-4"
|
|
149
|
+
fill="none"
|
|
150
|
+
viewBox="0 0 24 24"
|
|
151
|
+
stroke="currentColor"
|
|
152
|
+
strokeWidth={2}
|
|
153
|
+
>
|
|
154
|
+
<path
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
|
158
|
+
/>
|
|
149
159
|
</svg>
|
|
150
160
|
</button>
|
|
151
161
|
</div>
|
|
@@ -179,7 +189,9 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
179
189
|
)}
|
|
180
190
|
</div>
|
|
181
191
|
|
|
182
|
-
{profile.phone &&
|
|
192
|
+
{profile.phone && (
|
|
193
|
+
<p className="text-muted-foreground mt-2 text-sm">{profile.phone}</p>
|
|
194
|
+
)}
|
|
183
195
|
|
|
184
196
|
<p className="text-muted-foreground mt-3 text-xs">
|
|
185
197
|
{t('memberSince')}{' '}
|
|
@@ -197,7 +209,9 @@ export function ProfileSection({ profile, onProfileUpdate, className }: ProfileS
|
|
|
197
209
|
<p
|
|
198
210
|
className={cn(
|
|
199
211
|
'mt-2 text-sm',
|
|
200
|
-
message.type === 'success'
|
|
212
|
+
message.type === 'success'
|
|
213
|
+
? 'text-green-600 dark:text-green-400'
|
|
214
|
+
: 'text-red-600 dark:text-red-400'
|
|
201
215
|
)}
|
|
202
216
|
>
|
|
203
217
|
{message.text}
|
|
@@ -23,6 +23,7 @@ export function useStoreInfo() {
|
|
|
23
23
|
// ---- Auth Context ----
|
|
24
24
|
interface AuthContextValue {
|
|
25
25
|
isLoggedIn: boolean;
|
|
26
|
+
authLoading: boolean;
|
|
26
27
|
token: string | null;
|
|
27
28
|
login: (token: string) => void;
|
|
28
29
|
logout: () => void;
|
|
@@ -30,6 +31,7 @@ interface AuthContextValue {
|
|
|
30
31
|
|
|
31
32
|
const AuthContext = createContext<AuthContextValue>({
|
|
32
33
|
isLoggedIn: false,
|
|
34
|
+
authLoading: true,
|
|
33
35
|
token: null,
|
|
34
36
|
login: () => {},
|
|
35
37
|
logout: () => {},
|
|
@@ -65,6 +67,7 @@ export function StoreProvider({ children }: { children: React.ReactNode }) {
|
|
|
65
67
|
const [storeInfo, setStoreInfo] = useState<StoreInfo | null>(null);
|
|
66
68
|
const [storeLoading, setStoreLoading] = useState(true);
|
|
67
69
|
const [token, setToken] = useState<string | null>(null);
|
|
70
|
+
const [authLoading, setAuthLoading] = useState(true);
|
|
68
71
|
const [cart, setCart] = useState<Cart | null>(null);
|
|
69
72
|
const [cartLoading, setCartLoading] = useState(true);
|
|
70
73
|
|
|
@@ -75,6 +78,7 @@ export function StoreProvider({ children }: { children: React.ReactNode }) {
|
|
|
75
78
|
if (stored) {
|
|
76
79
|
setToken(stored);
|
|
77
80
|
}
|
|
81
|
+
setAuthLoading(false);
|
|
78
82
|
|
|
79
83
|
client
|
|
80
84
|
.getStoreInfo()
|
|
@@ -134,7 +138,7 @@ export function StoreProvider({ children }: { children: React.ReactNode }) {
|
|
|
134
138
|
|
|
135
139
|
return (
|
|
136
140
|
<StoreInfoContext.Provider value={{ storeInfo, loading: storeLoading }}>
|
|
137
|
-
<AuthContext.Provider value={{ isLoggedIn: !!token, token, login, logout }}>
|
|
141
|
+
<AuthContext.Provider value={{ isLoggedIn: !!token, authLoading, token, login, logout }}>
|
|
138
142
|
<CartContext.Provider
|
|
139
143
|
value={{ cart, cartLoading, refreshCart, itemCount, totals }}
|
|
140
144
|
>
|