create-brainerce-store 1.17.0 → 1.19.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.
Files changed (65) hide show
  1. package/dist/index.js +31 -9
  2. package/messages/en.json +366 -359
  3. package/messages/he.json +366 -359
  4. package/package.json +45 -45
  5. package/templates/nextjs/base/next.config.ts +31 -31
  6. package/templates/nextjs/base/scripts/fetch-store-info.mjs +81 -81
  7. package/templates/nextjs/base/src/app/.well-known/apple-developer-merchantid-domain-association/route.ts +26 -26
  8. package/templates/nextjs/base/src/app/account/layout.tsx +9 -9
  9. package/templates/nextjs/base/src/app/account/page.tsx +122 -122
  10. package/templates/nextjs/base/src/app/api/auth/logout/route.ts +14 -14
  11. package/templates/nextjs/base/src/app/api/auth/me/route.ts +56 -56
  12. package/templates/nextjs/base/src/app/api/auth/oauth-callback/route.ts +59 -59
  13. package/templates/nextjs/base/src/app/api/auth/reset-callback/route.ts +41 -41
  14. package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +77 -77
  15. package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +198 -198
  16. package/templates/nextjs/base/src/app/auth/callback/page.tsx +92 -92
  17. package/templates/nextjs/base/src/app/cart/layout.tsx +9 -9
  18. package/templates/nextjs/base/src/app/cart/page.tsx +204 -199
  19. package/templates/nextjs/base/src/app/checkout/layout.tsx +9 -9
  20. package/templates/nextjs/base/src/app/checkout/page.tsx +860 -860
  21. package/templates/nextjs/base/src/app/forgot-password/page.tsx +112 -112
  22. package/templates/nextjs/base/src/app/layout.tsx.ejs +75 -0
  23. package/templates/nextjs/base/src/app/login/layout.tsx +9 -9
  24. package/templates/nextjs/base/src/app/login/page.tsx +59 -59
  25. package/templates/nextjs/base/src/app/order-confirmation/layout.tsx +9 -9
  26. package/templates/nextjs/base/src/app/order-confirmation/page.tsx +254 -254
  27. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +67 -67
  28. package/templates/nextjs/base/src/app/products/[slug]/product-client-section.tsx +486 -486
  29. package/templates/nextjs/base/src/app/products/layout.tsx +18 -18
  30. package/templates/nextjs/base/src/app/products/page.tsx +431 -431
  31. package/templates/nextjs/base/src/app/register/layout.tsx +9 -9
  32. package/templates/nextjs/base/src/app/register/page.tsx +65 -65
  33. package/templates/nextjs/base/src/app/reset-password/page.tsx +132 -132
  34. package/templates/nextjs/base/src/app/robots.ts +14 -14
  35. package/templates/nextjs/base/src/app/sitemap.ts +25 -25
  36. package/templates/nextjs/base/src/app/verify-email/page.tsx +258 -258
  37. package/templates/nextjs/base/src/components/account/address-book.tsx +432 -432
  38. package/templates/nextjs/base/src/components/account/order-history.tsx +350 -350
  39. package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +137 -137
  40. package/templates/nextjs/base/src/components/auth/register-form.tsx +232 -232
  41. package/templates/nextjs/base/src/components/cart/cart-bundle-offer.tsx +247 -111
  42. package/templates/nextjs/base/src/components/cart/cart-item.tsx +153 -153
  43. package/templates/nextjs/base/src/components/cart/cart-upgrade-banner.tsx +142 -142
  44. package/templates/nextjs/base/src/components/cart/free-shipping-bar.tsx +59 -59
  45. package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +415 -415
  46. package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +243 -83
  47. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +519 -473
  48. package/templates/nextjs/base/src/components/layout/footer.tsx +41 -41
  49. package/templates/nextjs/base/src/components/layout/header.tsx +336 -336
  50. package/templates/nextjs/base/src/components/layout/language-switcher.tsx.ejs +63 -0
  51. package/templates/nextjs/base/src/components/products/discount-badge.tsx +22 -22
  52. package/templates/nextjs/base/src/components/products/frequently-bought-together.tsx +202 -202
  53. package/templates/nextjs/base/src/components/products/product-card.tsx +218 -218
  54. package/templates/nextjs/base/src/components/products/recommendation-section.tsx +107 -107
  55. package/templates/nextjs/base/src/components/products/stock-badge.tsx +63 -63
  56. package/templates/nextjs/base/src/components/products/variant-selector.tsx +292 -292
  57. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +72 -72
  58. package/templates/nextjs/base/src/i18n.ts.ejs +21 -0
  59. package/templates/nextjs/base/src/lib/auth.ts +149 -149
  60. package/templates/nextjs/base/src/lib/brainerce.ts.ejs +9 -0
  61. package/templates/nextjs/base/src/lib/translations.ts.ejs +31 -0
  62. package/templates/nextjs/base/src/middleware.ts.ejs +81 -0
  63. package/templates/nextjs/base/src/providers/store-provider.tsx.ejs +41 -0
  64. package/templates/nextjs/base/src/lib/translations.ts +0 -11
  65. package/templates/nextjs/base/src/middleware.ts +0 -25
