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.
- package/dist/index.js +7 -4
- package/package.json +1 -1
- package/templates/nextjs/base/next.config.ts +11 -4
- package/templates/nextjs/base/src/app/checkout/page.tsx +975 -975
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +5 -5
- package/templates/nextjs/base/src/components/layout/language-switcher.tsx.ejs +12 -5
- package/templates/nextjs/base/src/lib/navigation.tsx.ejs +17 -4
- package/templates/nextjs/base/src/middleware.ts.ejs +35 -12
|
@@ -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
|
|
80
|
-
<p className="text-
|
|
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-
|
|
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: '
|
|
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
|
|
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
|
|
40
|
-
} else {
|
|
41
|
-
segments.splice(1, 0, newLocale);
|
|
46
|
+
segments.splice(1, 1);
|
|
42
47
|
}
|
|
43
|
-
|
|
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
|
|
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))
|
|
22
|
-
|
|
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
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const pathWithoutLocale =
|
|
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
|
|
84
|
-
|
|
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
|
-
//
|
|
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);
|