create-brainerce-store 1.4.1 → 1.5.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.
- package/dist/index.js +1 -1
- package/messages/en.json +9 -1
- package/messages/he.json +9 -1
- package/package.json +1 -1
- package/templates/nextjs/base/src/app/account/page.tsx +8 -4
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +90 -90
- package/templates/nextjs/base/src/app/cart/page.tsx +110 -110
- package/templates/nextjs/base/src/app/checkout/page.tsx +614 -614
- package/templates/nextjs/base/src/app/login/page.tsx +58 -58
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +193 -193
- package/templates/nextjs/base/src/app/page.tsx +98 -98
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +435 -435
- package/templates/nextjs/base/src/app/products/page.tsx +246 -246
- package/templates/nextjs/base/src/app/register/page.tsx +68 -68
- package/templates/nextjs/base/src/app/verify-email/page.tsx +293 -293
- package/templates/nextjs/base/src/components/account/order-history.tsx +198 -198
- package/templates/nextjs/base/src/components/account/profile-section.tsx +189 -40
- package/templates/nextjs/base/src/components/auth/login-form.tsx +94 -94
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +137 -137
- package/templates/nextjs/base/src/components/auth/register-form.tsx +184 -184
- package/templates/nextjs/base/src/components/cart/cart-item.tsx +153 -153
- package/templates/nextjs/base/src/components/cart/cart-summary.tsx +70 -70
- package/templates/nextjs/base/src/components/cart/coupon-input.tsx +134 -134
- package/templates/nextjs/base/src/components/cart/reservation-countdown.tsx +103 -103
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +305 -305
- package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +64 -64
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +350 -344
- package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +199 -199
- package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +110 -110
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +65 -65
- package/templates/nextjs/base/src/components/layout/footer.tsx +38 -38
- package/templates/nextjs/base/src/components/layout/header.tsx +332 -332
- package/templates/nextjs/base/src/components/products/product-card.tsx +96 -96
- package/templates/nextjs/base/src/components/products/product-grid.tsx +35 -35
- package/templates/nextjs/base/src/components/shared/loading-spinner.tsx +32 -32
- package/templates/nextjs/base/src/lib/translations.ts +11 -11
- package/templates/nextjs/base/src/providers/store-provider.tsx.ejs +5 -1
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
import Link from 'next/link';
|
|
5
|
-
import type { Product, DiscountBanner } from 'brainerce';
|
|
6
|
-
import { getClient } from '@/lib/brainerce';
|
|
7
|
-
import { useStoreInfo } from '@/providers/store-provider';
|
|
8
|
-
import { ProductGrid } from '@/components/products/product-grid';
|
|
9
|
-
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
10
|
-
import { useTranslations } from '@/lib/translations';
|
|
11
|
-
|
|
12
|
-
export default function HomePage() {
|
|
13
|
-
const { storeInfo, loading: storeLoading } = useStoreInfo();
|
|
14
|
-
const [products, setProducts] = useState<Product[]>([]);
|
|
15
|
-
const [banners, setBanners] = useState<DiscountBanner[]>([]);
|
|
16
|
-
const [loading, setLoading] = useState(true);
|
|
17
|
-
const t = useTranslations('home');
|
|
18
|
-
const tc = useTranslations('common');
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
async function load() {
|
|
22
|
-
try {
|
|
23
|
-
const client = getClient();
|
|
24
|
-
const [productsRes, bannersRes] = await Promise.allSettled([
|
|
25
|
-
client.getProducts({ limit: 8, sortBy: 'createdAt', sortOrder: 'desc' }),
|
|
26
|
-
client.getDiscountBanners(),
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
if (productsRes.status === 'fulfilled') {
|
|
30
|
-
setProducts(productsRes.value.data);
|
|
31
|
-
}
|
|
32
|
-
if (bannersRes.status === 'fulfilled') {
|
|
33
|
-
setBanners(bannersRes.value);
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error('Failed to load home page data:', err);
|
|
37
|
-
} finally {
|
|
38
|
-
setLoading(false);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
load();
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
if (storeLoading || loading) {
|
|
46
|
-
return (
|
|
47
|
-
<div className="flex min-h-[60vh] items-center justify-center">
|
|
48
|
-
<LoadingSpinner size="lg" />
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<div>
|
|
55
|
-
{/* Discount Banners */}
|
|
56
|
-
{banners.length > 0 && (
|
|
57
|
-
<div className="bg-primary text-primary-foreground">
|
|
58
|
-
<div className="mx-auto max-w-7xl px-4 py-2 sm:px-6 lg:px-8">
|
|
59
|
-
<div className="flex items-center justify-center gap-4 overflow-x-auto text-sm font-medium">
|
|
60
|
-
{banners.map((banner) => (
|
|
61
|
-
<span key={banner.ruleId}>{banner.text}</span>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
)}
|
|
67
|
-
|
|
68
|
-
{/* Hero Section */}
|
|
69
|
-
<section className="bg-muted">
|
|
70
|
-
<div className="mx-auto max-w-7xl px-4 py-20 text-center sm:px-6 lg:px-8">
|
|
71
|
-
<h1 className="text-foreground text-4xl font-bold tracking-tight sm:text-5xl">
|
|
72
|
-
{t('welcomeTo')} {storeInfo?.name || tc('store')}
|
|
73
|
-
</h1>
|
|
74
|
-
<p className="text-muted-foreground mx-auto mt-4 max-w-2xl text-lg">
|
|
75
|
-
{t('heroSubtitle')}
|
|
76
|
-
</p>
|
|
77
|
-
<Link
|
|
78
|
-
href="/products"
|
|
79
|
-
className="bg-primary text-primary-foreground mt-8 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
80
|
-
>
|
|
81
|
-
{tc('shopNow')}
|
|
82
|
-
</Link>
|
|
83
|
-
</div>
|
|
84
|
-
</section>
|
|
85
|
-
|
|
86
|
-
{/* Featured Products */}
|
|
87
|
-
<section className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
|
|
88
|
-
<div className="mb-8 flex items-center justify-between">
|
|
89
|
-
<h2 className="text-foreground text-2xl font-bold">{t('featuredProducts')}</h2>
|
|
90
|
-
<Link href="/products" className="text-primary text-sm font-medium hover:underline">
|
|
91
|
-
{tc('viewAll')}
|
|
92
|
-
</Link>
|
|
93
|
-
</div>
|
|
94
|
-
<ProductGrid products={products} />
|
|
95
|
-
</section>
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import type { Product, DiscountBanner } from 'brainerce';
|
|
6
|
+
import { getClient } from '@/lib/brainerce';
|
|
7
|
+
import { useStoreInfo } from '@/providers/store-provider';
|
|
8
|
+
import { ProductGrid } from '@/components/products/product-grid';
|
|
9
|
+
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
10
|
+
import { useTranslations } from '@/lib/translations';
|
|
11
|
+
|
|
12
|
+
export default function HomePage() {
|
|
13
|
+
const { storeInfo, loading: storeLoading } = useStoreInfo();
|
|
14
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
15
|
+
const [banners, setBanners] = useState<DiscountBanner[]>([]);
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
const t = useTranslations('home');
|
|
18
|
+
const tc = useTranslations('common');
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
async function load() {
|
|
22
|
+
try {
|
|
23
|
+
const client = getClient();
|
|
24
|
+
const [productsRes, bannersRes] = await Promise.allSettled([
|
|
25
|
+
client.getProducts({ limit: 8, sortBy: 'createdAt', sortOrder: 'desc' }),
|
|
26
|
+
client.getDiscountBanners(),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
if (productsRes.status === 'fulfilled') {
|
|
30
|
+
setProducts(productsRes.value.data);
|
|
31
|
+
}
|
|
32
|
+
if (bannersRes.status === 'fulfilled') {
|
|
33
|
+
setBanners(bannersRes.value);
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('Failed to load home page data:', err);
|
|
37
|
+
} finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
load();
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
if (storeLoading || loading) {
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex min-h-[60vh] items-center justify-center">
|
|
48
|
+
<LoadingSpinner size="lg" />
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
{/* Discount Banners */}
|
|
56
|
+
{banners.length > 0 && (
|
|
57
|
+
<div className="bg-primary text-primary-foreground">
|
|
58
|
+
<div className="mx-auto max-w-7xl px-4 py-2 sm:px-6 lg:px-8">
|
|
59
|
+
<div className="flex items-center justify-center gap-4 overflow-x-auto text-sm font-medium">
|
|
60
|
+
{banners.map((banner) => (
|
|
61
|
+
<span key={banner.ruleId}>{banner.text}</span>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{/* Hero Section */}
|
|
69
|
+
<section className="bg-muted">
|
|
70
|
+
<div className="mx-auto max-w-7xl px-4 py-20 text-center sm:px-6 lg:px-8">
|
|
71
|
+
<h1 className="text-foreground text-4xl font-bold tracking-tight sm:text-5xl">
|
|
72
|
+
{t('welcomeTo')} {storeInfo?.name || tc('store')}
|
|
73
|
+
</h1>
|
|
74
|
+
<p className="text-muted-foreground mx-auto mt-4 max-w-2xl text-lg">
|
|
75
|
+
{t('heroSubtitle')}
|
|
76
|
+
</p>
|
|
77
|
+
<Link
|
|
78
|
+
href="/products"
|
|
79
|
+
className="bg-primary text-primary-foreground mt-8 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
80
|
+
>
|
|
81
|
+
{tc('shopNow')}
|
|
82
|
+
</Link>
|
|
83
|
+
</div>
|
|
84
|
+
</section>
|
|
85
|
+
|
|
86
|
+
{/* Featured Products */}
|
|
87
|
+
<section className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
|
|
88
|
+
<div className="mb-8 flex items-center justify-between">
|
|
89
|
+
<h2 className="text-foreground text-2xl font-bold">{t('featuredProducts')}</h2>
|
|
90
|
+
<Link href="/products" className="text-primary text-sm font-medium hover:underline">
|
|
91
|
+
{tc('viewAll')}
|
|
92
|
+
</Link>
|
|
93
|
+
</div>
|
|
94
|
+
<ProductGrid products={products} />
|
|
95
|
+
</section>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|