@@ -1,112 +1,112 @@
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
- const resetUrl = `${window.location.origin}/api/auth/reset-callback`;
25
- await client.forgotPassword(email, { resetUrl });
26
- setSent(true);
27
- } catch (err) {
28
- const message =
29
- err instanceof Error ? err.message : 'Something went wrong. Please try again.';
30
- setError(message);
31
- } finally {
32
- setLoading(false);
33
- }
34
- }
35
-
36
- return (
37
- <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
38
- <div className="w-full max-w-md space-y-6">
39
- <div className="text-center">
40
- <h1 className="text-foreground text-2xl font-bold">{t('forgotPasswordTitle')}</h1>
41
- <p className="text-muted-foreground mt-1 text-sm">{t('forgotPasswordSubtitle')}</p>
42
- </div>
43
-
44
- {error && (
45
- <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
46
- {error}
47
- </div>
48
- )}
49
-
50
- {sent ? (
51
- <div className="space-y-4">
52
- <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">
53
- {t('resetLinkSent')}
54
- </div>
55
- <Link
56
- href="/login"
57
- className="text-primary block text-center text-sm font-medium hover:underline"
58
- >
59
- {t('backToLogin')}
60
- </Link>
61
- </div>
62
- ) : (
63
- <form onSubmit={handleSubmit} className="space-y-4">
64
- <div>
65
- <label
66
- htmlFor="forgot-email"
67
- className="text-foreground mb-1.5 block text-sm font-medium"
68
- >
69
- {t('email')}
70
- </label>
71
- <input
72
- id="forgot-email"
73
- type="email"
74
- required
75
- value={email}
76
- onChange={(e) => setEmail(e.target.value)}
77
- placeholder={t('emailPlaceholder')}
78
- autoComplete="email"
79
- 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"
80
- />
81
- </div>
82
-
83
- <button
84
- type="submit"
85
- disabled={loading}
86
- 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"
87
- >
88
- {loading ? (
89
- <>
90
- <LoadingSpinner
91
- size="sm"
92
- className="border-primary-foreground/30 border-t-primary-foreground"
93
- />
94
- {t('sendingResetLink')}
95
- </>
96
- ) : (
97
- t('sendResetLink')
98
- )}
99
- </button>
100
-
101
- <Link
102
- href="/login"
103
- className="text-muted-foreground block text-center text-sm hover:underline"
104
- >
105
- {t('backToLogin')}
106
- </Link>
107
- </form>
108
- )}
109
- </div>
110
- </div>
111
- );
112
- }
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
+ const resetUrl = `${window.location.origin}/api/auth/reset-callback`;
25
+ await client.forgotPassword(email, { resetUrl });
26
+ setSent(true);
27
+ } catch (err) {
28
+ const message =
29
+ err instanceof Error ? err.message : 'Something went wrong. Please try again.';
30
+ setError(message);
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ }
35
+
36
+ return (
37
+ <div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
38
+ <div className="w-full max-w-md space-y-6">
39
+ <div className="text-center">
40
+ <h1 className="text-foreground text-2xl font-bold">{t('forgotPasswordTitle')}</h1>
41
+ <p className="text-muted-foreground mt-1 text-sm">{t('forgotPasswordSubtitle')}</p>
42
+ </div>
43
+
44
+ {error && (
45
+ <div className="bg-destructive/10 border-destructive/20 text-destructive rounded-lg border px-4 py-3 text-sm">
46
+ {error}
47
+ </div>
48
+ )}
49
+
50
+ {sent ? (
51
+ <div className="space-y-4">
52
+ <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">
53
+ {t('resetLinkSent')}
54
+ </div>
55
+ <Link
56
+ href="/login"
57
+ className="text-primary block text-center text-sm font-medium hover:underline"
58
+ >
59
+ {t('backToLogin')}
60
+ </Link>
61
+ </div>
62
+ ) : (
63
+ <form onSubmit={handleSubmit} className="space-y-4">
64
+ <div>
65
+ <label
66
+ htmlFor="forgot-email"
67
+ className="text-foreground mb-1.5 block text-sm font-medium"
68
+ >
69
+ {t('email')}
70
+ </label>
71
+ <input
72
+ id="forgot-email"
73
+ type="email"
74
+ required
75
+ value={email}
76
+ onChange={(e) => setEmail(e.target.value)}
77
+ placeholder={t('emailPlaceholder')}
78
+ autoComplete="email"
79
+ 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"
80
+ />
81
+ </div>
82
+
83
+ <button
84
+ type="submit"
85
+ disabled={loading}
86
+ 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"
87
+ >
88
+ {loading ? (
89
+ <>
90
+ <LoadingSpinner
91
+ size="sm"
92
+ className="border-primary-foreground/30 border-t-primary-foreground"
93
+ />
94
+ {t('sendingResetLink')}
95
+ </>
96
+ ) : (
97
+ t('sendResetLink')
98
+ )}
99
+ </button>
100
+
101
+ <Link
102
+ href="/login"
103
+ className="text-muted-foreground block text-center text-sm hover:underline"
104
+ >
105
+ {t('backToLogin')}
106
+ </Link>
107
+ </form>
108
+ )}
109
+ </div>
110
+ </div>
111
+ );
112
+ }
@@ -1,3 +1,77 @@
1
+ <% if (i18nEnabled) { %>
2
+ import type { Metadata } from 'next';
3
+ <%- fontImport %>
4
+ import { StoreProvider } from '@/providers/store-provider';
5
+ import { Header } from '@/components/layout/header';
6
+ import { Footer } from '@/components/layout/footer';
7
+ import { getDirection, supportedLocales } from '@/i18n';
8
+ import '../globals.css';
9
+
10
+ <%- fontVariable %>
11
+
12
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
13
+
14
+ export const metadata: Metadata = {
15
+ metadataBase: new URL(baseUrl),
16
+ title: {
17
+ default: '<%= storeName %>',
18
+ template: `%s | <%= storeName %>`,
19
+ },
20
+ description: '<%= storeName %>',
21
+ alternates: {
22
+ canonical: '/',
23
+ },
24
+ openGraph: {
25
+ siteName: '<%= storeName %>',
26
+ type: 'website',
27
+ },
28
+ robots: {
29
+ index: true,
30
+ follow: true,
31
+ },
32
+ };
33
+
34
+ const organizationJsonLd = {
35
+ '@context': 'https://schema.org',
36
+ '@type': 'Organization',
37
+ name: '<%= storeName %>',
38
+ url: baseUrl,
39
+ };
40
+
41
+ export function generateStaticParams() {
42
+ return supportedLocales.map((locale) => ({ locale }));
43
+ }
44
+
45
+ export default function RootLayout({
46
+ children,
47
+ params,
48
+ }: {
49
+ children: React.ReactNode;
50
+ params: { locale: string };
51
+ }) {
52
+ const dir = getDirection(params.locale);
53
+
54
+ return (
55
+ <html lang={params.locale} dir={dir}>
56
+ <head>
57
+ <script
58
+ type="application/ld+json"
59
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationJsonLd) }}
60
+ />
61
+ </head>
62
+ <body className={font.className}>
63
+ <StoreProvider locale={params.locale}>
64
+ <div className="min-h-screen flex flex-col">
65
+ <Header />
66
+ <main className="flex-1">{children}</main>
67
+ <Footer />
68
+ </div>
69
+ </StoreProvider>
70
+ </body>
71
+ </html>
72
+ );
73
+ }
74
+ <% } else { %>
1
75
  import type { Metadata } from 'next';
2
76
  <%- fontImport %>
3
77
  import { StoreProvider } from '@/providers/store-provider';
@@ -62,3 +136,4 @@ export default function RootLayout({
62
136
  </html>
63
137
  );
64
138
  }
139
+ <% } %>
@@ -1,9 +1,9 @@
1
- import type { Metadata } from 'next';
2
-
3
- export const metadata: Metadata = {
4
- robots: { index: false, follow: false },
5
- };
6
-
7
- export default function Layout({ children }: { children: React.ReactNode }) {
8
- return <>{children}</>;
9
- }
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ robots: { index: false, follow: false },
5
+ };
6
+
7
+ export default function Layout({ children }: { children: React.ReactNode }) {
8
+ return <>{children}</>;
9
+ }
@@ -1,59 +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 { 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
+ '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,9 +1,9 @@
1
- import type { Metadata } from 'next';
2
-
3
- export const metadata: Metadata = {
4
- robots: { index: false, follow: false },
5
- };
6
-
7
- export default function Layout({ children }: { children: React.ReactNode }) {
8
- return <>{children}</>;
9
- }
1
+ import type { Metadata } from 'next';
2
+
3
+ export const metadata: Metadata = {
4
+ robots: { index: false, follow: false },
5
+ };
6
+
7
+ export default function Layout({ children }: { children: React.ReactNode }) {
8
+ return <>{children}</>;
9
+ }