create-brainerce-store 1.6.0 → 1.7.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 -286
- package/messages/he.json +286 -286
- package/package.json +1 -1
- package/templates/nextjs/base/.env.local.ejs +3 -1
- package/templates/nextjs/base/next.config.ts +31 -9
- package/templates/nextjs/base/src/app/api/auth/logout/route.ts +14 -0
- package/templates/nextjs/base/src/app/api/auth/me/route.ts +49 -0
- package/templates/nextjs/base/src/app/api/auth/oauth-callback/route.ts +59 -0
- package/templates/nextjs/base/src/app/api/auth/reset-callback/route.ts +41 -0
- package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +68 -0
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +190 -0
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +92 -90
- package/templates/nextjs/base/src/app/forgot-password/page.tsx +2 -1
- package/templates/nextjs/base/src/app/login/page.tsx +59 -58
- package/templates/nextjs/base/src/app/register/page.tsx +64 -68
- package/templates/nextjs/base/src/app/reset-password/page.tsx +14 -43
- package/templates/nextjs/base/src/app/verify-email/page.tsx +258 -293
- package/templates/nextjs/base/src/components/auth/login-form.tsx +101 -101
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +137 -137
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +379 -372
- package/templates/nextjs/base/src/lib/auth.ts +148 -0
- package/templates/nextjs/base/src/lib/brainerce.ts.ejs +6 -26
- package/templates/nextjs/base/src/middleware.ts +25 -0
- package/templates/nextjs/base/src/providers/store-provider.tsx.ejs +50 -27
|
@@ -1,58 +1,59 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import Link from 'next/link';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
router.push(
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
auth
|
|
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
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { useAuth } from '@/providers/store-provider';
|
|
7
|
+
import { proxyLogin } from '@/lib/auth';
|
|
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 result = await proxyLogin(email, password);
|
|
22
|
+
|
|
23
|
+
if (result.requiresVerification) {
|
|
24
|
+
// Verification token is NOT the auth JWT — safe to pass in URL
|
|
25
|
+
router.push('/verify-email');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Cookie was set by the proxy; refresh auth state
|
|
30
|
+
await auth.login();
|
|
31
|
+
router.push('/');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const message = err instanceof Error ? err.message : 'Login failed. Please try again.';
|
|
34
|
+
setError(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
|
|
40
|
+
<div className="w-full max-w-md space-y-6">
|
|
41
|
+
<div className="text-center">
|
|
42
|
+
<h1 className="text-foreground text-2xl font-bold">{t('welcomeBack')}</h1>
|
|
43
|
+
<p className="text-muted-foreground mt-1 text-sm">{t('signInSubtitle')}</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<LoginForm onSubmit={handleLogin} error={error} />
|
|
47
|
+
|
|
48
|
+
<OAuthButtons />
|
|
49
|
+
|
|
50
|
+
<p className="text-muted-foreground text-center text-sm">
|
|
51
|
+
{t('noAccount')}{' '}
|
|
52
|
+
<Link href="/register" className="text-primary font-medium hover:underline">
|
|
53
|
+
{t('createOne')}
|
|
54
|
+
</Link>
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -1,68 +1,64 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import Link from 'next/link';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { RegisterForm } from '@/components/auth/register-form';
|
|
9
|
-
import { OAuthButtons } from '@/components/auth/oauth-buttons';
|
|
10
|
-
import { useTranslations } from '@/lib/translations';
|
|
11
|
-
|
|
12
|
-
export default function RegisterPage() {
|
|
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 handleRegister(data: {
|
|
19
|
-
firstName: string;
|
|
20
|
-
lastName: string;
|
|
21
|
-
email: string;
|
|
22
|
-
password: string;
|
|
23
|
-
}) {
|
|
24
|
-
try {
|
|
25
|
-
setError(null);
|
|
26
|
-
const
|
|
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
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { useAuth } from '@/providers/store-provider';
|
|
7
|
+
import { proxyRegister } from '@/lib/auth';
|
|
8
|
+
import { RegisterForm } from '@/components/auth/register-form';
|
|
9
|
+
import { OAuthButtons } from '@/components/auth/oauth-buttons';
|
|
10
|
+
import { useTranslations } from '@/lib/translations';
|
|
11
|
+
|
|
12
|
+
export default function RegisterPage() {
|
|
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 handleRegister(data: {
|
|
19
|
+
firstName: string;
|
|
20
|
+
lastName: string;
|
|
21
|
+
email: string;
|
|
22
|
+
password: string;
|
|
23
|
+
}) {
|
|
24
|
+
try {
|
|
25
|
+
setError(null);
|
|
26
|
+
const result = await proxyRegister(data);
|
|
27
|
+
|
|
28
|
+
if (result.requiresVerification) {
|
|
29
|
+
// Cookie already set by proxy; verify-email uses it for auth
|
|
30
|
+
router.push('/verify-email');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Cookie was set by the proxy; refresh auth state
|
|
35
|
+
await auth.login();
|
|
36
|
+
router.push('/');
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : 'Registration failed. Please try again.';
|
|
39
|
+
setError(message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
|
|
45
|
+
<div className="w-full max-w-md space-y-6">
|
|
46
|
+
<div className="text-center">
|
|
47
|
+
<h1 className="text-foreground text-2xl font-bold">{t('createAccountTitle')}</h1>
|
|
48
|
+
<p className="text-muted-foreground mt-1 text-sm">{t('joinSubtitle')}</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<RegisterForm onSubmit={handleRegister} error={error} />
|
|
52
|
+
|
|
53
|
+
<OAuthButtons />
|
|
54
|
+
|
|
55
|
+
<p className="text-muted-foreground text-center text-sm">
|
|
56
|
+
{t('alreadyHaveAccount')}{' '}
|
|
57
|
+
<Link href="/login" className="text-primary font-medium hover:underline">
|
|
58
|
+
{t('signIn')}
|
|
59
|
+
</Link>
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { useRouter
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
5
|
import Link from 'next/link';
|
|
6
|
-
import {
|
|
6
|
+
import { proxyResetPassword } from '@/lib/auth';
|
|
7
7
|
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
8
8
|
import { useTranslations } from '@/lib/translations';
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
export default function ResetPasswordPage() {
|
|
11
11
|
const router = useRouter();
|
|
12
|
-
const searchParams = useSearchParams();
|
|
13
12
|
const t = useTranslations('auth');
|
|
14
13
|
|
|
15
|
-
const token = searchParams.get('token');
|
|
16
|
-
|
|
17
14
|
const [newPassword, setNewPassword] = useState('');
|
|
18
15
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
19
16
|
const [loading, setLoading] = useState(false);
|
|
@@ -22,7 +19,7 @@ function ResetPasswordContent() {
|
|
|
22
19
|
|
|
23
20
|
async function handleSubmit(e: React.FormEvent) {
|
|
24
21
|
e.preventDefault();
|
|
25
|
-
if (loading
|
|
22
|
+
if (loading) return;
|
|
26
23
|
|
|
27
24
|
if (newPassword !== confirmPassword) {
|
|
28
25
|
setError(t('passwordsMustMatch'));
|
|
@@ -32,8 +29,7 @@ function ResetPasswordContent() {
|
|
|
32
29
|
try {
|
|
33
30
|
setLoading(true);
|
|
34
31
|
setError(null);
|
|
35
|
-
|
|
36
|
-
await client.resetPassword(token, newPassword);
|
|
32
|
+
await proxyResetPassword(newPassword);
|
|
37
33
|
setSuccess(true);
|
|
38
34
|
setTimeout(() => router.push('/login'), 2000);
|
|
39
35
|
} catch (err) {
|
|
@@ -45,23 +41,6 @@ function ResetPasswordContent() {
|
|
|
45
41
|
}
|
|
46
42
|
}
|
|
47
43
|
|
|
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
44
|
return (
|
|
66
45
|
<div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
|
|
67
46
|
<div className="w-full max-w-md space-y-6">
|
|
@@ -71,8 +50,14 @@ function ResetPasswordContent() {
|
|
|
71
50
|
</div>
|
|
72
51
|
|
|
73
52
|
{error && (
|
|
74
|
-
<div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
|
|
75
|
-
{error}
|
|
53
|
+
<div className="bg-destructive/10 border-destructive/20 text-destructive space-y-2 rounded-lg border px-4 py-3 text-sm">
|
|
54
|
+
<p>{error}</p>
|
|
55
|
+
<Link
|
|
56
|
+
href="/forgot-password"
|
|
57
|
+
className="text-primary block font-medium hover:underline"
|
|
58
|
+
>
|
|
59
|
+
{t('sendResetLink')}
|
|
60
|
+
</Link>
|
|
76
61
|
</div>
|
|
77
62
|
)}
|
|
78
63
|
|
|
@@ -145,17 +130,3 @@ function ResetPasswordContent() {
|
|
|
145
130
|
</div>
|
|
146
131
|
);
|
|
147
132
|
}
|
|
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
|
-
}
|