create-brainerce-store 1.5.7 → 1.6.0
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 +286 -268
- package/messages/he.json +286 -268
- package/package.json +1 -1
- package/templates/nextjs/base/src/app/forgot-password/page.tsx +111 -0
- package/templates/nextjs/base/src/app/reset-password/page.tsx +161 -0
- package/templates/nextjs/base/src/components/account/order-history.tsx +278 -276
- package/templates/nextjs/base/src/components/account/profile-section.tsx +10 -8
- package/templates/nextjs/base/src/components/auth/login-form.tsx +101 -94
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +28 -6
|
@@ -1,94 +1,101 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const [
|
|
18
|
-
const [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { useTranslations } from '@/lib/translations';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
8
|
+
|
|
9
|
+
interface LoginFormProps {
|
|
10
|
+
onSubmit: (email: string, password: string) => Promise<void>;
|
|
11
|
+
error?: string | null;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function LoginForm({ onSubmit, error, className }: LoginFormProps) {
|
|
16
|
+
const t = useTranslations('auth');
|
|
17
|
+
const [email, setEmail] = useState('');
|
|
18
|
+
const [password, setPassword] = useState('');
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
|
|
21
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
if (loading) return;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
await onSubmit(email, password);
|
|
28
|
+
} finally {
|
|
29
|
+
setLoading(false);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<form onSubmit={handleSubmit} className={cn('space-y-4', className)}>
|
|
35
|
+
{error && (
|
|
36
|
+
<div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
|
|
37
|
+
{error}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<label htmlFor="login-email" className="text-foreground mb-1.5 block text-sm font-medium">
|
|
43
|
+
{t('email')}
|
|
44
|
+
</label>
|
|
45
|
+
<input
|
|
46
|
+
id="login-email"
|
|
47
|
+
type="email"
|
|
48
|
+
required
|
|
49
|
+
value={email}
|
|
50
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
51
|
+
placeholder={t('emailPlaceholder')}
|
|
52
|
+
autoComplete="email"
|
|
53
|
+
className="border-border bg-background text-foreground placeholder:text-muted-foreground focus:ring-primary/20 focus:border-primary h-10 w-full rounded border px-3 text-sm focus:outline-none focus:ring-2"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div>
|
|
58
|
+
<label
|
|
59
|
+
htmlFor="login-password"
|
|
60
|
+
className="text-foreground mb-1.5 block text-sm font-medium"
|
|
61
|
+
>
|
|
62
|
+
{t('password')}
|
|
63
|
+
</label>
|
|
64
|
+
<input
|
|
65
|
+
id="login-password"
|
|
66
|
+
type="password"
|
|
67
|
+
required
|
|
68
|
+
value={password}
|
|
69
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
70
|
+
placeholder={t('passwordPlaceholder')}
|
|
71
|
+
autoComplete="current-password"
|
|
72
|
+
className="border-border bg-background text-foreground placeholder:text-muted-foreground focus:ring-primary/20 focus:border-primary h-10 w-full rounded border px-3 text-sm focus:outline-none focus:ring-2"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className="flex justify-end">
|
|
77
|
+
<Link href="/forgot-password" className="text-primary text-sm hover:underline">
|
|
78
|
+
{t('forgotPassword')}
|
|
79
|
+
</Link>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<button
|
|
83
|
+
type="submit"
|
|
84
|
+
disabled={loading}
|
|
85
|
+
className="bg-primary text-primary-foreground flex h-10 w-full items-center justify-center gap-2 rounded text-sm font-medium transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
86
|
+
>
|
|
87
|
+
{loading ? (
|
|
88
|
+
<>
|
|
89
|
+
<LoadingSpinner
|
|
90
|
+
size="sm"
|
|
91
|
+
className="border-primary-foreground/30 border-t-primary-foreground"
|
|
92
|
+
/>
|
|
93
|
+
{t('signingIn')}
|
|
94
|
+
</>
|
|
95
|
+
) : (
|
|
96
|
+
t('signIn')
|
|
97
|
+
)}
|
|
98
|
+
</button>
|
|
99
|
+
</form>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -120,9 +120,21 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
|
|
|
120
120
|
}, []);
|
|
121
121
|
|
|
122
122
|
const handleSdkPaymentError = useCallback((response: unknown) => {
|
|
123
|
+
const TRANSIENT_SDK_ERRORS = [
|
|
124
|
+
'Wallet not initialized',
|
|
125
|
+
"SDK was not loaded as needed and therefore can't run",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const msg = (response as { message?: string })?.message || '';
|
|
129
|
+
// Grow SDK fires transient errors during startup before its internal
|
|
130
|
+
// state is ready. The polling mechanism in Step 3 retries
|
|
131
|
+
// renderPaymentOptions() until the SDK is fully initialized.
|
|
132
|
+
if (TRANSIENT_SDK_ERRORS.some((e) => msg.includes(e))) {
|
|
133
|
+
console.info('Payment SDK: transient startup error, waiting for retry...', msg);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
123
136
|
console.error('Payment SDK error:', response);
|
|
124
|
-
|
|
125
|
-
setError(msg);
|
|
137
|
+
setError(msg || t('paymentError'));
|
|
126
138
|
}, []);
|
|
127
139
|
|
|
128
140
|
// Step 1: Load SDK scripts — just load, don't init yet
|
|
@@ -214,6 +226,7 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
|
|
|
214
226
|
: paymentIntent.clientSecret;
|
|
215
227
|
|
|
216
228
|
let rendered = false;
|
|
229
|
+
let walletReady = false;
|
|
217
230
|
let pollId: ReturnType<typeof setInterval>;
|
|
218
231
|
|
|
219
232
|
function tryRenderPaymentOptions() {
|
|
@@ -239,20 +252,29 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
|
|
|
239
252
|
onError: handleSdkPaymentError,
|
|
240
253
|
onWalletChange: (state: string) => {
|
|
241
254
|
console.info('Payment SDK wallet state:', state);
|
|
255
|
+
walletReady = true;
|
|
242
256
|
tryRenderPaymentOptions();
|
|
243
257
|
},
|
|
244
258
|
},
|
|
245
259
|
});
|
|
246
260
|
|
|
247
|
-
// Poll as fallback
|
|
261
|
+
// Poll as fallback — but respect the SDK's readiness signal:
|
|
262
|
+
// - First 4s: only render if onWalletChange has fired (SDK says it's ready)
|
|
263
|
+
// - After 4s: force-attempt even without wallet signal (onWalletChange may not fire)
|
|
264
|
+
const WALLET_GRACE_PERIOD = 4000;
|
|
265
|
+
const initTime = Date.now();
|
|
266
|
+
|
|
248
267
|
const timeoutId = setTimeout(() => {
|
|
249
|
-
tryRenderPaymentOptions();
|
|
268
|
+
if (walletReady) tryRenderPaymentOptions();
|
|
250
269
|
if (!rendered) {
|
|
251
270
|
let attempts = 0;
|
|
252
|
-
const maxAttempts = 16; // 16 * 500ms =
|
|
271
|
+
const maxAttempts = 16; // 16 * 500ms = 8s after initial 1s delay
|
|
253
272
|
pollId = setInterval(() => {
|
|
254
273
|
attempts++;
|
|
255
|
-
|
|
274
|
+
const elapsed = Date.now() - initTime;
|
|
275
|
+
if (walletReady || elapsed > WALLET_GRACE_PERIOD) {
|
|
276
|
+
tryRenderPaymentOptions();
|
|
277
|
+
}
|
|
256
278
|
if (!rendered && attempts >= maxAttempts) {
|
|
257
279
|
clearInterval(pollId);
|
|
258
280
|
console.error('Payment SDK: renderPaymentOptions failed after max attempts');
|