Auto detect currency
This commit is contained in:
@@ -75,7 +75,7 @@ const UpgradeBanner: React.FC = () => {
|
||||
// Handle upgrade button click
|
||||
const handleUpgrade = () => {
|
||||
openCheckout('server', {
|
||||
currency: 'usd',
|
||||
// Currency auto-detected from locale in CheckoutContext
|
||||
minimumSeats: 1,
|
||||
onSuccess: () => {
|
||||
// Banner will auto-hide on next render when license is detected
|
||||
|
||||
+13
-3
@@ -10,12 +10,16 @@ import StaticPlanSection from '@app/components/shared/config/configSections/plan
|
||||
import { alert } from '@app/components/toast';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { isSupabaseConfigured } from '@app/services/supabaseClient';
|
||||
import { getPreferredCurrency, setCachedCurrency } from '@app/utils/currencyDetection';
|
||||
|
||||
const AdminPlanSection: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { openCheckout } = useCheckout();
|
||||
const { licenseInfo, refetchLicense } = useLicense();
|
||||
const [currency, setCurrency] = useState<string>('usd');
|
||||
const [currency, setCurrency] = useState<string>(() => {
|
||||
// Initialize with auto-detected currency on first render
|
||||
return getPreferredCurrency(i18n.language);
|
||||
});
|
||||
const [useStaticVersion, setUseStaticVersion] = useState(false);
|
||||
const [showLicenseKey, setShowLicenseKey] = useState(false);
|
||||
const [licenseKeyInput, setLicenseKeyInput] = useState<string>('');
|
||||
@@ -102,6 +106,12 @@ const AdminPlanSection: React.FC = () => {
|
||||
}
|
||||
}, [licenseInfo, t]);
|
||||
|
||||
const handleCurrencyChange = useCallback((newCurrency: string) => {
|
||||
setCurrency(newCurrency);
|
||||
// Persist user's manual selection to localStorage
|
||||
setCachedCurrency(newCurrency);
|
||||
}, []);
|
||||
|
||||
const handleUpgradeClick = useCallback(
|
||||
(planGroup: PlanTierGroup) => {
|
||||
// Only allow upgrades for server and enterprise tiers
|
||||
@@ -171,7 +181,7 @@ const AdminPlanSection: React.FC = () => {
|
||||
onUpgradeClick={handleUpgradeClick}
|
||||
onManageClick={handleManageClick}
|
||||
currency={currency}
|
||||
onCurrencyChange={setCurrency}
|
||||
onCurrencyChange={handleCurrencyChange}
|
||||
currencyOptions={currencyOptions}
|
||||
/>
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ import { alert } from '@app/components/toast';
|
||||
import { pollLicenseKeyWithBackoff, activateLicenseKey, resyncExistingLicense } from '@app/utils/licenseCheckoutUtils';
|
||||
import { useLicense } from '@app/contexts/LicenseContext';
|
||||
import { isSupabaseConfigured } from '@app/services/supabaseClient';
|
||||
import { getPreferredCurrency } from '@app/utils/currencyDetection';
|
||||
|
||||
export interface CheckoutOptions {
|
||||
minimumSeats?: number; // Override calculated seats for enterprise
|
||||
currency?: string; // Optional currency override (defaults to 'gbp')
|
||||
currency?: string; // Optional currency override (auto-detected from locale)
|
||||
onSuccess?: (sessionId: string) => void; // Callback after successful payment
|
||||
onError?: (error: string) => void; // Callback on error
|
||||
}
|
||||
@@ -35,15 +36,18 @@ interface CheckoutProviderProps {
|
||||
|
||||
export const CheckoutProvider: React.FC<CheckoutProviderProps> = ({
|
||||
children,
|
||||
defaultCurrency = 'gbp'
|
||||
defaultCurrency
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { refetchLicense } = useLicense();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedPlanGroup, setSelectedPlanGroup] = useState<PlanTierGroup | null>(null);
|
||||
const [minimumSeats, setMinimumSeats] = useState<number>(1);
|
||||
const [currentCurrency, setCurrentCurrency] = useState(defaultCurrency);
|
||||
const [currentCurrency, setCurrentCurrency] = useState(() => {
|
||||
// Use provided default or auto-detect from locale
|
||||
return defaultCurrency || getPreferredCurrency(i18n.language);
|
||||
});
|
||||
const [currentOptions, setCurrentOptions] = useState<CheckoutOptions>({});
|
||||
const [hostedCheckoutSuccess, setHostedCheckoutSuccess] = useState<{
|
||||
isUpgrade: boolean;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Currency detection utility
|
||||
* Auto-detects user's preferred currency from browser locale
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'preferredCurrency';
|
||||
|
||||
/**
|
||||
* Map of locale codes to currency codes
|
||||
* Covers all major locales and their corresponding currencies
|
||||
*/
|
||||
const LOCALE_TO_CURRENCY_MAP: Record<string, string> = {
|
||||
// English variants
|
||||
'en-US': 'usd',
|
||||
'en-CA': 'usd',
|
||||
'en-AU': 'usd',
|
||||
'en-NZ': 'usd',
|
||||
'en-GB': 'gbp',
|
||||
'en-IE': 'eur',
|
||||
|
||||
// European locales - Euro
|
||||
'de-DE': 'eur',
|
||||
'de-AT': 'eur',
|
||||
'de-CH': 'eur',
|
||||
'fr-FR': 'eur',
|
||||
'fr-BE': 'eur',
|
||||
'fr-CH': 'eur',
|
||||
'it-IT': 'eur',
|
||||
'es-ES': 'eur',
|
||||
'pt-PT': 'eur',
|
||||
'nl-NL': 'eur',
|
||||
'nl-BE': 'eur',
|
||||
'pl-PL': 'eur',
|
||||
'ro-RO': 'eur',
|
||||
'el-GR': 'eur',
|
||||
'fi-FI': 'eur',
|
||||
'sv-SE': 'eur',
|
||||
'da-DK': 'eur',
|
||||
'no-NO': 'eur',
|
||||
|
||||
// Chinese variants
|
||||
'zh-CN': 'cny',
|
||||
'zh-TW': 'cny',
|
||||
'zh-HK': 'cny',
|
||||
'zh-SG': 'cny',
|
||||
|
||||
// Indian locales
|
||||
'hi-IN': 'inr',
|
||||
'en-IN': 'inr',
|
||||
'bn-IN': 'inr',
|
||||
'te-IN': 'inr',
|
||||
'ta-IN': 'inr',
|
||||
'mr-IN': 'inr',
|
||||
|
||||
// Brazilian Portuguese
|
||||
'pt-BR': 'brl',
|
||||
|
||||
// Indonesian
|
||||
'id-ID': 'idr',
|
||||
'jv-ID': 'idr',
|
||||
|
||||
// Other major locales defaulting to USD
|
||||
'ja-JP': 'usd',
|
||||
'ko-KR': 'usd',
|
||||
'ru-RU': 'usd',
|
||||
'ar-SA': 'usd',
|
||||
'th-TH': 'usd',
|
||||
'vi-VN': 'usd',
|
||||
'tr-TR': 'usd',
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect currency from browser locale
|
||||
* @param locale - Browser locale string (e.g., 'en-US', 'de-DE')
|
||||
* @returns Currency code ('usd', 'gbp', 'eur', etc.)
|
||||
*/
|
||||
export function detectCurrencyFromLocale(locale: string): string {
|
||||
// Try exact match first
|
||||
if (LOCALE_TO_CURRENCY_MAP[locale]) {
|
||||
return LOCALE_TO_CURRENCY_MAP[locale];
|
||||
}
|
||||
|
||||
// Try matching just the language code (e.g., 'en' from 'en-US')
|
||||
const languageCode = locale.split('-')[0];
|
||||
const matchingLocale = Object.keys(LOCALE_TO_CURRENCY_MAP).find(
|
||||
key => key.startsWith(languageCode)
|
||||
);
|
||||
|
||||
if (matchingLocale) {
|
||||
return LOCALE_TO_CURRENCY_MAP[matchingLocale];
|
||||
}
|
||||
|
||||
// Default fallback to USD
|
||||
return 'usd';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached currency preference from localStorage
|
||||
* @returns Cached currency code or null if not set
|
||||
*/
|
||||
export function getCachedCurrency(): string | null {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
} catch (error) {
|
||||
console.warn('Failed to read currency from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save currency preference to localStorage
|
||||
* @param currency - Currency code to cache
|
||||
*/
|
||||
export function setCachedCurrency(currency: string): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, currency);
|
||||
} catch (error) {
|
||||
console.warn('Failed to save currency to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preferred currency with auto-detection fallback
|
||||
* Priority: localStorage > locale detection > default (USD)
|
||||
* @param currentLocale - Current browser/i18n locale
|
||||
* @returns Currency code
|
||||
*/
|
||||
export function getPreferredCurrency(currentLocale: string): string {
|
||||
// 1. Check localStorage (user has previously selected)
|
||||
const cached = getCachedCurrency();
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 2. Auto-detect from locale
|
||||
const detected = detectCurrencyFromLocale(currentLocale);
|
||||
|
||||
// 3. Cache the detection for future visits
|
||||
setCachedCurrency(detected);
|
||||
|
||||
return detected;
|
||||
}
|
||||
Reference in New Issue
Block a user