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.
Files changed (37) hide show
  1. package/dist/index.js +1 -1
  2. package/messages/en.json +9 -1
  3. package/messages/he.json +9 -1
  4. package/package.json +1 -1
  5. package/templates/nextjs/base/src/app/account/page.tsx +8 -4
  6. package/templates/nextjs/base/src/app/auth/callback/page.tsx +90 -90
  7. package/templates/nextjs/base/src/app/cart/page.tsx +110 -110
  8. package/templates/nextjs/base/src/app/checkout/page.tsx +614 -614
  9. package/templates/nextjs/base/src/app/login/page.tsx +58 -58
  10. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +193 -193
  11. package/templates/nextjs/base/src/app/page.tsx +98 -98
  12. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +435 -435
  13. package/templates/nextjs/base/src/app/products/page.tsx +246 -246
  14. package/templates/nextjs/base/src/app/register/page.tsx +68 -68
  15. package/templates/nextjs/base/src/app/verify-email/page.tsx +293 -293
  16. package/templates/nextjs/base/src/components/account/order-history.tsx +198 -198
  17. package/templates/nextjs/base/src/components/account/profile-section.tsx +189 -40
  18. package/templates/nextjs/base/src/components/auth/login-form.tsx +94 -94
  19. package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +137 -137
  20. package/templates/nextjs/base/src/components/auth/register-form.tsx +184 -184
  21. package/templates/nextjs/base/src/components/cart/cart-item.tsx +153 -153
  22. package/templates/nextjs/base/src/components/cart/cart-summary.tsx +70 -70
  23. package/templates/nextjs/base/src/components/cart/coupon-input.tsx +134 -134
  24. package/templates/nextjs/base/src/components/cart/reservation-countdown.tsx +103 -103
  25. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +305 -305
  26. package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +64 -64
  27. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +350 -344
  28. package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +199 -199
  29. package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +110 -110
  30. package/templates/nextjs/base/src/components/checkout/tax-display.tsx +65 -65
  31. package/templates/nextjs/base/src/components/layout/footer.tsx +38 -38
  32. package/templates/nextjs/base/src/components/layout/header.tsx +332 -332
  33. package/templates/nextjs/base/src/components/products/product-card.tsx +96 -96
  34. package/templates/nextjs/base/src/components/products/product-grid.tsx +35 -35
  35. package/templates/nextjs/base/src/components/shared/loading-spinner.tsx +32 -32
  36. package/templates/nextjs/base/src/lib/translations.ts +11 -11
  37. 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.4.1",
34
+ version: "1.5.0",
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/messages/en.json CHANGED
@@ -229,7 +229,15 @@
229
229
  "statusShipped": "Shipped",
230
230
  "statusDelivered": "Delivered",
231
231
  "statusCancelled": "Cancelled",
232
- "statusRefunded": "Refunded"
232
+ "statusRefunded": "Refunded",
233
+ "editProfile": "Edit Profile",
234
+ "firstName": "First Name",
235
+ "lastName": "Last Name",
236
+ "phone": "Phone",
237
+ "save": "Save",
238
+ "cancel": "Cancel",
239
+ "profileUpdated": "Profile updated successfully",
240
+ "profileUpdateFailed": "Failed to update profile"
233
241
  },
234
242
  "orderConfirmation": {
235
243
  "pageTitle": "Order Confirmation",
package/messages/he.json CHANGED
@@ -229,7 +229,15 @@
229
229
  "statusShipped": "נשלח",
230
230
  "statusDelivered": "נמסר",
231
231
  "statusCancelled": "בוטל",
232
- "statusRefunded": "הוחזר"
232
+ "statusRefunded": "הוחזר",
233
+ "editProfile": "עריכת פרופיל",
234
+ "firstName": "שם פרטי",
235
+ "lastName": "שם משפחה",
236
+ "phone": "טלפון",
237
+ "save": "שמירה",
238
+ "cancel": "ביטול",
239
+ "profileUpdated": "הפרופיל עודכן בהצלחה",
240
+ "profileUpdateFailed": "עדכון הפרופיל נכשל"
233
241
  },
234
242
  "orderConfirmation": {
235
243
  "pageTitle": "אישור הזמנה",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -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 && <ProfileSection profile={profile} className="mb-8" />}
