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
@@ -1,58 +1,58 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { useRouter } from 'next/navigation';
5
- import Link from 'next/link';
6
- import { getClient } from '@/lib/brainerce';
7
- import { useAuth } from '@/providers/store-provider';
8
- import { LoginForm } from '@/components/auth/login-form';
9
- import { OAuthButtons } from '@/components/auth/oauth-buttons';
10
- import { useTranslations } from '@/lib/translations';
11
-
12
- export default function LoginPage() {
13
- const router = useRouter();
14
- const auth = useAuth();
15
- const t = useTranslations('auth');
16
- const [error, setError] = useState<string | null>(null);
17
-
18
- async function handleLogin(email: string, password: string) {
19
- try {
20
- setError(null);
21
- const client = getClient();
22
- const result = await client.loginCustomer(email, password);
23
-
24
- if (result.requiresVerification) {
25
- router.push(`/verify-email?token=${encodeURIComponent(result.token)}`);
26
- return;
27
- }
28
-
29
- auth.login(result.token);
30
- router.push('/');
31
- } catch (err) {
32
- const message = err instanceof Error ? err.message : 'Login failed. Please try again.';
33
- setError(message);
34
- }
35
- }
36
-
37
- return (
38
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
39
- <div className="w-full max-w-md space-y-6">
40
- <div className="text-center">
41
- <h1 className="text-foreground text-2xl font-bold">{t('welcomeBack')}</h1>
42
- <p className="text-muted-foreground mt-1 text-sm">{t('signInSubtitle')}</p>
43
- </div>
44
-
45
- <LoginForm onSubmit={handleLogin} error={error} />
46
-
47
- <OAuthButtons />
48
-
49
- <p className="text-muted-foreground text-center text-sm">
50
- {t('noAccount')}{' '}
51
- <Link href="/register" className="text-primary font-medium hover:underline">
52
- {t('createOne')}
53
- </Link>
54
- </p>
55
- </div>
56
- </div>
57
- );
58
- }
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import { getClient } from '@/lib/brainerce';
7
+ import { useAuth } from '@/providers/store-provider';
8
+ import { LoginForm } from '@/components/auth/login-form';
9
+ import { OAuthButtons } from '@/components/auth/oauth-buttons';
10
+ import { useTranslations } from '@/lib/translations';
11
+
12
+ export default function LoginPage() {
13
+ const router = useRouter();
14
+ const auth = useAuth();
15
+ const t = useTranslations('auth');
16
+ const [error, setError] = useState<string | null>(null);
17
+
18
+ async function handleLogin(email: string, password: string) {
19
+ try {
20
+ setError(null);
21
+ const client = getClient();
22
+ const result = await client.loginCustomer(email, password);
23
+
24
+ if (result.requiresVerification) {
25
+ router.push(`/verify-email?token=${encodeURIComponent(result.token)}`);
26
+ return;
27
+ }
28
+
29
+ auth.login(result.token);
30
+ router.push('/');
31
+ } catch (err) {
32
+ const message = err instanceof Error ? err.message : 'Login failed. Please try again.';
33
+ setError(message);
34
+ }
35
+ }
36
+
37
+ return (
38
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
39
+ <div className="w-full max-w-md space-y-6">
40
+ <div className="text-center">
41
+ <h1 className="text-foreground text-2xl font-bold">{t('welcomeBack')}</h1>
42
+ <p className="text-muted-foreground mt-1 text-sm">{t('signInSubtitle')}</p>
43
+ </div>
44
+
45
+ <LoginForm onSubmit={handleLogin} error={error} />
46
+
47
+ <OAuthButtons />
48
+
49
+ <p className="text-muted-foreground text-center text-sm">
50
+ {t('noAccount')}{' '}
51
+ <Link href="/register" className="text-primary font-medium hover:underline">
52
+ {t('createOne')}
53
+ </Link>
54
+ </p>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
@@ -1,193 +1,193 @@
1
- 'use client';
2
-
3
- import { Suspense, useEffect, useState } from 'react';
4
- import { useSearchParams } from 'next/navigation';
5
- import Link from 'next/link';
6
- import type { WaitForOrderResult } from 'brainerce';
7
- import { getClient } from '@/lib/brainerce';
8
- import { useCart } from '@/providers/store-provider';
9
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
10
- import { useTranslations } from '@/lib/translations';
11
-
12
- function OrderConfirmationContent() {
13
- const searchParams = useSearchParams();
14
- const checkoutId = searchParams.get('checkout_id');
15
-
16
- const { refreshCart } = useCart();
17
- const t = useTranslations('orderConfirmation');
18
- const tc = useTranslations('common');
19
- const [result, setResult] = useState<WaitForOrderResult | null>(null);
20
- const [loading, setLoading] = useState(true);
21
- const [error, setError] = useState<string | null>(null);
22
-
23
- useEffect(() => {
24
- if (!checkoutId) {
25
- setError(t('missingCheckoutInfo'));
26
- setLoading(false);
27
- return;
28
- }
29
-
30
- async function waitForOrder() {
31
- try {
32
- const client = getClient();
33
-
34
- // Clear cart state after successful payment
35
- client.handlePaymentSuccess(checkoutId!);
36
- await refreshCart();
37
-
38
- const orderResult = await client.waitForOrder(checkoutId!, {
39
- maxWaitMs: 30000,
40
- });
41
- setResult(orderResult);
42
- } catch (err) {
43
- const message = err instanceof Error ? err.message : 'Failed to confirm order';
44
- setError(message);
45
- } finally {
46
- setLoading(false);
47
- }
48
- }
49
-
50
- waitForOrder();
51
- }, [checkoutId, refreshCart]);
52
-
53
- if (loading) {
54
- return (
55
- <div className="flex min-h-[60vh] flex-col items-center justify-center">
56
- <LoadingSpinner size="lg" />
57
- <p className="text-muted-foreground mt-4">{t('confirming')}</p>
58
- <p className="text-muted-foreground mt-1 text-xs">{t('confirmingHint')}</p>
59
- </div>
60
- );
61
- }
62
-
63
- if (error) {
64
- return (
65
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
66
- <svg
67
- className="text-destructive mx-auto mb-4 h-16 w-16"
68
- fill="none"
69
- viewBox="0 0 24 24"
70
- stroke="currentColor"
71
- >
72
- <path
73
- strokeLinecap="round"
74
- strokeLinejoin="round"
75
- strokeWidth={1.5}
76
- 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"
77
- />
78
- </svg>
79
- <h1 className="text-foreground text-2xl font-bold">{t('errorTitle')}</h1>
80
- <p className="text-muted-foreground mt-2">{error}</p>
81
- <p className="text-muted-foreground mt-1 text-sm">{t('errorChargedHint')}</p>
82
- <Link
83
- href="/"
84
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
85
- >
86
- {t('returnHome')}
87
- </Link>
88
- </div>
89
- );
90
- }
91
-
92
- // Order was created successfully
93
- if (result?.success) {
94
- const orderNumber = result.status.orderNumber;
95
-
96
- return (
97
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
98
- <svg
99
- className="text-primary mx-auto mb-4 h-16 w-16"
100
- fill="none"
101
- viewBox="0 0 24 24"
102
- stroke="currentColor"
103
- >
104
- <path
105
- strokeLinecap="round"
106
- strokeLinejoin="round"
107
- strokeWidth={1.5}
108
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
109
- />
110
- </svg>
111
-
112
- <h1 className="text-foreground text-2xl font-bold">{t('thankYou')}</h1>
113
-
114
- {orderNumber && (
115
- <p className="text-foreground mt-3 text-lg">
116
- {t('orderNumber')} <span className="font-semibold">{orderNumber}</span>
117
- </p>
118
- )}
119
-
120
- <p className="text-muted-foreground mt-2">{t('confirmationEmail')}</p>
121
-
122
- <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
123
- <Link
124
- href="/products"
125
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
126
- >
127
- {tc('continueShopping')}
128
- </Link>
129
-
130
- <Link
131
- href="/account"
132
- className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
133
- >
134
- {t('viewOrders')}
135
- </Link>
136
- </div>
137
- </div>
138
- );
139
- }
140
-
141
- // Order not yet confirmed (polling timed out) - still show success
142
- return (
143
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
144
- <svg
145
- className="text-primary mx-auto mb-4 h-16 w-16"
146
- fill="none"
147
- viewBox="0 0 24 24"
148
- stroke="currentColor"
149
- >
150
- <path
151
- strokeLinecap="round"
152
- strokeLinejoin="round"
153
- strokeWidth={1.5}
154
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
155
- />
156
- </svg>
157
-
158
- <h1 className="text-foreground text-2xl font-bold">{t('paymentReceived')}</h1>
159
-
160
- <p className="text-muted-foreground mt-2">{t('orderProcessing')}</p>
161
-
162
- <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
163
- <Link
164
- href="/products"
165
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
166
- >
167
- {tc('continueShopping')}
168
- </Link>
169
-
170
- <Link
171
- href="/account"
172
- className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
173
- >
174
- {t('viewOrders')}
175
- </Link>
176
- </div>
177
- </div>
178
- );
179
- }
180
-
181
- export default function OrderConfirmationPage() {
182
- return (
183
- <Suspense
184
- fallback={
185
- <div className="flex min-h-[60vh] items-center justify-center">
186
- <LoadingSpinner size="lg" />
187
- </div>
188
- }
189
- >
190
- <OrderConfirmationContent />
191
- </Suspense>
192
- );
193
- }
1
+ 'use client';
2
+
3
+ import { Suspense, useEffect, useState } from 'react';
4
+ import { useSearchParams } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import type { WaitForOrderResult } from 'brainerce';
7
+ import { getClient } from '@/lib/brainerce';
8
+ import { useCart } from '@/providers/store-provider';
9
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
10
+ import { useTranslations } from '@/lib/translations';
11
+
12
+ function OrderConfirmationContent() {
13
+ const searchParams = useSearchParams();
14
+ const checkoutId = searchParams.get('checkout_id');
15
+
16
+ const { refreshCart } = useCart();
17
+ const t = useTranslations('orderConfirmation');
18
+ const tc = useTranslations('common');
19
+ const [result, setResult] = useState<WaitForOrderResult | null>(null);
20
+ const [loading, setLoading] = useState(true);
21
+ const [error, setError] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ if (!checkoutId) {
25
+ setError(t('missingCheckoutInfo'));
26
+ setLoading(false);
27
+ return;
28
+ }
29
+
30
+ async function waitForOrder() {
31
+ try {
32
+ const client = getClient();
33
+
34
+ // Clear cart state after successful payment
35
+ client.handlePaymentSuccess(checkoutId!);
36
+ await refreshCart();
37
+
38
+ const orderResult = await client.waitForOrder(checkoutId!, {
39
+ maxWaitMs: 30000,
40
+ });
41
+ setResult(orderResult);
42
+ } catch (err) {
43
+ const message = err instanceof Error ? err.message : 'Failed to confirm order';
44
+ setError(message);
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ }
49
+
50
+ waitForOrder();
51
+ }, [checkoutId, refreshCart]);
52
+
53
+ if (loading) {
54
+ return (
55
+ <div className="flex min-h-[60vh] flex-col items-center justify-center">
56
+ <LoadingSpinner size="lg" />
57
+ <p className="text-muted-foreground mt-4">{t('confirming')}</p>
58
+ <p className="text-muted-foreground mt-1 text-xs">{t('confirmingHint')}</p>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (error) {
64
+ return (
65
+ <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
66
+ <svg
67
+ className="text-destructive mx-auto mb-4 h-16 w-16"
68
+ fill="none"
69
+ viewBox="0 0 24 24"
70
+ stroke="currentColor"
71
+ >
72
+ <path
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ strokeWidth={1.5}
76
+ 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"
77
+ />
78
+ </svg>
79
+ <h1 className="text-foreground text-2xl font-bold">{t('errorTitle')}</h1>
80
+ <p className="text-muted-foreground mt-2">{error}</p>
81
+ <p className="text-muted-foreground mt-1 text-sm">{t('errorChargedHint')}</p>
82
+ <Link
83
+ href="/"
84
+ className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
85
+ >
86
+ {t('returnHome')}
87
+ </Link>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ // Order was created successfully
93
+ if (result?.success) {
94
+ const orderNumber = result.status.orderNumber;
95
+
96
+ return (
97
+ <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
98
+ <svg
99
+ className="text-primary mx-auto mb-4 h-16 w-16"
100
+ fill="none"
101
+ viewBox="0 0 24 24"
102
+ stroke="currentColor"
103
+ >
104
+ <path
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ strokeWidth={1.5}
108
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
109
+ />
110
+ </svg>
111
+
112
+ <h1 className="text-foreground text-2xl font-bold">{t('thankYou')}</h1>
113
+
114
+ {orderNumber && (
115
+ <p className="text-foreground mt-3 text-lg">
116
+ {t('orderNumber')} <span className="font-semibold">{orderNumber}</span>
117
+ </p>
118
+ )}
119
+
120
+ <p className="text-muted-foreground mt-2">{t('confirmationEmail')}</p>
121
+
122
+ <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
123
+ <Link
124
+ href="/products"
125
+ className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
126
+ >
127
+ {tc('continueShopping')}
128
+ </Link>
129
+
130
+ <Link
131
+ href="/account"
132
+ className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
133
+ >
134
+ {t('viewOrders')}
135
+ </Link>
136
+ </div>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ // Order not yet confirmed (polling timed out) - still show success
142
+ return (
143
+ <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
144
+ <svg
145
+ className="text-primary mx-auto mb-4 h-16 w-16"
146
+ fill="none"
147
+ viewBox="0 0 24 24"
148
+ stroke="currentColor"
149
+ >
150
+ <path
151
+ strokeLinecap="round"
152
+ strokeLinejoin="round"
153
+ strokeWidth={1.5}
154
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
155
+ />
156
+ </svg>
157
+
158
+ <h1 className="text-foreground text-2xl font-bold">{t('paymentReceived')}</h1>
159
+
160
+ <p className="text-muted-foreground mt-2">{t('orderProcessing')}</p>
161
+
162
+ <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
163
+ <Link
164
+ href="/products"
165
+ className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
166
+ >
167
+ {tc('continueShopping')}
168
+ </Link>
169
+
170
+ <Link
171
+ href="/account"
172
+ className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
173
+ >
174
+ {t('viewOrders')}
175
+ </Link>
176
+ </div>
177
+ </div>
178
+ );
179
+ }
180
+
181
+ export default function OrderConfirmationPage() {
182
+ return (
183
+ <Suspense
184
+ fallback={
185
+ <div className="flex min-h-[60vh] items-center justify-center">
186
+ <LoadingSpinner size="lg" />
187
+ </div>
188
+ }
189
+ >
190
+ <OrderConfirmationContent />
191
+ </Suspense>
192
+ );
193
+ }