create-brainerce-store 1.5.1 → 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/messages/en.json +266 -266
- package/messages/he.json +266 -266
- package/package.json +45 -45
- package/templates/nextjs/base/src/app/account/page.tsx +112 -112
- package/templates/nextjs/base/src/app/checkout/page.tsx +23 -1
- package/templates/nextjs/base/src/components/account/profile-section.tsx +224 -224
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import type { CustomerProfile, Order } from 'brainerce';
|
|
6
|
-
import { getClient } from '@/lib/brainerce';
|
|
7
|
-
import { useAuth } from '@/providers/store-provider';
|
|
8
|
-
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
9
|
-
import { ProfileSection } from '@/components/account/profile-section';
|
|
10
|
-
import { OrderHistory } from '@/components/account/order-history';
|
|
11
|
-
import { useTranslations } from '@/lib/translations';
|
|
12
|
-
|
|
13
|
-
export default function AccountPage() {
|
|
14
|
-
const router = useRouter();
|
|
15
|
-
const { isLoggedIn, authLoading, logout } = useAuth();
|
|
16
|
-
const t = useTranslations('account');
|
|
17
|
-
const tc = useTranslations('common');
|
|
18
|
-
const [profile, setProfile] = useState<CustomerProfile | null>(null);
|
|
19
|
-
const [orders, setOrders] = useState<Order[]>([]);
|
|
20
|
-
const [loading, setLoading] = useState(true);
|
|
21
|
-
const [error, setError] = useState<string | null>(null);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (authLoading) return;
|
|
25
|
-
|
|
26
|
-
if (!isLoggedIn) {
|
|
27
|
-
router.push('/login');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function loadAccountData() {
|
|
32
|
-
try {
|
|
33
|
-
const client = getClient();
|
|
34
|
-
const [profileResult, ordersResult] = await Promise.allSettled([
|
|
35
|
-
client.getMyProfile(),
|
|
36
|
-
client.getMyOrders({ limit: 20 }),
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
if (profileResult.status === 'fulfilled') {
|
|
40
|
-
setProfile(profileResult.value);
|
|
41
|
-
} else {
|
|
42
|
-
setError('Failed to load profile.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (ordersResult.status === 'fulfilled') {
|
|
46
|
-
setOrders(ordersResult.value.data);
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
const message = err instanceof Error ? err.message : 'Failed to load account data.';
|
|
50
|
-
setError(message);
|
|
51
|
-
} finally {
|
|
52
|
-
setLoading(false);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
loadAccountData();
|
|
57
|
-
}, [isLoggedIn, authLoading, router]);
|
|
58
|
-
|
|
59
|
-
if (authLoading || !isLoggedIn) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (loading) {
|
|
64
|
-
return (
|
|
65
|
-
<div className="flex min-h-[60vh] items-center justify-center">
|
|
66
|
-
<LoadingSpinner size="lg" />
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (error && !profile) {
|
|
72
|
-
return (
|
|
73
|
-
<div className="mx-auto max-w-3xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
74
|
-
<h1 className="text-foreground text-2xl font-bold">{tc('error')}</h1>
|
|
75
|
-
<p className="text-muted-foreground mt-2">{error}</p>
|
|
76
|
-
<button
|
|
77
|
-
type="button"
|
|
78
|
-
onClick={() => window.location.reload()}
|
|
79
|
-
className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
80
|
-
>
|
|
81
|
-
{tc('tryAgain')}
|
|
82
|
-
</button>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div className="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
|
|
89
|
-
<div className="mb-6 flex items-center justify-between">
|
|
90
|
-
<h1 className="text-foreground text-2xl font-bold">{t('myAccount')}</h1>
|
|
91
|
-
<button
|
|
92
|
-
type="button"
|
|
93
|
-
onClick={logout}
|
|
94
|
-
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
95
|
-
>
|
|
96
|
-
{t('signOut')}
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
{/* Profile Section */}
|
|
101
|
-
{profile && (
|
|
102
|
-
<ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-8" />
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{/* Order History */}
|
|
106
|
-
<div>
|
|
107
|
-
<h2 className="text-foreground mb-4 text-lg font-semibold">{t('orderHistory')}</h2>
|
|
108
|
-
<OrderHistory orders={orders} />
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import type { CustomerProfile, Order } from 'brainerce';
|
|
6
|
+
import { getClient } from '@/lib/brainerce';
|
|
7
|
+
import { useAuth } from '@/providers/store-provider';
|
|
8
|
+
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
9
|
+
import { ProfileSection } from '@/components/account/profile-section';
|
|
10
|
+
import { OrderHistory } from '@/components/account/order-history';
|
|
11
|
+
import { useTranslations } from '@/lib/translations';
|
|
12
|
+
|
|
13
|
+
export default function AccountPage() {
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const { isLoggedIn, authLoading, logout } = useAuth();
|
|
16
|
+
const t = useTranslations('account');
|
|
17
|
+
const tc = useTranslations('common');
|
|
18
|
+
const [profile, setProfile] = useState<CustomerProfile | null>(null);
|
|
19
|
+
const [orders, setOrders] = useState<Order[]>([]);
|
|
20
|
+
const [loading, setLoading] = useState(true);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (authLoading) return;
|
|
25
|
+
|
|
26
|
+
if (!isLoggedIn) {
|
|
27
|
+
router.push('/login');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function loadAccountData() {
|
|
32
|
+
try {
|
|
33
|
+
const client = getClient();
|
|
34
|
+
const [profileResult, ordersResult] = await Promise.allSettled([
|
|
35
|
+
client.getMyProfile(),
|
|
36
|
+
client.getMyOrders({ limit: 20 }),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (profileResult.status === 'fulfilled') {
|
|
40
|
+
setProfile(profileResult.value);
|
|
41
|
+
} else {
|
|
42
|
+
setError('Failed to load profile.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (ordersResult.status === 'fulfilled') {
|
|
46
|
+
setOrders(ordersResult.value.data);
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const message = err instanceof Error ? err.message : 'Failed to load account data.';
|
|
50
|
+
setError(message);
|
|
51
|
+
} finally {
|
|
52
|
+
setLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
loadAccountData();
|
|
57
|
+
}, [isLoggedIn, authLoading, router]);
|
|
58
|
+
|
|
59
|
+
if (authLoading || !isLoggedIn) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (loading) {
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex min-h-[60vh] items-center justify-center">
|
|
66
|
+
<LoadingSpinner size="lg" />
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (error && !profile) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="mx-auto max-w-3xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
74
|
+
<h1 className="text-foreground text-2xl font-bold">{tc('error')}</h1>
|
|
75
|
+
<p className="text-muted-foreground mt-2">{error}</p>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={() => window.location.reload()}
|
|
79
|
+
className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
80
|
+
>
|
|
81
|
+
{tc('tryAgain')}
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
|
|
89
|
+
<div className="mb-6 flex items-center justify-between">
|
|
90
|
+
<h1 className="text-foreground text-2xl font-bold">{t('myAccount')}</h1>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
onClick={logout}
|
|
94
|
+
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
95
|
+
>
|
|
96
|
+
{t('signOut')}
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Profile Section */}
|
|
101
|
+
{profile && (
|
|
102
|
+
<ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-8" />
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
{/* Order History */}
|
|
106
|
+
<div>
|
|
107
|
+
<h2 className="text-foreground mb-4 text-lg font-semibold">{t('orderHistory')}</h2>
|
|
108
|
+
<OrderHistory orders={orders} />
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -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">
|