mirror of
https://github.com/Snouzy/workout-cool.git
synced 2026-05-19 14:40:35 +00:00
10efc94827
* feat(premium): implement new pricing page with detailed feature comparison, testimonials, and FAQs to enhance user experience and support mission-driven approach fix(premium): update PremiumManager to return structured plans including Free, Supporter, and Premium options for better clarity and user choice docs(premium): add README for premium UI components detailing architecture, design system, and usage instructions to facilitate development and onboarding * feat(premium): enhance premium features and pricing structure with yearly option and updated UI elements fix(premium): update FAQ content for clarity and accuracy, and remove deprecated features from the comparison table style(premium): improve layout and styling for better user experience across premium upgrade card and feature comparison table * feat(premium): refactor checkout process to retrieve plan details by internal ID and update checkout creation logic chore(premium): remove unused mobile sticky CTA component to streamline codebase fix(pricing): update text for clarity and improve accessibility in various components style(pricing): adjust layout and styling for better responsiveness and visual consistency across pricing sections * chore(premium/ui): remove unused export of MobileStickyCard to clean up codebase * feat(locales): add comprehensive translations for premium features, pricing, and FAQs in English, Spanish, French, Portuguese, Russian, and Chinese to enhance user experience and accessibility across multiple languages refactor(premium UI): implement translation keys for dynamic text rendering in the premium upgrade card, feature comparison table, and pricing FAQ sections to improve maintainability and localization support * chore(pricing-testimonials.tsx): add TODO comment to indicate the need for testimonials implementation * feat(locales): update feature descriptions across multiple languages for clarity and consistency refactor(premium.manager.ts): remove hardcoded free and paid plans to streamline plan management and improve maintainability chore(premium-upgrade-card.tsx): remove deprecated features from the premium upgrade card to reflect current offerings * feat(premium): implement multi-region pricing plans and update environment variables for Stripe price IDs to support various regions fix(premium): update checkout logic to use planId directly instead of fetching plan details refactor(premium): enhance region detection logic for better user experience and add dynamic pricing fetching in the PremiumUpgradeCard component chore(ci): update CI configuration to include new environment variables for multi-region pricing docs: remove outdated error messages from localization files and update premium active state messages for better clarity * fix(seed-subscription-plans): update externalId to use EU-specific environment variables for Stripe pricing plans to ensure correct pricing for the EU region
108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
// middleware.ts
|
|
import { createI18nMiddleware } from "next-international/middleware";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { getSessionCookie } from "better-auth/cookies";
|
|
|
|
function detectUserLocale(request: NextRequest): string {
|
|
const acceptLanguage = request.headers.get("accept-language");
|
|
|
|
if (!acceptLanguage) return "en";
|
|
|
|
// Parse Accept-Language header
|
|
const languages = acceptLanguage
|
|
.split(",")
|
|
.map((lang) => {
|
|
const [locale, quality = "1"] = lang.trim().split(";q=");
|
|
return { locale: locale.toLowerCase(), quality: parseFloat(quality) };
|
|
})
|
|
.sort((a, b) => b.quality - a.quality);
|
|
|
|
// Map browser locales to supported locales
|
|
const supportedLocales = ["en", "fr", "es", "zh-cn", "ru", "pt"];
|
|
|
|
for (const { locale } of languages) {
|
|
// Exact match
|
|
if (supportedLocales.includes(locale)) {
|
|
return locale === "zh-cn" ? "zh-CN" : locale;
|
|
}
|
|
|
|
// Language match (fr-FR -> fr)
|
|
const lang = locale.split("-")[0];
|
|
if (supportedLocales.includes(lang)) {
|
|
return lang;
|
|
}
|
|
|
|
// Chinese variants
|
|
if (locale.startsWith("zh")) {
|
|
return "zh-CN";
|
|
}
|
|
}
|
|
|
|
return "en"; // fallback
|
|
}
|
|
|
|
const I18nMiddleware = createI18nMiddleware({
|
|
locales: ["en", "fr", "es", "zh-CN", "ru", "pt"],
|
|
defaultLocale: "en",
|
|
urlMappingStrategy: "rewrite",
|
|
});
|
|
|
|
export async function middleware(request: NextRequest) {
|
|
const pathname = request.nextUrl.pathname;
|
|
const detectedLocale = detectUserLocale(request);
|
|
|
|
// If on root path and no locale detected yet, redirect to detected locale
|
|
if (pathname === "/" && !request.cookies.get("detected-locale")) {
|
|
const url = new URL(`/${detectedLocale}`, request.url);
|
|
const response = NextResponse.redirect(url);
|
|
|
|
response.cookies.set("detected-locale", detectedLocale, {
|
|
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
httpOnly: false,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
// Normal i18n middleware processing
|
|
const response = I18nMiddleware(request);
|
|
|
|
// Extract current locale from pathname
|
|
const localeMatch = pathname.match(/^\/([a-z]{2}(?:-[A-Z]{2})?)/);
|
|
const currentLocale = localeMatch ? localeMatch[1] : detectedLocale;
|
|
|
|
// Set Content-Language header for SEO
|
|
response.headers.set("Content-Language", currentLocale);
|
|
|
|
// Store detected locale in cookie for future visits
|
|
if (!request.cookies.get("detected-locale")) {
|
|
response.cookies.set("detected-locale", detectedLocale, {
|
|
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
httpOnly: false,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
});
|
|
}
|
|
|
|
const searchParams = request.nextUrl.searchParams.toString();
|
|
response.headers.set("searchParams", searchParams);
|
|
|
|
if (request.nextUrl.pathname.includes("/dashboard")) {
|
|
const session = getSessionCookie(request);
|
|
|
|
if (!session) {
|
|
return NextResponse.redirect(new URL("/", request.url));
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!api|static|_next|manifest.json|favicon.ico|robots.txt|sw.js|apple-touch-icon.png|android-chrome-.*\\.png|images|logo|icons|sitemap.xml|ads.txt).*)",
|
|
],
|
|
};
|