101
+ {profile && (
102
+ <ProfileSection profile={profile} onProfileUpdate={setProfile} className="mb-8" />
103
+ )}
100
104
 
101
105
  {/* Order History */}
102
106
  <div>
@@ -1,90 +1,90 @@
1
- 'use client';
2
-
3
- import { Suspense, useEffect, useState, useRef } from 'react';
4
- import { useRouter, useSearchParams } from 'next/navigation';
5
- import { useAuth } from '@/providers/store-provider';
6
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
- import { useTranslations } from '@/lib/translations';
8
-
9
- function OAuthCallbackContent() {
10
- const router = useRouter();
11
- const searchParams = useSearchParams();
12
- const auth = useAuth();
13
- const [error, setError] = useState<string | null>(null);
14
- const processedRef = useRef(false);
15
- const t = useTranslations('auth');
16
-
17
- const oauthSuccess = searchParams.get('oauth_success');
18
- const token = searchParams.get('token');
19
- const oauthError = searchParams.get('oauth_error');
20
-
21
- useEffect(() => {
22
- // Prevent double-processing in React StrictMode
23
- if (processedRef.current) return;
24
- processedRef.current = true;
25
-
26
- if (oauthError) {
27
- setError(oauthError);
28
- return;
29
- }
30
-
31
- if (oauthSuccess === 'true' && token) {
32
- auth.login(token);
33
- router.push('/');
34
- } else {
35
- setError(t('authFailedDesc'));
36
- }
37
- }, [oauthSuccess, token, oauthError, auth, router]);
38
-
39
- if (error) {
40
- return (
41
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
42
- <div className="w-full max-w-md space-y-4 text-center">
43
- <svg
44
- className="text-destructive mx-auto h-12 w-12"
45
- fill="none"
46
- viewBox="0 0 24 24"
47
- stroke="currentColor"
48
- >
49
- <path
50
- strokeLinecap="round"
51
- strokeLinejoin="round"
52
- strokeWidth={1.5}
53
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.834-2.694-.834-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
54
- />
55
- </svg>
56
- <h1 className="text-foreground text-2xl font-bold">{t('authFailed')}</h1>
57
- <p className="text-muted-foreground text-sm">{error}</p>
58
- <button
59
- type="button"
60
- onClick={() => router.push('/login')}
61
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
62
- >
63
- {t('backToLogin')}
64
- </button>
65
- </div>
66
- </div>
67
- );
68
- }
69
-
70
- return (
71
- <div className="flex min-h-[60vh] flex-col items-center justify-center px-4 py-12">
72
- <LoadingSpinner size="lg" />
73
- <p className="text-muted-foreground mt-4">{t('completingSignIn')}</p>
74
- </div>
75
- );
76
- }
77
-
78
- export default function OAuthCallbackPage() {
79
- return (
80
- <Suspense
81
- fallback={
82
- <div className="flex min-h-[60vh] items-center justify-center">
83
- <LoadingSpinner size="lg" />
84
- </div>
85
- }
86
- >
87
- <OAuthCallbackContent />
88
- </Suspense>
89
- );
90
- }
1
+ 'use client';
2
+
3
+ import { Suspense, useEffect, useState, useRef } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { useAuth } from '@/providers/store-provider';
6
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
+ import { useTranslations } from '@/lib/translations';
8
+
9
+ function OAuthCallbackContent() {
10
+ const router = useRouter();
11
+ const searchParams = useSearchParams();
12
+ const auth = useAuth();
13
+ const [error, setError] = useState<string | null>(null);
14
+ const processedRef = useRef(false);
15
+ const t = useTranslations('auth');
16
+
17
+ const oauthSuccess = searchParams.get('oauth_success');
18
+ const token = searchParams.get('token');
19
+ const oauthError = searchParams.get('oauth_error');
20
+
21
+ useEffect(() => {
22
+ // Prevent double-processing in React StrictMode
23
+ if (processedRef.current) return;
24
+ processedRef.current = true;
25
+
26
+ if (oauthError) {
27
+ setError(oauthError);
28
+ return;
29
+ }
30
+
31
+ if (oauthSuccess === 'true' && token) {
32
+ auth.login(token);
33
+ router.push('/');
34
+ } else {
35
+ setError(t('authFailedDesc'));
36
+ }
37
+ }, [oauthSuccess, token, oauthError, auth, router]);
38
+
39
+ if (error) {
40
+ return (
41
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
42
+ <div className="w-full max-w-md space-y-4 text-center">
43
+ <svg
44
+ className="text-destructive mx-auto h-12 w-12"
45
+ fill="none"
46
+ viewBox="0 0 24 24"
47
+ stroke="currentColor"
48
+ >
49
+ <path
50
+ strokeLinecap="round"
51
+ strokeLinejoin="round"
52
+ strokeWidth={1.5}
53
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.834-2.694-.834-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
54
+ />
55
+ </svg>
56
+ <h1 className="text-foreground text-2xl font-bold">{t('authFailed')}</h1>
57
+ <p className="text-muted-foreground text-sm">{error}</p>
58
+ <button
59
+ type="button"
60
+ onClick={() => router.push('/login')}
61
+ className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
62
+ >
63
+ {t('backToLogin')}
64
+ </button>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <div className="flex min-h-[60vh] flex-col items-center justify-center px-4 py-12">
72
+ <LoadingSpinner size="lg" />
73
+ <p className="text-muted-foreground mt-4">{t('completingSignIn')}</p>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ export default function OAuthCallbackPage() {
79
+ return (
80
+ <Suspense
81
+ fallback={
82
+ <div className="flex min-h-[60vh] items-center justify-center">
83
+ <LoadingSpinner size="lg" />
84
+ </div>
85
+ }
86
+ >
87
+ <OAuthCallbackContent />
88
+ </Suspense>
89
+ );
90
+ }
@@ -1,110 +1,110 @@
1
- 'use client';
2
-
3
- import Link from 'next/link';
4
- import { useCart } from '@/providers/store-provider';
5
- import { CartItem } from '@/components/cart/cart-item';
6
- import { CartSummary } from '@/components/cart/cart-summary';
7
- import { CouponInput } from '@/components/cart/coupon-input';
8
- import { CartNudges } from '@/components/cart/cart-nudges';
9
- import { ReservationCountdown } from '@/components/cart/reservation-countdown';
10
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
11
- import { useTranslations } from '@/lib/translations';
12
-
13
- export default function CartPage() {
14
- const { cart, cartLoading, refreshCart, itemCount } = useCart();
15
- const t = useTranslations('cart');
16
- const tc = useTranslations('common');
17
-
18
- if (cartLoading) {
19
- return (
20
- <div className="flex min-h-[60vh] items-center justify-center">
21
- <LoadingSpinner size="lg" />
22
- </div>
23
- );
24
- }
25
-
26
- // Empty cart state
27
- if (!cart || cart.items.length === 0) {
28
- return (
29
- <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
30
- <svg
31
- className="text-muted-foreground mx-auto mb-4 h-16 w-16"
32
- fill="none"
33
- viewBox="0 0 24 24"
34
- stroke="currentColor"
35
- >
36
- <path
37
- strokeLinecap="round"
38
- strokeLinejoin="round"
39
- strokeWidth={1.5}
40
- d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
41
- />
42
- </svg>
43
- <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
44
- <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
45
- <Link
46
- href="/products"
47
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
48
- >
49
- {tc('continueShopping')}
50
- </Link>
51
- </div>
52
- );
53
- }
54
-
55
- return (
56
- <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
57
- <h1 className="text-foreground mb-6 text-2xl font-bold">
58
- {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
59
- </h1>
60
-
61
- {/* Reservation countdown */}
62
- {cart.reservation?.hasReservation && (
63
- <ReservationCountdown reservation={cart.reservation} className="mb-6" />
64
- )}
65
-
66
- <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
67
- {/* Cart Items */}
68
- <div className="lg:col-span-2">
69
- {/* Nudges */}
70
- {cart.nudges && cart.nudges.length > 0 && (
71
- <CartNudges nudges={cart.nudges} className="mb-4" />
72
- )}
73
-
74
- {/* Cart items */}
75
- <div>
76
- {cart.items.map((item) => (
77
- <CartItem key={item.id} item={item} onUpdate={refreshCart} />
78
- ))}
79
- </div>
80
-
81
- {/* Coupon input */}
82
- <div className="border-border mt-6 border-t pt-4">
83
- <CouponInput cart={cart} onUpdate={refreshCart} />
84
- </div>
85
- </div>
86
-
87
- {/* Summary sidebar */}
88
- <div className="lg:col-span-1">
89
- <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
90
- <CartSummary />
91
-
92
- <Link
93
- href="/checkout"
94
- 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"
95
- >
96
- {t('proceedToCheckout')}
97
- </Link>
98
-
99
- <Link
100
- href="/products"
101
- 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"
102
- >
103
- {tc('continueShopping')}
104
- </Link>
105
- </div>
106
- </div>
107
- </div>
108
- </div>
109
- );
110
- }
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useCart } from '@/providers/store-provider';
5
+ import { CartItem } from '@/components/cart/cart-item';
6
+ import { CartSummary } from '@/components/cart/cart-summary';
7
+ import { CouponInput } from '@/components/cart/coupon-input';
8
+ import { CartNudges } from '@/components/cart/cart-nudges';
9
+ import { ReservationCountdown } from '@/components/cart/reservation-countdown';
10
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
11
+ import { useTranslations } from '@/lib/translations';
12
+
13
+ export default function CartPage() {
14
+ const { cart, cartLoading, refreshCart, itemCount } = useCart();
15
+ const t = useTranslations('cart');
16
+ const tc = useTranslations('common');
17
+
18
+ if (cartLoading) {
19
+ return (
20
+ <div className="flex min-h-[60vh] items-center justify-center">
21
+ <LoadingSpinner size="lg" />
22
+ </div>
23
+ );
24
+ }
25
+
26
+ // Empty cart state
27
+ if (!cart || cart.items.length === 0) {
28
+ return (
29
+ <div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
30
+ <svg
31
+ className="text-muted-foreground mx-auto mb-4 h-16 w-16"
32
+ fill="none"
33
+ viewBox="0 0 24 24"
34
+ stroke="currentColor"
35
+ >
36
+ <path
37
+ strokeLinecap="round"
38
+ strokeLinejoin="round"
39
+ strokeWidth={1.5}
40
+ d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
41
+ />
42
+ </svg>
43
+ <h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
44
+ <p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
45
+ <Link
46
+ href="/products"
47
+ className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
48
+ >
49
+ {tc('continueShopping')}
50
+ </Link>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ return (
56
+ <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
57
+ <h1 className="text-foreground mb-6 text-2xl font-bold">
58
+ {t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
59
+ </h1>
60
+
61
+ {/* Reservation countdown */}
62
+ {cart.reservation?.hasReservation && (
63
+ <ReservationCountdown reservation={cart.reservation} className="mb-6" />
64
+ )}
65
+
66
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
67
+ {/* Cart Items */}
68
+ <div className="lg:col-span-2">
69
+ {/* Nudges */}
70
+ {cart.nudges && cart.nudges.length > 0 && (
71
+ <CartNudges nudges={cart.nudges} className="mb-4" />
72
+ )}
73
+
74
+ {/* Cart items */}
75
+ <div>
76
+ {cart.items.map((item) => (
77
+ <CartItem key={item.id} item={item} onUpdate={refreshCart} />
78
+ ))}
79
+ </div>
80
+
81
+ {/* Coupon input */}
82
+ <div className="border-border mt-6 border-t pt-4">
83
+ <CouponInput cart={cart} onUpdate={refreshCart} />
84
+ </div>
85
+ </div>
86
+
87
+ {/* Summary sidebar */}
88
+ <div className="lg:col-span-1">
89
+ <div className="bg-muted/50 border-border sticky top-24 rounded-lg border p-6">
90
+ <CartSummary />
91
+
92
+ <Link
93
+ href="/checkout"
94
+ 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"
95
+ >
96
+ {t('proceedToCheckout')}
97
+ </Link>
98
+
99
+ <Link
100
+ href="/products"
101
+ 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"
102
+ >
103
+ {tc('continueShopping')}
104
+ </Link>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ );
110
+ }