create-brainerce-store 1.28.0 → 1.28.4

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.
@@ -60,6 +60,7 @@ export default async function RootLayout({
60
60
  <script
61
61
  type="application/ld+json"
62
62
  nonce={nonce}
63
+ suppressHydrationWarning
63
64
  dangerouslySetInnerHTML={{
64
65
  __html: JSON.stringify(organizationJsonLd)
65
66
  .replace(/</g, '\\u003c')
@@ -133,6 +134,7 @@ export default async function RootLayout({
133
134
  <script
134
135
  type="application/ld+json"
135
136
  nonce={nonce}
137
+ suppressHydrationWarning
136
138
  dangerouslySetInnerHTML={{
137
139
  __html: JSON.stringify(organizationJsonLd)
138
140
  .replace(/</g, '\\u003c')
@@ -76,8 +76,8 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
76
76
  // but we re-check here so the component is safe to render in any context.
77
77
  if (!isValidCheckoutId(checkoutId)) {
78
78
  return (
79
- <div className={cn('rounded-md border border-destructive/50 p-4', className)}>
80
- <p className="text-sm text-destructive">{t('paymentError')}</p>
79
+ <div className={cn('border-destructive/50 rounded-md border p-4', className)}>
80
+ <p className="text-destructive text-sm">{t('paymentError')}</p>
81
81
  </div>
82
82
  );
83
83
  }
@@ -29,18 +29,25 @@ const LOCALE_NAMES: Record<string, string> = {
29
29
  vi: 'Tiếng Việt',
30
30
  };
31
31
 
32
+ const defaultLocale = '<%= defaultLocale %>';
33
+
32
34
  export function LanguageSwitcher() {
33
35
  const pathname = usePathname();
34
- const currentLocale = pathname.split('/')[1] || '<%= defaultLocale %>';
36
+ const firstSeg = pathname.split('/')[1];
37
+ const currentLocale = supportedLocales.includes(firstSeg as (typeof supportedLocales)[number])
38
+ ? firstSeg
39
+ : defaultLocale;
35
40
 
36
41
  function switchLocale(newLocale: string) {
42
+ // Strip any existing locale segment, then re-add only if the new one
43
+ // isn't the default — matches the as-needed prefix scheme in middleware.
37
44
  const segments = pathname.split('/');
38
45
  if (supportedLocales.includes(segments[1] as (typeof supportedLocales)[number])) {
39
- segments[1] = newLocale;
40
- } else {
41
- segments.splice(1, 0, newLocale);
46
+ segments.splice(1, 1);
42
47
  }
43
- window.location.href = segments.join('/');
48
+ const bare = segments.join('/') || '/';
49
+ window.location.href =
50
+ newLocale === defaultLocale ? bare : `/${newLocale}${bare === '/' ? '' : bare}`;
44
51
  }
45
52
 
46
53
  if (supportedLocales.length <= 1) return null;
@@ -64,11 +64,13 @@ export async function ProductJsonLd({ product, url, currency = 'USD' }: ProductJ
64
64
  <script
65
65
  type="application/ld+json"
66
66
  nonce={nonce}
67
+ suppressHydrationWarning
67
68
  dangerouslySetInnerHTML={{ __html: serializeJsonLd(productJsonLd) }}
68
69
  />
69
70
  <script
70
71
  type="application/ld+json"
71
72
  nonce={nonce}
73
+ suppressHydrationWarning
72
74
  dangerouslySetInnerHTML={{ __html: serializeJsonLd(breadcrumbJsonLd) }}
73
75
  />
74
76
  </>
@@ -14,12 +14,25 @@ function getLocale(pathname: string): string {
14
14
  return supportedLocales.includes(segment) ? segment : defaultLocale;
15
15
  }
16
16
 
17
- function localizePath(path: string, locale: string): string {
17
+ function stripLocale(path: string): string {
18
18
  if (!path.startsWith('/')) return path;
19
- // Don't double-prefix
20
19
  const firstSeg = path.split('/')[1];
21
- if (supportedLocales.includes(firstSeg)) return path;
22
- return `/${locale}${path}`;
20
+ if (supportedLocales.includes(firstSeg)) {
21
+ return path.slice(`/${firstSeg}`.length) || '/';
22
+ }
23
+ return path;
24
+ }
25
+
26
+ // As-needed locale prefix:
27
+ // defaultLocale → no prefix (/foo)
28
+ // other locale → /{locale}/foo
29
+ // Any incoming path is first stripped of an existing locale segment so we
30
+ // never double-prefix even if callers already localized.
31
+ function localizePath(path: string, locale: string): string {
32
+ if (!path.startsWith('/')) return path;
33
+ const bare = stripLocale(path);
34
+ if (locale === defaultLocale) return bare;
35
+ return bare === '/' ? `/${locale}` : `/${locale}${bare}`;
23
36
  }
24
37
 
25
38
  /**
@@ -20,9 +20,15 @@ function generateNonce(): string {
20
20
  }
21
21
 
22
22
  function buildCsp(nonce: string): string {
23
+ const isDev = process.env.NODE_ENV === 'development';
24
+ // Next dev uses webpack's eval-source-map devtool, which requires 'unsafe-eval'
25
+ // to execute module code. Prod builds never eval, so this only loosens dev.
26
+ const scriptSrc = isDev
27
+ ? `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval'`
28
+ : `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`;
23
29
  return [
24
30
  "default-src 'self'",
25
- `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
31
+ scriptSrc,
26
32
  "style-src 'self' 'unsafe-inline' https://cdn.meshulam.co.il",
27
33
  "img-src 'self' data: blob: https:",
28
34
  "font-src 'self' data:",
@@ -61,25 +67,48 @@ export function middleware(request: NextRequest) {
61
67
 
62
68
  const pathnameLocale = getLocaleFromPath(pathname);
63
69
 
64
- // Redirect to default locale if no locale prefix
65
- if (!pathnameLocale) {
66
- const url = request.nextUrl.clone();
67
- url.pathname = `/${defaultLocale}${pathname}`;
68
- return applyCspHeaders(NextResponse.redirect(url), nonce);
69
- }
70
-
71
- // Auth protection (with locale prefix)
72
- const pathWithoutLocale = pathname.replace(`/${pathnameLocale}`, '') || '/';
70
+ // As-needed locale routing:
71
+ // /foo → rewrite to /{defaultLocale}/foo (URL stays clean)
72
+ // /{default}/foo → 308-redirect to /foo (canonical, avoids duplicate content)
73
+ // /{other}/foo proceed as-is
74
+ //
75
+ // Effective locale used for auth/header is `defaultLocale` for clean URLs
76
+ // and the explicit prefix otherwise.
77
+ const effectiveLocale = pathnameLocale || defaultLocale;
78
+ const pathWithoutLocale = pathnameLocale
79
+ ? pathname.slice(`/${pathnameLocale}`.length) || '/'
80
+ : pathname;
81
+
82
+ // Auth protection — check before any rewrite/redirect so the login
83
+ // redirect lands on the correct canonical URL.
73
84
  const isProtected = PROTECTED_PATHS.some((p) => pathWithoutLocale.startsWith(p));
74
85
  if (isProtected) {
75
86
  const token = request.cookies.get(TOKEN_COOKIE);
76
87
  if (!token?.value) {
77
- const loginUrl = new URL(`/${pathnameLocale}/login`, request.url);
78
- return applyCspHeaders(NextResponse.redirect(loginUrl), nonce);
88
+ const loginPath =
89
+ effectiveLocale === defaultLocale ? '/login' : `/${effectiveLocale}/login`;
90
+ return applyCspHeaders(NextResponse.redirect(new URL(loginPath, request.url)), nonce);
79
91
  }
80
92
  }
81
93
 
82
- // Set locale header for server components + forward nonce
94
+ // Canonicalize: strip the default-locale prefix so /en/foo /foo.
95
+ if (pathnameLocale === defaultLocale) {
96
+ const url = request.nextUrl.clone();
97
+ url.pathname = pathWithoutLocale;
98
+ return applyCspHeaders(NextResponse.redirect(url, 308), nonce);
99
+ }
100
+
101
+ // No locale prefix → rewrite internally to /{defaultLocale}/...
102
+ // so the [locale] segment resolves; URL in the bar stays unprefixed.
103
+ if (!pathnameLocale) {
104
+ const url = request.nextUrl.clone();
105
+ url.pathname = `/${defaultLocale}${pathname === '/' ? '' : pathname}`;
106
+ const response = NextResponse.rewrite(url, { request: { headers: requestHeaders } });
107
+ response.headers.set('x-locale', defaultLocale);
108
+ return applyCspHeaders(response, nonce);
109
+ }
110
+
111
+ // Non-default locale prefix → proceed as-is.
83
112
  const response = NextResponse.next({ request: { headers: requestHeaders } });
84
113
  response.headers.set('x-locale', pathnameLocale);
85
114
  return applyCspHeaders(response, nonce);
@@ -105,9 +134,15 @@ function generateNonce(): string {
105
134
  }
106
135
 
107
136
  function buildCsp(nonce: string): string {
137
+ const isDev = process.env.NODE_ENV === 'development';
138
+ // Next dev uses webpack's eval-source-map devtool, which requires 'unsafe-eval'
139
+ // to execute module code. Prod builds never eval, so this only loosens dev.
140
+ const scriptSrc = isDev
141
+ ? `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval'`
142
+ : `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`;
108
143
  return [
109
144
  "default-src 'self'",
110
- `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
145
+ scriptSrc,
111
146
  "style-src 'self' 'unsafe-inline' https://cdn.meshulam.co.il",
112
147
  "img-src 'self' data: blob: https:",
113
148
  "font-src 'self' data:",