create-brainerce-store 1.28.1 → 1.28.8

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.
@@ -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
  }
@@ -557,14 +557,14 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
557
557
 
558
558
  if (sdk.renderType === 'iframe') {
559
559
  if (!isAllowedPaymentUrl(paymentIntent.clientSecret)) return null;
560
- const formattedAmount = formatPrice(Number(paymentIntent.amount) || 0, {
560
+ const formattedAmount = formatPrice((Number(paymentIntent.amount) || 0) / 100, {
561
561
  currency: paymentIntent.currency,
562
562
  }) as string;
563
563
  return (
564
564
  <>
565
565
  {/* Modal overlay */}
566
566
  <div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/50 py-6 backdrop-blur-sm">
567
- <div className="bg-background relative mx-4 flex w-full max-w-lg flex-col overflow-hidden rounded-2xl shadow-2xl">
567
+ <div className="bg-background relative mx-4 flex w-full max-w-2xl flex-col overflow-hidden rounded-2xl shadow-2xl">
568
568
  {/* Header */}
569
569
  <div className="border-border flex items-center justify-between gap-4 border-b px-5 py-4">
570
570
  <div className="flex min-w-0 flex-col">
@@ -605,7 +605,7 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
605
605
  <iframe
606
606
  src={paymentIntent.clientSecret}
607
607
  className="w-full border-0"
608
- style={{ height: '70vh' }}
608
+ style={{ height: '80vh' }}
609
609
  title={t('payment')}
610
610
  allow="payment"
611
611
  />
@@ -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;
@@ -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
  /**
@@ -67,25 +67,48 @@ export function middleware(request: NextRequest) {
67
67
 
68
68
  const pathnameLocale = getLocaleFromPath(pathname);
69
69
 
70
- // Redirect to default locale if no locale prefix
71
- if (!pathnameLocale) {
72
- const url = request.nextUrl.clone();
73
- url.pathname = `/${defaultLocale}${pathname}`;
74
- return applyCspHeaders(NextResponse.redirect(url), nonce);
75
- }
76
-
77
- // Auth protection (with locale prefix)
78
- 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.
79
84
  const isProtected = PROTECTED_PATHS.some((p) => pathWithoutLocale.startsWith(p));
80
85
  if (isProtected) {
81
86
  const token = request.cookies.get(TOKEN_COOKIE);
82
87
  if (!token?.value) {
83
- const loginUrl = new URL(`/${pathnameLocale}/login`, request.url);
84
- 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);
85
91
  }
86
92
  }
87
93
 
88
- // 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.
89
112
  const response = NextResponse.next({ request: { headers: requestHeaders } });
90
113
  response.headers.set('x-locale', pathnameLocale);
91
114
  return applyCspHeaders(response, nonce);