create-brainerce-store 1.41.0 → 1.41.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.41.0",
3
+ "version": "1.41.1",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -33,7 +33,7 @@
33
33
  "typescript": "^5.4.0"
34
34
  },
35
35
  "engines": {
36
- "node": ">=18"
36
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
37
37
  },
38
38
  "keywords": [
39
39
  "brainerce",
@@ -28,5 +28,8 @@
28
28
  "postcss": "^8.4.0",
29
29
  "tailwindcss": "^3.4.0",
30
30
  "typescript": "^5.4.0"
31
+ },
32
+ "engines": {
33
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
31
34
  }
32
35
  }
@@ -17,6 +17,7 @@ import { notFound } from 'next/navigation';
17
17
 
18
18
  import { getServerClient } from '@/lib/brainerce';
19
19
  import { sanitizeHtml } from '@/lib/sanitize';
20
+ import { decodeSlug } from '@/lib/utils';
20
21
 
21
22
  <% if (i18nEnabled) { %>
22
23
  type PageProps = {
@@ -24,7 +25,8 @@ type PageProps = {
24
25
  };
25
26
 
26
27
  export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
27
- const { locale, slug } = await params;
28
+ const { locale, slug: rawSlug } = await params;
29
+ const slug = decodeSlug(rawSlug);
28
30
  const page = await getServerClient(locale).content.page.getBySlug(slug, locale).catch(() => null);
29
31
  if (!page) return { title: slug };
30
32
  const seo = page.data.seo ?? {};
@@ -36,7 +38,8 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
36
38
  }
37
39
 
38
40
  export default async function StaticPage({ params }: PageProps) {
39
- const { locale, slug } = await params;
41
+ const { locale, slug: rawSlug } = await params;
42
+ const slug = decodeSlug(rawSlug);
40
43
  const page = await getServerClient(locale).content.page.getBySlug(slug, locale).catch(() => null);
41
44
  if (!page) notFound();
42
45
  return (
@@ -57,7 +60,8 @@ type PageProps = {
57
60
  };
58
61
 
59
62
  export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
60
- const { slug } = await params;
63
+ const { slug: rawSlug } = await params;
64
+ const slug = decodeSlug(rawSlug);
61
65
  const page = await getServerClient().content.page.getBySlug(slug).catch(() => null);
62
66
  if (!page) return { title: slug };
63
67
  const seo = page.data.seo ?? {};
@@ -69,7 +73,8 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
69
73
  }
70
74
 
71
75
  export default async function StaticPage({ params }: PageProps) {
72
- const { slug } = await params;
76
+ const { slug: rawSlug } = await params;
77
+ const slug = decodeSlug(rawSlug);
73
78
  const page = await getServerClient().content.page.getBySlug(slug).catch(() => null);
74
79
  if (!page) notFound();
75
80
  return (
@@ -2,6 +2,7 @@ import type { Metadata } from 'next';
2
2
  import { notFound } from 'next/navigation';
3
3
  import { getServerClient } from '@/lib/brainerce';
4
4
  import { buildMetaDescription } from '@/lib/seo';
5
+ import { decodeSlug } from '@/lib/utils';
5
6
  import { ProductJsonLd } from '@/components/seo/product-json-ld';
6
7
  import { ReviewsSection } from '@/components/reviews/reviews-section';
7
8
  import { ProductClientSection } from './product-client-section';
@@ -11,7 +12,8 @@ type Props = {
11
12
  };
12
13
 
13
14
  export async function generateMetadata({ params }: Props): Promise<Metadata> {
14
- const { slug, locale } = await params;
15
+ const { slug: rawSlug, locale } = await params;
16
+ const slug = decodeSlug(rawSlug);
15
17
 
16
18
  try {
17
19
  const client = getServerClient(locale);
@@ -53,7 +55,8 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
53
55
  }
54
56
 
55
57
  export default async function ProductDetailPage({ params }: Props) {
56
- const { slug, locale } = await params;
58
+ const { slug: rawSlug, locale } = await params;
59
+ const slug = decodeSlug(rawSlug);
57
60
 
58
61
  let product;
59
62
  try {
@@ -1,6 +1,21 @@
1
- import { clsx, type ClassValue } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ /**
9
+ * Normalize a Next.js dynamic-route param into its decoded human-readable
10
+ * form. App Router usually decodes params automatically, but some runtime
11
+ * + middleware combinations leak percent-encoded values for non-ASCII
12
+ * slugs (e.g. Hebrew `%D7%A7…`), which then surface in canonical URLs and
13
+ * SEO tags. Idempotent on already-decoded input.
14
+ */
15
+ export function decodeSlug(slug: string): string {
16
+ try {
17
+ return decodeURIComponent(slug);
18
+ } catch {
19
+ return slug;
20
+ }
21
+ }