import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { Divider, Loader, Alert, Group, Text, Collapse, Button, TextInput, Stack, Paper } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { usePlans } from '@app/hooks/usePlans'; import licenseService, { PlanTierGroup, mapLicenseToTier } from '@app/services/licenseService'; import { useCheckout } from '@app/contexts/CheckoutContext'; import { useLicense } from '@app/contexts/LicenseContext'; import AvailablePlansSection from '@app/components/shared/config/configSections/plan/AvailablePlansSection'; import StaticPlanSection from '@app/components/shared/config/configSections/plan/StaticPlanSection'; import { alert } from '@app/components/toast'; import LocalIcon from '@app/components/shared/LocalIcon'; import { InfoBanner } from '@app/components/shared/InfoBanner'; import { useLicenseAlert } from '@app/hooks/useLicenseAlert'; import { isSupabaseConfigured } from '@app/services/supabaseClient'; import { getPreferredCurrency, setCachedCurrency } from '@app/utils/currencyDetection'; const AdminPlanSection: React.FC = () => { const { t, i18n } = useTranslation(); const { openCheckout } = useCheckout(); const { licenseInfo, refetchLicense } = useLicense(); const [currency, setCurrency] = useState(() => { // 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(''); const [savingLicense, setSavingLicense] = useState(false); const { plans, loading, error, refetch } = usePlans(currency); const licenseAlert = useLicenseAlert(); // Check if we should use static version useEffect(() => { // Only use static version if Supabase is not configured or there's an error // Stripe key is not required - hosted checkout works without it if (!isSupabaseConfigured || error) { setUseStaticVersion(true); } }, [error]); const handleSaveLicense = async () => { try { setSavingLicense(true); // Allow empty string to clear/remove license const response = await licenseService.saveLicenseKey(licenseKeyInput.trim()); if (response.success) { // Refresh license context to update all components await refetchLicense(); alert({ alertType: 'success', title: t('admin.settings.premium.key.success', 'License Key Saved'), body: t('admin.settings.premium.key.successMessage', 'Your license key has been activated successfully. No restart required.'), }); // Clear input setLicenseKeyInput(''); } else { alert({ alertType: 'error', title: t('admin.error', 'Error'), body: response.error || t('admin.settings.saveError', 'Failed to save license key'), }); } } catch (error) { console.error('Failed to save license key:', error); alert({ alertType: 'error', title: t('admin.error', 'Error'), body: t('admin.settings.saveError', 'Failed to save license key'), }); } finally { setSavingLicense(false); } }; const currencyOptions = [ { value: 'gbp', label: 'British pound (GBP, £)' }, { value: 'usd', label: 'US dollar (USD, $)' }, { value: 'eur', label: 'Euro (EUR, €)' }, { value: 'cny', label: 'Chinese yuan (CNY, ¥)' }, { value: 'inr', label: 'Indian rupee (INR, ₹)' }, { value: 'brl', label: 'Brazilian real (BRL, R$)' }, { value: 'idr', label: 'Indonesian rupiah (IDR, Rp)' }, ]; const handleManageClick = useCallback(async () => { try { // Only allow PRO or ENTERPRISE licenses to access billing portal if (!licenseInfo?.licenseType || licenseInfo.licenseType === 'NORMAL') { throw new Error('No valid license found. Please purchase a license before accessing the billing portal.'); } if (!licenseInfo?.licenseKey) { throw new Error('License key missing. Please contact support.'); } // Create billing portal session with license key const response = await licenseService.createBillingPortalSession( window.location.href, licenseInfo.licenseKey ); // Open billing portal in new tab window.open(response.url, '_blank'); } catch (error: any) { console.error('Failed to open billing portal:', error); alert({ alertType: 'error', title: t('billing.portal.error', 'Failed to open billing portal'), body: error.message || 'Please try again or contact support.', }); } }, [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 if (planGroup.tier === 'free') { return; } // Prevent free tier users from directly accessing enterprise (must have server first) const currentTier = mapLicenseToTier(licenseInfo); if (currentTier === 'free' && planGroup.tier === 'enterprise') { alert({ alertType: 'warning', title: t('plan.enterprise.requiresServer', 'Server Plan Required'), body: t( 'plan.enterprise.requiresServerMessage', 'Please upgrade to the Server plan first before upgrading to Enterprise.' ), }); return; } // Use checkout context to open checkout modal openCheckout(planGroup.tier, { currency, onSuccess: () => { // Refetch plans after successful payment // License context will auto-update refetch(); }, }); }, [openCheckout, currency, refetch, licenseInfo, t] ); const shouldShowLicenseWarning = licenseAlert.active && licenseAlert.audience === 'admin'; const formattedUserCount = useMemo(() => { if (licenseAlert.totalUsers == null) { return t('plan.licenseWarning.overLimit', 'more than {{limit}}', { limit: licenseAlert.freeTierLimit, }); } return licenseAlert.totalUsers.toLocaleString(); }, [licenseAlert.totalUsers, licenseAlert.freeTierLimit, t]); const scrollToPlans = useCallback(() => { const el = document.getElementById('available-plans-section'); if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, []); // Show static version if Stripe is not configured or there's an error if (useStaticVersion) { return ; } // Early returns after all hooks are called if (loading) { return (
); } if (error) { // Fallback to static version on error return ; } if (!plans || plans.length === 0) { return ( Plans data is not available at the moment. ); } return (
{shouldShowLicenseWarning && ( )} {/* License Key Section */}
} > {t('admin.settings.premium.licenseKey.info', 'If you have a license key or certificate file from a direct purchase, you can enter it here to activate premium or enterprise features.')} {/* Severe warning if license already exists */} {licenseInfo?.licenseKey && ( } title={t('admin.settings.premium.key.overwriteWarning.title', '⚠️ Warning: Existing License Detected')} > {t('admin.settings.premium.key.overwriteWarning.line1', 'Overwriting your current license key cannot be undone.')} {t('admin.settings.premium.key.overwriteWarning.line2', 'Your previous license will be permanently lost unless you have backed it up elsewhere.')} {t('admin.settings.premium.key.overwriteWarning.line3', 'Important: Keep license keys private and secure. Never share them publicly.')} )} setLicenseKeyInput(e.target.value)} placeholder={licenseInfo?.licenseKey || '00000000-0000-0000-0000-000000000000'} type="password" disabled={savingLicense} />
); }; export default AdminPlanSection;