create-brainerce-store 1.6.0 → 1.6.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.
@@ -1,111 +1,111 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import Link from 'next/link';
5
- import { getClient } from '@/lib/brainerce';
6
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
- import { useTranslations } from '@/lib/translations';
8
-
9
- export default function ForgotPasswordPage() {
10
- const t = useTranslations('auth');
11
- const [email, setEmail] = useState('');
12
- const [loading, setLoading] = useState(false);
13
- const [sent, setSent] = useState(false);
14
- const [error, setError] = useState<string | null>(null);
15
-
16
- async function handleSubmit(e: React.FormEvent) {
17
- e.preventDefault();
18
- if (loading) return;
19
-
20
- try {
21
- setLoading(true);
22
- setError(null);
23
- const client = getClient();
24
- await client.forgotPassword(email);
25
- setSent(true);
26
- } catch (err) {
27
- const message =
28
- err instanceof Error ? err.message : 'Something went wrong. Please try again.';
29
- setError(message);
30
- } finally {
31
- setLoading(false);
32
- }
33
- }
34
-
35
- return (
36
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
37
- <div className="w-full max-w-md space-y-6">
38
- <div className="text-center">
39
- <h1 className="text-foreground text-2xl font-bold">{t('forgotPasswordTitle')}</h1>
40
- <p className="text-muted-foreground mt-1 text-sm">{t('forgotPasswordSubtitle')}</p>
41
- </div>
42
-
43
- {error && (
44
- <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
45
- {error}
46
- </div>
47
- )}
48
-
49
- {sent ? (
50
- <div className="space-y-4">
51
- <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300">
52
- {t('resetLinkSent')}
53
- </div>
54
- <Link
55
- href="/login"
56
- className="text-primary block text-center text-sm font-medium hover:underline"
57
- >
58
- {t('backToLogin')}
59
- </Link>
60
- </div>
61
- ) : (
62
- <form onSubmit={handleSubmit} className="space-y-4">
63
- <div>
64
- <label
65
- htmlFor="forgot-email"
66
- className="text-foreground mb-1.5 block text-sm font-medium"
67
- >
68
- {t('email')}
69
- </label>
70
- <input
71
- id="forgot-email"
72
- type="email"
73
- required
74
- value={email}
75
- onChange={(e) => setEmail(e.target.value)}
76
- placeholder={t('emailPlaceholder')}
77
- autoComplete="email"
78
- 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"
79
- />
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('sendingResetLink')}
94
- </>
95
- ) : (
96
- t('sendResetLink')
97
- )}
98
- </button>
99
-
100
- <Link
101
- href="/login"
102
- className="text-muted-foreground block text-center text-sm hover:underline"
103
- >
104
- {t('backToLogin')}
105
- </Link>
106
- </form>
107
- )}
108
- </div>
109
- </div>
110
- );
111
- }
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { getClient } from '@/lib/brainerce';
6
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
7
+ import { useTranslations } from '@/lib/translations';
8
+
9
+ export default function ForgotPasswordPage() {
10
+ const t = useTranslations('auth');
11
+ const [email, setEmail] = useState('');
12
+ const [loading, setLoading] = useState(false);
13
+ const [sent, setSent] = useState(false);
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ async function handleSubmit(e: React.FormEvent) {
17
+ e.preventDefault();
18
+ if (loading) return;
19
+
20
+ try {
21
+ setLoading(true);
22
+ setError(null);
23
+ const client = getClient();
24
+ await client.forgotPassword(email);
25
+ setSent(true);
26
+ } catch (err) {
27
+ const message =
28
+ err instanceof Error ? err.message : 'Something went wrong. Please try again.';
29
+ setError(message);
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ }
34
+
35
+ return (
36
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
37
+ <div className="w-full max-w-md space-y-6">
38
+ <div className="text-center">
39
+ <h1 className="text-foreground text-2xl font-bold">{t('forgotPasswordTitle')}</h1>
40
+ <p className="text-muted-foreground mt-1 text-sm">{t('forgotPasswordSubtitle')}</p>
41
+ </div>
42
+
43
+ {error && (
44
+ <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
45
+ {error}
46
+ </div>
47
+ )}
48
+
49
+ {sent ? (
50
+ <div className="space-y-4">
51
+ <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300">
52
+ {t('resetLinkSent')}
53
+ </div>
54
+ <Link
55
+ href="/login"
56
+ className="text-primary block text-center text-sm font-medium hover:underline"
57
+ >
58
+ {t('backToLogin')}
59
+ </Link>
60
+ </div>
61
+ ) : (
62
+ <form onSubmit={handleSubmit} className="space-y-4">
63
+ <div>
64
+ <label
65
+ htmlFor="forgot-email"
66
+ className="text-foreground mb-1.5 block text-sm font-medium"
67
+ >
68
+ {t('email')}
69
+ </label>
70
+ <input
71
+ id="forgot-email"
72
+ type="email"
73
+ required
74
+ value={email}
75
+ onChange={(e) => setEmail(e.target.value)}
76
+ placeholder={t('emailPlaceholder')}
77
+ autoComplete="email"
78
+ 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"
79
+ />
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('sendingResetLink')}
94
+ </>
95
+ ) : (
96
+ t('sendResetLink')
97
+ )}
98
+ </button>
99
+
100
+ <Link
101
+ href="/login"
102
+ className="text-muted-foreground block text-center text-sm hover:underline"
103
+ >
104
+ {t('backToLogin')}
105
+ </Link>
106
+ </form>
107
+ )}
108
+ </div>
109
+ </div>
110
+ );
111
+ }
@@ -1,161 +1,161 @@
1
- 'use client';
2
-
3
- import { Suspense, useState } from 'react';
4
- import { useRouter, useSearchParams } from 'next/navigation';
5
- import Link from 'next/link';
6
- import { getClient } from '@/lib/brainerce';
7
- import { LoadingSpinner } from '@/components/shared/loading-spinner';
8
- import { useTranslations } from '@/lib/translations';
9
-
10
- function ResetPasswordContent() {
11
- const router = useRouter();
12
- const searchParams = useSearchParams();
13
- const t = useTranslations('auth');
14
-
15
- const token = searchParams.get('token');
16
-
17
- const [newPassword, setNewPassword] = useState('');
18
- const [confirmPassword, setConfirmPassword] = useState('');
19
- const [loading, setLoading] = useState(false);
20
- const [error, setError] = useState<string | null>(null);
21
- const [success, setSuccess] = useState(false);
22
-
23
- async function handleSubmit(e: React.FormEvent) {
24
- e.preventDefault();
25
- if (loading || !token) return;
26
-
27
- if (newPassword !== confirmPassword) {
28
- setError(t('passwordsMustMatch'));
29
- return;
30
- }
31
-
32
- try {
33
- setLoading(true);
34
- setError(null);
35
- const client = getClient();
36
- await client.resetPassword(token, newPassword);
37
- setSuccess(true);
38
- setTimeout(() => router.push('/login'), 2000);
39
- } catch (err) {
40
- const message =
41
- err instanceof Error ? err.message : 'Something went wrong. Please try again.';
42
- setError(message);
43
- } finally {
44
- setLoading(false);
45
- }
46
- }
47
-
48
- if (!token) {
49
- return (
50
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
51
- <div className="w-full max-w-md space-y-4 text-center">
52
- <h1 className="text-foreground text-2xl font-bold">{t('invalidResetLink')}</h1>
53
- <p className="text-muted-foreground text-sm">{t('invalidResetLinkDesc')}</p>
54
- <Link
55
- href="/forgot-password"
56
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
57
- >
58
- {t('sendResetLink')}
59
- </Link>
60
- </div>
61
- </div>
62
- );
63
- }
64
-
65
- return (
66
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
67
- <div className="w-full max-w-md space-y-6">
68
- <div className="text-center">
69
- <h1 className="text-foreground text-2xl font-bold">{t('resetPasswordTitle')}</h1>
70
- <p className="text-muted-foreground mt-1 text-sm">{t('resetPasswordSubtitle')}</p>
71
- </div>
72
-
73
- {error && (
74
- <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
75
- {error}
76
- </div>
77
- )}
78
-
79
- {success ? (
80
- <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300">
81
- {t('passwordResetSuccess')}
82
- </div>
83
- ) : (
84
- <form onSubmit={handleSubmit} className="space-y-4">
85
- <div>
86
- <label
87
- htmlFor="new-password"
88
- className="text-foreground mb-1.5 block text-sm font-medium"
89
- >
90
- {t('newPassword')}
91
- </label>
92
- <input
93
- id="new-password"
94
- type="password"
95
- required
96
- minLength={8}
97
- value={newPassword}
98
- onChange={(e) => setNewPassword(e.target.value)}
99
- placeholder={t('newPasswordPlaceholder')}
100
- autoComplete="new-password"
101
- 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"
102
- />
103
- </div>
104
-
105
- <div>
106
- <label
107
- htmlFor="confirm-password"
108
- className="text-foreground mb-1.5 block text-sm font-medium"
109
- >
110
- {t('confirmPassword')}
111
- </label>
112
- <input
113
- id="confirm-password"
114
- type="password"
115
- required
116
- minLength={8}
117
- value={confirmPassword}
118
- onChange={(e) => setConfirmPassword(e.target.value)}
119
- placeholder={t('confirmPasswordPlaceholder')}
120
- autoComplete="new-password"
121
- 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"
122
- />
123
- </div>
124
-
125
- <button
126
- type="submit"
127
- disabled={loading}
128
- 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"
129
- >
130
- {loading ? (
131
- <>
132
- <LoadingSpinner
133
- size="sm"
134
- className="border-primary-foreground/30 border-t-primary-foreground"
135
- />
136
- {t('resettingPassword')}
137
- </>
138
- ) : (
139
- t('resetPassword')
140
- )}
141
- </button>
142
- </form>
143
- )}
144
- </div>
145
- </div>
146
- );
147
- }
148
-
149
- export default function ResetPasswordPage() {
150
- return (
151
- <Suspense
152
- fallback={
153
- <div className="flex min-h-[60vh] items-center justify-center">
154
- <LoadingSpinner size="lg" />
155
- </div>
156
- }
157
- >
158
- <ResetPasswordContent />
159
- </Suspense>
160
- );
161
- }
1
+ 'use client';
2
+
3
+ import { Suspense, useState } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import { getClient } from '@/lib/brainerce';
7
+ import { LoadingSpinner } from '@/components/shared/loading-spinner';
8
+ import { useTranslations } from '@/lib/translations';
9
+
10
+ function ResetPasswordContent() {
11
+ const router = useRouter();
12
+ const searchParams = useSearchParams();
13
+ const t = useTranslations('auth');
14
+
15
+ const token = searchParams.get('token');
16
+
17
+ const [newPassword, setNewPassword] = useState('');
18
+ const [confirmPassword, setConfirmPassword] = useState('');
19
+ const [loading, setLoading] = useState(false);
20
+ const [error, setError] = useState<string | null>(null);
21
+ const [success, setSuccess] = useState(false);
22
+
23
+ async function handleSubmit(e: React.FormEvent) {
24
+ e.preventDefault();
25
+ if (loading || !token) return;
26
+
27
+ if (newPassword !== confirmPassword) {
28
+ setError(t('passwordsMustMatch'));
29
+ return;
30
+ }
31
+
32
+ try {
33
+ setLoading(true);
34
+ setError(null);
35
+ const client = getClient();
36
+ await client.resetPassword(token, newPassword);
37
+ setSuccess(true);
38
+ setTimeout(() => router.push('/login'), 2000);
39
+ } catch (err) {
40
+ const message =
41
+ err instanceof Error ? err.message : 'Something went wrong. Please try again.';
42
+ setError(message);
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ }
47
+
48
+ if (!token) {
49
+ return (
50
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
51
+ <div className="w-full max-w-md space-y-4 text-center">
52
+ <h1 className="text-foreground text-2xl font-bold">{t('invalidResetLink')}</h1>
53
+ <p className="text-muted-foreground text-sm">{t('invalidResetLinkDesc')}</p>
54
+ <Link
55
+ href="/forgot-password"
56
+ className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
57
+ >
58
+ {t('sendResetLink')}
59
+ </Link>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
67
+ <div className="w-full max-w-md space-y-6">
68
+ <div className="text-center">
69
+ <h1 className="text-foreground text-2xl font-bold">{t('resetPasswordTitle')}</h1>
70
+ <p className="text-muted-foreground mt-1 text-sm">{t('resetPasswordSubtitle')}</p>
71
+ </div>
72
+
73
+ {error && (
74
+ <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
75
+ {error}
76
+ </div>
77
+ )}
78
+
79
+ {success ? (
80
+ <div className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-300">
81
+ {t('passwordResetSuccess')}
82
+ </div>
83
+ ) : (
84
+ <form onSubmit={handleSubmit} className="space-y-4">
85
+ <div>
86
+ <label
87
+ htmlFor="new-password"
88
+ className="text-foreground mb-1.5 block text-sm font-medium"
89
+ >
90
+ {t('newPassword')}
91
+ </label>
92
+ <input
93
+ id="new-password"
94
+ type="password"
95
+ required
96
+ minLength={8}
97
+ value={newPassword}
98
+ onChange={(e) => setNewPassword(e.target.value)}
99
+ placeholder={t('newPasswordPlaceholder')}
100
+ autoComplete="new-password"
101
+ 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"
102
+ />
103
+ </div>
104
+
105
+ <div>
106
+ <label
107
+ htmlFor="confirm-password"
108
+ className="text-foreground mb-1.5 block text-sm font-medium"
109
+ >
110
+ {t('confirmPassword')}
111
+ </label>
112
+ <input
113
+ id="confirm-password"
114
+ type="password"
115
+ required
116
+ minLength={8}
117
+ value={confirmPassword}
118
+ onChange={(e) => setConfirmPassword(e.target.value)}
119
+ placeholder={t('confirmPasswordPlaceholder')}
120
+ autoComplete="new-password"
121
+ 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"
122
+ />
123
+ </div>
124
+
125
+ <button
126
+ type="submit"
127
+ disabled={loading}
128
+ 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"
129
+ >
130
+ {loading ? (
131
+ <>
132
+ <LoadingSpinner
133
+ size="sm"
134
+ className="border-primary-foreground/30 border-t-primary-foreground"
135
+ />
136
+ {t('resettingPassword')}
137
+ </>
138
+ ) : (
139
+ t('resetPassword')
140
+ )}
141
+ </button>
142
+ </form>
143
+ )}
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ export default function ResetPasswordPage() {
150
+ return (
151
+ <Suspense
152
+ fallback={
153
+ <div className="flex min-h-[60vh] items-center justify-center">
154
+ <LoadingSpinner size="lg" />
155
+ </div>
156
+ }
157
+ >
158
+ <ResetPasswordContent />
159
+ </Suspense>
160
+ );
161
+ }