Update homepage routes to include language and mode
Build & Deploy / Build & Push Docker Image (push) Successful in 2m51s
Test / Type Check (all packages) (push) Successful in 52s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Successful in 49s
Test / Homepage Unit Tests (push) Successful in 44s
Test / Storefront Unit Tests (push) Successful in 39s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 40s
Test / API Integration Tests (push) Successful in 1m0s
Build & Deploy / Build & Push Docker Image (push) Successful in 2m51s
Test / Type Check (all packages) (push) Successful in 52s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Successful in 49s
Test / Homepage Unit Tests (push) Successful in 44s
Test / Storefront Unit Tests (push) Successful in 39s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 40s
Test / API Integration Tests (push) Successful in 1m0s
This commit is contained in:
+98
-45
@@ -1,14 +1,21 @@
|
||||
import {
|
||||
defaultMode,
|
||||
defaultLocale,
|
||||
isMode,
|
||||
isLocale,
|
||||
localeCookie,
|
||||
localeCookieMaxAgeSeconds,
|
||||
localeFromAcceptLanguage,
|
||||
localizedPath,
|
||||
localizedModePath,
|
||||
routeIdFromAnyLocaleSlug,
|
||||
routeIdFromSlug,
|
||||
} from '@/lib/localization/config';
|
||||
import { buildContentSecurityPolicy, createNonce } from '@/lib/security/csp';
|
||||
import {
|
||||
isThemePreference,
|
||||
themeCookie,
|
||||
themeCookieMaxAgeSeconds,
|
||||
} from '@/lib/theme/config';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
@@ -39,6 +46,38 @@ function resolveApiOrigin(): string | undefined {
|
||||
return origins.size > 0 ? Array.from(origins).join(' ') : undefined;
|
||||
}
|
||||
|
||||
function resolveRequestLocale(request: NextRequest) {
|
||||
const cookieLocale = request.cookies.get(localeCookie)?.value;
|
||||
return isLocale(cookieLocale)
|
||||
? cookieLocale
|
||||
: localeFromAcceptLanguage(request.headers.get('accept-language')) || defaultLocale;
|
||||
}
|
||||
|
||||
function resolveRequestMode(request: NextRequest) {
|
||||
const cookieMode = request.cookies.get(themeCookie)?.value;
|
||||
return isThemePreference(cookieMode) ? cookieMode : defaultMode;
|
||||
}
|
||||
|
||||
function setLocaleCookie(response: NextResponse, locale: string): void {
|
||||
response.cookies.set(localeCookie, locale, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
function setModeCookie(response: NextResponse, mode: string): void {
|
||||
response.cookies.set(themeCookie, mode, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: themeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function proxy(request: NextRequest): NextResponse {
|
||||
const nonce = createNonce();
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
@@ -49,20 +88,13 @@ export function proxy(request: NextRequest): NextResponse {
|
||||
requestHeaders.set('Content-Security-Policy', csp);
|
||||
|
||||
if (request.nextUrl.pathname === '/') {
|
||||
const cookieLocale = request.cookies.get(localeCookie)?.value;
|
||||
const locale = isLocale(cookieLocale)
|
||||
? cookieLocale
|
||||
: localeFromAcceptLanguage(request.headers.get('accept-language')) || defaultLocale;
|
||||
const locale = resolveRequestLocale(request);
|
||||
const mode = resolveRequestMode(request);
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${locale}`;
|
||||
destination.pathname = `/${locale}/${mode}`;
|
||||
const response = NextResponse.redirect(destination);
|
||||
response.cookies.set(localeCookie, locale, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
setLocaleCookie(response, locale);
|
||||
setModeCookie(response, mode);
|
||||
return applySecurityHeaders(response, csp);
|
||||
}
|
||||
|
||||
@@ -72,32 +104,34 @@ export function proxy(request: NextRequest): NextResponse {
|
||||
destination.pathname = `/${pathSegments.slice(1).join('/')}`;
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
|
||||
// Redirect locale-less paths (e.g. /sign-in → /en/sign-in), but not proxied app paths or files.
|
||||
const firstSegment = request.nextUrl.pathname.split('/')[1];
|
||||
const isFile = /\.\w+$/.test(request.nextUrl.pathname);
|
||||
if (firstSegment && !isFile && !isLocale(firstSegment) && !reservedProxySegments.has(firstSegment)) {
|
||||
const cookieLocale = request.cookies.get(localeCookie)?.value;
|
||||
const locale = isLocale(cookieLocale)
|
||||
? cookieLocale
|
||||
: localeFromAcceptLanguage(request.headers.get('accept-language')) || defaultLocale;
|
||||
if (isLocale(pathSegments[0]) && isMode(pathSegments[1]) && pathSegments[2] === 'storefront') {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${locale}${request.nextUrl.pathname}`;
|
||||
destination.pathname = `/${pathSegments.slice(2).join('/')}`;
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
|
||||
if (isLocale(firstSegment) && request.nextUrl.pathname === `/${firstSegment}/subscription`) {
|
||||
// Redirect locale-less paths (e.g. /sign-in → /en/system/sign-in), but not proxied app paths or files.
|
||||
const firstSegment = request.nextUrl.pathname.split('/')[1];
|
||||
const isFile = /\.\w+$/.test(request.nextUrl.pathname);
|
||||
if (firstSegment && !isFile && !isLocale(firstSegment) && !reservedProxySegments.has(firstSegment)) {
|
||||
const locale = resolveRequestLocale(request);
|
||||
const mode = resolveRequestMode(request);
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${locale}/${mode}${request.nextUrl.pathname}`;
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
|
||||
if (
|
||||
isLocale(pathSegments[0]) &&
|
||||
isMode(pathSegments[1]) &&
|
||||
request.nextUrl.pathname === `/${pathSegments[0]}/${pathSegments[1]}/subscription`
|
||||
) {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = '/dashboard/subscription';
|
||||
const response = NextResponse.redirect(destination);
|
||||
response.cookies.set(localeCookie, firstSegment, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
response.cookies.set(dashboardLanguageCookie, firstSegment, {
|
||||
setLocaleCookie(response, pathSegments[0]);
|
||||
setModeCookie(response, pathSegments[1]);
|
||||
response.cookies.set(dashboardLanguageCookie, pathSegments[0], {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
@@ -107,29 +141,48 @@ export function proxy(request: NextRequest): NextResponse {
|
||||
return applySecurityHeaders(response, csp);
|
||||
}
|
||||
|
||||
if (isLocale(firstSegment)) {
|
||||
const segments = request.nextUrl.pathname.split('/').filter(Boolean);
|
||||
const slug = segments.slice(1).join('/');
|
||||
if (slug && !routeIdFromSlug(firstSegment, slug)) {
|
||||
if (isLocale(pathSegments[0]) && !isMode(pathSegments[1])) {
|
||||
const mode = resolveRequestMode(request);
|
||||
const slug = pathSegments.slice(1).join('/');
|
||||
if (!slug) {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = localizedModePath('home', pathSegments[0], mode);
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
const routeId = routeIdFromSlug(pathSegments[0], slug) ?? routeIdFromAnyLocaleSlug(slug);
|
||||
if (routeId) {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = localizedModePath(routeId, pathSegments[0], mode);
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
if (slug === 'component-lab') {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = `/${pathSegments[0]}/${mode}/component-lab`;
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLocale(pathSegments[0]) && isMode(pathSegments[1])) {
|
||||
requestHeaders.set('x-theme-preference', pathSegments[1]);
|
||||
const slug = pathSegments.slice(2).join('/');
|
||||
if (slug && !routeIdFromSlug(pathSegments[0], slug)) {
|
||||
const routeId = routeIdFromAnyLocaleSlug(slug);
|
||||
if (routeId) {
|
||||
const destination = request.nextUrl.clone();
|
||||
destination.pathname = localizedPath(routeId, firstSegment);
|
||||
destination.pathname = localizedModePath(routeId, pathSegments[0], pathSegments[1]);
|
||||
return applySecurityHeaders(NextResponse.redirect(destination), csp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response = NextResponse.next({ request: { headers: requestHeaders } });
|
||||
const explicitLocale = request.nextUrl.pathname.split('/')[1];
|
||||
const explicitLocale = pathSegments[0];
|
||||
if (isLocale(explicitLocale)) {
|
||||
response.cookies.set(localeCookie, explicitLocale, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
maxAge: localeCookieMaxAgeSeconds,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: false,
|
||||
});
|
||||
setLocaleCookie(response, explicitLocale);
|
||||
}
|
||||
const explicitMode = pathSegments[1];
|
||||
if (isMode(explicitMode)) {
|
||||
setModeCookie(response, explicitMode);
|
||||
}
|
||||
return applySecurityHeaders(response, csp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user